CloudFormation 自定義資源允許管理 AWS 之外的資源,提升了基礎設施即程式碼的彈性。然而,在實際應用中,需要妥善處理資源的更新、刪除和錯誤情況,才能確保 CloudFormation 堆積疊的穩定性。本文將探討這些進階議題,並介紹如何使用 CloudFormation 登入檔來管理自定義資源,進一步提升資源管理的效率。對於已刪除的資源,自定義資源的刪除操作需避免觸發錯誤。檢查資源是否存在後,才執行刪除操作,若資源不存在,則直接傳送成功訊息給 CloudFormation。此流程確保 CloudFormation 堆積疊狀態與實際資源狀態同步,避免因資源狀態不一致導致的錯誤。此外,自定義資源的錯誤處理機制也至關重要。透過捕捉和處理異常,可以有效地防止錯誤擴散,並提供更友好的錯誤訊息,方便排查問題。

使用自定義資源處理更新、刪除和失敗

在前面的章節中,我們學習瞭如何使用AWS CloudFormation的自定義資源(Custom Resource, CR)來管理非AWS資源。本章節將探討如何處理自定義資源的更新、刪除和失敗情況,以增強CR的穩定性和錯誤處理能力。

傳送刪除成功訊息給CloudFormation

當CloudFormation嘗試刪除一個不存在的資源時,我們的Lambda函式需要能夠正確處理這種情況,以避免進入無限迴圈的刪除失敗狀態。

# customdb.py
def handler(event, context):
    # ...
    missing_props = [prop for prop in required_props if prop not in event['ResourceProperties']]
    if missing_props:
        if event['RequestType'] == "Delete":
            send(event, context, SUCCESS, response_data={})
            sys.exit(0)
        reason = f"Required properties are missing: {missing_props}"
        send(event, context, FAILED, response_reason=reason, response_data={})
        raise CustomResourceException(reason)
    # ...

內容解密:

  1. 檢查event['ResourceProperties']中是否缺少必要的屬性。
  2. 如果是刪除請求且缺少屬性,則向CloudFormation傳送成功訊息並離開。
  3. 如果不是刪除請求,則傳送失敗訊息並丟擲異常。

正確處理資源刪除

當資料函式庫或使用者已被手動刪除,CR仍嘗試刪除時,我們需要避免錯誤。

# customdb.py
def delete_db(dbname, dbuser, rdsendpoint, rdsuser, rdspassword):
    # ...
    db_exists_query = f"SHOW DATABASES LIKE '{dbname}'"
    user_exists_query = f"SELECT user FROM mysql.user where user='{dbuser}'"
    try:
        # ...
        if cursor.execute(db_exists_query):
            cursor.execute(delete_db_query)
        if cursor.execute(user_exists_query):
            cursor.execute(delete_user_query)
        # ...
    except Exception as err:
        raise CustomResourceException(err)

內容解密:

  1. 檢查資料函式庫和使用者是否存在。
  2. 如果存在,則執行刪除操作。
  3. 捕捉並處理任何刪除過程中發生的異常。

建立自己的資源登入檔

在前一章中,我們使用CloudFormation的自定義資源來管理非AWS資源。現在,我們將學習如何使用CloudFormation登入檔來儲存和管理自定義資源型別、模組和鉤子。

瞭解CloudFormation登入檔

CloudFormation登入檔是一個集中存放自定義資源型別、模組和鉤子的地方,讓使用者能夠更好地管理和重用這些資源。

啟用和使用公共擴充套件

我們可以透過AWS賬戶啟用和使用第三方的公共擴充套件。

建立和使用私有擴充套件

我們將開發自己的私有登入檔,以實作使用CloudFormation建立自定義資料函式庫的功能,就像我們在上一章中使用自定義資源一樣。

使用Plantuml圖表呈現流程

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title CloudFormation自定義資源進階應用

