

# 使用 CloudFormation 提供的资源类型扩展模板的功能
使用 CloudFormation 提供的资源类型

CloudFormation 提供了多种资源类型，您可以在堆栈模板中使用这些资源类型来扩展其功能，超越简单的堆栈模板。

这些资源类型包括：


| 资源类型 | 说明 | 文档 | 
| --- | --- | --- | 
|  自定义资源  |  您可以利用此 `AWS::CloudFormation::CustomResource` 资源类型来创建自定义资源，从而执行特定的预置任务或包含不能通过 CloudFormation 资源类型提供的资源。  |  [自定义资源](template-custom-resources.md) | 
|  宏  |  此 `AWS::CloudFormation::Macro` 资源类型定义了可以在 CloudFormation 模板上执行自定义处理的可复用代码片段。宏可以在堆栈创建或更新期间修改模板、生成其他资源或执行其他自定义操作。  | [模板宏](template-macros.md) | 
|  嵌套堆栈  |  借助此 `AWS::CloudFormation::Stack` 资源类型，您可以在 CloudFormation 模板中创建嵌套堆栈，从而提高堆栈架构的模块化和可复用性水平。  | [嵌套堆栈](using-cfn-nested-stacks.md) | 
|  堆栈集  |  此 `AWS::CloudFormation::StackSet` 资源类型会创建或更新一种 CloudFormation 堆栈集，也就是一个可以跨多个 Amazon Web Services 账户 和区域部署的堆栈容器。  | [使用 StackSets 管理堆栈](what-is-cfnstacksets.md) | 
|  等待条件  |  在满足特定条件之前（例如，长时间运行进程的成功完成或外部资源的可用性），该 `AWS::CloudFormation::WaitCondition` 资源类型会暂停堆栈的创建或更新。  | [等待条件](using-cfn-waitcondition.md) | 
|  等待条件句柄  |  该 `AWS::CloudFormation::WaitConditionHandle` 资源类型可与 `AWS::CloudFormation::WaitCondition` 资源类型配合使用，它提供了预签名 URL，用于发送表示特定条件已得到满足的信号。这些信号允许堆栈创建或更新过程继续进行下去。  | [等待条件](using-cfn-waitcondition.md) | 

# 使用自定义资源创建自定义预置逻辑
自定义资源

自定义资源为您提供了一种将自定义预置逻辑写入 CloudFormation 模板，并让 CloudFormation 在创建、更新（如果更改了自定义资源）或删除堆栈期间运行该逻辑的方式。预置要求涉及无法使用 CloudFormation 的内置资源类型表达的复杂逻辑或工作流程时，这会很有帮助。

例如，您可能需要包含不可作为 CloudFormation 资源类型的资源。您可以使用自定义资源包含这些资源。这样，您仍然可以在一个堆栈中管理所有相关资源。

要在 CloudFormation 模板中定义自定义资源，可以使用 `AWS::CloudFormation::CustomResource` 或 `Custom::MyCustomResourceTypeName` 资源类型。自定义资源需要一个属性，即服务令牌，它指定 CloudFormation 发送请求的目标，如 Amazon SNS 主题或 Lambda 函数。

以下主题提供有关如何使用自定义资源的信息。

