部署具有以下资源的模板时,我的 AWS CloudFormation 堆栈创建失败:

  1. AWS Lambda 函数资源。
  2. Amazon S3 存储桶资源,其具有引用 Lambda 函数NotificationConfiguration 属性。
  3. Lambda 权限资源,其具有匹配 Lambda 函数和 S3 存储桶的 FunctionNameSourceArn 属性。
    注意:最佳实践是向 S3 事件源的 Lambda 权限资源添加 SourceAccount 属性,这是因为 S3 Amazon 资源名称 (ARN) 不包含账户 ID。尽管对于大多数其他事件源来说,SourceArn 属性足够了,还是应考虑为 S3 事件源添加 SourceAccount 属性,以防止出现以下情况:您删除存储桶,却发现有人重新创建了该存储桶,结果向该存储桶的新所有者授予了调用您的 Lambda 函数的完全权限。

如果堆栈失败,会生成类似以下内容的错误:

Unable to validate the following destination configurations

创建存储桶时,S3 必须通过检查存储桶是否有权向 Lambda 函数推送事件来验证通知配置。权限资源 (必须存在才能通过此检查) 需要存储桶名称。因此,权限资源与存储桶互相依赖。

注意:如果像下面这样尝试通过实现 DependsOn 资源属性来解决此问题,则会出现错误:

"MyS3BucketPermission": {
  "Properties": {
    "Action": "lambda:InvokeFunction",
    ...
    ...
    "SourceArn": {
      "Ref": "MyS3Bucket"
    }
  },
  "Type": "AWS::Lambda::Permission"
},
"Resources": {
  "MyS3Bucket": {
    "DependsOn" : "MyS3BucketPermission",

DependsOn 资源属性错误:

Circular dependency between resources

在此示例中,S3 存储桶资源与 Lambda 权限资源的 SourceArn 属性形成循环依赖:两者都不存在,但又无法在对方不存在的情况下创建自身。

发生此问题时,可以使用 Fn::Join 内部函数配合堆栈参数避免循环依赖。

考虑此示例模板,其中存储桶名称“BucketPrefix”作为参数提供给“AWS::S3::Bucket”和“AWS::Lambda::Permission”资源。

注意:此示例假定您的 AWS 账户之前未使用该存储桶名称。如果您需要对模板重复使用此代码段,每次都必须提供不同的存储桶前缀。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "BucketPrefix": {
      "Type": "String",
      "Default": "test-bucket-name"
    }
  },
  "Resources": {
    "EncryptionServiceBucket": {
      "DependsOn": "LambdaInvokePermission",
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": {
          "Fn::Sub": "${BucketPrefix}-encryption-service"
        },
        "NotificationConfiguration": {
          "LambdaConfigurations": [
            {
              "Function": {
                "Fn::GetAtt": [
                  "AppendItemToListFunction",
                  "Arn"
                ]
              },
              "Event": "s3:ObjectCreated:*",
              "Filter": {
                "S3Key": {
                  "Rules": [
                    {
                      "Name": "suffix",
                      "Value": "zip"
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    },
    "LambdaInvokePermission": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "AppendItemToListFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "s3.amazonaws.com",
        "SourceAccount": {
          "Ref": "AWS::AccountId"
        },
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:aws:s3:::",
              {
                "Fn::Sub": "${BucketPrefix}-encryption-service"
              }
            ]
          ]
        }
      }
    },
    "AppendItemToListFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "LambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "",
              [
                "var response = require('cfn-response');",
                "exports.handler = function(event, context) {",
                " var responseData = {Value: event.ResourceProperties.List};",
                " responseData.Value.push(event.ResourceProperties.AppendedItem);",
                " response.send(event, context, response.SUCCESS, responseData);",
                "};"
              ]
            ]
          }
        },
        "Runtime": "nodejs4.3"
      }
    },
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyName": "root",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:*"
                  ],
                  "Resource": "arn:aws:logs:*:*:*"
                }
              ]
            }
          }
        ]
      }
    }
  }
}

这可以避免循环依赖,因为它按以下顺序创建资源:​

  1. IAM 角色
  2. Lambda 函数
  3. Lambda 权限
  4. S3 存储桶

使用这种方法时,S3 能够正常验证通知配置并创建存储桶。

其他可能的解决方法包括:

  • 创建没有通知配置的存储桶,然后将其添加到下一次堆栈更新中。
  • 创建约束较少的 Lambda 权限。例如,通过省略 SourceArn 来允许特定 AWS 账户的调用。
  • 创建自定义资源以在堆栈工作流程结束时运行。所有其他资源创建后,此资源将通知配置添加到存储桶。

此页面对您有帮助吗? |

返回 AWS Support 知识中心

需要帮助? 请访问 AWS 支持中心

发布时间:2017 年 10 月 3 日

更新时间:2018 年 9 月 6 日