【问题标题】:How to create a new version of a Lambda function using CloudFormation?如何使用 CloudFormation 创建新版本的 Lambda 函数?
【发布时间】:2017-05-18 01:30:05
【问题描述】:

我正在尝试使用 CloudFormation 创建新版本的 Lambda 函数。

我想拥有同一个 Lambda 函数的多个版本,以便 (a) 指向不同版本的别名 - 例如 DEV 和 PROD - 并且 (b) 能够回滚到早期版本

这是我的 Lambda 版本的定义:

LambdaVersion:
  Type: AWS::Lambda::Version
  Properties:
    FunctionName:
      Ref: LambdaFunction

运行“aws cloudformation create-stack”时会创建一个版本,但随后的“aws cloudformation update-stack”命令不执行任何操作。没有创建新的 Lambda 版本。

在将新的 zip 文件上传到 S3 然后运行“update-stack”后,我正在尝试获取新版本的 Lambda 函数。我可以使用 CloudFormation 吗? AWS::Lambda::Version 真的坏了(正如这里提到的https://github.com/hashicorp/terraform/issues/6067#issuecomment-211708071)还是我没有得到什么?

2017 年 1 月 11 日更新 亚马逊支持官方回复: “...对于要发布的任何新版本,您需要定义一个附加 (sic) AWS::Lambda::Version 资源...”

AWS CloudFormation/Lambda 团队,如果您正在阅读本文 - 这是不可接受的。修复它。

【问题讨论】:

    标签: amazon-web-services aws-lambda amazon-cloudformation


    【解决方案1】:

    我有一个类似的用例(需要使用 CloudFormation 来管理要在 CloudFront 中使用 @edge 的 lambda 函数,为此始终需要特定的 lambda 函数版本,而不是 $LATEST),我的搜索让我找到了这个首先提出问题,但经过更多挖掘后,我很高兴地发现现在有了 AWS 无服务器应用程序模型的新 AutoPublishAlias 功能对自动 lambda 版本控制的原生支持(基本上是您的 CloudFormation 的一组可选的额外高级构造模板)。

    在此宣布:https://github.com/awslabs/serverless-application-model/issues/41#issuecomment-347723981

    详情见:

    基本上你在AWS::Serverless::Function定义中包含AutoPublishAlias

    MyFunction:
      Type: "AWS::Serverless::Function"
      Properties:
        # ...
        AutoPublishAlias: MyAlias
    

    然后在 CloudFormation 模板的其他地方,您可以将最新发布的版本引用为 !Ref MyFunction.Version(yaml 语法)。

    【讨论】:

    • 这很简单,应该是新批准的答案。谢谢
    • 虽然这似乎是一个很好的答案,但应注意该答案依赖于 AWS SAM。请注意,函数类型是 AWS::Serverless::Function。不适合我。
    • 这可能不是批准的答案,因为我们中的一些人可能会因为使用 AutoPublishAlias 时出现的版本泄漏问题 (github.com/serverless/serverless/issues/400) 及其影响而出现在这里。例如,我将其用于“登台”,但也希望针对同一版本设置“生产”别名。在 AutoPublishAlias 只能解决我的部分 CI/CD 挑战的情况下,自定义资源可以工作。
    【解决方案2】:

    AWS::Lambda::Version 没用。您必须为每个 Lambda 版本添加一个新资源。如果您想为每个 Cloudformation 更新发布新版本,则必须破解系统。

    我解决了这个问题,创建了一个 Lambda 支持的自定义资源,每次部署都会触发该资源。在这个 Lambda 中,我正在为参数中给出的 Lambda 函数创建一个新版本。

    对于 Lambda 的来源,您可以查看http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip

    这是使用此部署 Lambda 函数的示例 Cloudformation(您可能需要进行一些修改):

    {
      "AWSTemplateFormatVersion": "2010-09-09",
      "Parameters": {
        "DeploymentTime": {
          "Type": "String",
          "Description": "It is a timestamp value which shows the deployment time. Used to rotate sources."
        }
      },
      "Resources": {
        "LambdaFunctionToBeVersioned": {
          "Type": "AWS::Lambda::Function",
           ## HERE DEFINE YOUR LAMBDA AS USUAL ##
        },
        "DeploymentLambdaRole": {
          "Type": "AWS::IAM::Role",
          "Properties": {
            "AssumeRolePolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Principal": {
                    "Service": [
                      "lambda.amazonaws.com"
                    ]
                  },
                  "Action": [
                    "sts:AssumeRole"
                  ]
                }
              ]
            },
            "Path": "/",
            "ManagedPolicyArns": [
              "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
            ],
            "Policies": [
              {
                "PolicyName": "LambdaExecutionPolicy",
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": [
                        "lambda:PublishVersion"
                      ],
                      "Resource": [
                        "*"
                      ]
                    }
                  ]
                }
              }
            ]
          }
        },
        "DeploymentLambda": {
          "Type": "AWS::Lambda::Function",
          "Properties": {
            "Role": {
              "Fn::GetAtt": [
                "DeploymentLambdaRole",
                "Arn"
              ]
            },
            "Handler": "serverless.handler",
            "Runtime": "nodejs4.3",
            "Code": {
              "S3Bucket": {
                "Fn::Sub": "serverless-arch-${AWS::Region}"
              },
              "S3Key": "serverless.zip"
            }
          }
        },
        "LambdaVersion": {
          "Type": "Custom::LambdaVersion",
          "Properties": {
            "ServiceToken": {
              "Fn::GetAtt": [
                "DeploymentLambda",
                "Arn"
              ]
            },
            "FunctionName": {
              "Ref": "LambdaFunctionToBeVersioned"
            },
            "DeploymentTime": {
              "Ref": "DeploymentTime"
            }
          }
        }
      }
    }
    

    (免责声明:此代码是我书中的一部分,有关 Lambda 和 API 网关的更多信息,您可以查看:https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195

    【讨论】:

    • 谢谢!这太疯狂了。我通过我们的企业支持计划向 AWS 提出支持请求,如果他们确认 AWS::Lambda::Version 没有用,我会尝试这个并接受这个答案。
    • @boris 嗨 Boris,如果自 2018 年以来实施此功能,是否有任何更新?
    • 太棒了,我花了两天时间尝试在每次发布时限制角色创建,非常感谢!
    • HERE_DEFINE_YOUR_LAMBDA 应该放入什么?那不是实际的 Lambda 函数代码,那是什么?
    • @GrzegorzOledzki 我已经编辑了代码以澄清。这是实际的 Lambda 代码。
    【解决方案3】:

    此帖子已过时。我在这里更新它,以便其他人可以看到截至 2020 年 6 月 9 日的版本控制 Lambda 的正确解决方案,而无需额外的自定义版本控制 Lambda。

    这个:

    Description: Lambda Example
    Resources:
      Function:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.handler
          Code:
            ZipFile: |
              'Example Code';
          Runtime: nodejs12.x
          Timeout: 5
    

    变成这样:

    Description: Lambda Example
    Transform: AWS::Serverless-2016-10-31
    Resources:
      Function:
        Type: AWS::Serverless::Function
        Properties:
          AutoPublishAlias: live
          Handler: index.handler
          InlineCode: |
            'Example Code';
          Runtime: nodejs12.x
          Timeout: 5
    

    Transform: 允许在 CloudFormation 模板中使用AWS::Serverless::Function,这反过来又支持 lambda 版本控制。

    不要像我一样让上面过时的“最佳答案”——专为那个人设计的书——把你扔进兔子洞。

    不客气。

    【讨论】:

    • 这很简单而且效果很好 - 它应该是批准的答案。
    • 这对 codeuri 不起作用。它确实发布了实时版本,但没有更新功能代码。你有 codeuri 的工作示例吗?
    • 应该注意 AutoPublishAlias 通过将给定别名附加到函数名称作为 CFN 资源名称来完成转换。这意味着您不能使用包含非字母数字字符的别名。
    • 这可能有其自身的问题:github.com/aws/aws-cdk/issues/5334
    【解决方案4】:

    AWS::Lambda::Version 资源仅代表一个已发布的 Lambda 函数版本 - 它不会在每次更新代码时自动发布新版本。为此,您有两种选择:

    1。自定义资源

    您可以实现自己的Custom Resource,在每次更新时调用PublishVersion

    对于这种方法,您仍然需要在每次更新堆栈时更改至少一个参数,以触发自定义资源的更新,从而触发 PublishVersion 操作。 (不过,您不必实际更新模板。)

    这是一个完整的工作示例:

    Description: Publish a new version of a Lambda function whenever the code is updated.
    Parameters:
      Nonce:
        Description: Change this string when code is updated.
        Type: String
        Default: "Test"
    Resources:
      MyCustomResource:
        Type: Custom::Resource
        Properties:
          ServiceToken: !GetAtt MyFunction.Arn
          Nonce: !Ref Nonce
      MyFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.handler
          Role: !GetAtt LambdaExecutionRole.Arn
          Code:
            ZipFile: !Sub |
              var response = require('cfn-response');
              exports.handler = function(event, context) {
                return response.send(event, context, response.SUCCESS, {Result: '${Nonce}'});
              };
          Runtime: nodejs4.3
      LambdaDeploy:
        Type: Custom::LambdaVersion
        Properties:
          ServiceToken: !GetAtt LambdaDeployFunction.Arn
          FunctionName: !Ref MyFunction
          Nonce: !Ref Nonce
      LambdaDeployFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: "index.handler"
          Role: !GetAtt LambdaExecutionRole.Arn
          Code:
            ZipFile: !Sub |
              var AWS = require('aws-sdk');
              var response = require('cfn-response');
              exports.handler = (event, context) => {
                console.log("Request received:\n", JSON.stringify(event));
                if (event.RequestType == 'Delete') {
                  return response.send(event, context, response.SUCCESS);
                }
                var lambda = new AWS.Lambda();
                lambda.publishVersion({FunctionName: event.ResourceProperties.FunctionName}).promise().then((data) => {
                  return response.send(event, context, response.SUCCESS, {Version: data.Version}, data.FunctionArn);
                }).catch((e) => {
                  return response.send(event, context, response.FAILED, e);
                });
              };
          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: /
          ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          Policies:
          - PolicyName: PublishVersion
            PolicyDocument:
              Version: 2012-10-17
              Statement:
              - Effect: Allow
                Action: ['lambda:PublishVersion']
                Resource: '*'
    Outputs:
      LambdaVersion:
        Value: !GetAtt LambdaDeploy.Version
      CustomResourceResult:
        Value: !GetAtt MyCustomResource.Result
    

    2。模板预处理器

    您可以使用像embedded Ruby 这样的模板预处理器(或者只是在每次部署时手动更新您的模板),通过在每次更新代码时更改AWS::Lambda::Version 资源的Logical ID 来在每次更新代码时发布新版本.

    例子:

    # template.yml
    Description: Publish a new version of a Lambda function whenever the code is updated.
    <%nonce = rand 10000%>
    Resources:
      LambdaVersion<%=nonce%>:
        Type: AWS::Lambda::Version
        Properties:
          FunctionName: !Ref MyFunction
      MyCustomResource:
        Type: Custom::Resource
        Properties:
          ServiceToken: !GetAtt MyFunction.Arn
          Nonce: <%=nonce%>
      MyFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.handler
          Role: !GetAtt LambdaExecutionRole.Arn
          Code:
            ZipFile: !Sub |
              var response = require('cfn-response');
              exports.handler = function(event, context) {
                return response.send(event, context, response.SUCCESS, {Result: '<%=nonce%>'});
              };
          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: /
          ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    Outputs:
      LambdaVersion:
        Value: !GetAtt LambdaVersion<%=nonce%>.Version
      CustomResourceResult:
        Value: !GetAtt MyCustomResource.Result
    

    要在通过erb 模板预处理器传递template.yml 时创建/更新堆栈,请运行:

    aws cloudformation [create|update]-stack \
      --stack-name [stack_name] \
      --template-body file://<(ruby -rerb -e "puts ERB.new(ARGF.read).result" < template.yml) \
      --capabilities CAPABILITY_IAM
    

    【讨论】:

    • 我想拥有同一个 Lambda 函数的多个版本,以便 (a) 指向不同版本的别名 - 例如 DEV 和 PROD - 并且 (b) 能够回滚到早期版本
    • 您可能需要考虑使用 CodePipline 将不同的 CloudFormation 配置模板部署到同一个 CloudFormation 模板中。这可能是维护开发和生产环境的一种不那么脆弱的方式。
    • @boris 感谢您提供额外的上下文,我已经用两种不同方法的完整示例模板更新了我的答案。
    • 此解决方案是否也适用于 S3 存储桶(不使用 Code:)。我得到错误。 A version for this Lambda function exists ( 1 ). Modify the function to create a new version
    • @wjordan 是的,同意 boris,除非您(愚蠢地?)在 CloudFormation 中添加无限的 LambdaVersion 列表,否则 Lambda 不会自动保留您可以轻松回滚到的先前版本的列表。
    【解决方案5】:

    答案已于 2018 年 2 月更新

    您可以使用AWS SAM (Serverless Application Model) 及其sam packagesam deploy 命令来更新Lambda。它们类似于 aws cloudformation packageaws cloudformation deploy 命令,但也可以让您自动更新 Lambda 版本。

    SAM 可以打包您的代码(或获取您创建的 ZIP 包),将其上传到 S3,并从中更新 Lambda 的$LATEST 版本。 (如果这就是你所需要的,这也可以用aws cloudformation 来完成,不用SAM;代码示例与下面相同,但只使用CloudFormation 的标准声明)。然后,使用 SAM,如果进行了相应配置,您还可以自动发布版本并更新别名以指向它。它还可以选择使用 AWS CodeDeploy 将流量从以前的版本逐渐移动到新版本,并在出现错误时回滚。所有这些都在Safe Lambda deployments 中进行了解释。


    从技术上讲,这个想法是每次更新堆栈时,都需要您的AWS::Lambda::FunctionCode 指向 S3 中的 new 包。这将确保当您更新堆栈时,Lambda 的 $LATEST 版本将从新包中更新。然后,您还可以自动发布新版本并将别名切换到它。

    为此,创建一个类似于 CloudFormation 模板(超集)的 SAM 模板。它可能包括特定于 SAM 的声明,例如下面的 AWS::Serverless::Function 声明。将Code 指向源代码目录(或预打包的ZIP),并设置AutoPublishAlias 属性。

    ...
    
    MyFunction:
        Type: AWS::Serverless::Function
        Properties:
          ...  # all usual CloudFormation properties are accepted 
          AutoPublishAlias: dev  # will publish a Version and create/update Alias `dev` to point to it
          Code: ./my/lambda/src
    ...
    

    运行:

    $ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket my-bucket
    

    这会将源目录内容打包为 ZIP(如果 Code 还不是 ZIP),以新的自动生成密钥将其上传到 S3,并生成最终 CloudFormation 模板到 packaged.yaml,为您提供正确的 Code参考它;像这样:

    ...
    MyFunction:
        Properties:
          Code:
            S3Bucket: my-bucket
            S3Key: ddeeaacc44ddee33ddaaee223344
    ...
    

    现在您可以使用生成的packaged.yaml 和 SAM 来创建函数版本:

    sam deploy --template-file packaged.yaml --stack-name my-stack [--capabilities ...]
    

    这将更新 Lambda 的 $LATEST 版本,如果定义了 AutoPublishAlias,则将其发布为新版本并更新别名以指向新发布的版本。

    完整的模板代码请参见examples in SAM GitHub repo

    【讨论】:

    • 这并没有解决通过 cloudformation 向 lambda 函数添​​加多个版本的问题。所有这一切都是使用提供的一些不错的工具来生成一些 cloudformation 并更新$LATEST 处的代码。它不会向 lambda 函数添​​加版本。
    • 是的,确实,我没有意识到这个问题也包括这个。我已经更新了答案以包含这个(发布版本并更新别名)。
    【解决方案6】:

    寻找与从 S3 部署的 Lambda 函数一起使用的类似东西。

    我的用例是这样的:

    • 您有一个可以从 S3 存储桶位置创建 Lambda 函数的 cloudformation 模板
    • 您需要更新此函数,以便在本地进行代码更改并将更改推送到 S3
    • 您现在想要将这些更改推送到 Lambda,因此您尝试更新堆栈,而 cloudformation 说没有要更新的更改,因此您必须求助于使用 AWS Lambda 控制台手动更新代码。

    对此不满意,我寻找替代方案并遇到了这个问题。 没有一个答案对我完全有效,所以我采取了一些想法并在这里调整了答案,并制作了我自己用 Python 编写的版本。

    此代码改编自 @wjordan 的答案,因此感谢他的想法和原始答案。区别在于:

    • 这是用 Python 编写的
    • 它适用于从 S3 存储桶部署的 Lambda 代码
    • 更新代码并发布新版本

    您需要一个随机数参数。当代码需要重新发布到 Lambda 时,您可以更改此参数的值。这是为了确保 cloudformation 将更新您的自定义资源。当自定义资源更新时,它将运行最终更新您的 Lambda 代码的 Python 代码。

    希望这对某人有所帮助。

    Description: Publish a new version of a Lambda function whenever the code is updated.
    Parameters:
      Nonce:
        Description: Change this string when code is updated.
        Type: String
        Default: "Test"
    Resources:
      MyCustomResource:
        Type: Custom::Resource
        Properties:
          ServiceToken: !GetAtt MyFunction.Arn
          Nonce: !Ref Nonce
      MyFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.handler
          Role: !GetAtt LambdaExecutionRole.Arn
          Code:
            S3Bucket: BucketContainingYourLambdaFunction
            S3Key: KeyToYourLambdaFunction.zip
          Runtime: "python3.6"
      LambdaExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Principal: {Service: [lambda.amazonaws.com]}
              Action: ['sts:AssumeRole']
          Path: /
          ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      LambdaDeployCustomResource:
        Type: Custom::LambdaVersion
        Properties:
          ServiceToken: !GetAtt LambdaDeployFunction.Arn
          FunctionName: !Ref MyFunction
          S3Bucket: BucketContainingYourLambdaFunction
          S3Key: KeyToYourLambdaFunction.zip
          Nonce: !Ref Nonce
      LambdaDeployFunction:
        Type: AWS::Lambda::Function
        DependsOn: LambdaDeployFunctionExecutionRole
        Properties:
          Handler: "index.handler"
          Role: !GetAtt LambdaDeployFunctionExecutionRole.Arn
          Code:
            ZipFile: !Sub |
              import boto3
              import json
              import logging
              import cfnresponse
              import time
              from botocore.exceptions import ClientError
    
              def handler(event, context):
                logger = logging.getLogger()
                logger.setLevel(logging.INFO)
                logger.info (f"Input parameters from cloud formation: {event}")
                responseData = {}
                if (event["RequestType"] == 'Delete'):
                  logger.info("Responding to delete event...")
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
    
                try:            
                  lambdaClient = boto3.client('lambda')
                  s3Bucket = event['ResourceProperties']['S3Bucket']
                  s3Key = event['ResourceProperties']['S3Key']
                  functionName = event['ResourceProperties']['FunctionName']
                  logger.info("Updating the function code for Lambda function '{}' to use the code stored in S3 bucket '{}' at key location '{}'".format(functionName, s3Bucket, s3Key))
                  logger.info("Sleeping for 5 seconds to allow IAM permisisons to take effect")
                  time.sleep(5)             
                  response = lambdaClient.update_function_code(
                    FunctionName=functionName,
                    S3Bucket='{}'.format(s3Bucket),
                    S3Key='{}'.format(s3Key),
                    Publish=True)
                  responseValue = "Function: {}, Version: {}, Last Modified: {}".format(response["FunctionName"],response["Version"],response["LastModified"])
                  responseData['Data'] = responseValue
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, response["FunctionArn"])
                except ClientError as e:
                  errorMessage = e.response['Error']['Message']
                  logger.error(errorMessage)
                  cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
          Runtime: "python3.6"
          Timeout: "30"
      LambdaDeployFunctionExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Principal: 
                Service: lambda.amazonaws.com
              Action: 
                - sts:AssumeRole
          Path: /
          ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          Policies:
          - PolicyName: ReadS3BucketContainingLambdaCode
            PolicyDocument:
              Version: 2012-10-17
              Statement:
              - Effect: Allow
                Action: 
                  - s3:GetObject              
                Resource: ArnOfS3BucketContainingLambdaCode/*
          - PolicyName: UpdateCodeAndPublishVersion
            PolicyDocument:
              Version: 2012-10-17
              Statement:
              - Effect: Allow
                Action: 
                  - lambda:UpdateFunctionCode
                  - lambda:PublishVersion
                Resource: '*'
    Outputs:
      LambdaVersion:
        Value: !GetAtt LambdaDeploy.Version
      CustomResourceResult:
        Value: !GetAtt MyCustomResource.Result 
    

    【讨论】:

      【解决方案7】:

      很遗憾,使用 CloudFormation 无法做到这一点。您需要在 CloudFormation 模板中为每个版本添加新的 AWS::Lambda::Version 部分。

      最接近的解决方案是创建 .erb 模板并让它生成包含所有版本的 CloudFormation 模板。

      【讨论】:

        【解决方案8】:
        1. 我们可以制作一个Lambda部署包;
        2. 通过 Lambda 将版本作为 Cloud Formation 参数之一的软件包,例如 "LambdaPakcageNameWithVersion";
        3. 使用 “LambdaPakcageNameWithVersion”作为 Lambda 代码 s3 密钥;
        4. 新的 运行 aws-cli 命令时会部署 lamdba 包 更新 cloudformation 堆栈或运行 CI/CD 管道。

          MyLambda:
            Type: AWS::Lambda::Function
            Properties:
              Role: LambdaRole
              Code:
                S3Bucket: LambdaPackageS3Bucket
                S3Key: !Sub "${LambdaPakcageNameWithVersion}"
              FunctionName: LambdaFunctionName
              Handler: lambda_function.lambda_handler
              Runtime: python3.6
              Timeout: 60

        【讨论】:

          【解决方案9】:

          这有点小技巧,取决于使用 gitlab-ci(或类似的东西),但我发现将提交哈希传递到 cloudformation 模板(通过模板的参数)非常有用。

          (有点像@Jerry 的回答,但使用了提交哈希。)

          在这种情况下,您可以执行以下操作:

          在你的模板中有一个提交哈希的参数,例如:

          AWSTemplateFormatVersion: '2010-09-09'
          Description: Template for Lambda Sample.
          Parameters:
            ciCommitSha:
              Type: String
            s3Bucket:
              Type: String
            ...
          

          然后您可以在 lambda 资源中引用它,如下所示:

            CFNLambda:
              Type: AWS::Lambda::Function
              Properties:
                FunctionName: cfn_trigger_fn
                Description: lambda which gets triggered by cloudformation
                Runtime: python3.7
                Code:
                  S3Bucket: !Ref s3Bucket
                  S3Key: !Join [ ".", [ !Ref ciCommitSha, "zip"]]
                Handler: function.handler
                ...
          

          然后,您的 ci 管道需要看起来像(假设您调用 cloudformation 模板 stack-template.yaml):

          variables:
            REGION: us-east-1
            S3_BUCKET_NAME: my-bucket
          
          stages:
           - build
           - push
           - deploy
          
          build-package:
            stage: build
            script:
              - some code to produce a deployment package called function.zip
            artifacts:
              name: deployment_package
              paths:
                - function.zip
          
          
          push-code:
            stage: push
            script:
              - aws s3 cp function.zip s3://$S3_BUCKET_NAME/$CI_COMMIT_SHA.zip
          
          deploy-trigger-stack:
            stage: deploy
            script: 
                - aws cloudformation deploy
                      --template-file stack-template.yaml
                      --stack-name my-stack
                      --region $REGION
                      --no-fail-on-empty-changeset
                      --capabilities CAPABILITY_NAMED_IAM
                      --parameter-overrides
                      ciCommitSha=$CI_COMMIT_SHA
                      s3Bucket=$S3_BUCKET_NAME
          

          您也可以使用此技术在 EC2 元数据上触发 cfn-init..

          【讨论】:

            【解决方案10】:

            为我工作了以下内容:

            "LambdaAlias": {
                        "Type": "AWS::Lambda::Alias",
                        "DeletionPolicy" : "Retain",
                        "Properties": {
                            "FunctionName": {
                                "Ref": "LambdaFunction"
                            },
                            "FunctionVersion": {
                                "Fn::GetAtt": ["LambdaVersion","Version"]
                            },
                            "Name": "MyAlias"
                        }
            

            【讨论】:

            • 所以我只需要添加删除策略:保留
            【解决方案11】:

            我使用 CI/CD、一个 ant 脚本和 git 修订解决了这个问题,以便在 S3 存储桶中为每个提交创建一个唯一的 zip 名称。

            CI/CD 调用 ant 脚本以将 git 修订版替换为 lambda 代码 zip 文件和 cloudformation 模板的名称。这些引用是在将代码和 cloudformation 脚本复制到 S3 之前进行的。这类似于 SAM 的工作方式,但它适用于普通的旧 Cloudformation 堆栈,重要的是可能需要跨多个帐户部署的堆栈集。在撰写本文时,SAM 与 CF 堆栈集不兼容

            有两个文件:ant 文件和一个属性文件,它告诉 ant 文件要压缩哪些 lambda 源目录。

            首先是build.xml ant文件:

            <project basedir="." name="AWS Lambda Tooling Bucket Zip" default="ziplambdas">
                <!-- this ant file is responsible for zipping up lambda source code that needs to be placed on an S3 bucket for deployment.
                    It reads a file `lambda-zip-build.properties` that contains a list of lambda folders and the corresponding zip names.
                    This allows a lambda to include source code and any required library packages into a single zip for deployment.
                    For further information refer to the comments at the top of the zip properties file.
                -->
            
                <property name="ant.home" value="${env.ANT_HOME}" />
                <taskdef resource="net/sf/antcontrib/antlib.xml">
                    <classpath path="${ant.home}/lib/ant-contrib-1.0b3.jar" />
                </taskdef>
            
                <!-- <available file=".git" type="dir" property="git.present"/> -->
                <available file="../../.git" type="dir" property="git.present"/>
            
                <!-- get the git revision to make a unique filename on S3. This allows the zip key to be replaced, forcing an update if CloudFormation is deployed. Clunky,
                     AWS Support raised but advice was to use SAM, which is not compatible with StackSets ... *sigh* -->
                <target name="gitrevision" description="Store git revision in ${repository.version}" if="git.present">
                    <exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
                        <arg value="describe"/>
                        <arg value="--tags"/>
                        <arg value="--always"/>
                        <arg value="HEAD"/>
                    </exec>
                    <condition property="repository.version" value="${git.revision}" else="unknown">
                        <and>
                            <isset property="git.revision"/>
                            <length string="${git.revision}" trim="yes" length="0" when="greater"/>
                        </and>
                    </condition>
                    <echo>git revision is ${git.revision} </echo>
                </target>
            
                <target name="replace.git.revision.in.files" depends="gitrevision" description="replace the git marker text in cloudformation files and zip properties file">
                    <replace dir="." token="@git.revision@" value="${git.revision}" summary="yes"/>
                </target>
            
                <property file="lambda.zip.build.properties"/>
            
                <!-- zip the lambda code into a unique zip name based on the git revision -->
                <target name="ziplambdas" description="Create Zip files based on the property list" depends="replace.git.revision.in.files">
                    <property file="lambda.zip.build.properties" prefix="zipme." />
                    <propertyselector property="zip.list" match="^zipme\.(.*)" select="\1"/>
            
                    <foreach list="${zip.list}" delimiter="," target="zip" param="folder"/>
                </target>
            
                <target name="zip">
                    <propertycopy property="zip.path" from="${folder}" />
                    <basename property="zip.file" file="${zip.path}" />
                    <echo message="${folder} is being zipped to ${zip.path}"/>
                    <zip destfile="${zip.path}">
                        <zipfileset dir="${folder}">
                           <exclude name="**/${zip.file}"/>
                        </zipfileset> 
                    </zip>
                </target>
            
            </project>
            

            lambda.zip.build.properties 文件如下所示:

            # This property file contains instructions for CI/CD Build Process to zip directories containing lambda code to place on the S3  bucket.
            # Lambda source code when deployed by CloudFormation must be available inside a Zip file in a S3 bucket.
            # CI/CD runs an ant task that reads this file to create the appropriate zip files referenced by the CloudFormation scripts. 
            # 
            # Each property key value pair below contains a key of the top level directory containing the lambda code (in python, javascript or whatever), 
            # and a value of the path to the zip file that should be deployed to S3. The @git.revision@ tag is substituted with the actual git revision before copying to S3.
            # This allows the lambda S3key to change for each deployment and forces a lambda code update. 
            #
            # for example: myproject/lambda/src=myproject/lambda/my-src-@git.revision@.zip
            #              ^^ Directory    ^^ Zip File
            #
            ###################################################################################################################################################################################
            myproject/lambda/src=myproject/lambda/lambda-code-@git.revision@.zip
            # place your key value pairs above here...
            

            然后是 CloudFormation 模板:

            Resources:
              MyLambda:
                Type: AWS::Lambda::Function
                Properties:
                # git.revision is placed when code is zipped up by CI/CD and placed on S3 bucket. It allows a unique name for each commit and thereby forces
                # lambda code to be replaced on cloudformation stackset redeployment.
                  Code:
                    S3Bucket: mybucket
                    S3Key: myproject/lambda/lambda-code-@git.revision@.zip
                  Handler: autotag-costcentre.lambda_handler
                  MemorySize: 128
                  Runtime: python3.7
                  Timeout: 10
                  .... etc
            

            结果是一个带有唯一名称 lambda-code-0f993c3.zip 的 zip 文件和一个带有 S3Key 引用唯一名称的 Cloudformation 模板。

            S3Key: myproject/lambda/lambda-code-0f993c3.zip
            

            从 S3 位置部署模板,它将强制每次刷新现有的 lambda 代码。

            【讨论】:

              【解决方案12】:

              我对文件夹进行校验和,并将其附加到 lambda 的 S3Key

              - lambda_src_version=$(find ${Lambda1} -type f -print0  | xargs -0 sha1sum | sha1sum )
              - lambda_src_version =$(echo ${lambda_src_version//[[:blank:]]/})
              - export S3Key="lambda_functions/${stage}/cloudformation-${lambda_src_version}.zip"
              - zip - -r . -x '*.git*' | aws s3 cp - s3://${S3Bucket}/${S3Key}
              - sam deploy --template-file cloudformation.yml --stack-name XXX --parameter-overrides Lambda1Bucket=${S3Bucket} Lambda1CodeZip="${S3Key}"
              

              // cloudformation.yml sn-p

              Parameters:      
                Lambda1CodeZip:
                  Type: String
                Lambda1Bucket:
                  Type: String 
              
              Type: 'AWS::Lambda::Function'
                  Properties: 
                    Code:
                      S3Bucket: !Sub ${Lambda1Bucket}
                      S3Key: !Sub ${Lambda1CodeZip}
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-03-27
                • 2018-10-02
                • 1970-01-01
                • 2021-02-10
                • 2018-06-16
                • 2021-11-29
                • 2021-03-27
                • 2023-01-12
                相关资源
                最近更新 更多