**Topics**
+ [

## 自定义资源的工作原理
](#how-custom-resources-work)
+ [

## 响应超时
](#response-timeout)
+ [

# CloudFormation 自定义资源请求和响应参考
](crpg-ref.md)
+ [

# Amazon SNS 支持的自定义资源
](template-custom-resources-sns.md)
+ [

# Lambda 支持的自定义资源
](template-custom-resources-lambda.md)

**注意**  
CloudFormation 注册表和自定义资源各有其优点。自定义资源具有以下优点：  
您不需要注册资源。
您无需注册即可将整个资源作为模板的一部分。
支持 `Create`、`Update` 和 `Delete` 操作
基于注册表的资源具有以下优点：  
支持对第三方应用程序资源进行建模、预置和管理
支持 `Create`、`Read`、`Update`、`Delete` 和 `List` (`CRUDL`) 操作
支持对私有和第三方资源类型的偏差检测
与自定义资源不同，基于注册表的资源不需要关联 Amazon SNS 主题或 Lambda 函数即可执行 `CRUDL` 操作。有关更多信息，请参阅 [通过 CloudFormation 注册表管理扩展](registry.md)。

## 自定义资源的工作原理


设置新自定义资源的一般过程包括以下步骤。这些步骤涉及两个角色：拥有该自定义资源的*自定义资源提供者*和创建包含某个自定义资源类型的模板的*模板开发者*。这两个角色可以是同一个人，但如果不是同一个人，自定义资源提供者应该与模板开发者合作。

1. 自定义资源提供者编写的逻辑决定了如何处理来自 CloudFormation 的请求以及如何对自定义资源执行操作。

1. 自定义资源提供者可创建 Amazon SNS 主题或 Lambda 函数，然后 CloudFormation 可向该主题或函数发送请求。该 Amazon SNS 主题或 Lambda 函数必须位于堆栈所在区域。

1. 自定义资源提供者将 Amazon SNS 主题 ARN 或 Lambda 函数 ARN 提供给模板开发者。

1. 模板开发者定义其 CloudFormation 模板中的自定义资源。这包括服务令牌和任何输入数据参数。服务令牌和输入数据的结构由自定义资源提供者定义。服务令牌指定了 Amazon SNS 主题 ARN 或 Lambda 函数 ARN，并且始终为必填项，而输入数据是可选的，具体取决于自定义资源。

现在，每当有人使用模板创建、更新或删除该自定义资源时，CloudFormation 都会向指定的服务令牌发送请求并等待响应，然后才会执行堆栈操作。

以下总结了使用该模板创建堆栈的流程：

1. CloudFormation 向指定的服务令牌发送请求。该请求包含请求类型以及自定义资源用于向其发送请求的预签名 Amazon S3 存储桶 URL 等信息。有关请求中包含的内容的更多信息，请参阅[CloudFormation 自定义资源请求和响应参考](crpg-ref.md)。

   以下示例数据演示了 CloudFormation 在 `Create` 请求中包含的内容：在本示例中，`ResourceProperties` 允许 CloudFormation 创建发送到 Lambda 函数的自定义负载。

   ```
   {
      "RequestType" : "Create",
      "RequestId" : "unique id for this create request",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "ResponseURL" : "http://pre-signed-S3-url-for-response",
      "ResourceType" : "Custom::TestResource",
      "LogicalResourceId" : "MyTestResource",
      "ResourceProperties" : {
         "Name" : "Value",
         "List" : [ "1", "2", "3" ]
      }
   }
   ```

1. 自定义资源提供商处理 CloudFormation 请求并向预签名 URL 返回 `SUCCESS` 或 `FAILED` 响应。custom resource provider 提供采用 JSON 格式文件的响应并将响应上传到预签名的 S3 URL。有关更多信息，请参阅*《Amazon Simple Storage Service 用户指南》*中的[使用预签名 URL 上传对象](https://docs.amazonaws.cn/AmazonS3/latest/userguide/PresignedUrlUploadObject.html)。

   在响应中，custom resource provider还可以包含template developer可以访问的名称-值对。例如，如果请求成功，响应可以包含输出数据，如果请求失败，可以包含错误消息。有关响应的更多信息，请参阅[CloudFormation 自定义资源请求和响应参考](crpg-ref.md)。
**重要**  
如果名称值对包含敏感信息，应使用 `NoEcho` 字段遮蔽自定义资源的输出。否则，这些值通过显示属性值的 API（例如 `DescribeStackEvents`）可见。  
有关使用 `NoEcho` 遮蔽敏感信息的更多信息，请参阅 [请勿将凭证嵌入您的模板](security-best-practices.md#creds) 最佳实践。

   custom resource provider负责侦听和响应请求。例如，对于 Amazon SNS 通知，自定义资源提供商必须侦听并响应发送到特定主题 ARN 的通知。CloudFormation 在预签名 URL 位置等待并侦听响应。

   以下示例数据说明自定义资源在响应中可以包含的内容：

   ```
   {
      "Status" : "SUCCESS",
      "RequestId" : "unique id for this create request",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "LogicalResourceId" : "MyTestResource",
      "PhysicalResourceId" : "TestResource1",
      "Data" : {
         "OutputName1" : "Value1",
         "OutputName2" : "Value2",
      }
   }
   ```

1. 获得 `SUCCESS` 响应后，CloudFormation 继续堆栈操作。如果收到 `FAILED` 响应或未返回任何响应，则操作失败。来自自定义资源的所有输出数据都存储在预签名 URL 位置。模板开发人员可使用 [Fn::GetAtt](resources-section-structure.md#resource-properties-getatt) 函数检索该数据。

**注意**  
如果您使用 Amazon PrivateLink，则 VPC 中的自定义资源必须能够访问特定于 CloudFormation 的 S3 存储桶。自定义资源必须将响应发送到预签名的 Amazon S3 URL。如果这些资源不能向 Amazon S3 发送响应，则 CloudFormation 将不会收到响应，并且堆栈操作会失败。有关更多信息，请参阅 [使用接口端点 (Amazon PrivateLink) 访问 Amazon CloudFormation](vpc-interface-endpoints.md)。

## 响应超时


自定义资源的默认超时为 3600 秒（1 小时）。如果在此期间内没有收到任何响应，则该堆栈操作将会失败。

您可以根据预期从自定义资源收到响应将需要的时间来调整超时值。例如，在预置将调用某个 Lambda 函数的自定义资源并且预期会在五分钟内收到响应时，您可以在堆栈模板中通过指定 `ServiceTimeout` 属性将超时设置为五分钟。有关更多信息，请参阅 [CloudFormation 自定义资源请求和响应参考](crpg-ref.md)。这样，如果 Lambda 函数出现错误导致其停滞，CloudFormation 将在五分钟后使堆栈操作失败，而不是等待整整一个小时。

但是，请注意不要将超时值设置得过低。为避免意外超时，请确保您的自定义资源有足够的时间来执行必要的操作并返回响应。

# CloudFormation 自定义资源请求和响应参考
请求和响应参考

Amazon CloudFormation 通过与您的自定义资源提供商通信的请求-响应协议管理自定义资源。每个请求都包含一个请求类型（`Create`、`Update` 或 `Delete`），并遵循这一高级工作流：

1. 模板开发者在模板中使用 `ServiceToken` 和 `ServiceTimeout` 定义自定义资源并启动堆栈操作。

1. CloudFormation 通过 SNS 或 Lambda 向自定义资源提供商发送 JSON 请求。

1. 该自定义资源提供商会处理请求，并在超时时间到期之前向预签名的 Amazon S3 存储桶 URL 返回一个 JSON 响应。

1. CloudFormation 会读取响应并继续堆栈操作。如果在超时期限结束之前没有收到任何响应，则该请求被视为不成功，并且堆栈操作将会失败。

有关更多信息，请参阅 [自定义资源的工作原理](template-custom-resources.md#how-custom-resources-work)。

本节介绍每种请求类型的结构、参数和预期响应。

**注意**  
响应正文的总大小不能超过 4096 字节。

## 模板设置


在模板中定义自定义资源时，模板开发者会使用具有以下属性的 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html)：

`ServiceToken`  
来自与堆栈相同区域的 Amazon SNS 主题 ARN 或 Lambda 函数 ARN。  
*是否必需*：是  
*类型*：字符串

`ServiceTimeout`  
自定义资源操作超时前的最长时间（以秒为单位）。此值必须介于 1 和 3600 之间。默认值：3600 秒（1 小时）。  
*必需*：否  
*类型*：字符串

支持其他资源属性。资源属性将以 `ResourceProperties` 形式包含在请求中。自定义资源提供商必须确定哪些属性有效以及它们的可接受值。

## 请求对象


------
#### [ Create ]

当模板开发者创建包含自定义资源的堆栈时，CloudFormation 会发送一条将 `RequestType` 设置为 `Create` 的请求。

创建请求包含以下字段：

`RequestType`  
`Create`.  
*是否必需*：是  
*类型*：字符串

`RequestId`  
请求的唯一 ID。  
将 `StackId` 与 `RequestId` 组合形成一个值，您可以使用该值唯一标识对特定自定义资源的请求。  
*是否必需*：是  
*类型*：字符串

`StackId`  
标识包含自定义资源的堆栈的 Amazon 资源名称（ARN）。  
将 `StackId` 与 `RequestId` 组合形成一个值，您可以使用该值唯一标识对特定自定义资源的请求。  
*是否必需*：是  
*类型*：字符串

`ResponseURL`  
响应 URL 标识一个预签名 S3 存储桶，该存储桶接收从自定义资源提供商到 CloudFormation 的响应。  
*是否必需*：是  
*类型*：字符串

`ResourceType`  
CloudFormation 模板中模板开发人员选择的自定义资源的资源类型。自定义资源类型名称的长度最多为 60 个字符，并且可包含字母数字字符和以下字符：`_@-`。  
*是否必需*：是  
*类型*：字符串

`LogicalResourceId`  
CloudFormation 模板中模板开发人员选择的自定义资源的名称（逻辑 ID）。  
*是否必需*：是  
*类型*：字符串

`ResourceProperties`  
此字段包含模板开发人员发送的 `Properties` 对象的内容。其内容由自定义资源提供商定义。  
*必需*：否  
*类型*：JSON 对象

*示例*

```
{
   "RequestType" : "Create",
   "RequestId" : "unique-request-id",
   "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/id",
   "ResponseURL" : "pre-signed-url-for-create-response",
   "ResourceType" : "Custom::MyCustomResourceType",
   "LogicalResourceId" : "resource-logical-id",
   "ResourceProperties" : {
      "key1" : "string",
      "key2" : [ "list" ],
      "key3" : { "key4" : "map" }
   }
}
```

------
#### [ Update ]

当模板开发者更改模板中某个自定义资源的属性并更新堆栈时，CloudFormation 会向自定义资源提供者发送一条将 `RequestType` 设置为 `Update` 的请求。这意味着自定义资源代码无需检测资源中的更改，因为在请求类型为 `Update` 时，代码会知道其属性已经更改。

更新请求包含以下字段：

`RequestType`  
`Update`.  
*是否必需*：是  
*类型*：字符串

`RequestId`  
请求的唯一 ID。  
将 `StackId` 与 `RequestId` 组合形成一个值，您可以使用该值唯一标识对特定自定义资源的请求。  
*是否必需*：是  
*类型*：字符串

`StackId`  
标识包含自定义资源的堆栈的 Amazon 资源名称（ARN）。  
将 `StackId` 与 `RequestId` 组合形成一个值，您可以使用该值唯一标识对特定自定义资源的请求。  
*是否必需*：是  
*类型*：字符串

`ResponseURL`  
响应 URL 标识一个预签名 S3 存储桶，该存储桶接收从自定义资源提供商到 CloudFormation 的响应。  
*是否必需*：是  
*类型*：字符串

`ResourceType`  
CloudFormation 模板中模板开发人员选择的自定义资源的资源类型。自定义资源类型名称的长度最多为 60 个字符，并且可包含字母数字字符和以下字符：`_@-`。更新期间不能更改类型。  
*是否必需*：是  
*类型*：字符串

`LogicalResourceId`  
CloudFormation 模板中模板开发人员选择的自定义资源的名称（逻辑 ID）。  
*是否必需*：是  
*类型*：字符串

`PhysicalResourceId`  
自定义资源提供商定义的物理 ID，该 ID 对于该提供程序是唯一的。  
*是否必需*：是  
*类型*：字符串

`ResourceProperties`  
此字段包含模板开发人员发送的 `Properties` 对象的内容。其内容由自定义资源提供商定义。  
*必需*：否  
*类型*：JSON 对象

`OldResourceProperties`  
仅用于 `Update` 请求。模板开发人员之前在 CloudFormation 模板中声明的资源属性值。  
*是否必需*：是  
*类型*：JSON 对象

*示例*

```
{
   "RequestType" : "Update",
   "RequestId" : "unique-request-id",
   "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/id",
   "ResponseURL" : "pre-signed-url-for-update-response",
   "ResourceType" : "Custom::MyCustomResourceType",
   "LogicalResourceId" : "resource-logical-id",
   "PhysicalResourceId" : "provider-defined-physical-id",
   "ResourceProperties" : {
      "key1" : "new-string",
      "key2" : [ "new-list" ],
      "key3" : { "key4" : "new-map" }
   },
   "OldResourceProperties" : {
      "key1" : "string",
      "key2" : [ "list" ],
      "key3" : { "key4" : "map" }
   }
}
```

------
#### [ Delete ]

当模板开发者删除该堆栈或从模板中移除该自定义资源然后更新堆栈时，CloudFormation 会发送一条将 `RequestType` 设置为 `Delete` 的请求。

删除请求包含以下字段：

`RequestType`  
`Delete`.  
*是否必需*：是  
*类型*：字符串

`RequestId`  
请求的唯一 ID。  
*是否必需*：是  
*类型*：字符串

`StackId`  
标识包含自定义资源的堆栈的 Amazon 资源名称（ARN）。  
*是否必需*：是  
*类型*：字符串

`ResponseURL`  
响应 URL 标识一个预签名 S3 存储桶，该存储桶接收从自定义资源提供商到 CloudFormation 的响应。  
*是否必需*：是  
*类型*：字符串

`ResourceType`  
CloudFormation 模板中模板开发人员选择的自定义资源的资源类型。自定义资源类型名称的长度最多为 60 个字符，并且可包含字母数字字符和以下字符：`_@-`。  
*是否必需*：是  
*类型*：字符串

`LogicalResourceId`  
CloudFormation 模板中模板开发人员选择的自定义资源的名称（逻辑 ID）。  
*是否必需*：是  
*类型*：字符串

`PhysicalResourceId`  
自定义资源提供商定义的物理 ID，该 ID 对于该提供程序是唯一的。  
*是否必需*：是  
*类型*：字符串

`ResourceProperties`  
此字段包含模板开发人员发送的 `Properties` 对象的内容。其内容由自定义资源提供商定义。  
*必需*：否  
*类型*：JSON 对象

*示例*

```
{
   "RequestType" : "Delete",
   "RequestId" : "unique-request-id",
   "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/id",
   "ResponseURL" : "pre-signed-url-for-delete-response",
   "ResourceType" : "Custom::MyCustomResourceType",
   "LogicalResourceId" : "resource-logical-id",
   "PhysicalResourceId" : "provider-defined-physical-id",
   "ResourceProperties" : {
      "key1" : "string",
      "key2" : [ "list" ],
      "key3" : { "key4" : "map" }
   }
}
```

------

## 响应对象


对于所有的请求类型，自定义资源提供商会向预签名 URL 发送一条响应。如果自定义资源提供商没有发送响应，CloudFormation 会等待直到操作超时。

该响应必须为具有以下字段的 JSON 对象：

`Status`  
必须是 `SUCCESS` 或 `FAILED`。  
*是否必需*：是  
*类型*：字符串

`RequestId`  
请求的唯一 ID。完全按照请求中显示的值复制该值。  
*是否必需*：是  
*类型*：字符串

`StackId`  
标识包含自定义资源的堆栈的 Amazon 资源名称（ARN）。完全按照请求中显示的值复制该值。  
*是否必需*：是  
*类型*：字符串

`LogicalResourceId`  
CloudFormation 模板中模板开发人员选择的自定义资源的名称（逻辑 ID）。完全按照请求中显示的值复制该值。  
*是否必需*：是  
*类型*：字符串

`PhysicalResourceId`  
该值应该为自定义资源供应商的唯一标识符，并且最大为 1KB。该值必须为非空字符串，并且对于同一资源的所有响应都必须相同。  
更新自定义资源时，`PhysicalResourceId` 返回的值决定了更新行为。如果值保持相同，则 CloudFormation 将其视为正常更新。如果值发生更改，CloudFormation 会将该更新解读为替换，并向旧资源发送删除请求。有关更多信息，请参阅 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html)。  
*是否必需*：是  
*类型*：字符串

`Reason`  
描述失败响应的原因。  
如果 `Status` 为 `FAILED`，则必需。否则，它是可选的。  
*必需*：条件  
*类型*：字符串

`NoEcho`  
指示在使用 `Fn::GetAtt` 函数检索时是否遮蔽自定义资源的输出。如果设置为 `true`，则*除模板 `Metadata` 区段中存储的值*外，所有返回值都将用星号 (\$1\$1\$1\$1\$1) 遮蔽。CloudFormation 不会转换、修改或编辑您在 `Metadata` 部分中包含的任何信息。默认值为 `false`。  
有关使用 `NoEcho` 遮蔽敏感信息的更多信息，请参阅 [请勿将凭证嵌入您的模板](security-best-practices.md#creds) 最佳实践。  
仅适用于 `Create` 和 `Update` 响应。不支持 `Delete` 响应。  
*必需*：否  
*类型*：布尔值

`Data`  
自定义资源提供商定义的名称值对，随响应一起发送。您可以使用 `Fn::GetAtt` 在模板中按名称访问此处提供的值。  
仅适用于 `Create` 和 `Update` 响应。不支持 `Delete` 响应。  
如果名称值对包含敏感信息，应使用 `NoEcho` 字段遮蔽自定义资源的输出。否则，这些值通过显示属性值的 API（例如 `DescribeStackEvents`）可见。
*必需*：否  
*类型*：JSON 对象

### 成功响应示例


#### `Create` 和 `Update` 响应


```
{
   "Status": "SUCCESS",
   "RequestId": "unique-request-id",
   "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/name/id",
   "LogicalResourceId": "resource-logical-id", 
   "PhysicalResourceId": "provider-defined-physical-id",
   "NoEcho": true,
   "Data": {
      "key1": "value1",
      "key2": "value2"
   }
}
```

#### `Delete` 响应


```
{
   "Status": "SUCCESS",
   "RequestId": "unique-request-id",
   "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/name/id",
   "LogicalResourceId": "resource-logical-id", 
   "PhysicalResourceId": "provider-defined-physical-id"
}
```

### 失败的响应示例


```
{
   "Status": "FAILED",
   "RequestId": "unique-request-id",
   "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/name/id",
   "LogicalResourceId": "resource-logical-id",
   "PhysicalResourceId": "provider-defined-physical-id",
   "Reason": "Required failure reason string"
}
```

# Amazon SNS 支持的自定义资源


以下主题展示了如何使用服务令牌配置自定义资源，该令牌指定了 CloudFormation 向其发送请求的 Amazon SNS 主题。您还将了解自定义资源堆栈创建、更新和删除过程中发送和接收的事件和消息的顺序。

通过自定义资源和 Amazon SNS，您可以启用方案，例如向堆栈添加新资源和向堆栈注入动态数据。例如，创建堆栈时，CloudFormation 可以向被 Amazon EC2 实例上运行的应用程序监控的主题发送 `Create` 请求。Amazon SNS 通知触发应用程序以执行其他预置任务，如检索列在白名单中的弹性 IP 池。完成后，应用程序发送响应（和任何输出数据），通知 CloudFormation 继续执行堆栈操作。

将 Amazon SNS 主题指定为自定义资源的目标时，CloudFormation 会在涉及自定义资源的堆栈操作期间，向指定的 SNS 主题发送消息。要处理这些消息并执行必要的操作，必须有一个订阅 SNS 主题的受支持端点。

有关自定义资源及其工作原理的介绍，请参阅[自定义资源的工作原理](template-custom-resources.md#how-custom-resources-work)。有关 Amazon SNS 及其工作原理的信息，请参阅 [Amazon Simple Notification Service Developer Guide](https://docs.amazonaws.cn/sns/latest/dg/)。

## 使用 Amazon SNS 创建自定义资源


**Topics**
+ [

### 步骤 1：堆栈创建
](#crpg-walkthrough-stack-creation)
+ [

### 步骤 2：堆栈更新
](#crpg-walkthrough-stack-updates)
+ [

### 步骤 3：堆栈删除
](#crpg-walkthrough-stack-deletion)

### 步骤 1：堆栈创建


1. <a name="crpg-walkthrough-stack-creation-customer-template"></a>模板开发人员创建包含自定义资源的 CloudFormation 堆栈。

   在以下模板示例中，对具有逻辑 ID `Custom::SeleniumTester` 的自定义资源使用自定义资源类型名称 `MySeleniumTest`。自定义资源类型名称必须是字母数字字符，最大长度为 60 个字符。

   自定义资源类型是使用服务令牌、可选提供商特定属性以及由自定义资源提供商定义的可选 [Fn::GetAtt](resources-section-structure.md#resource-properties-getatt) 属性声明的。使用这些属性和特性可以将信息从template developer传递给custom resource provider，反之亦然。服务令牌指定资源提供商已配置的 Amazon SNS 主题。

   ```
   {
      "AWSTemplateFormatVersion" : "2010-09-09",
      "Resources" : {
         "MySeleniumTest" : {
            "Type": "Custom::SeleniumTester",
            "Version" : "1.0",
            "Properties" : {
               "ServiceToken": "arn:aws:sns:us-west-2:123456789012:CRTest",
               "seleniumTester" : "SeleniumTest()",
               "endpoints" : [ "http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com" ],
               "frequencyOfTestsPerHour" : [ "3", "2", "4" ]
            }
         }
      },
      "Outputs" : {
         "topItem" : {
            "Value" : { "Fn::GetAtt" : ["MySeleniumTest", "resultsPage"] }
         },
         "numRespondents" : {
            "Value" : { "Fn::GetAtt" : ["MySeleniumTest", "lastUpdate"] }
         }
      }
   }
   ```
**注意**  
在提供商响应 CloudFormation 期间，自定义资源提供商会返回使用 `Fn::GetAtt` 访问的数据的名称和值。如果custom resource provider是第三方，则template developer必须从custom resource provider获取这些返回值的名称。

1. <a name="crpg-walkthrough-stack-creation-provider-request"></a>CloudFormation 使用 `"RequestType" : "Create"` 向资源提供者发送一条 Amazon SNS 通知，其中包含有关堆栈的信息、堆栈模板中的自定义资源属性和用于响应的 S3 URL。

   用于发送通知的 SNS 主题嵌入在模板的 `ServiceToken` 属性中。要避免使用硬编码值，模板开发人员可以使用模板参数，以便在启动堆栈时输入值。

   以下示例显示一个自定义资源 `Create` 请求，其中包含一个用 `Custom::SeleniumTester` 的 `LogicalResourceId` 创建的自定义资源类型名称 `MySeleniumTester`：

   ```
   {
      "RequestType" : "Create",
      "RequestId" : "unique-request-id",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "ResponseURL" : "http://pre-signed-S3-url-for-response",
      "ResourceType" : "Custom::SeleniumTester",
      "LogicalResourceId" : "MySeleniumTester",
      "ResourceProperties" : {
         "seleniumTester" : "SeleniumTest()",
         "endpoints" : [ "http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com" ],
         "frequencyOfTestsPerHour" : [ "3", "2", "4" ]
      }
   }
   ```

   有关 `Create` 请求的请求对象的详细信息，请参阅 [请求和响应参考](crpg-ref.md) 主题。

1. <a name="crpg-walkthrough-stack-creation-provider-response"></a>custom resource provider处理template developer发送的数据，并确定 `Create` 请求是否已成功。然后，资源提供者使用 CloudFormation 发送的 S3 URL 来发送 `SUCCESS` 或 `FAILED` 响应。

   根据响应类型，CloudFormation 将需要不同的响应字段。有关特定请求类型的响应字段的信息，请参阅 [请求和响应参考](crpg-ref.md) 部分中该请求类型的文档。

   在响应创建或更新请求时，custom resource provider 可以在响应的 `Data` 字段中返回数据元素。这些是名称/值对，*名称*对应于用于堆栈模板中的自定义资源的 `Fn::GetAtt` 属性。该*值*是模板开发人员对具有此属性名称的资源调用 `Fn::GetAtt` 时返回的数据。

   以下是自定义资源响应的示例：

   ```
   {
      "Status" : "SUCCESS",
      "RequestId" : "unique-request-id",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "LogicalResourceId" : "MySeleniumTester",
      "PhysicalResourceId" : "Tester1",
      "Data" : {
         "resultsPage" : "http://www.myexampledomain/test-results/guid",
         "lastUpdate" : "2012-11-14T03:30Z"
      }
   }
   ```

   有关 `Create` 请求的响应对象的详细信息，请参阅 [请求和响应参考](crpg-ref.md) 主题。

   `StackId`、`RequestId` 和 `LogicalResourceId` 字段必须从请求中逐字复制。

1. <a name="crpg-walkthrough-stack-creation-stack-status"></a> CloudFormation 将堆栈状态声明为 `CREATE_COMPLETE` 或 `CREATE_FAILED`。如果堆栈已成功创建，模板开发人员通过 [Fn::GetAtt](resources-section-structure.md#resource-properties-getatt) 访问已创建的自定义资源的输出值，可以使用这些值。

   例如，用于举例说明的自定义资源模板使用 `Fn::GetAtt` 将资源输出复制到堆栈输出：

   ```
   "Outputs" : {
      "topItem" : {
         "Value" : { "Fn::GetAtt" : ["MySeleniumTest", "resultsPage"] }
      },
      "numRespondents" : {
         "Value" : { "Fn::GetAtt" : ["MySeleniumTest", "lastUpdate"] }
      }
   }
   ```

### 步骤 2：堆栈更新


要更新现有堆栈，您必须提交一个指定了堆栈资源属性更新的模板，如下面的示例所示。CloudFormation 只更新模板中指定了更改的资源。有关更多信息，请参阅 [理解堆栈资源的更新行为](using-cfn-updating-stacks-update-behaviors.md)。

您可以更新需要替换基础物理资源的自定义资源。在 CloudFormation 模板中更新某个自定义资源时，CloudFormation 会向该自定义资源发送更新请求。如果需要替换自定义资源，新的自定义资源必须使用新的物理 ID 发送响应。CloudFormation 收到响应时，会比较新旧自定义资源的 `PhysicalResourceId`。如果不同，CloudFormation 会将更新视为替换，并向旧资源发送删除请求，如 [步骤 3：堆栈删除](#crpg-walkthrough-stack-deletion) 中所示。

**注意**  
如果您没有对自定义资源进行更改，CloudFormation 在堆栈更新过程中不会向资源发送请求。

1. <a name="crpg-walkthrough-stack-updates-customer-template"></a>template developer启动对包含自定义资源的堆栈的更新。在更新期间，template developer可以在堆栈模板中指定新属性。

   下面是一个使用自定义资源类型的堆栈模板 `Update` 示例：

   ```
   {
      "AWSTemplateFormatVersion" : "2010-09-09",
      "Resources" : {
         "MySeleniumTest" : {
            "Type": "Custom::SeleniumTester",
            "Version" : "1.0",
            "Properties" : {
               "ServiceToken": "arn:aws:sns:us-west-2:123456789012:CRTest",
               "seleniumTester" : "SeleniumTest()",
               "endpoints" : [ "http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com",
                  "http://mynewsite.com" ],
               "frequencyOfTestsPerHour" : [ "3", "2", "4", "3" ]
            }
         }
      },
      "Outputs" : {
         "topItem" : {
            "Value" : { "Fn::GetAtt" : ["MySeleniumTest", "resultsPage"] }
         },
         "numRespondents" : {
            "Value" : { "Fn::GetAtt" : ["MySeleniumTest", "lastUpdate"] }
         }
      }
   }
   ```

1. <a name="crpg-walkthrough-stack-updates-provider-request"></a>CloudFormation 会使用 `"RequestType" : "Update"` 向资源提供者发送一条 Amazon SNS 通知，其中包含与 `Create` 调用类似的信息，不同的是，`OldResourceProperties` 字段包含旧的资源属性，而 ResourceProperties 包含已更新的（如果有）资源属性。

   以下是一个 `Update` 请求的示例：

   ```
   {
      "RequestType" : "Update",
      "RequestId" : "unique-request-id",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "ResponseURL" : "http://pre-signed-S3-url-for-response",
      "ResourceType" : "Custom::SeleniumTester",
      "LogicalResourceId" : "MySeleniumTester",
      "PhysicalResourceId" : "Tester1",
      "ResourceProperties" : {
         "seleniumTester" : "SeleniumTest()",
         "endpoints" : [ "http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com",
            "http://mynewsite.com" ],
         "frequencyOfTestsPerHour" : [ "3", "2", "4", "3" ]
      },
      "OldResourceProperties" : {
         "seleniumTester" : "SeleniumTest()",
         "endpoints" : [ "http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com" ],
         "frequencyOfTestsPerHour" : [ "3", "2", "4" ]
      }
   }
   ```

   有关 `Update` 请求的请求对象的详细信息，请参阅 [请求和响应参考](crpg-ref.md) 主题。

1. <a name="crpg-walkthrough-stack-updates-provider-response"></a>自定义资源提供商处理由 CloudFormation 发送的数据。自定义资源执行更新并向 S3 URL 发送 `SUCCESS` 或 `FAILED` 响应。然后，CloudFormation 比较新旧自定义资源的 `PhysicalResourceIDs`。如果不同，CloudFormation 将认为更新需要替换，并向旧资源发送删除请求。下面的示例说明custom resource provider对 `Update` 请求的响应。

   ```
   {
      "Status" : "SUCCESS",
      "RequestId" : "unique-request-id",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "LogicalResourceId" : "MySeleniumTester",
      "PhysicalResourceId" : "Tester2"
   }
   ```

   有关 `Update` 请求的响应对象的详细信息，请参阅 [请求和响应参考](crpg-ref.md) 主题。

   `StackId`、`RequestId` 和 `LogicalResourceId` 字段必须从请求中逐字复制。

1. <a name="crpg-walkthrough-stack-updates-stack-status"></a>CloudFormation 将堆栈状态声明为 `UPDATE_COMPLETE` 或 `UPDATE_FAILED`。如果更新失败，堆栈将回滚。如果堆栈更新成功，template developer可以使用 `Fn::GetAtt` 访问已创建自定义资源的任何新输出值。

### 步骤 3：堆栈删除


1. <a name="crpg-walkthrough-stack-deletion-customer-template"></a>模板开发人员删除包含自定义资源的堆栈。CloudFormation 将获取堆栈模板中指定的当前属性及 SNS 主题，并准备向自定义资源提供商发出请求。

1. <a name="crpg-walkthrough-stack-deletion-provider-request"></a>CloudFormation 使用 `"RequestType" : "Delete"` 向资源提供者发送一条 Amazon SNS 通知，其中包含有关堆栈的当前信息、堆栈模板中的自定义资源属性和用于响应的 S3 URL。

   只要您删除堆栈或进行自定义资源的删除或替换更新，CloudFormation 都会比较新旧自定义资源的 `PhysicalResourceId`。如果不同，CloudFormation 会将更新视为替换，并向旧资源 (`OldPhysicalResource`) 发送删除请求，如下面的 `Delete` 请求示例所示。

   ```
   {
      "RequestType" : "Delete",
      "RequestId" : "unique-request-id",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "ResponseURL" : "http://pre-signed-S3-url-for-response",
      "ResourceType" : "Custom::SeleniumTester",
      "LogicalResourceId" : "MySeleniumTester",
      "PhysicalResourceId" : "Tester1",
      "ResourceProperties" : {
         "seleniumTester" : "SeleniumTest()",
         "endpoints" : [ "http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com",
            "http://mynewsite.com" ],
         "frequencyOfTestsPerHour" : [ "3", "2", "4", "3" ]
      }
   }
   ```

   有关 `Delete` 请求的请求对象的详细信息，请参阅 [请求和响应参考](crpg-ref.md) 主题。

   `DescribeStackResource`、`DescribeStackResources` 和 `ListStackResources` 显示用户定义的名称 (如果已指定)。

1. <a name="crpg-walkthrough-stack-deletion-provider-response"></a>自定义资源提供商处理 CloudFormation 发送的数据，并确定 `Delete` 请求是否已成功。然后，资源提供者使用 CloudFormation 发送的 S3 URL 来发送 `SUCCESS` 或 `FAILED` 响应。要成功删除带自定义资源的堆栈，custom resource provider 必须成功响应删除请求。

   以下是custom resource provider响应 `Delete` 请求的示例：

   ```
   {
      "Status" : "SUCCESS",
      "RequestId" : "unique-request-id",
      "StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/mystack/5b918d10-cd98-11ea-90d5-0a9cd3354c10",
      "LogicalResourceId" : "MySeleniumTester",
      "PhysicalResourceId" : "Tester1"
   }
   ```

   有关 `Delete` 请求的响应对象的详细信息，请参阅 [请求和响应参考](crpg-ref.md) 主题。

   `StackId`、`RequestId` 和 `LogicalResourceId` 字段必须从请求中逐字复制。

1. <a name="crpg-walkthrough-stack-updates-stack-status-delete"></a>CloudFormation 将堆栈状态声明为 `DELETE_COMPLETE` 或 `DELETE_FAILED`。

# Lambda 支持的自定义资源


如果将 Lambda 函数与自定义资源关联，则在创建、更新或删除自定义资源时就会调用该函数。CloudFormation 将调用 Lambda API 来调用此函数，并将所有请求数据（如请求类型和资源属性）传递给此函数。通过将 Lambda 函数的强大功能和可自定义性与 CloudFormation 结合，您可以支持各种方案，例如在堆栈创建期间动态查找 AMI ID，或者实现和使用实用工具函数（例如，字符串反转函数）。

有关自定义资源及其工作原理的介绍，请参阅[自定义资源的工作原理](template-custom-resources.md#how-custom-resources-work)。

**Topics**
+ [

# 演练：使用 Lambda 支持的自定义资源创建延迟机制
](walkthrough-lambda-backed-custom-resources.md)
+ [

# `cfn-response` 模块
](cfn-lambda-function-code-cfnresponsemodule.md)

# 演练：使用 Lambda 支持的自定义资源创建延迟机制
演练：使用 Lambda 支持的自定义资源创建延迟机制

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

**注意**  
虽然之前建议使用 Lambda 支持的自定义资源来检索 AMI ID，但现在建议使用 Amazon 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)

## 概述


此演练中使用的示例堆栈模板创建了 Lambda 支持的自定义资源。此自定义资源在堆栈创建过程中引入了可配置的延迟（默认为 60 秒）。只有在修改自定义资源的属性时，才会在堆栈更新期间出现延迟。

此模板预置以下资源：
+ 自定义资源，
+ Lambda 函数，和
+ 使 Lambda 能够将日志写入到 CloudWatch 的 IAM 角色。

它还定义了两个输出：
+ 函数等待的实际时间。
+ 每次执行 Lambda 函数期间生成的唯一标识符。



**注意**  
CloudFormation 是一项免费服务，但 Lambda 会根据您的函数请求数量和代码执行时间收费。有关 Lambda 定价的更多信息，请参阅 [Amazon Lambda 定价](https://www.amazonaws.cn/lambda/pricing/)。

## 示例模板


您可以查看具有以下延迟机制的 Lambda 支持的自定义资源示例模板：

### JSON


```
{
  "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


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

## 示例模板演练


以下代码段解释了示例模板的相关部分，以帮助您了解 Lambda 函数如何与自定义资源关联并理解输出。

[AWS::Lambda::Function](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-function.html) 资源 `CFNWaiter`  
`AWS::Lambda::Function` 资源指定了函数的源代码、处理程序名称、运行时环境和执行角色的 Amazon 资源名称（ARN）。  
由于 `Handler` 属性使用 Python 源代码，因此将其设置为 `index.handler`。有关使用内联函数源代码时可接受的处理程序标识符的更多信息，请参阅 [ AWS::Lambda::Function 代码](https://docs.amazonaws.cn/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


```
...
    "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


```
...
  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.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-iam-role.html) 资源 `LambdaExecutionRole`  
`AWS::IAM:Role` 资源为 Lambda 函数创建一个执行角色，其中包括允许 Lambda 使用该角色的代入角色策略。它还包含允许 CloudWatch Logs 访问的策略。

### JSON


```
...
    "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


```
...
  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.amazonaws.cn/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html) 资源 `CFNWaiterCustomResource`  
自定义资源使用 `!GetAtt CFNWaiter.Arn` 将其 ARN 链接到 Lambda 函数。它将为创建和更新操作实现 60 秒的等待时间，如 `ServiceTimeout` 中设置的那样。只有当属性被修改时，才会调用资源进行更新操作。

### JSON


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

### YAML


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

`Outputs`  
此模板的 `Outputs` 是 `TimeWaited` 和 `WaiterId`。`TimeWaited` 值使用 `Fn::GetAtt` 函数提供等待者资源实际等待的时间。`WaiterId` 使用 `Fn::GetAtt` 函数来提供生成并与执行关联的唯一 ID。

### JSON


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

### YAML


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

## 先决条件


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

## 启动堆栈


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

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

1. 通过以下网址打开 CloudFormation 控制台：[https://console.aws.amazon.com/cloudformation/](https://console.amazonaws.cn/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.amazonaws.cn/cloudwatch/home#logs:)中查看此函数的日志。日志流的名称与自定义资源的物理 ID 相同，您可通过查看堆栈的资源来查找该名称。有关更多信息，请参阅《Amazon CloudWatch 用户指南》中的[查看日志数据](https://docs.amazonaws.cn/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html#ViewingLogData)**。

## 清理资源


删除堆栈以清理您创建的所有堆栈资源，从而避免为不必要的资源付费。

**删除堆栈**

1. 从 CloudFormation 控制台中，选择 **SampleCustomResourceStack** 堆栈。

1. 选择 **Actions**，然后选择 **Delete Stack**。

1. 在确认消息中，选择 **Yes, Delete**。

您创建的所有资源都会被删除。

既然您已了解如何创建和使用 Lambda 支持的自定义资源，可以使用本演练中的示例模板和代码来生成和试验其他堆栈和函数。

## 相关信息

+ [CloudFormation 自定义资源参考](crpg-ref.md)
+ [AWS::CloudFormation::CustomResource](https://docs.amazonaws.cn/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html)

# `cfn-response` 模块


在 CloudFormation 模板中，您可以指定 Lambda 函数作为自定义资源的对象。如果使用 `ZipFile` 属性指定[函数](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-function.html)的源代码，可以加载 `cfn-response` 模块，以将 Lambda 函数的响应发送到自定义资源。`cfn-response` 模块是一个库，可简化向调用 Lambda 函数的自定义资源发送响应的过程。该模块包含一个 `send` 方法，可以通过 Amazon S3 预签名 URL (`ResponseURL`) 将[响应对象](crpg-ref.md#crpg-ref-responses)发送到自定义资源。

只有当您使用 `cfn-response` 属性编写您的源代码时，`ZipFile` 模块可用。它对于存储在 Amazon S3 存储桶中的源代码不可用。对于 存储桶中的代码，您必须编写自己的函数来发送响应。

**注意**  
在执行 `send` 方法后，Lambda 函数将终止，因此，将忽略在该方法之后写入的任何内容。

## 加载 `cfn-response` 模块


对于 Node.js 函数，使用 `require()` 函数来加载 `cfn-response` 模块。例如，以下代码示例创建一个名为 `cfn-response` 的 `response` 对象：

```
var response = require('cfn-response');
```

对于 Python，使用 `import` 语句来加载 `cfnresponse` 模块，如下例所示：

**注意**  
使用这个确切的导入语句。如果使用导入语句的其他变体，CloudFormation 将不会包含响应模块。

```
import cfnresponse
```

## `send` 方法参数


您可以将以下参数用于 `send` 方法。

`event`  
[自定义资源请求](crpg-ref.md#crpg-ref-requesttypes)中的字段。

`context`  
Lambda 函数特定的对象，可用于指定何时函数和任何回调完成执行，或从 Lambda 执行环境中访问信息。有关更多信息，请参阅《Amazon Lambda 开发人员指南》**中的[使用 Node.js 构建 Lambda 函数](https://docs.amazonaws.cn/lambda/latest/dg/lambda-nodejs.html)。

`responseStatus`  
函数是否成功完成。使用 `cfnresponse` 模块常量指定状态：`SUCCESS` 表示执行成功；`FAILED` 表示执行失败。

`responseData`  
自定义资源[响应对象](crpg-ref.md#crpg-ref-responses)的 `Data` 字段。数据为名称-值对列表。

`physicalResourceId`  
可选。调用此函数的自定义资源的唯一标识符。默认情况下，该模块使用与 Lambda 函数关联的 Amazon CloudWatch Logs 日志流的名称。  
为 `PhysicalResourceId` 返回的值可以更改自定义资源更新操作。如果返回的值相同，则将其视为正常更新。如果返回的值不同，CloudFormation 会将该更新视为替换，并向旧资源发送删除请求。有关更多信息，请参阅 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html)。

`noEcho`  
可选。指示在使用 `Fn::GetAtt` 函数检索自定义资源的输出时是否遮蔽它。如果设置为 `true`，则所有返回的值都将用星号 (\$1\$1\$1\$1\$1) 遮蔽，但存储在下面指定位置的信息除外。默认情况下，此值为 `false`。  
使用 `NoEcho` 属性不会遮蔽在以下各区段中存储的任何信息：  
+ `Metadata` 模板区段。CloudFormation 不会转换、修改或编辑您在 `Metadata` 部分中包含的任何信息。有关更多信息，请参阅 [Metadata](https://docs.amazonaws.cn/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html)。
+ `Outputs` 模板区段。有关更多信息，请参阅 [Outputs](https://docs.amazonaws.cn/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)。
+ 资源定义的 `Metadata` 属性。有关更多信息，请参阅 [`Metadata` 属性](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-attribute-metadata.html)。
强烈建议您不要使用这些机制来包含敏感信息，例如密码。
有关使用 `NoEcho` 遮蔽敏感信息的更多信息，请参阅 [请勿将凭证嵌入您的模板](security-best-practices.md#creds) 最佳实践。

## 示例


### Node.js


在下面的 Node.js 示例中，内联 Lambda 函数获取输入值并将其乘以 5。内联函数对于较小的函数尤为有用，因为它们让您能够在模板中直接指定源代码，而不必先创建包再将包上传到 Amazon S3 存储桶中。函数使用 `cfn-response` `send` 方法将结果发回给调用此函数的自定义资源。

#### JSON


```
"ZipFile": { "Fn::Join": ["", [
  "var response = require('cfn-response');",
  "exports.handler = function(event, context) {",
  "  var input = parseInt(event.ResourceProperties.Input);",
  "  var responseData = {Value: input * 5};",
  "  response.send(event, context, response.SUCCESS, responseData);",
  "};"
]]}
```

#### YAML


```
ZipFile: >
  var response = require('cfn-response');
  exports.handler = function(event, context) {
    var input = parseInt(event.ResourceProperties.Input);
    var responseData = {Value: input * 5};
    response.send(event, context, response.SUCCESS, responseData);
  };
```

### Python


在下面的 Python 示例中，内联 Lambda 函数采用一个整数值并将它乘以 5。

#### JSON


```
"ZipFile" : { "Fn::Join" : ["\n", [
  "import json",
  "import cfnresponse",
  "def handler(event, context):",
  "   responseValue = int(event['ResourceProperties']['Input']) * 5",
  "   responseData = {}",
  "   responseData['Data'] = responseValue",
  "   cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, \"CustomResourcePhysicalID\")"
]]}
```

#### YAML


```
ZipFile: |
  import json
  import cfnresponse
  def handler(event, context):
    responseValue = int(event['ResourceProperties']['Input']) * 5
    responseData = {}
    responseData['Data'] = responseValue
    cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
```

## 模块源代码


**Topics**
+ [

### 异步 Node.js 源代码
](#cfn-lambda-function-code-cfnresponsemodule-source-nodejs-async)
+ [

### Node.js 源代码
](#cfn-lambda-function-code-cfnresponsemodule-source-nodejs)
+ [

### Python 源代码
](#cfn-lambda-function-code-cfnresponsemodule-source-python)

### 异步 Node.js 源代码


以下是在处理程序异步进行处理时，Node.js 函数的响应模块源代码。请查看这些内容，以理解此模块的功能并帮助您实现自己的响应函数。

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

exports.SUCCESS = "SUCCESS";
exports.FAILED = "FAILED";

exports.send = function(event, context, responseStatus, responseData, physicalResourceId, noEcho) {

    return new Promise((resolve, reject) => {
        var responseBody = JSON.stringify({
            Status: responseStatus,
            Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
            PhysicalResourceId: physicalResourceId || context.logStreamName,
            StackId: event.StackId,
            RequestId: event.RequestId,
            LogicalResourceId: event.LogicalResourceId,
            NoEcho: noEcho || false,
            Data: responseData
        });

        console.log("Response body:\n", responseBody);

        var https = require("https");
        var url = require("url");

        var parsedUrl = url.parse(event.ResponseURL);
        var options = {
            hostname: parsedUrl.hostname,
            port: 443,
            path: parsedUrl.path,
            method: "PUT",
            headers: {
                "content-type": "",
                "content-length": responseBody.length
            }
        };

        var request = https.request(options, function(response) {
            console.log("Status code: " + parseInt(response.statusCode));
            resolve(context.done());
        });

        request.on("error", function(error) {
            console.log("send(..) failed executing https.request(..): " + maskCredentialsAndSignature(error));
            reject(context.done(error));
        });

        request.write(responseBody);
        request.end();
    })
}
 
function maskCredentialsAndSignature(message) {
    return message.replace(/X-Amz-Credential=[^&\s]+/i, 'X-Amz-Credential=*****')
        .replace(/X-Amz-Signature=[^&\s]+/i, 'X-Amz-Signature=*****');
}
```

### Node.js 源代码


以下是在处理程序不是异步进行处理时，Node.js 函数的响应模块源代码。请查看这些内容，以理解此模块的功能并帮助您实现自己的响应函数。

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
 
exports.SUCCESS = "SUCCESS";
exports.FAILED = "FAILED";

exports.send = function(event, context, responseStatus, responseData, physicalResourceId, noEcho) {

    var responseBody = JSON.stringify({
        Status: responseStatus,
        Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
        PhysicalResourceId: physicalResourceId || context.logStreamName,
        StackId: event.StackId,
        RequestId: event.RequestId,
        LogicalResourceId: event.LogicalResourceId,
        NoEcho: noEcho || false,
        Data: responseData
    });

    console.log("Response body:\n", responseBody);

    var https = require("https");
    var url = require("url");

    var parsedUrl = url.parse(event.ResponseURL);
    var options = {
        hostname: parsedUrl.hostname,
        port: 443,
        path: parsedUrl.path,
        method: "PUT",
        headers: {
            "content-type": "",
            "content-length": responseBody.length
        }
    };

    var request = https.request(options, function(response) {
        console.log("Status code: " + parseInt(response.statusCode));
        context.done();
    });

    request.on("error", function(error) {
        console.log("send(..) failed executing https.request(..): " + maskCredentialsAndSignature(error));
        context.done();
    });

    request.write(responseBody);
    request.end();
}
```

### Python 源代码


以下是 Python 函数的响应模块源代码。

```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
 
from __future__ import print_function
import urllib3
import json
import re

SUCCESS = "SUCCESS"
FAILED = "FAILED"

http = urllib3.PoolManager()


def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False, reason=None):
    responseUrl = event['ResponseURL']

    responseBody = {
        'Status' : responseStatus,
        'Reason' : reason or "See the details in CloudWatch Log Stream: {}".format(context.log_stream_name),
        'PhysicalResourceId' : physicalResourceId or context.log_stream_name,
        'StackId' : event['StackId'],
        'RequestId' : event['RequestId'],
        'LogicalResourceId' : event['LogicalResourceId'],
        'NoEcho' : noEcho,
        'Data' : responseData
    }

    json_responseBody = json.dumps(responseBody)

    print("Response body:")
    print(json_responseBody)

    headers = {
        'content-type' : '',
        'content-length' : str(len(json_responseBody))
    }

    try:
        response = http.request('PUT', responseUrl, headers=headers, body=json_responseBody)
        print("Status code:", response.status)


    except Exception as e:

        print("send(..) failed executing http.request(..):", mask_credentials_and_signature(e))
 
 
def mask_credentials_and_signature(message):
    message = re.sub(r'X-Amz-Credential=[^&\s]+', 'X-Amz-Credential=*****', message, flags=re.IGNORECASE)
    return re.sub(r'X-Amz-Signature=[^&\s]+', 'X-Amz-Signature=*****', message, flags=re.IGNORECASE)
```

# 使用模板宏对 CloudFormation 模板执行自定义处理
模板宏

您可以使用宏对模板执行自定义处理，包括查找并替换操作等简单操作以及整个模板的大型转换。

要了解可能性的广度，请考虑 `AWS::Include` 和 `AWS::Serverless` 转换，这些转换是由 CloudFormation 托管的宏：
+ [AWS::Include 转换](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-include.html)可让您将样板文件模板代码段插入您的模板中。
+ [AWS::Serverless 转换](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-serverless.html)采用以 Amazon Serverless Application Model（Amazon SAM）语法编写的完整模板，将其转换并扩展为符合规范的 CloudFormation 模板。有关无服务器应用程序和 Amazon SAM 的更多信息，请参阅 [Amazon Serverless Application Model 开发人员指南](https://docs.amazonaws.cn/serverless-application-model/latest/developerguide/what-is-sam.html)。

**Topics**
+ [

## 计费
](#template-macros-billing)
+ [

## 宏示例
](#template-macros-examples-list)
+ [

## 相关资源
](#template-macros-related-resources)
+ [

# CloudFormation 宏概述
](template-macros-overview.md)
+ [

# 创建 CloudFormation 宏定义
](template-macros-author.md)
+ [

# 简单字符串替换宏的示例
](macros-example.md)
+ [

# 对所处理的模板进行故障排除
](template-macros-troubleshoot-processed-template.md)

## 计费


当宏运行时，将对 Lambda 函数的所有者收取与执行该函数相关的任何费用。

`AWS::Include` 和 `AWS::Serverless` 转换是由 CloudFormation 托管的宏。使用它们不产生任何费用。

## 宏示例


除了本部分的示例之外，您还可以在 [GitHub 存储库](https://github.com/aws-cloudformation/aws-cloudformation-templates/tree/main/CloudFormation/MacrosExamples)中找到示例宏，包括源代码和模板。这些示例按“原样”提供，用于教学目的。

## 相关资源

+ [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-macro.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-macro.html)
+ [CloudFormation 模板 Transform 部分](transform-section-structure.md)
+ [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/intrinsic-function-reference-transform.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/intrinsic-function-reference-transform.html)
+ [AWS::Serverless 转换](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-serverless.html)
+ [AWS::Include 转换](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-include.html)

# CloudFormation 宏概述
宏概述

使用宏处理模板有两个主要步骤：创建宏本身，然后使用宏来对模板执行处理。

要创建宏定义，您必须创建以下内容：
+ 一个执行模板处理的 Lambda 函数。此 Lambda 函数接受一个代码段或整个模板，以及您定义的任何其他参数。它返回已处理的模板代码段或整个模板作为响应。
+ [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-macro.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-macro.html) 类型的资源，使用户能够从 CloudFormation 模板内调用 Lambda 函数。此资源指定要为此宏调用的 Lambda函数的 ARN，以及辅助调试的其他可选属性。要在账户中创建此资源，请创建一个包含 `AWS::CloudFormation::Macro` 资源的模板，然后利用该模板创建具有自行管理权限的堆栈或堆栈集。Amazon CloudFormationStackSets 目前不支持通过引用 宏的模板创建或更新具有服务托管权限的堆栈集。

要使用宏，请在您的模板中引用宏：
+ 要处理模板的一段或部分，请在 `Fn::Transform` 函数中引用宏（位置是相对于要转换的模板内容而言的）。当使用 `Fn::Transform` 时，您还可以传递它需要的任何指定参数。
+ 要处理整个模板，请在模板的 [Transform](transform-section-structure.md) 部分中引用宏。

接下来，您通常会创建一个更改集，然后执行它。（处理宏可能会添加您可能不知道的多个资源。为确保您了解宏所引入的所有更改，我们强烈建议您使用更改集。） CloudFormation 将指定的模板内容以及任何其他指定参数一并传递给在宏资源中指定的 Lambda 函数。Lambda 函数返回已处理的模板内容，无论是代码段还是整个模板。

在调用模板中的所有宏之后，CloudFormation 会生成包含已处理模板内容的更改集。检查更改集后，执行它来应用更改。

![\[使用 Fn::Transform 内置函数或模板的 Transform 部分，将模板内容和关联参数传递给宏的底层 Lambda 函数，该函数返回处理的模板内容。\]](http://docs.amazonaws.cn/AWSCloudFormation/latest/UserGuide/images/template-macro-use.png)


## 如何直接创建堆栈


要使用引用宏的模板创建或更新堆栈，通常需要创建一个更改集，然后执行它。更改集描述 CloudFormation 将基于处理后的模板执行的操作。处理宏可添加您可能不知道的多个资源。为确保您已了解宏所引入的所有更改，我们强烈建议您使用更改集。在查看更改集后，您可以执行它来实际应用更改。

宏可以将 IAM 资源添加到您的模板中。对于这些资源，CloudFormation 要求您[确认其功能](control-access-with-iam.md#using-iam-capabilities)。由于 CloudFormation 无法在处理模板前了解所添加的资源，因此您可能需要在创建更改集时确认 IAM 功能，具体取决于引用的宏是否包含 IAM 资源。这样，当您运行该更改集时，CloudFormation 会具有创建 IAM 资源所需的功能。

要直接从处理的模板来创建或更新堆栈，而不事先查看更改集中提议的更改，可以在 `CreateStack` 或 `UpdateStack` 请求期间指定 `CAPABILITY_AUTO_EXPAND` 功能。仅当您知道宏在处理什么时，才应直接从包含宏的堆栈模板创建堆栈。您不能将更改集与堆栈集宏一起使用，必须直接更新堆栈集。

有关更多信息，请参阅《Amazon CloudFormation API 参考》** 中的 [https://docs.amazonaws.cn/AWSCloudFormation/latest/APIReference/API_CreateStack.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/APIReference/API_CreateStack.html) 或 [https://docs.amazonaws.cn/AWSCloudFormation/latest/APIReference/API_UpdateStack.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/APIReference/API_UpdateStack.html)。

**重要**  
如果堆栈集模板引用了一个或多个宏，则必须直接从处理过的模板创建堆栈集，而无需先查看更改集中生成的更改。处理宏可添加您可能不知道的多个资源。在通过直接引用宏的模板创建或更新堆栈集之前，请确保您知道宏执行的处理操作。

为减少从引用宏的模板启动堆栈的步骤数，可使用 `package` 和 `deploy` Amazon CLI 命令。有关更多信息，请参阅[使用 Amazon CLI 将本地构件上传到 S3 存储桶](using-cfn-cli-package.md)和[创建包含转换的堆栈](service_code_examples.md#deploy-sdk)。

## 注意事项


使用宏时，请记住以下注意事项和限制：
+ 只有在提供 Lambda 的 Amazon Web Services 区域才支持宏。有关提供 Lambda 的区域的列表，请参阅 [Amazon Lambda 端点和配额](https://docs.amazonaws.cn/general/latest/gr/lambda-service.html)。
+ 任何已处理模板代码段都必须是有效的 JSON。
+ 任何已处理的模板代码段都必须通过创建堆栈、更新堆栈、创建堆栈集或更新堆栈集等操作的验证检查。
+ CloudFormation 会先解析宏，然后再处理模板。生成的模板必须是有效的 JSON，并且不得超出模板大小限制。
+ 由于 CloudFormation 处理模板中元素的顺序，宏无法在其返回给 CloudFormation 的已处理模板内容中包含模块。有关更多信息，请参阅 [宏评估顺序](template-macros-author.md#template-macros-order)。
+ 使用更新回滚功能时，CloudFormation 会使用原始模板的副本。即使包含的代码段发生更改，它也会回滚到原始模板。
+ 宏中不能嵌套宏，因为我们不会迭代处理宏。
+ 目前在宏中不支持 `Fn::ImportValue` 内部函数。
+ 模板中包含的内部函数会在任何宏之后被评估。因此，宏返回的已处理模板内容可以包括对内部函数的调用，并且像往常一样对其进行评估。
+ StackSets 目前不支持通过引用 CloudFormation 宏的模板，使用服务托管权限创建或更新堆栈集。

## 宏账户范围和权限


您只能在创建宏的账户中将宏用作资源。宏的名称在给定账户中必须是唯一的。但是，您可以通过在底层 Lambda 函数上启用跨账户访问，然后创建宏定义以在多个账户中引用该函数，从而在多个账户中提供相同的功能。在下面的示例中，三个账户包含宏定义，每个宏定义都指向相同的 Lambda 函数。

![\[通过允许对 Lambda 函数进行跨账户访问，Amazon 能让您在引用该函数的多个账户中创建宏。\]](http://docs.amazonaws.cn/AWSCloudFormation/latest/UserGuide/images/template-macro-accounts.png)


要创建宏定义，用户必须具有在指定账户中创建堆栈或堆栈集的权限。

要使 CloudFormation 成功运行模板中包含的宏，用户必须对底层 Lambda 函数具有 `Invoke` 权限。为防止可能的权限升级，CloudFormation 会在运行宏时模拟用户。

有关更多信息，请参阅*《Amazon Lambda 开发人员指南》*中的[在 Amazon Lambda 中管理权限](https://docs.amazonaws.cn/lambda/latest/dg/lambda-permissions.html)和*《服务授权参考》*中的 [Amazon Lambda 操作、资源和条件键](https://docs.amazonaws.cn/service-authorization/latest/reference/list_awslambda.html)。

# 创建 CloudFormation 宏定义
创建宏定义

创建宏定义时，该宏定义会使底层 Lambda 函数在指定的账户中可用，以便 CloudFormation 可以调用该函数来处理模板。

## 事件映射


当 CloudFormation 调用宏的 Lambda 函数时，它会以 JSON 格式发送请求，其结构如下所示：

```
{
    "region" : "us-east-1",
    "accountId" : "$ACCOUNT_ID",
    "fragment" : { ... },
    "transformId" : "$TRANSFORM_ID",
    "params" : { ... },
    "requestId" : "$REQUEST_ID",
    "templateParameterValues" : { ... }
}
```
+ `region`

  宏所在的区域。
+ `accountId`

  宏从中调用 Lambda 函数的账户的账户 ID。
+ `fragment`

  可用于自定义处理的模板内容，采用 JSON 格式。
  + 对于 `Transform` 模板部分中包含的宏，这是整个模板，除 `Transform` 部分外。
  + 对于在 `Fn::Transform` 内部函数调用中包含的宏，这包括基于内部函数在模板中的位置的所有同级节点（及其子节点），除 `Fn::Transform` 函数外。有关更多信息，请参阅 [宏模板范围](#template-macros-scope)。
+ `transformId`

  调用此函数的宏的名称。
+ `params`

  对于 `Fn::Transform` 函数调用，是函数的任何指定参数。CloudFormation 在将这些参数传递给函数之前不会进行评估。

  对于在 `Transform` 模板部分中包含的宏，此部分为空。
+ `requestId`

  调用此函数的请求的 ID。
+ `templateParameterValues`

  在模板的 [Parameters](parameters-section-structure.md) 部分中指定的任何参数。CloudFormation 在将这些参数传递给函数之前会进行评估。

## 响应格式


CloudFormation 要求 Lambda 函数以如下 JSON 格式返回响应：

```
{
    "requestId" : "$REQUEST_ID",
    "status" : "$STATUS",
    "fragment" : { ... },
    "errorMessage": "optional error message for failures"
}
```
+ `requestId`

  调用此函数的请求的 ID。这必须在调用函数时与 CloudFormation 提供的请求 ID 匹配。
+ `status`

  请求的状态（不区分大小写）。应设置为 `success`。CloudFormation 会将任何其他响应都视为失败。
+ `fragment`

  CloudFormation 要包含在已处理模板中的已处理模板内容，包括同级内容。CloudFormation 会将传递给 Lambda 函数的模板内容替换为在 Lambda 响应中收到的模板片段。

  已处理模板内容必须是有效的 JSON，并且其在已处理模板中的包含必须生成有效的模板。

  如果函数实际上没有更改 CloudFormation 传递的模板内容，但您仍需要将该内容包含在已处理模板中，则函数需要将该模板内容在其响应中返回到 CloudFormation。
+ `errorMessage`

  解释转换失败原因的错误消息。CloudFormation 将在堆栈的 **Stack details**（堆栈详细信息）页面的 **Events**（事件）窗格中显示此错误消息。

  例如：

  ```
  Error creating change set: Transform
                              Amazon Web Services 账户 account
                              number::macro name failed with:
                              error message string.
  ```

## 创建宏定义


**创建 CloudFormation 宏定义**

1. [构建 Lambda 函数](https://docs.amazonaws.cn/lambda/latest/dg/getting-started.html)来处理模板内容。该函数可以处理模板的任何部分，甚至是整个模板。

1. 创建包含 `AWS::CloudFormation::Macro` 资源类型的 CloudFormation 模板，并指定 `Name` 和 `FunctionName` 属性。`FunctionName` 属性必须包含在 CloudFormation 运行宏时调用的 Lambda 函数的 ARN。

1. （可选）为了帮助调试，您还可以在为宏创建 `AWS::CloudFormation::Macro` 资源类型时指定 `LogGroupName` 和 `LogRoleArn` 属性。借助这些属性，您可以指定 CloudFormation 在调用宏的底层 Lambda 函数时向其发送错误日志信息的 CloudWatch Logs 日志组，以及 CloudFormation 在将日志条目发送到这些日志时应代入的角色。

1. 在要使用宏的账户中使用带宏的模板[创建堆栈](cfn-console-create-stack.md)。或在管理员账户中使用带宏的模板[创建具有自行管理权限的堆栈集](stacksets-getting-started-create-self-managed.md)，然后在目标账户中创建堆栈实例。

1. 在 CloudFormation 成功创建包含宏定义的堆栈之后，该宏便可在这些账户中使用。您可以通过在模板中，在与要处理的模板内容相关的适当位置引用宏来使用宏。

## 宏模板范围


在模板的 `Transform` 部分中引用的宏可以处理该模板的全部内容。

在 `Fn::Transform` 函数中引用的宏可以处理模板中该 `Fn::Transform` 函数的任何同级元素（包括子元素）的内容。

例如，在下面的模板示例中，`AWS::Include` 可以根据包含它的 `Fn::Transform` 函数的位置处理 `MyBucket` 属性。`MyMacro` 可以处理整个模板的内容，因为它包含在 `Transform` 部分中。

```
# Start of processable content for MyMacro
AWSTemplateFormatVersion: 2010-09-09 
 Transform: [MyMacro]
 Resources:
    WaitCondition:
      Type: AWS::CloudFormation::WaitCondition
    MyBucket:
      Type: AWS::S3::Bucket
      # Start of processable content for AWS::Include
      Properties:
        BucketName: amzn-s3-demo-bucket1
        Tags: [{"key":"value"}] 
        'Fn::Transform':
          - Name: 'AWS::Include'
              Parameters:
                Location: s3://amzn-s3-demo-bucket2/MyFileName.yaml
        CorsConfiguration: []
        # End of processable content for AWS::Include
    MyEc2Instance:
      Type: AWS::EC2::Instance
      Properties:
        ImageID: ami-1234567890abcdef0
# End of processable content for MyMacro
```

## 宏评估顺序


您可以在给定模板中引用多个宏，包括由 CloudFormation 托管的转换，例如 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-include.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-include.html) 和 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-serverless.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/transform-aws-serverless.html)。

将会根据宏在模板中的位置，从嵌套最深的宏向外直到最一般的宏，按顺序评估宏。对于模板中相同位置的宏，将会根据列出的顺序按顺序进行评估。

诸如 `AWS::Include` 和 `AWS::Transform` 之类的转换在操作顺序和范围方面被视为与任何其他宏相同。

例如，在下面的模板示例中，CloudFormation 会首先评估 `PolicyAdder` 宏，因为这是模板中最深的嵌套宏。然后，CloudFormation 会首先评估 `MyMacro`，然后再评估 `AWS::Serverless`，因为在 `Transform` 部分中它列于 `AWS::Serverless` 之前。

```
AWSTemplateFormatVersion: 2010-09-09
 Transform: [MyMacro, AWS::Serverless]
 Resources:
    WaitCondition:
      Type: AWS::CloudFormation::WaitCondition
    MyBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: amzn-s3-demo-bucket
        Tags: [{"key":"value"}]
        'Fn::Transform':
          - Name: PolicyAdder
        CorsConfiguration: []
    MyEc2Instance:
      Type: AWS::EC2::Instance
      Properties:
        ImageID: ami-1234567890abcdef0
```

# 简单字符串替换宏的示例
简单字符串替换宏的示例

下面的示例演示了使用宏的过程：从在模板中定义宏，到为宏创建 Lambda 函数，再到在模板中使用宏。

在此示例中，我们创建一个简单的宏，它插入指定的字符串来替换已处理模板中的指定目标内容。然后我们将使用它在已处理模板的指定位置插入空白 `WaitHandleCondition`。

## 创建宏


在使用宏之前，我们首先要完成两件事：创建执行所需模板处理的 Lambda 函数，然后通过创建宏定义使该 Lambda 函数可用于 CloudFormation。

以下示例模板包含示例宏的定义。为了使宏在特定 Amazon Web Services 账户中可用，应从模板创建一个堆栈。宏定义指定宏名称、简要描述，并引用在模板中使用此宏时 CloudFormation 调用的 Lambda 函数的 ARN。（我们没有包含 `LogGroupName` 或 `LogRoleARN` 属性来用于记录错误日志。） 

在本示例中，假定从此模板创建的堆栈名为 `JavaMacroFunc`。由于宏 `Name` 属性设置为堆栈名称，因此生成的宏也名为 `JavaMacroFunc`。

```
AWSTemplateFormatVersion: 2010-09-09
  Resources:
    Macro:
      Type: AWS::CloudFormation::Macro
      Properties:
        Name: !Sub '${AWS::StackName}'
        Description: Adds a blank WaitConditionHandle named WaitHandle
        FunctionName: 'arn:aws:lambda:us-east-1:012345678910:function:JavaMacroFunc'
```

## 使用宏


为了使用宏，我们将使用 `Fn::Transform` 内置函数将其包含在模板中。

当我们使用下面的模板创建堆栈时，CloudFormation 会调用我们的示例宏。底层 Lambda 函数将一个指定的字符串替换为另一个指定的字符串。在这种情况下，结果是空的 `AWS::CloudFormation::WaitConditionHandle` 插入到已处理模板中。

```
Parameters:
  ExampleParameter:
    Type: String
    Default: 'SampleMacro'

Resources:
  2a:
    Fn::Transform:
      Name: "JavaMacroFunc"
      Parameters:
        replacement: 'AWS::CloudFormation::WaitConditionHandle'
        target: '$$REPLACEMENT$$'
    Type: '$$REPLACEMENT$$'
```
+ 要调用的宏被指定为 `JavaMacroFunc`，它来自上一个宏定义示例。
+ 将会向该宏传递两个参数，`target` 和 `replacement`，它们代表目标字符串及其所需的替换值。
+ 该宏可以对 `Type` 节点的内容进行操作，因为 `Type` 是引用该宏的 `Fn::Transform` 函数的同级节点。
+ 生成的 `AWS::CloudFormation::WaitConditionHandle` 名为 `2a`。
+ 该模板还包含一个该宏也可以访问的模板参数 `ExampleParameter`（但在这种情况下不使用）。

## Lambda 输入数据


当 CloudFormation 在堆栈创建期间处理我们的示例模板时，它将以下事件映射传递给在 `JavaMacroFunc` 宏定义中引用的 Lambda 函数。
+ `region` : `us-east-1`
+ `accountId` : `012345678910`
+ `fragment` :

  ```
  {
    "Type": "$$REPLACEMENT$$"
  }
  ```
+ `transformId` : `012345678910::JavaMacroFunc`
+ `params` : 

  ```
  {
      "replacement": "AWS::CloudFormation::WaitConditionHandle",
      "target": "$$REPLACEMENT$$"
  }
  ```
+ `requestId` : `5dba79b5-f117-4de0-9ce4-d40363bfb6ab`
+ `templateParameterValues` :

  ```
  {
      "ExampleParameter": "SampleMacro"
  }
  ```

`fragment` 包含 JSON，表示该宏可以处理的模板片段。此片段由 `Fn::Transform` 函数调用的同级函数调用组成，但不包括函数调用本身。此外，`params` 包含 JSON，表示宏参数。在这种情况下，是替换和目标。同样，`templateParameterValues` 包含 JSON，表示为整个模板指定的参数。

## Lambda 函数代码


以下是 Lambda 函数的实际代码，它是 `JavaMacroFunc` 示例宏的底层。它迭代响应中包含的模板片段（无论是字符串、列表还是映射格式），从而查找指定的目标字符串。如果它找到指定的目标字符串，则 Lambda 函数会使用指定的替换字符串替换目标字符串。否则，该函数会保持模板片段不变。然后，该函数将下面详细讨论的预期属性的映射返回到 CloudFormation。

```
package com.macroexample.lambda.demo;

import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class LambdaFunctionHandler implements RequestHandler<Map<String, Object>, Map<String, Object>> {

	private static final String REPLACEMENT = "replacement";
	private static final String TARGET = "target";
	private static final String PARAMS = "params";
	private static final String FRAGMENT = "fragment";
	private static final String REQUESTID = "requestId";
	private static final String STATUS = "status";
	private static final String SUCCESS = "SUCCESS";
	private static final String FAILURE = "FAILURE";
    @Override
    public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
        // TODO: implement your handler
    	final Map<String, Object> responseMap = new HashMap<String, Object>();
        responseMap.put(REQUESTID, event.get(REQUESTID));
        responseMap.put(STATUS, FAILURE);
    	try {
	        if (!event.containsKey(PARAMS)) {
	        	throw new RuntimeException("Params are required");
	        }
	    	
	        final Map<String, Object> params = (Map<String, Object>) event.get(PARAMS);
	        if (!params.containsKey(REPLACEMENT) || !params.containsKey(TARGET)) {
	        	throw new RuntimeException("replacement or target under Params are required");
	        }
	    	
	    	final String replacement = (String) params.get(REPLACEMENT);
	    	final String target = (String) params.get(TARGET);
	    	final Object fragment = event.getOrDefault(FRAGMENT, new HashMap<String, Object>());
	    	final Object retFragment;
	    	if (fragment instanceof String) {
	    		retFragment = iterateAndReplace(replacement, target, (String) fragment);
	    	} else if (fragment instanceof List) {
	    		retFragment = iterateAndReplace(replacement, target, (List<Object>) fragment);
	    	} else if (fragment instanceof Map) {
	    		retFragment = iterateAndReplace(replacement, target, (Map<String, Object>) fragment);
	    	} else {
	    		retFragment = fragment;
	    	}
	        responseMap.put(STATUS, SUCCESS);
	        responseMap.put(FRAGMENT, retFragment);
	        return responseMap;
    	} catch (Exception e) {
    		e.printStackTrace();
    		context.getLogger().log(e.getMessage());
    		return responseMap;
    	}
    }
    
    private Map<String, Object> iterateAndReplace(final String replacement, final String target, final Map<String, Object> fragment) {
    	final Map<String, Object> retFragment = new HashMap<String, Object>();
    	final List<String> replacementKeys = new ArrayList<>();
    	fragment.forEach((k, v) -> {
    		if (v instanceof String) {
    			retFragment.put(k, iterateAndReplace(replacement, target, (String)v));
    		} else if (v instanceof List) {
    			retFragment.put(k, iterateAndReplace(replacement, target, (List<Object>)v));
    		} else if (v instanceof Map ) {
    			retFragment.put(k, iterateAndReplace(replacement, target, (Map<String, Object>) v));
    		} else {
    			retFragment.put(k, v);
    		}
    	});
    	return retFragment;
    }

    private List<Object> iterateAndReplace(final String replacement, final String target, final List<Object> fragment) {
    	final List<Object> retFragment = new ArrayList<>();
    	fragment.forEach(o -> {
    		if (o instanceof String) {
    			retFragment.add(iterateAndReplace(replacement, target, (String) o));
    		} else if (o instanceof List) {
    			retFragment.add(iterateAndReplace(replacement, target, (List<Object>) o));
    		} else if (o instanceof Map) {
    			retFragment.add(iterateAndReplace(replacement, target, (Map<String, Object>) o));
    		} else {
    			retFragment.add(o);
    		}
    	});
    	return retFragment;
    }
    
    private String iterateAndReplace(final String replacement, final String target, final String fragment) {
    	System.out.println(replacement + " == " + target + " == " + fragment );
    	if (fragment != null AND_AND fragment.equals(target))
    		return replacement;
    	return fragment;
    }
}
```

## Lambda 函数响应


以下是 Lambda 函数返回到 CloudFormation 进行处理的映射。
+ `requestId` : `5dba79b5-f117-4de0-9ce4-d40363bfb6ab`
+ `status` : `SUCCESS`
+ `fragment` :

  ```
  {
    "Type": "AWS::CloudFormation::WaitConditionHandle"
  }
  ```

从 CloudFormation 发送的 `requestId` 匹配项，以及 `SUCCESS` 的 `status` 值表示 Lambda 函数成功处理了请求中包含的模板片段。在此响应中，`fragment` 包含 JSON，表示要插入到已处理模板中的内容，而不是原始模板代码段。

## 生成的已处理模板


在 CloudFormation 从 Lambda 函数收到成功响应后，它将返回的模板片段插入到已处理模板中。

下面是我们示例的生成的已处理模板。不再包括引用 `JavaMacroFunc` 宏的 `Fn::Transform` 内置函数调用。Lambda 函数返回的模板片段包含在适当的位置，结果是内容 `"Type": "$$REPLACEMENT$$"` 已替换为 `"Type": "AWS::CloudFormation::WaitConditionHandle"`。

```
{
    "Parameters": {
        "ExampleParameter": {
            "Default": "SampleMacro",
            "Type": "String"
        }
    },
    "Resources": {
        "2a": {
            "Type": "AWS::CloudFormation::WaitConditionHandle"
        }
    }
}
```

# 对所处理的模板进行故障排除
对所处理的模板进行故障排除

使用宏时，可以在 CloudFormation 控制台中找到所处理的模板。

模板的阶段指示其处理状态：
+ `Original`：用户最初提交的用于创建或更新堆栈或堆栈集的模板。
+ `Processed`：CloudFormation 在处理任何引用的宏之后，用于创建或更新堆栈或堆栈集的模板。即使原始模板格式化为 YAML，已处理模板也会格式化为 JSON。

要排除故障，可使用所处理的模板。如果模板不引用宏，则原始模板和已处理模板是相同的。

有关更多信息，请参阅 [从 CloudFormation 控制台查看堆栈信息](cfn-console-view-stack-data-resources.md)。

要使用 Amazon CLI 获取所处理的模板，应使用 [get-template](service_code_examples.md#get-template-sdk) 命令。

## 大小限制


当处理的堆栈模板直接传递到 `CreateStack`、`UpdateStack` 或 `ValidateTemplate` 请求时，其最大大小为 51200 字节；或者当使用 Amazon S3 模板 URL 作为 S3 对象传递时，为 1MB。但是，在处理期间，CloudFormation 会更新模板的临时状态，因为它连续处理模板中包含的宏。因此，在处理期间，模板的大小可能会临时超出完全处理的模板所允许的大小。CloudFormation 允许为这些处理中的模板留出一些缓冲。但是，在设计模板和宏时，要始终考虑处理的堆栈模板所允许的最大大小。

如果在处理模板期间 CloudFormation 返回一个 `Transformation data limit exceeded` 错误，则说明您的模板超出了 CloudFormation 所允许的处理期间模板的最大大小。

要解决这一问题，可执行下列操作：
+ 将您的模板分成多个模板，以避免超出为处理中的模板规定的最大大小。例如：
  + 使用嵌套堆栈模板来封装模板的不同部分。有关更多信息，请参阅 [使用嵌套堆栈将模板拆分为可重复使用的部分](using-cfn-nested-stacks.md)。
  + 创建多个堆栈，并使用跨堆栈引用来相互交换信息。有关更多信息，请参阅 [引用其他 CloudFormation 堆栈中的资源输出](walkthrough-crossstackref.md)。
+ 减小给定宏返回的模板片段大小。CloudFormation 不会篡改宏返回的片段内容。

# 使用嵌套堆栈将模板拆分为可重复使用的部分
嵌套堆栈

随着基础设施的发展，您可能会发现自己在多个模板中反复创建相同的资源配置。为避免这种冗余，您可以将这些常用配置分成专用模板。然后，您可以使用其他模板中的 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-stack.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-stack.html) 资源来引用这些专用模板，创建嵌套堆栈。

例如，假如您拥有用于大多数堆栈的负载均衡器配置。您可以为负载均衡器创建专用模板，而不是将相同的配置复制并粘贴到您的模板中。然后，您可以从需要相同负载均衡器配置的其他模板中引用此模板。

嵌套堆栈本身可以包含其他嵌套堆栈，构成一个堆栈层次结构，如下图所示。*根堆栈*是所有嵌套堆栈最终归属的顶级堆栈。每个嵌套堆栈都有一个直属父堆栈。对于第一级的嵌套堆栈而言，根堆栈也是父堆栈。
+ 堆栈 A 是该层次结构中所有其他嵌套堆栈的根堆栈。
+ 对于堆栈 B 来说，堆栈 A 既是父堆栈，也是根堆栈。
+ 对于堆栈 D，堆栈 C 是父堆栈；而对于堆栈 C 来说，堆栈 B 是父堆栈。

![\[作为另一个堆栈的一部分而创建的嵌套堆栈具有一个直属父堆栈，同时也具有顶级根堆栈。\]](http://docs.amazonaws.cn/AWSCloudFormation/latest/UserGuide/images/cfn-console-nested-stacks.png)


**Topics**
+ [

## 拆分模板之前和之后的示例
](#create-nested-stack-template)
+ [

## 嵌套堆栈架构的示例
](#nested-stack-examples)
+ [

## 在嵌套堆栈上执行堆栈操作
](#perform-stack-operations-on-nested-stacks)
+ [

## 相关信息
](#nested-stacks-related-information)

## 拆分模板之前和之后的示例


此示例演示如何提取单个大型 CloudFormation 模板，然后使用嵌套模板将其重组为结构化程度更高、可重复使用的设计。最初，“嵌套堆栈前”模板显示在一个文件中定义了所有资源。随着资源数量不断增长，该文件可能会变得混乱且难以管理。“嵌套堆栈后”模板将资源拆分为若干较小的独立模板。每个嵌套堆栈都处理一组特定的相关资源，从而使整体结构更有条理，更易于维护。


| 嵌套堆栈前 | 嵌套堆栈后 | 
| --- | --- | 
| <pre>AWSTemplateFormatVersion: 2010-09-09<br />Parameters:<br />  InstanceType:<br />    Type: String<br />    Default: t2.micro<br />    Description: The EC2 instance type<br />  <br />  Environment:<br />    Type: String<br />    Default: Production<br />    Description: The deployment environment<br /><br />Resources:<br />  MyEC2Instance:<br />    Type: AWS::EC2::Instance<br />    Properties:<br />      ImageId: ami-1234567890abcdef0<br />      InstanceType: !Ref InstanceType<br /><br />  MyS3Bucket:<br />    Type: AWS::S3::Bucket</pre> | <pre>AWSTemplateFormatVersion: 2010-09-09<br />Resources:<br />  MyFirstNestedStack:<br />    Type: AWS::CloudFormation::Stack<br />    Properties:<br />      TemplateURL: https://s3.amazonaws.com/amzn-s3-demo-bucket/first-nested-stack.yaml<br />      Parameters:<br />        # Pass parameters to the nested stack if needed<br />        InstanceType: t3.micro<br /><br />  MySecondNestedStack:<br />    Type: AWS::CloudFormation::Stack<br />    Properties:<br />      TemplateURL: https://s3.amazonaws.com/amzn-s3-demo-bucket/second-nested-stack.yaml<br />      Parameters:<br />        # Pass parameters to the nested stack if needed<br />        Environment: Testing<br />    DependsOn: MyFirstNestedStack</pre> | 

## 嵌套堆栈架构的示例


本部分演示了一种嵌套堆栈架构，该架构由引用嵌套堆栈的顶级堆栈组成。嵌套堆栈部署 Node.js Lambda 函数，从顶级堆栈接收参数值，并返回通过顶级堆栈公开的输出。

**Topics**
+ [

### 步骤 1：在本地系统上为嵌套堆栈创建模板
](#create-a-nested-stack-template)
+ [

### 步骤 2：在本地系统上为顶级堆栈创建模板
](#create-a-nested-stack-parent-template)
+ [

### 步骤 3：打包并部署模板
](#create-a-nested-stack-parent-template)

### 步骤 1：在本地系统上为嵌套堆栈创建模板


以下示例显示嵌套堆栈模板的格式。

#### YAML


```
 1. AWSTemplateFormatVersion: 2010-09-09
 2. Description: Nested stack template for Lambda function deployment
 3. Parameters:
 4.   MemorySize:
 5.     Type: Number
 6.     Default: 128
 7.     MinValue: 128
 8.     MaxValue: 10240
 9.     Description: Lambda function memory allocation (128-10240 MB)
10. Resources:
11.   LambdaFunction:
12.     Type: AWS::Lambda::Function
13.     Properties:
14.       FunctionName: !Sub "${AWS::StackName}-Function"
15.       Runtime: nodejs18.x
16.       Handler: index.handler
17.       Role: !GetAtt LambdaExecutionRole.Arn
18.       Code:
19.         ZipFile: |
20.           exports.handler = async (event) => {
21.             return {
22.               statusCode: 200,
23.               body: JSON.stringify('Hello from Lambda!')
24.             };
25.           };
26.       MemorySize: !Ref MemorySize
27.   LambdaExecutionRole:
28.     Type: AWS::IAM::Role
29.     Properties:
30.       AssumeRolePolicyDocument:
31.         Version: '2012-10-17'
32.         Statement:
33.           - Effect: Allow
34.             Principal:
35.               Service: lambda.amazonaws.com
36.             Action: sts:AssumeRole
37.       ManagedPolicyArns:
38.         - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
39. Outputs:
40.   LambdaArn:
41.     Description: ARN of the created Lambda function
42.     Value: !GetAtt LambdaFunction.Arn
```

### 步骤 2：在本地系统上为顶级堆栈创建模板


以下示例显示了顶层堆栈模板的格式以及引用您在上一步中所创建堆栈的 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-stack.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-stack.html) 资源。

#### YAML


```
 1. AWSTemplateFormatVersion: 2010-09-09
 2. Description: Top-level stack template that deploys a nested stack
 3. Resources:
 4.   NestedStack:
 5.     Type: AWS::CloudFormation::Stack
 6.     Properties:
 7.       TemplateURL: /path_to_template/nested-template.yaml
 8.       Parameters:
 9.         MemorySize: 256
10. Outputs:
11.   NestedStackLambdaArn:
12.     Description: ARN of the Lambda function from nested stack
13.     Value: !GetAtt NestedStack.Outputs.LambdaArn
```

### 步骤 3：打包并部署模板


**注意**  
在本地使用模板时，Amazon CLI **package** 命令可以帮助您准备部署模板。它会自动处理将本地构件上传到 Amazon S3（包括 `TemplateURL`），并生成一个新的模板文件，其中包含对这些 S3 位置的更新引用。有关更多信息，请参阅 [使用 Amazon CLI 将本地构件上传到 S3 存储桶](using-cfn-cli-package.md)。

接下来，您可以使用 [https://docs.amazonaws.cn/cli/latest/reference/cloudformation/package.html](https://docs.amazonaws.cn/cli/latest/reference/cloudformation/package.html) 命令将嵌套模板上传到 Amazon S3 存储桶。

```
aws cloudformation package \
  --s3-bucket amzn-s3-demo-bucket \
  --template /path_to_template/top-level-template.yaml \
  --output-template-file packaged-template.yaml \
  --output json
```

该命令在 `--output-template-file` 指定的路径上生成一个新模板。它将 `TemplateURL` 引用替换为 Amazon S3 位置，如下图所示。

**结果模板**

```
AWSTemplateFormatVersion: 2010-09-09
Description: Top-level stack template that deploys a nested stack
Resources:
  NestedStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.us-west-2.amazonaws.com/amzn-s3-demo-bucket/8b3bb7aa7abfc6e37e2d06b869484bed.template
      Parameters:
        MemorySize: 256
Outputs:
  NestedStackLambdaArn:
    Description: ARN of the Lambda function from nested stack
    Value:
      Fn::GetAtt:
      - NestedStack
      - Outputs.LambdaArn
```

运行 **package** 命令后，您可以使用 [https://docs.amazonaws.cn/cli/latest/reference/cloudformation/deploy/](https://docs.amazonaws.cn/cli/latest/reference/cloudformation/deploy/) 命令部署已处理的模板。对于包含 IAM 资源的嵌套堆栈，您必须通过包含 `--capabilities` 选项来确认 IAM 功能。

```
aws cloudformation deploy \
  --template-file packaged-template.yaml \
  --stack-name stack-name \
  --capabilities CAPABILITY_NAMED_IAM
```

## 在嵌套堆栈上执行堆栈操作


使用嵌套堆栈时，在操作过程中必须谨慎处理。某些堆栈操作（如堆栈更新等）应从根堆栈启动，而不是直接在嵌套堆栈上执行。更新根堆栈时，只有包含模板更改的嵌套堆栈才会更新。

此外，嵌套堆栈的存在可能会影响根堆栈上的操作。例如，如果一个嵌套堆栈卡在 `UPDATE_ROLLBACK_IN_PROGRESS` 状态，则根堆栈将等待该嵌套堆栈完成其回滚后再继续。在继续更新操作之前，请确保您拥有 IAM 权限，以在其回滚时取消堆栈更新。有关更多信息，请参阅 [使用 Amazon Identity and Access Management 控制 CloudFormation 访问权限](control-access-with-iam.md)。

按照以下过程查找根堆栈和嵌套堆栈。

**查看嵌套堆栈的根堆栈**

1. 登录到 Amazon Web Services 管理控制台 并打开 Amazon CloudFormation 控制台 [https://console.aws.amazon.com/cloudformation](https://console.amazonaws.cn/cloudformation/)。

1. 在**堆栈**页面上，选择您要查看其根堆栈的嵌套堆栈的名称。

   嵌套堆栈的堆栈名称上方会显示 **NESTED**。

1. 在**堆栈信息**选项卡的**概述**部分中，选择列为**根堆栈**的堆栈名称。

**查看属于根堆栈的嵌套堆栈**

1. 从要查看其嵌套堆栈的根堆栈中，选择**资源**选项卡。

1. 在**类型**列中，查找类型为 **AWS::CloudFormation::Stack** 的资源。

## 相关信息

+ [嵌套现有的堆栈](resource-import-nested-stacks.md)
+ [理解堆栈资源的更新行为](using-cfn-updating-stacks-update-behaviors.md)
+ [继续回滚失败的嵌套堆栈更新](using-cfn-updating-stacks-continueupdaterollback.md#nested-stacks)
+ [嵌套堆栈回滚失败](troubleshooting.md#troubleshooting-errors-nested-stacks-are-stuck)

# 在 CloudFormation 模板中创建等待条件
等待条件

本主题介绍了如何在模板中创建等待条件以协调堆栈资源的创建或跟踪配置过程的进度。例如，您可以在应用程序配置部分完成后开始另一个资源的创建，或者也可以在安装和配置过程中发送信号以跟踪其进度。

当 CloudFormation 创建含有等待条件的堆栈时：
+ 它会跟其他任何资源一样创建等待条件，并将等待条件的状态设置为 `CREATE_IN_PROGRESS`。
+ CloudFormation 等待至收到必要数量的成功信号或等待条件的超时时段到期。
+ 如果在超时时段到期之前收到了必要数量的成功信号：
  + 等待条件的状态更改为 `CREATE_COMPLETE`
  + 堆栈创建继续
+ 如果超时到期或收到了故障信号：
  + 等待条件的状态更改为 `CREATE_FAILED`
  + 堆栈回滚

**重要**  
对于 Amazon EC2 和 Auto Scaling 资源，我们建议您使用 CreationPolicy 属性而非等待条件。当实例创建过程成功完成后，将“CreationPolicy”属性添加到这些资源，并使用 cfn-signal 帮助程序脚本发送信号。  
有关更多信息，请参阅 [CreationPolicy 属性](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-attribute-creationpolicy.html)。

**注意**  
如果使用 Amazon PrivateLink，则响应等待条件的 VPC 中的资源必须能够访问 CloudFormation 特定的 Amazon Simple Storage Service (Amazon S3) 存储桶。资源必须将等待条件响应发送到预签名 Amazon S3 URL。如果这些资源不能向 Amazon S3 发送响应，则 CloudFormation 将不会收到响应，并且堆栈操作会失败。有关更多信息，请参阅[使用接口端点 (Amazon PrivateLink) 访问 Amazon CloudFormation](vpc-interface-endpoints.md)以及[使用存储桶策略控制从 VPC 端点的访问](https://docs.amazonaws.cn/AmazonS3/latest/userguide/example-bucket-policies-vpc-endpoint.html)。

**Topics**
+ [

## 在模板中创建等待条件
](#creating-wait-condition)
+ [

## 等待条件信号语法
](#wait-condition-signal-syntax)
+ [

## 访问信号数据
](#wait-condition-access-signal-data)

## 在模板中创建等待条件


**1. 等待条件句柄**  
首先在堆栈模板中定义 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-waitconditionhandle.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-waitconditionhandle.html) 资源。该资源会生成发送信号所需的预签名 URL。这样您就可以发送信号，而无需提供 Amazon 凭证。例如：

```
Resources:
  MyWaitHandle:
    Type: AWS::CloudFormation::WaitConditionHandle
```

**2. 等待条件**  
接下来，在堆栈模板中定义 [https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-waitcondition.html](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-waitcondition.html) 资源。`AWS::CloudFormation::WaitCondition` 的基本结构如下所示：

```
  MyWaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    Properties:
      Handle: String
      Timeout: String
      Count: Integer
```

`AWS::CloudFormation::WaitCondition` 资源有两个必要属性和一个可选属性。
+ `Handle`（必要）：对模板中声明的 `WaitConditionHandle` 的引用。
+ `Timeout`（必要）：CloudFormation 在收到必要数量的信号之前等待的秒数。`Timeout` 是一种最低时限属性，表示超时不会早于您指定的时间发生，但可能在指定时间后不久发生。您可指定的最长时间为 43200 秒 (12 小时)。
+ `Count`（可选）：CloudFormation 在将等待条件状态设置为 `CREATE_COMPLETE` 并继续创建堆栈前之前必须收到的成功信号数量。如果未指定，则默认值为 1。

通常，您想要等待条件在特定资源创建后立即开始。可以通过为等待条件添加 `DependsOn` 属性来完成此操作。向等待条件添加 `DependsOn` 属性时，CloudFormation 先在 `DependsOn` 属性中创建资源，然后创建等待条件。有关更多信息，请参阅 [DependsOn 属性](https://docs.amazonaws.cn/AWSCloudFormation/latest/TemplateReference/aws-attribute-dependson.html)。

以下示例展示等待条件：
+ 在成功创建 `MyEC2Instance` 资源后开始
+ 将 `MyWaitHandle` 资源用作 `WaitConditionHandle`
+ 超时为 4500 秒
+ 默认 `Count` 为 1（因为未指定任何 `Count` 属性）

```
  MyWaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    DependsOn: MyEC2Instance
    Properties:
      Handle: !Ref MyWaitHandle
      Timeout: '4500'
```

**3. 发送信号**  
为向 CloudFormation 发出成功或失败的信号，通常需要运行一些代码或脚本。例如，在 EC2 实例上运行的应用程序可能会执行一些额外的配置工作，然后向 CloudFormation 发出信号指示已完成。

信号必须发送至由等待条件句柄生成的预签名 URL。使用预签名 URL 发送成功或失败信号。

**发送信号**

1. 要检索模板中的预签名 URL，需使用带有等待条件句柄逻辑名称的 `Ref` 内置函数。

   如下例所示，您的模板可声明一个 Amazon EC2 实例，并使用 Amazon EC2 `UserData` 属性将预签名 URL 传递到该 EC2 实例。这样在这些实例上运行的脚本或应用程序就可以向 CloudFormation 发出成功或失败的信号。

   ```
     MyEC2Instance:
       Type: AWS::EC2::Instance
       Properties:
       InstanceType: t2.micro  # Example instance type
       ImageId: ami-055e3d4f0bbeb5878  # Change this as needed (Amazon Linux 2023 in us-west-2)
       UserData:
         Fn::Base64: 
           Fn::Join: 
             - ""
             - - "SignalURL="
               - { "Ref": "MyWaitHandle" }
   ```

   这会产生类似于下述信息的 `UserData` 输出：

   ```
   SignalURL=https://amzn-s3-demo-bucket.s3.amazonaws.com/....
   ```

   注意：在 Amazon Web Services 管理控制台和命令行工具中，预签名 URL 显示为等待条件句柄资源的物理 ID。

1. （可选）要检测堆栈进入等待条件的时间，可以使用以下方法之一：
   + 如果您通过启用的通知创建堆栈，那么 CloudFormation 将向指定主题发布针对每项堆栈事件的通知。如果您或您的应用程序订阅了该主题，您可以监测针对等待条件句柄创建事件的通知，并通过通知消息来检索预签名 URL。
   + 您还可以使用 Amazon Web Services 管理控制台、Amazon CLI 或 SDK 来监控堆栈事件。

1. 您需要通过预签名 URL 发送 HTTP 请求消息之后，才能发送信号。请求方法必须为 `PUT`，并且 `Content-Type` 标题必须为空字符串或省略。请求消息必须为 [等待条件信号语法](#wait-condition-signal-syntax) 指定表单的 JSON 结构。

   您必须发送由 `Count` 属性指定的成功信号数，以便 CloudFormation 继续创建堆栈。如果您的 `Count` 超过 1，则在发送到特定等待条件的所有信号中，每个信号的 `UniqueId` 值必须是唯一的。`UniqueId` 是随机的字母数字字符串。

   `curl` 命令是发送信号的一种方式。以下示例显示了向等待条件发送成功信号的 `curl` 命令行。

   ```
   $ curl -T /tmp/a \
     "https://amzn-s3-demo-bucket.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-west-2%3A034017226601%3Astack%2Fstack-gosar-20110427004224-test-stack-with-WaitCondition--VEYW%2Fe498ce60-70a1-11e0-81a7-5081d0136786%2FmyWaitConditionHandle?Expires=1303976584&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=ik1twT6hpS4cgNAw7wyOoRejVoo%3D"
   ```

   其中文件 *`/tmp/a`* 包含以下 JSON 结构：

   ```
   {
      "Status" : "SUCCESS",
      "Reason" : "Configuration Complete",
      "UniqueId" : "ID1234",
      "Data" : "Application has completed configuration."
   }
   ```

   此示例显示发送同一成功信号的 `curl` 命令行，不同的是，它将 JSON 结构作为命令行参数发送。

   ```
   $ curl -X PUT \
     -H 'Content-Type:' --data-binary '{"Status" : "SUCCESS","Reason" : "Configuration Complete","UniqueId" : "ID1234","Data" : "Application has completed configuration."}' \
     "https://amzn-s3-demo-bucket.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-west-2%3A034017226601%3Astack%2Fstack-gosar-20110427004224-test-stack-with-WaitCondition--VEYW%2Fe498ce60-70a1-11e0-81a7-5081d0136786%2FmyWaitConditionHandle?Expires=1303976584&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=ik1twT6hpS4cgNAw7wyOoRejVoo%3D"
   ```

## 等待条件信号语法


在向等待条件句柄生成的 URL 发送信号时，您必须使用以下 JSON 格式：

```
{
  "Status" : "StatusValue",
  "UniqueId" : "Some UniqueId",
  "Data" : "Some Data",
  "Reason" : "Some Reason"
}
```

### 属性


`Status` 字段必须是以下任一值：
+ `SUCCESS`
+ `FAILURE`

`UniqueId` 字段标识发送至 CloudFormation 的信号。如果等待条件的 `Count` 属性大于 1，那么在针对特定等待条件发送的所有信号中，`UniqueId` 值必须唯一；否则，CloudFormation 会将该信号视为对先前已发送的具有相同 `UniqueId` 的信号的重传，并将其忽略。

`Data` 字段可包含要通过信号发回的任何信息。可以通过使用模板中的 [Fn::GetAtt ](resources-section-structure.md#resource-properties-getatt)函数来访问 `Data` 值。

`Reason` 字段为字符串，除与 JSON 格式相一致外，其内容中无任何其他限制。

## 访问信号数据


要访问由有效信号发送的数据，可以在 CloudFormation 模板中为等待条件创建输出值。例如：

```
Outputs:
  WaitConditionData:
    Description: The data passed back as part of signalling the WaitCondition
    Value: !GetAtt MyWaitCondition.Data
```

然后可以使用 [https://docs.amazonaws.cn/cli/latest/reference/cloudformation/describe-stacks.html](https://docs.amazonaws.cn/cli/latest/reference/cloudformation/describe-stacks.html) 命令或 CloudFormation 控制台的**输出**选项卡来查看该数据。

`Fn::GetAtt` 函数将返回 `UniqueId` 和 `Data` 作为 JSON 结构中的名称/值对。例如：

```
{"Signal1":"Application has completed configuration."}
```