CloudFormation 提供多種內建資源型別,但對於特定需求,我們需要自定義資源型別。私有擴充套件讓開發者得以擴充 CloudFormation 的功能,整合自定義資源或第三方服務。建立私有擴充套件涉及定義資源架構(包含屬性和行為)、實作資源處理程式(處理資源的生命週期操作)以及編寫測試案例。透過 AWS CLI 工具,我們可以編譯、測試、提交和佈署這些擴充套件。文章中提供的程式碼範例展示瞭如何使用 Python 實作資源處理程式,並使用 JSON 定義資源架構和測試資料。此外,文章也說明瞭如何使用巨集簡化範本撰寫,例如自動填入 AMI ID、新增額外資源以及讓資源宣告更易於理解。巨集的使用能提升範本的可讀性和可維護性,並減少程式碼重複。

使用私有擴充套件自訂 CloudFormation 資源型別

在前面的章節中,我們探討瞭如何使用 CloudFormation 來管理 AWS 資源。本章節將探討如何建立和使用私有擴充套件來擴充 CloudFormation 的功能,使其能夠支援自訂資源型別。

建立私有資源型別的必要性

CloudFormation 提供了豐富的資源型別來管理 AWS 資源,但有時我們需要管理自訂資源或第三方服務。這時,私有擴充套件就變得非常有用。私有擴充套件允許我們定義自訂資源型別,使其能夠與 CloudFormation 無縫整合。

建立私有資源型別的步驟

1. 定義資源架構

首先,我們需要定義自訂資源型別的架構。這個架構描述了資源的屬性和行為。以下是一個範例:

{
  "typeName": "Org::Storage::Database",
  "description": "A custom database resource",
  "properties": {
    "DatabaseName": {
      "type": "string",
      "description": "The name of the database"
    },
    "DatabaseUser": {
      "type": "string",
      "description": "The username for the database"
    },
    // ...
  }
}

2. 實作資源處理程式

接下來,我們需要實作資源處理程式,以處理資源的建立、更新和刪除操作。以下是一個 Python 範例:

@resource.handler(Action.CREATE)
def create_handler(session, request, callback_context):
    try:
        model = request.desiredResourceState
        # 建立資料函式庫的邏輯
        progress = ProgressEvent(
            status=OperationStatus.IN_PROGRESS,
            resourceModel=model,
        )
        # ...
        progress.status = OperationStatus.SUCCESS
        return progress
    except Exception as e:
        return ProgressEvent.failed(
            HandlerErrorCode.HandlerInternalFailure,
            f'Failed to create resource. Reason: {e}')

@resource.handler(Action.DELETE)
def delete_handler(session, request, callback_context):
    try:
        model = request.desiredResourceState
        # 刪除資料函式庫的邏輯
        progress = ProgressEvent(
            status=OperationStatus.IN_PROGRESS,
            resourceModel=model,
        )
        # ...
        progress.status = OperationStatus.SUCCESS
        return progress
    except Exception as e:
        return ProgressEvent.failed(
            HandlerErrorCode.HandlerInternalFailure,
            f'Failed to delete resource. Reason: {e}')

3. 新增測試資料

為了執行合約測試,我們需要新增一些測試資料。以下是一個範例:

// inputs_1_create.json
{
  "DatabaseName": "myDatabase",
  "DatabaseUser": "MyDatabaseUser",
  "DatabasePassword": "SuperUser12",
  "RdsHost": "db_identifier.us-east-1.rds.amazonaws.com",
  "RdsUser": "rdsuser",
  "RdsPassword": "barfoo12344321"
}

測試和佈署私有資源型別

1. 編譯資源型別

執行以下命令來編譯資源型別:

$ cfn submit --dry-run

2. 啟動本地 Lambda 函式

執行以下命令來啟動本地 Lambda 函式:

$ sam local start-lambda

3. 執行測試

執行以下命令來執行測試:

$ cfn test

4. 提交資源型別

執行以下命令來提交資源型別:

$ cfn submit -v --region us-east-1

使用私有資源型別

建立一個新的 CloudFormation 範本,使用自訂資源型別:

// database.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Custom DB
Parameters:
  # ...
Resources:
  Database:
    Type: Org::Storage::Database
    Properties:
      DatabaseName: !Ref DBName
      DatabaseUser: !Ref DBUser
      DatabasePassword: !Ref DBPassword
      RdsHost: !ImportValue RdsEndpoint
      RdsUser: !Ref RdsUser
      RdsPassword: !Ref RdsPassword

執行以下命令來佈署範本:

