

# 演练：使用 Lambda 支持的自定义资源创建延迟机制
<a name="walkthrough-lambda-backed-custom-resources"></a>

本演练向您展示如何使用示例 CloudFormation 模板配置和启动 Lambda 支持的自定义资源。此模板创建了一种延迟机制，可在指定时间暂停堆栈部署。当您需要在资源预置期间引入故意的延迟时（例如在创建依赖资源之前等待资源稳定时），这可能非常有用。

**注意**  
虽然之前建议使用 Lambda 支持的自定义资源来检索 AMI ID，但现在建议使用 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 资源名称（ARN）。  
由于 `Handler` 属性使用 Python 源代码，因此将其设置为 `index.handler`。有关使用内联函数源代码时可接受的处理程序标识符的更多信息，请参阅 [ AWS::Lambda::Function 代码](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` 将其 ARN 链接到 Lambda 函数。它将为创建和更新操作实现 60 秒的等待时间，如 `ServiceTimeout` 中设置的那样。只有当属性被修改时，才会调用资源进行更新操作。

### 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>

您必须拥有 IAM 权限才能使用所有相应的服务，例如 Lambda 和 CloudFormation。

## 启动堆栈
<a name="walkthrough-lambda-backed-custom-resources-createfunction-createstack"></a>

**要创建 堆栈，请执行以下操作：**

1. 从[示例模板](#walkthrough-lambda-backed-custom-resources-sample-template)部分找到您喜欢的模板（YAML 或 JSON），并将其保存到您的计算机，名称为 `samplelambdabackedcustomresource.template`。

1. 通过以下网址打开 CloudFormation 控制台：[https://console.aws.amazon.com/cloudformation/](https://console.aws.amazon.com/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)