Как создать новую версию лямбда-функции с помощью CloudFormation?


Я пытаюсь создать новую версию лямбда-функции, используя CloudFormation.

Я хочу иметь несколько версий одной и той же лямбда - функции, чтобы я мог (а) указывать псевдонимы в разных версиях - таких как DEV и PROD- и (б) иметь возможность откатиться к более ранней версии

Это определение моей лямбда-версии:

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

Версия создается при выполнении команды "aws cloudformation create-stack", но последующие команды " aws cloudformation update-stack сделать ничего. Новые лямбда-версии не создаются.

Я пытаюсь получить новую версию функции Lambda, созданную после загрузки нового zip-файла в S3, а затем запустить "update-stack". Могу ли я сделать это с помощью CloudFormation? Действительно ли AWS::Lambda::Version сломана (как упоминалось здесь https://github.com/hashicorp/terraform/issues/6067#issuecomment-211708071 ) или я просто чего-то не понимаю?

Обновление 1/11/17 Официальный ответ от службы поддержки Amazon: "...для любого нового версия для публикации необходимо определить дополнение (sic) AWS::Lambda::Version resource..."

AWS CloudFormation / Lambda team, если Вы читаете это-это неприемлемо. Исправить это.

7 18

7 ответов:

AWS:: Lambda:: Version не полезна. Вы должны добавить новый ресурс для каждой лямбда-версии. Если вы хотите публиковать новую версию для каждого обновления Cloudformation, вы должны взломать систему.

Я решил эту проблему, создав пользовательский ресурс с поддержкой лямбда-кода, который запускается для каждого развертывания. Внутри этой лямбды я создаю новую версию для лямбда-функции, заданной в параметре.

Для источника лямбды вы можете проверить http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip

Вот пример Cloudformation, использующий эту функцию развертывания Lambda (возможно, потребуется некоторая модификация):

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

У меня есть похожий вариант использования (необходимость использовать CloudFormation для управления лямбда-функцией, которая будет использоваться @edge в CloudFront, для которой всегда требуется конкретная версия лямбда-функции, а не $LATEST), и мои поиски сначала привели меня к этому вопросу, но после немного большего копания я был рад обнаружить, что теперь есть встроенная поддержка автоматического управления лямбда-версиями с новой функцией AutoPublishAlias модели приложения AWS Serverless (в основном дополнительный дополнительный набор конструкций более высокого уровня для вашего Шаблоны CloudFormation).

Объявлено здесь: https://github.com/awslabs/serverless-application-model/issues/41#issuecomment-347723981

Подробнее смотрите:

По существу вы включаете AutoPublishAlias в ваше AWS::Serverless::Function определение:

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

А затем в другом месте шаблона CloudFormation вы можете ссылайтесь на последнюю опубликованную версию как !Ref MyFunction.Version (синтаксис yaml).

AWS::Lambda::Version ресурс представляет только одну опубликованную версию лямбда-функции - он не будет автоматически публиковать новые версии при каждом обновлении кода. Для этого у вас есть два варианта:

1. Пользовательский ресурс

Вы можете реализовать свой собственный пользовательский ресурс , который вызывает 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 всякий раз, когда ваш код обновляется.

Пример:

# 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

Чтобы создать / обновить стек при передаче template.yml через препроцессор шаблона erb, выполните:

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

Ответ обновлен за февраль 2018

Вы можете использовать AWS SAM (Serverless Application Model), а также его команды sam package и sam deploy для обновления Lambda. Они похожи на команды aws cloudformation package и aws cloudformation deploy, но также позволяют автоматически обновлять лямбда-версии.

Сэм может упаковать ваш код (или взять ZIP-пакет, созданный иным способом), загрузить его в S3 и обновить из него версию $LATEST лямбды. (Если это все, что вам нужно, это также можно сделать с aws cloudformation, без SAM; примеры кода такие же, как и ниже, но только используют стандартные объявления CloudFormation). Затем, если SAM настроен соответствующим образом, Вы также можете автоматически публиковать версию и обновлять псевдоним, чтобы указать на нее. Он также может использовать AWS CodeDeploy для постепенного перемещения трафика с предыдущей версии на новую и отката в случае ошибок. Все это объясняется вбезопасных лямбда-развертываниях .

Технически идея заключается в том, что каждый раз, когда вы обновляете стек, вам нужно, чтобы ваш AWS::Lambda::Function ' s Code указывал на новый пакет в S3. Это гарантирует, что при обновлении стека $LATEST версия Lambda будет обновлена из нового пакета. Затем можно также автоматизировать публикацию новой версии и переключить на нее псевдоним.

Для него создайте шаблон 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
...

Теперь вы можете использовать generated packaged.yaml с SAM, чтобы создать версию функции:

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

Это обновит лямбду $LATEST версия, и, если AutoPublishAlias был определен, опубликуйте его как новую версию и обновите псевдоним, чтобы указать на новую опубликованную версию.

Смотрите примерыв Sam GitHub repo для полного кода шаблона.

К сожалению, это невозможно сделать с помощью CloudFormation. Вам нужно будет добавить новые разделы AWS::Lambda::Version в шаблон CloudFormation для каждой версии.

Наиболее близким решением было бы создание.erb шаблоны и заставить его генерировать Шаблоны CloudFormation со всеми версиями.

Ищу похожую вещь, которая работает с лямбда-функциями, развернутыми из S3.

Мой пример использования был таков:

  • у вас есть шаблон cloudformation, который создает лямбда-функцию из расположения корзины S3
  • вам нужно обновить эту функцию, чтобы внести изменения в код локально и отправить изменения в S3
  • Теперь вы хотите протолкнуть эти изменения в Lambda, поэтому вы пытаетесь обновить стек, и cloudformation говорит, что нет никаких изменений для обновления, поэтому вы должны прибегнуть к обновление кода вручную с помощью консоли AWS Lambda.
Не удовлетворенный этим, я искал альтернативу и наткнулся на этот вопрос. Ни один из ответов точно не работал для меня, поэтому я взял некоторые идеи и адаптировал ответы здесь и сделал свою собственную версию, написанную на Python.

Этот код адаптирован из ответа от @wjordan, так что надо отдать ему должное за идею и оригинальный ответ. Различия заключаются в следующем:

  • это написано на Python
  • это работает с лямбда-кодом, развернутым из корзины S3
  • он обновляет код и публикует новую версию

Вам нужен параметр nonce. Вы изменяете значение этого параметра, когда код должен быть повторно опубликован в Lambda. Это необходимо для того, чтобы cloudformation обновил ваш пользовательский ресурс. Когда пользовательский ресурс обновляется, он запускает код 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 
  1. мы можем сделать лямбда-пакет развертывания;
  2. передайте лямбду пакет с версией в качестве одного из параметров формирования облака, напр. "LambdaPakcageNameWithVersion";
  3. Использование "LambdaPakcageNameWithVersion" как ключ лямбда-кода s3;
  4. новое Пакет Lamdba будет развернут при выполнении команды aws-cli для обновите стек 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