package "AWS 雲端架構" {
    package "網路層" {
        component [VPC] as vpc
        component [Subnet] as subnet
        component [Security Group] as sg
        component [Route Table] as rt
    }

    package "運算層" {
        component [EC2] as ec2
        component [Lambda] as lambda
        component [ECS/EKS] as container
    }

    package "儲存層" {
        database [RDS] as rds
        database [DynamoDB] as dynamo
        storage [S3] as s3
    }

    package "服務層" {
        component [API Gateway] as apigw
        component [ALB/NLB] as lb
        queue [SQS] as sqs
    }
}

apigw --> lambda
apigw --> lb
lb --> ec2
lb --> container
lambda --> dynamo
lambda --> s3
ec2 --> rds
container --> rds
vpc --> subnet
subnet --> sg
sg --> rt

@enduml

圖表翻譯: 此圖示呈現了處理資源刪除的流程。首先檢查資源是否存在,如果存在,則執行刪除操作並捕捉任何異常。如果資源不存在,則直接傳送成功訊息給CloudFormation,最終結束流程。

問題

  1. 自定義資源的可能請求型別有哪些?
  2. 如果將Lambda函式與外部依賴項封裝,是否可以使用cfnresponse
  3. 哪些AWS服務用於自定義資源處理?
  4. 哪個自定義資源回應物件必須用於指定資源更改失敗的原因?
  5. 在Lambda中,eventcontext是什麼?

進一步閱讀

  • Alex DeBrie的關於自定義資源的部落格:https://www.alexdebrie.com/posts/cloudformation-custom-resources/
  • 包含自定義資源示例的GitHub倉函式庫:https://github.com/aws-samples/aws-cfn-custom-resource-examples

自建 CloudFormation 資源登入檔

瞭解 CloudFormation 登入檔

