Как создать новую версию лямбда-функции с помощью 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 ответов:
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
Подробнее смотрите:
- https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst#instant-traffic-shifting-using-lambda-aliases
- https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#referencing-lambda-version--alias-resources
По существу вы включаете
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), а также его команды
Сэм может упаковать ваш код (или взять ZIP-пакет, созданный иным способом), загрузить его в S3 и обновить из него версиюsam package
иsam deploy
для обновления Lambda. Они похожи на командыaws cloudformation package
иaws cloudformation deploy
, но также позволяют автоматически обновлять лямбда-версии.$LATEST
лямбды. (Если это все, что вам нужно, это также можно сделать сaws cloudformation
, без SAM; примеры кода такие же, как и ниже, но только используют стандартные объявленияCloudFormation
). Затем, если SAM настроен соответствующим образом, Вы также можете автоматически публиковать версию и обновлять псевдоним, чтобы указать на нее. Он также может использовать AWS CodeDeploy для постепенного перемещения трафика с предыдущей версии на новую и отката в случае ошибок. Все это объясняется вбезопасных лямбда-развертываниях .
Технически идея заключается в том, что каждый раз, когда вы обновляете стек, вам нужно, чтобы ваш
AWS::Lambda::Function
' sCode
указывал на новый пакет в 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.
Мой пример использования был таков:
Не удовлетворенный этим, я искал альтернативу и наткнулся на этот вопрос. Ни один из ответов точно не работал для меня, поэтому я взял некоторые идеи и адаптировал ответы здесь и сделал свою собственную версию, написанную на Python.
- у вас есть шаблон cloudformation, который создает лямбда-функцию из расположения корзины S3
- вам нужно обновить эту функцию, чтобы внести изменения в код локально и отправить изменения в S3
- Теперь вы хотите протолкнуть эти изменения в Lambda, поэтому вы пытаетесь обновить стек, и cloudformation говорит, что нет никаких изменений для обновления, поэтому вы должны прибегнуть к обновление кода вручную с помощью консоли AWS Lambda.
Этот код адаптирован из ответа от @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
- мы можем сделать лямбда-пакет развертывания;
- передайте лямбду пакет с версией в качестве одного из параметров формирования облака, напр. "LambdaPakcageNameWithVersion";
- Использование "LambdaPakcageNameWithVersion" как ключ лямбда-кода s3;
- новое Пакет 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