

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 逐步解說：使用 Lambda 支援的自訂資源建立延遲機制
<a name="walkthrough-lambda-backed-custom-resources"></a>

此逐步解說展示如何使用範例 CloudFormation 範本來設定和啟動 Lambda 支援的自訂資源。此範本會建立延遲機制，可暫停堆疊部署一段指定時間。當您需要在資源佈建期間刻意加入延遲時，例如等待資源穩定後再建立相依資源，這項功能相當實用。

**注意**  
雖然先前建議 Lambda 後端自訂資源用於擷取 AMI IDs，但現在建議使用 AWS Systems Manager 參數。此方法可讓您的範本更具可重複使用性，且更易於維護。如需詳細資訊，請參閱[從 Systems Manager Parameter Store 取得純文字值](dynamic-references-ssm.md)。

**Topics**
+ [概觀](#walkthrough-lambda-backed-custom-resources-overview)
+ [範例範本](#walkthrough-lambda-backed-custom-resources-sample-template)
+ [範例範本逐步解說](#walkthrough-lambda-backed-custom-resources-sample-template-walkthrough)
+ [先決條件](#walkthrough-lambda-backed-custom-resources-prerequisites)
+ [啟動堆疊](#walkthrough-lambda-backed-custom-resources-createfunction-createstack)
+ [清除資源](#walkthrough-lambda-backed-custom-resources-createfunction-cleanup)
+ [相關資訊](#w2aac11c45b9c24b9c23)

## 概觀
<a name="walkthrough-lambda-backed-custom-resources-overview"></a>

本操作指南使用的堆疊範例範本，會建立一個 Lambda 支援的自訂資源。此自訂資源會在堆疊建立期間加入可設定的延遲 (預設為 60 秒)。僅當自訂資源的屬性被修改時，堆疊更新期間才會發生延遲。

此範本會佈建以下資源：
+ 自訂資源，
+ Lambda 函式，以及
+ 讓 Lambda 將日誌寫入 CloudWatch 的 IAM 角色。

它還定義了兩個輸出：
+ 函數實際等待的時間。
+ 每次執行 Lambda 函式時產生的唯一識別碼。



**注意**  
CloudFormation 是免費服務，但 Lambda 會根據您函數的請求次數和程式碼執行時間收費。如需 Lambda 定價的詳細資訊，請參閱 [AWS Lambda 定價](https://aws.amazon.com/lambda/pricing/)。

## 範例範本
<a name="walkthrough-lambda-backed-custom-resources-sample-template"></a>

以下是具備延遲機制的 Lambda 支援自訂資源範例範本：

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-json"></a>

```
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [{
            "Effect": "Allow",
            "Principal": { "Service": ["lambda.amazonaws.com"] },
            "Action": ["sts:AssumeRole"]
          }]
        },
        "Path": "/",
        "Policies": [{
          "PolicyName": "AllowLogs",
          "PolicyDocument": {
            "Statement": [{
              "Effect": "Allow",
              "Action": ["logs:*"],
              "Resource": "*"
            }]
          }
        }]
      }
    },
    "CFNWaiter": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Runtime": "python3.9",
        "Timeout": 900,
        "Role": { "Fn::GetAtt": ["LambdaExecutionRole", "Arn"] },
        "Code": {
          "ZipFile": { "Fn::Join": ["\n", [
            "from time import sleep",
            "import json",
            "import cfnresponse",
            "import uuid",
            "",
            "def handler(event, context):",
            "  wait_seconds = 0",
            "  id = str(uuid.uuid1())",
            "  if event[\"RequestType\"] in [\"Create\", \"Update\"]:",
            "    wait_seconds = int(event[\"ResourceProperties\"].get(\"ServiceTimeout\", 0))",
            "    sleep(wait_seconds)",
            "  response = {",
            "    \"TimeWaited\": wait_seconds,",
            "    \"Id\": id ",
            "  }",
            "  cfnresponse.send(event, context, cfnresponse.SUCCESS, response, \"Waiter-\"+id)"
          ]]}
        }
      }
    },
    "CFNWaiterCustomResource": {
      "Type": "AWS::CloudFormation::CustomResource",
      "Properties": {
        "ServiceToken": { "Fn::GetAtt": ["CFNWaiter", "Arn"] },
        "ServiceTimeout": 60
      }
    }
  },
  "Outputs": {
    "TimeWaited": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "TimeWaited"] },
      "Export": { "Name": "TimeWaited" }
    },
    "WaiterId": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "Id"] },
      "Export": { "Name": "WaiterId" }
    }
  }
}
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-yaml"></a>

```
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: "AllowLogs"
          PolicyDocument:
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:*"
                Resource: "*"
  CFNWaiter:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Runtime: python3.9 
      Timeout: 900
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile:
          !Sub |
          from time import sleep
          import json
          import cfnresponse
          import uuid
​
          def handler(event, context):
            wait_seconds = 0
            id = str(uuid.uuid1())
            if event["RequestType"] in ["Create", "Update"]:
              wait_seconds = int(event["ResourceProperties"].get("ServiceTimeout", 0))
              sleep(wait_seconds)
            response = {
              "TimeWaited": wait_seconds,
              "Id": id 
            }
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response, "Waiter-"+id)
  CFNWaiterCustomResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt CFNWaiter.Arn
      ServiceTimeout: 60
Outputs:
  TimeWaited:
    Value: !GetAtt CFNWaiterCustomResource.TimeWaited
    Export:
      Name: TimeWaited
  WaiterId:
    Value: !GetAtt CFNWaiterCustomResource.Id
    Export:
      Name: WaiterId
```

## 範例範本逐步解說
<a name="walkthrough-lambda-backed-custom-resources-sample-template-walkthrough"></a>

以下程式碼片段將說明範例範本的相關部分，協助您了解 Lambda 函式如何與自訂資源相關聯，並了解其輸出。

[AWS::Lambda::Function](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-function.html) 資源 `CFNWaiter`  
`AWS::Lambda::Function` 資源會指定函式的原始程式碼、處理常式名稱、執行時期環境，以及執行角色的 Amazon Resource Name (ARN)。  
由於使用 Python 原始程式碼，因此 `Handler` 屬性設定為 `index.handler`。如需有關使用內嵌函數原始程式碼時可接受處理常式識別碼的詳細資訊，請參閱 [ AWS::Lambda::Function Code](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-zipfile)。  
由於原始檔是 Python 程式碼，`Runtime` 指定為 `python3.9`。  
`Timeout` 設定為 900 秒。  
`Role` 屬性會使用 `Fn::GetAtt` 函數來擷取範本中 `AWS::IAM::Role` 資源所宣告的 `LambdaExecutionRole` 執行角色 ARN。  
`Code` 屬性會使用 Python 函數來內嵌定義函數程式碼。範例範本中的 Python 函式會執行下列動作：  
+ 使用 UUID 建立唯一 ID
+ 檢查請求是建立請求還是更新請求
+ 在 `Create` 或 `Update` 請求期間為 `ServiceTimeout` 指定的持續時間休眠
+ 傳回等待時間及唯一 ID

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-lambda-resource-json"></a>

```
...
    "CFNWaiter": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Runtime": "python3.9",
        "Timeout": 900,
        "Role": { "Fn::GetAtt": ["LambdaExecutionRole", "Arn"] },
        "Code": {
          "ZipFile": { "Fn::Join": ["\n", [
            "from time import sleep",
            "import json",
            "import cfnresponse",
            "import uuid",
            "",
            "def handler(event, context):",
            "  wait_seconds = 0",
            "  id = str(uuid.uuid1())",
            "  if event[\"RequestType\"] in [\"Create\", \"Update\"]:",
            "    wait_seconds = int(event[\"ResourceProperties\"].get(\"ServiceTimeout\", 0))",
            "    sleep(wait_seconds)",
            "  response = {",
            "    \"TimeWaited\": wait_seconds,",
            "    \"Id\": id ",
            "  }",
            "  cfnresponse.send(event, context, cfnresponse.SUCCESS, response, \"Waiter-\"+id)"
          ]]}
        }
      }
    },
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-lambda-resource-yaml"></a>

```
...
  CFNWaiter:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Runtime: python3.9 
      Timeout: 900
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile:
          !Sub |
          from time import sleep
          import json
          import cfnresponse
          import uuid
​
          def handler(event, context):
            wait_seconds = 0
            id = str(uuid.uuid1())
            if event["RequestType"] in ["Create", "Update"]:
              wait_seconds = int(event["ResourceProperties"].get("ServiceTimeout", 0))
              sleep(wait_seconds)
            response = {
              "TimeWaited": wait_seconds,
              "Id": id 
            }
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response, "Waiter-"+id)
...
```

[AWS::IAM::Role](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-iam-role.html) 資源 `LambdaExecutionRole`  
`AWS::IAM:Role` 資源會為 Lambda 函式建立執行角色，其中包含允許 Lambda 使用該角色的擔任角色政策。此外，它還包含一個允許存取 CloudWatch Logs 的政策。

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-iam-role-json"></a>

```
...
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [{
            "Effect": "Allow",
            "Principal": { "Service": ["lambda.amazonaws.com"] },
            "Action": ["sts:AssumeRole"]
          }]
        },
        "Path": "/",
        "Policies": [{
          "PolicyName": "AllowLogs",
          "PolicyDocument": {
            "Statement": [{
              "Effect": "Allow",
              "Action": ["logs:*"],
              "Resource": "*"
            }]
          }
        }]
      }
    },
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-iam-role-yaml"></a>

```
...
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: "AllowLogs"
          PolicyDocument:
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:*"
                Resource: "*"
...
```

[AWS::CloudFormation::CustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html) 資源 `CFNWaiterCustomResource`  
自訂資源會透過 `!GetAtt CFNWaiter.Arn`，使用 Lambda 函式的 ARN 與其建立關聯。根據 `ServiceTimeout` 中的設定，它會為建立和更新操作實作 60 秒的等待時間。僅當屬性被修改時，才會在更新操作中調用此資源。

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-custom-resource-json"></a>

```
...
    "CFNWaiterCustomResource": {
      "Type": "AWS::CloudFormation::CustomResource",
      "Properties": {
        "ServiceToken": { "Fn::GetAtt": ["CFNWaiter", "Arn"] },
        "ServiceTimeout": 60
      }
    }
  },
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-custom-resource-yaml"></a>

```
...
  CFNWaiterCustomResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt CFNWaiter.Arn
      ServiceTimeout: 60
...
```

`Outputs`  
此範本的 `Outputs` 為 `TimeWaited` 和 `WaiterId`。`TimeWaited` 數值會使用 `Fn::GetAtt` 函數，提供等待程式資源實際等待的時間。`WaiterId` 會使用 `Fn::GetAtt` 函數來提供產生並與執行相關聯的唯一 ID。

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-output-json"></a>

```
...
  "Outputs": {
    "TimeWaited": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "TimeWaited"] },
      "Export": { "Name": "TimeWaited" }
    },
    "WaiterId": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "Id"] },
      "Export": { "Name": "WaiterId" }
    }
  }
}
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-output-yaml"></a>

```
...
Outputs:
  TimeWaited:
    Value: !GetAtt CFNWaiterCustomResource.TimeWaited
    Export:
      Name: TimeWaited
  WaiterId:
    Value: !GetAtt CFNWaiterCustomResource.Id
    Export:
      Name: WaiterId
...
```

## 先決條件
<a name="walkthrough-lambda-backed-custom-resources-prerequisites"></a>

您必須具備使用所有對應服務 (如 Lambda 和 CloudFormation) 的 IAM 許可。

## 啟動堆疊
<a name="walkthrough-lambda-backed-custom-resources-createfunction-createstack"></a>

**建立堆疊**

1. 從 [範例範本](#walkthrough-lambda-backed-custom-resources-sample-template) 區段尋找您偏好設定的範本 (YAML 或 JSON)，並將其儲存到名為 `samplelambdabackedcustomresource.template` 的機器。

1. 開啟位在 [https://console.aws.amazon.com/cloudformation/](https://console.aws.amazon.com/cloudformation/) 的 CloudFormation​ 主控台。

1. 從**堆疊**頁面的右上角，選擇**建立堆疊**，並選擇**使用新資源 (標準)**。

1. 對於**先決條件 - 準備範本**，選取**選擇現有範本**。

1. 對於**指定範本**下，選擇**上傳範本檔案**，然後選擇**選擇檔案**。

1. 選取您先前儲存的 `samplelambdabackedcustomresource.template` 範本檔案。

1. 選擇**下一步**。

1. 對於**秘密名稱**，輸入 **SampleCustomResourceStack**，然後選擇**下一步**。

1. 在此逐步解說中，您不需要新增標籤或指定進階設定，因此請選擇 **Next (下一步)**。

1. 確定堆疊名稱正確，然後選擇**更新**。

CloudFormation 可能需要幾分鐘的時間來建立您的堆疊。若要監控進度，請檢視堆疊事件。如需詳細資訊，請參閱[在 CloudFormation 主控台中檢視堆疊資訊](cfn-console-view-stack-data-resources.md)。

若堆疊建立成功，則系統會一併建立堆疊中所有資源 (如 Lambda 函式和自訂資源)。您已成功使用 Lambda 函式和自訂資源。

若 Lambda 函式回傳錯誤訊息，則可以在 CloudWatch Logs 的[主控台](https://console.aws.amazon.com/cloudwatch/home#logs:)檢視該函數的日誌。日誌串流名稱即為自訂資源的實體 ID，您能夠檢視堆疊資源以查詢該名稱。如需詳細資訊，請參閱《*Amazon CloudWatch 使用者指南*》中的[檢視日誌資料](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html#ViewingLogData)。

## 清除資源
<a name="walkthrough-lambda-backed-custom-resources-createfunction-cleanup"></a>

您可以刪除堆疊以清除建立的所有堆疊資源，便無需為不必要的資源支付費用。

**刪除堆疊**

1. 從 CloudFormation 主控台選擇 **SampleCustomResourceStack** 堆疊。

1. 選擇 **Actions (動作)**，然後選擇 **Delete Stack (刪除堆疊)**。

1. 在確認訊息中，選擇 **Yes, Delete (是，刪除)**。

系統將刪除您建立的所有資源。

現在，您已經了解如何建立並使用 Lambda 支援的自訂資源，即可善用本逐步說明的範例範本和程式碼來建置其他堆疊及函數。

## 相關資訊
<a name="w2aac11c45b9c24b9c23"></a>
+ [CloudFormation 自訂資源參考](crpg-ref.md)
+ [AWS::CloudFormation::CustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html)