$ aws cloudformation deploy \
  --stack-name mydatabase \
  --template-file database.yaml

清理資源

執行以下命令來清理資源:

$ aws cloudformation delete-stack \
  --stack-name mydatabase
$ aws cloudformation delete-stack \
  --stack-name rds
$ aws cloudformation deregister-type \
  --type RESOURCE \
  --type-name 'Org::Storage::Database'

測驗題

  1. 公有擴充套件和私有擴充套件之間的區別是什麼?
  2. 資源登入檔中有哪些擴充套件可用?
  3. 資源型別處理程式中需要執行哪些動作?
  4. 哪些命令列工具可用於註冊和登出私有擴充套件?
  5. 哪個命令根據架構生成資原始碼?

延伸閱讀

  • 公有 AWS 社群擴充套件:https://github.com/aws-cloudformation/community-registry-extensions/tree/main
  • 從 Cloudsoft 建立自訂資源型別的經驗:https://cloudsoft.io/blog/what-we-learned-from-building-60-third-party-resources-for-aws-cloudformation

程式碼解析

@resource.handler(Action.CREATE)
def create_handler(session, request, callback_context):
    try:
        model = request.desiredResourceState
        # 建立資料函式庫的邏輯
        progress = ProgressEvent(
            status=OperationStatus.IN_PROGRESS,
            resourceModel=model,
        )
        # ...
        progress.status = OperationStatus.SUCCESS
        return progress
    except Exception as e:
        return ProgressEvent.failed(
            HandlerErrorCode.HandlerInternalFailure,
            f'Failed to create resource. Reason: {e}')

此段程式碼定義了一個資源處理程式,用於處理資源的建立操作。它首先取得所需的資源狀態,然後執行建立資料函式庫的邏輯。最後,它傳回一個 ProgressEvent 物件,表示操作的結果。

圖表說明

此圖示展示了私有資源型別的建立和佈署流程。

  graph LR
    A[定義資源架構] --> B[實作資源處理程式]
    B --> C[新增測試資料]
    C --> D[編譯資源型別]
    D --> E[啟動本地 Lambda 函式]
    E --> F[執行測試]
    F --> G[提交資源型別]
    G --> H[使用私有資源型別]
    H --> I[清理資源]

圖表翻譯: 此圖表展示了建立和佈署私有資源型別的步驟。首先,我們需要定義資源架構和實作資源處理程式。接下來,我們新增測試資料並編譯資源型別。然後,我們啟動本地 Lambda 函式並執行測試。測試透過後,我們提交資源型別並使用它。最後,我們清理資源。

使用巨集、巢狀堆積疊和模組擴充套件範本

在處理範本時,我們經常需要動態地為資源指定,或是在堆積疊操作過程中快速更改範本。同時,我們也希望為堆積疊建立模組化的結構,以提高可重用性。

本章將探討範本巨集的使用案例,並開發自己的巨集。接著,我們將簡要介紹巢狀堆積疊,並說明為何它們的使用頻率不如以前,以及模組如何成功取代它們。最後,我們將建立自己的模組,並使用 SAM CLI 佈署堆積疊。

本章將涵蓋以下主題:

  • 瞭解範本巨集的使用案例
  • 介紹範本巨集
  • 編寫自己的巨集
  • 瞭解巢狀堆積疊的歷史
  • 建立和使用自己的 CloudFormation 模組

瞭解範本巨集的使用案例

在探討巨集的內部工作原理之前,我們需要了解可以使用它們解決哪些問題。讓我們來看看幾個案例和範例。

自動填充資源屬性值

假設我們有一個啟動範本,需要定義 Amazon Machine Image(AMI)ID。對於 Amazon Linux AMI,我們可以使用 AWS 的 Parameter Store:

Parameters:
  ImageId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'

Resources:
  LaunchTemplate:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        ImageId: !Ref ImageId

但是,如果我們不使用 Amazon Linux,而是使用 Ubuntu,我們就必須手動指定 AMI ID 並使用對映:

Mappings:
  RegionMap:
    us-east-1: "ami-123"
    us-west-1: "ami-456"
    # ...

Resources:
  Inst:
    Type: "AWS::EC2::Instance"
    Properties:
      ImageId: !FindInMap [RegionMap, !Ref AWS::Region]

使用巨集填寫AMI ID

或者,我們可以建立一個巨集來查詢 AMI ID,並在範本中使用它。以下範本是使用巨集填寫 AMI ID 的範例:

Resources:
  LaunchTemplate:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        ImageId:
          Fn::Transform:
            Name: FindAmi
            Parameters:
              OSFamily: "ubuntu"