CloudFormation 登入檔的發展始於 2019 年,當時宣佈並發布了私有登入檔(同時發布了其他重大更新,詳見 https://aws.amazon.com/blogs/aws/cloudformation-update-cli-third-party-resource-support-registry/)。此更新引入了一種集中式方法來建模、構建和維護自定義資源。

最初的主要原因是使 AWS 服務團隊能夠更快地接入新開發的服務。以前,這是 CloudFormation 團隊本身的責任,將服務接入任務交給服務團隊並提供適當的工具,提高了接入 CloudFormation 的速度。另一個好處是可以在單一位置集中維護自定義資源。

兩年後,AWS 發布了公共登入檔——一個由各種軟體供應商(如 MongoDB、Atlassian、DataDog 等)開發的資源型別的登入檔(https://aws.amazon.com/blogs/aws/introducing-a-public-registry-for-aws-cloudformation/)。

這使得使用者能夠對 AWS 外部的資源應用基礎設施即程式碼(Infrastructure-as-Code)實踐,使用熟悉的 CloudFormation 語法。

CloudFormation 登入檔不僅支援自定義資源型別,還支援模組(另一種擴充套件堆積疊和範本的方式)和掛鉤(允許進行伺服器驅動的資源驗證)。

此外,登入檔接管了資源供應處理。在前一章中,我們建立了自己的 Lambda 函式來處理我們開發的供應邏輯。現在,登入檔接管了託管方面,同時要求擴充套件建立者具備兩個重要條件:

  • 所有處理邏輯必須符合擴充套件規範並經過充分測試
  • 使用的任何資源型別必須符合資源提供者定義架構(https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html)

目前,登入檔由公共和私有部分組成。公共登入檔包含公共擴充套件,例如 MongoDB Atlas Cluster 或 DynamoDB Item 資源型別!私有登入檔包含私有擴充套件:我們自己開發並啟用的擴充套件。後者看起來令人困惑(私有擴充套件如何具有公共擴充套件?!),因此讓我們直接跳入使用擴充套件。

啟用和使用公共擴充套件

讓我們重複一下我們在前一章中所做的事情。我們構建了一個 Lambda 函式,該函式接收來自 CloudFormation 的 API 呼叫,並對我們已組態的 RDS 叢集執行 SQL 查詢。

現在,假設我們想要發布我們的自定義資源處理程式並讓任何人使用它。由於 Lambda 託管在我們的 AWS 帳戶上,因此 RDS 例項的使用者需要使其可從外部世界存取,從而引入安全風險。如果我們使用 Aurora Serverless V1,我們可以啟用 Data API,但仍需要使用 IAM 角色提供對它的存取許可權,這引入了以下兩方面的安全風險:

  • 我們作為 Lambda 的擁有者,必須公開我們的 AWS 帳戶 ID
  • 我們建立了一個 IAM 角色,授予使用者存取 Data API 的許可權

即使我們忽略上述安全風險,仍然存在其他風險,例如成本和可靠性。我們託管 Lambda 函式,這意味著我們擁有它,因此我們必須為其付費。在請求激增的情況下,我們的函式可能會被限流,從而為使用者引入服務品質下降的問題,如果它能夠有效地處理負載,我們將收到一個令人不愉快的 AWS 賬單。處理濫用呼叫者將需要我們暫停他們並構建諸如速率限制之類別的措施。

那麼,發布自定義資源並讓任何人使用的最佳方法是什麼?希望將其資源型別在公共登入檔中可用的擴充套件開發人員遵循以下發布工作流程:

圖 8.1 – 擴充套件發布流程

開發人員要使其擴充套件可供公眾使用,必須遵循以下步驟:

  1. 首先,發布者透過在 AWS Marketplace、Bitbucket 或 GitHub 上擁有帳戶來驗證自己,並使用用於發布的 AWS 帳戶。發布者在登入檔中將此 AWS 帳戶註冊為公共擴充套件的發布者。然後,發布者確保擴充套件已針對登入檔的合約進行測試,將資源型別擴充套件處理程式儲存在開源儲存函式庫(如 GitHub)中,並為擴充套件分配一個 SemVer 版本號。
  2. 發布者將擴充套件提交到登入檔。提交後,擴充套件即可供使用。
  3. 然後,客戶使用 AWS CLI 或控制檯啟用擴充套件。
  4. 啟用後,資源供應處理程式捆綁包將從發布者的登入檔複製到我們自己的登入檔。

要啟用擴充套件,我們需要一個 IAM 角色和一個 CloudWatch 日誌組,CloudFormation 將使用它們來使用擴充套件,例如,組態資源。

使用 CloudFormation 管理 DynamoDB 專案

使用 CloudFormation 來維護 DynamoDB 專案可能看起來很奇怪,但有一些有效的使用案例——例如,如果我們有一個 DynamoDB 表,用於儲存組態專案,並且我們希望使用 DynamoDB,因為它比 SSM 引數儲存更便宜。

在這種情況下,我們希望以迭代、可靠和原子性的方式更新表中的專案。幸運的是,AWS 社群已經發布了這樣的資源型別(https://github.com/aws-cloudformation/community-registry-extensions/tree/main/resources/DynamoDB_Item),因此我們只需要啟用並使用它。

在下面的練習中,我們將設定並新增自定義資源型別擴充套件以管理 DynamoDB 專案:

  1. 首先,讓我們開始設定我們的帳戶。我們使用 CloudFormation 組態必要的資源,從擴充套件將承擔的 IAM 角色開始:
// prerequisite.yaml
ExtensionIamRole:
  Type: "AWS::IAM::Role"
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Sid: "AllowAssumeRole"
          Effect: "Allow"

內容解密:

此段 CloudFormation 程式碼定義了一個名為 ExtensionIamRole 的 IAM 角色。該角色具有一個假設角色策略檔案,該檔案指定了允許承擔該角色的實體。Version 指定了策略語言的版本,而 Statement 部分定義了具體的許可權。在這裡,我們允許承擔該角色的操作,具體由 Sid(陳述式 ID)為 “AllowAssumeRole” 的宣告定義,其效果(Effect)是 “Allow”。

DynamoDB 資源型別的實際應用

在前面的步驟中,我們成功地設定了 IAM 角色,接下來我們將繼續組態 DynamoDB 資源型別擴充套件。

步驟詳解

  1. 組態 IAM 角色

    • 我們定義了一個名為 ExtensionIamRole 的 IAM 角色。
    • 此角色的假設角色策略檔案允許特定的實體承擔該角色。
  2. 啟用 DynamoDB Item 資源型別

    • 我們將使用 AWS 社群發布的 DynamoDB Item 資源型別。
    • 這種資源型別允許我們使用 CloudFormation 管理 DynamoDB 表中的專案。
  3. 資源供應處理程式

    • 資源供應處理程式是擴充套件的核心部分,負責處理資源的建立、更新和刪除操作。
    • 在我們的例子中,資源供應處理程式將由 AWS 社群提供的 DynamoDB Item 資源型別處理。

程式碼範例與解析

// dynamodb-item.yaml
Resources:
  MyDynamoDBTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: !Sub 'my-table-${AWS::Region}'
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

  MyDynamoDBItem:
    Type: 'AWSCommunity::DynamoDB::Item'
    Properties:
      TableName: !Ref MyDynamoDBTable
      Item:
        id: !Sub 'item-${AWS::Region}'
        value: 'Hello, World!'

內容解密:

此 CloudFormation 範本定義了兩個資源:一個 DynamoDB 表和一個 DynamoDB 專案。

  1. MyDynamoDBTable

    • 資源型別為 AWS::DynamoDB::Table,代表一個 DynamoDB 表。
    • TableName 使用 !Sub 函式動態生成,格式為 my-table-${AWS::Region},其中 ${AWS::Region} 將被當前區域替換。
    • AttributeDefinitions 定義了表的屬性,在這裡我們定義了一個名為 id 的屬性,其型別為 S(字串)。
    • KeySchema 指定了表的主鍵,在這裡 id 屬性被用作主鍵(HASH)。
    • ProvisionedThroughput 設定了表的預組態吞吐量,分別為讀取和寫入容量單位設定為 1。
  2. MyDynamoDBItem

    • 資源型別為 AWSCommunity::DynamoDB::Item,這是我們之前啟用的社群提供的資源型別,用於管理 DynamoDB 表中的專案。
    • TableName 屬性參照了前面建立的 MyDynamoDBTable 表。
    • Item 屬性定義了要插入表中的專案。在這裡,我們插入了一個包含 idvalue 屬性的專案。id 的值使用 !Sub 函式動態生成,而 value 被設定為靜態字串 'Hello, World!'

透過這種方式,我們可以使用 CloudFormation 來管理 DynamoDB 中的專案,從而實作基礎設施即程式碼(IaC)的管理方式,使我們的組態變更可追蹤、可重複且易於管理。

使用 CloudFormation 登入檔和擴充套件,我們可以進一步擴充套件其功能,以支援更多型別的資源和更複雜的組態。這包括但不限於:

  • 開發更多的自定義資源型別,以支援不同的 AWS 服務或第三方服務。
  • 利用掛鉤(Hooks)進行更細粒度的資源驗證和組態檢查。
  • 結合模組(Modules)來簡化複雜堆積疊的組態和管理。

透過不斷地擴充套件和最佳化我們的 CloudFormation 組態,我們可以實作更高效、更可靠的基礎設施管理。

使用與啟用公開擴充功能於 AWS CloudFormation

在現代雲端基礎設施管理中,AWS CloudFormation 提供了一種簡便的方法來定義和佈署基礎設施即程式碼(Infrastructure as Code, IaC)。然而,有時內建的資源型別並不足以滿足所有需求,這時就需要使用公開擴充功能(Public Extensions)。本文將介紹如何在 AWS CloudFormation 中啟用和使用公開擴充功能,以管理 DynamoDB 專案為例。

步驟 1:準備 CloudFormation 範本

首先,我們需要建立一個 CloudFormation 範本來設定必要的資源,包括 IAM 角色、DynamoDB 表格和日誌群組。

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  ExtensionIamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service: resources.cloudformation.amazonaws.com
            Action: 'sts:AssumeRole'

  Table:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      BillingMode: 'PAY_PER_REQUEST'
      AttributeDefinitions:
        - AttributeName: 'configItem'
          AttributeType: 'S'
      KeySchema:
        - AttributeName: 'configItem'
          KeyType: 'HASH'

  DynamoDbPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: 'ExtensionDynamoDBPolicy'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowExtensionAccessToDynamoDBTable'
            Effect: 'Allow'
            Action:
              - 'dynamodb:*Item'
            Resource: !GetAtt Table.Arn
      Roles:
        - !Ref ExtensionIamRole

  ExtensionLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      RetentionInDays: 7

  LogPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: 'ExtensionLogPolicy'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowExtensionLogging'
            Effect: 'Allow'
            Action:
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
            Resource:
              - !GetAtt ExtensionLogGroup.Arn
              - !Sub "${ExtensionLogGroup.Arn}:log-stream:*"
      Roles:
        - !Ref ExtensionIamRole

Outputs:
  RoleArn:
    Value: !GetAtt ExtensionIamRole.Arn
  LogGroup:
    Value: !Ref ExtensionLogGroup
  DynamoDbTable:
    Value: !Ref Table

內容解密:

此範本定義了必要的 AWS 資源,包括 IAM 角色、DynamoDB 表格、日誌群組和相關的 IAM 策略。ExtensionIamRole 允許 CloudFormation 資源擴充功能(Resources Type extension)擔任該角色。Table 定義了一個簡單的主鍵 configItem 的 DynamoDB 表格。DynamoDbPolicyLogPolicy 分別授予了對 DynamoDB 表格的操作許可權和 CloudWatch 日誌的寫入許可權。

步驟 2:佈署 CloudFormation 堆積疊

接下來,我們需要佈署這個 CloudFormation 堆積疊。

aws cloudformation deploy \
  --stack-name extension-dynamo-item \
  --template-file prerequisite.yaml \
  --capabilities CAPABILITY_IAM

內容解密:

此命令將 CloudFormation 範本佈署為一個名為 extension-dynamo-item 的堆積疊。--capabilities CAPABILITY_IAM 表示該堆積疊可能會建立 IAM 資源。

步驟 3:擷取堆積疊輸出

佈署完成後,我們需要擷取堆積疊的輸出值。

aws cloudformation describe-stacks \
  --stack-name extension-dynamo-item \
  --query 'Stacks[0].Outputs[]'

內容解密:

此命令查詢 extension-dynamo-item 堆積疊的輸出值,包括 RoleArnLogGroupDynamoDbTable

步驟 4:啟用擴充功能

擷取輸出值後,我們可以啟用 AwsCommunity::DynamoDB::Item 擴充功能。

LOGROUP=$(aws cloudformation describe-stacks --stack-name extension-dynamo-item --query 'Stacks[0].Outputs[0].OutputValue' --output text)
TABLE=$(aws cloudformation describe-stacks --stack-name extension-dynamo-item --query 'Stacks[0].Outputs[1].OutputValue' --output text)
ROLE=$(aws cloudformation describe-stacks --stack-name extension-dynamo-item --query 'Stacks[0].Outputs[2].OutputValue' --output text)

aws cloudformation activate-type \
  --type RESOURCE \
  --type-name AwsCommunity::DynamoDB::Item \
  --execution-role-arn $ROLE \
  --logging-config LogRoleArn=$ROLE,LogGroupName=$LOGROUP \
  --publisher-id c830e97710da0c9954d80ba8df021e5439e7134b

內容解密:

這些命令首先擷取堆積疊輸出的 LogGroupDynamoDbTableRoleArn 值,並將其存入環境變數中。然後,使用 aws cloudformation activate-type 命令啟用 AwsCommunity::DynamoDB::Item 擴充功能,並組態日誌和執行角色。

步驟 5:使用擴充功能

啟用擴充功能後,我們可以在 CloudFormation 範本中使用它。

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  TableName:
    Type: String

Resources:
  LogLevel:
    Type: 'AwsCommunity::DynamoDB::Item'
    Properties:
      TableName: !Ref TableName
      Item:
        LogLevel:
          S: 'DEBUG'
        Keys:
          - AttributeName: 'configurationItem'
            AttributeType: 'S'
            AttributeValue: 'ApplicationLogLevel'

內容解密:

此範本使用 AwsCommunity::DynamoDB::Item 擴充功能在指定的 DynamoDB 表格中建立一個專案。TableName 引數指定了要操作的表格名稱,Item 屬性定義了要建立的專案內容。