首页 文章

如何使用CloudFormation创建新版本的Lambda函数?

提问于
浏览
19

我正在尝试使用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)或者我只是没有得到什么?

Update 1/11/17 亚马逊支持的官方回复:“...对于要发布的任何新版本,您需要定义一个添加(原文如此)AWS :: Lambda :: Version资源......”

AWS CloudFormation / Lambda团队,如果您正在阅读此内容 - 这是不可接受的 . 修理它 .

7 回答

  • 1
    • 我们可以制作一个Lambda部署包;

    • 将Lambda包与版本一起作为Cloud Formation参数之一传递,例如"LambdaPakcageNameWithVersion";

    • 使用"LambdaPakcageNameWithVersion"作为Lambda代码s3键;

    • 在运行aws-cli命令以更新 Cloud 形式堆栈或运行CI / CD管道时,将部署新的Lamdba软件包 .

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

    Answer updated for February 2018

    You can use AWS SAM (Serverless Application Model), and its sam package and sam deploy commands to update 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版本将从新包中更新 . 然后,您还可以自动发布新版本并将Alias切换到该版本 .

    为此,创建一个SAM模板,类似于CloudFormation模板的(超集) . 它可能包含特定于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
    ...
    

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

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

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

    有关完整的模板代码,请参阅examples in SAM GitHub repo .

  • 4

    AWS::Lambda::Version资源仅表示单个已发布的Lambda函数版本 - 它不会在每次更新代码时自动发布新版本 . 要实现此目的,您有两种选择:

    1.自定义资源

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

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

    这是一个完整的,有效的例子:

    Launch Stack

    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
    
  • 9

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

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

  • 14

    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": "HERE_DEFINE_YOUR_LAMBDA"
        },
        "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 Gateway的更多信息,请查看:https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195

  • 0

    我有一个类似的用例(需要使用CloudFormation来管理在CloudFront中使用@edge的lambda函数,对于该函数,总是需要一个特定的lambda函数版本,而不是 $LATEST )并且我的搜索首先在这个问题上找到了我,但之后更多的挖掘我很高兴地发现现在支持自动lambda版本使用AWS无服务器应用程序模型的新 AutoPublishAlias 功能(基本上是为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语法) .

  • 0

    寻找与从S3部署的Lambda函数一样的类似事物 .

    我的用例是这样的:

    • 您有一个cloudformation模板,可以从S3存储桶位置创建Lambda函数

    • 您需要更新此功能,以便在本地进行代码更改并将更改推送到S3

    • 您现在想要将这些更改推送到Lambda,以便您尝试更新堆栈,并且cloudformation表示没有更新更新,因此您必须使用AWS Lambda控制台手动更新代码 .

    对此不满意我找了一个替代方案并遇到了这个问题 . 没有一个答案对我有用,所以我已经采取了一些想法并在这里修改了答案,并用Python编写了我自己的版本 .

    这段代码改编自@wjordan的答案,因此对他的想法和原始答案给予了褒奖 . 不同之处是:

    • 这是用Python编写的

    • 它适用于从S3存储桶部署的Lambda代码

    • 它更新代码并发布新版本

    您需要一个nonce参数 . 当代码需要重新发布到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
    

相关问题