如前面的範例所示,巨集名稱在內建函式 Fn::Transform 之後提供,後面跟著其引數。在堆積疊操作過程中,巨集將儲存必要的 AMI ID。

新增額外的資源

假設我們有一些資源對於每個堆積疊都是相同的,但必須是每個堆積疊的一部分。例如,應用程式的 CloudWatch 規則和警示。我們可以複製並貼上相同的資源宣告,但這被視為程式碼重複,是不好的做法。

對於這種情況,有一個名為 AWS::Include 的 CloudFormation 巨集。AWS::Include 巨集會在 S3 上查詢範本,並將當前範本與額外的範本合併。AWS::Include 巨集不是全域巨集,因此必須在 Resources 區段中宣告,如下所示:

Resources:
  # ... 一些資源...
  'Fn::Transform':
    Name: 'AWS::Include'
    Parameters:
      Location: 's3://mybucket/my_set_of_resources.yaml'

雖然這是一個常見的巨集,但我們應該避免使用重複的資源,遵循「不要重複自己」(DRY)模式,並正確組織我們的堆積疊。

使資源宣告更容易被開發人員理解

我們有一種標準的應用程式開發方式。假設每個應用程式都是在 Fargate 上執行的 Elastic Container Service(ECS)任務。它可能有也可能沒有額外的資源,並且它將始終在與其他應用程式相同的 ECS 叢集上執行。我們可以建立一個龐大的範本,包含條件元素,並要求開發人員提供許多引數。或者,我們可以使用巨集為開發人員建立一個簡單易用的範本。

以下範例範本顯示了這種標準應用程式的外觀:

Transform: StandardApplication

Resources:
  Application:
    Properties:
      TaskCount: 1
      Memory: 512
      CPU: 256
      RDS: "postgresql"
      RDSSize: "db.t2.micro"
      # ...

在轉換過程中,巨集將評估屬性並呈現新的範本,例如:

Resources:
  EcsTaskDefinition:
    Type: # ...
    Properties:
      Cpu: 256
      Memory: 512
      # ...

  EcsService:
    Type: #...
    Properties:
      DesiredCount: 1
      # ...
  # ...

開發人員將始終對其應用程式的基礎架構和資源擁有控制權,但不必深入瞭解 CloudFormation 和 AWS。

這一切聽起來不錯,但我們如何實作這一點?讓我們來看看範本巨集究竟是什麼。

內容解密:

在上述範例中,我們使用了 Fn::Transform 內建函式來呼叫巨集。巨集名稱和引數在 Fn::Transform 中指定。在堆積疊操作過程中,巨集將根據提供的引數執行相應的操作,並傳回結果。

探討範本巨集

範本巨集是一種強大的工具,允許我們在 CloudFormation 範本中執行自定義邏輯。透過使用巨集,我們可以簡化範本,提高可重用性,並使範本更易於維護。

巨集的工作原理

當 CloudFormation 處理範本時,它會遇到 Fn::Transform 內建函式。Fn::Transform 內建函式指定要呼叫的巨集名稱和引數。CloudFormation 會將範本的相關部分傳遞給巨集,巨集執行自定義邏輯並傳回結果。然後,CloudFormation 使用巨集傳回的結果繼續處理範本。

編寫自己的巨集

要編寫自己的巨集,我們需要建立一個 AWS Lambda 函式,該函式將執行自定義邏輯。Lambda 函式將接收範本的相關部分作為輸入,並傳回結果。

以下是一個簡單的巨集範例,該巨集根據提供的引數傳回 AMI ID:

import boto3

def lambda_handler(event, context):
    # 取得引數
    os_family = event['params']['OSFamily']

    # 查詢 AMI ID
    ec2 = boto3.client('ec2')
    response = ec2.describe_images(
        Filters=[
            {'Name': 'name', 'Values': [f'amzn2-ami-hvm-*-{os_family}-gp2']},
            {'Name': 'architecture', 'Values': ['x86_64']},
            {'Name': 'state', 'Values': ['available']}
        ]
    )

    # 傳回 AMI ID
    return {
        'requestId': event['requestId'],
        'status': 'success',
        'fragment': response['Images'][0]['ImageId']
    }

使用巨集

要在範本中使用巨集,我們需要在 Fn::Transform 內建函式中指定巨集名稱和引數。以下是一個使用上述巨集的範例:

Resources:
  LaunchTemplate:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        ImageId:
          Fn::Transform:
            Name: FindAmi
            Parameters:
              OSFamily: "ubuntu"

在這個範例中,FindAmi 巨集將根據提供的 OSFamily 引數傳回 AMI ID。