J'essaie de créer une nouvelle version d'une fonction Lambda à l'aide de CloudFormation.
Je souhaite disposer de plusieurs versions de la même fonction Lambda pour pouvoir (a) pointer des alias sur différentes versions - telles que DEV et PROD - et (b) pouvoir revenir à une version antérieure.
Voici la définition de ma version Lambda:
LambdaVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName:
Ref: LambdaFunction
Une version est créée lors de l'exécution de "aws cloudformation create-stack", mais les commandes suivantes "aws cloudformation update-stack" ne font rien. Aucune nouvelle version Lambda n'a été créée.
J'essaie d'obtenir une nouvelle version de la fonction Lambda créée après avoir téléchargé un nouveau fichier Zip sur S3, puis exécuté "update-stack". Puis-je le faire avec CloudFormation? AWS :: Lambda :: Version est-il vraiment cassé (comme mentionné ici https://github.com/hashicorp/terraform/issues/6067#issuecomment-211708071 ) ou est-ce que je ne reçois rien?
Mise à jour 1/11/17 Réponse officielle du support Amazon: "... pour toute nouvelle version à publier, vous devez définir une addition (sic)} AWS: : Lambda :: Version resource ... "
L'équipe AWS CloudFormation/Lambda, si vous lisez ceci, c'est inacceptable. Répare le.
AWS :: Lambda :: Version n'est pas utile. Vous devez ajouter une nouvelle ressource pour chaque version de Lambda. Si vous souhaitez publier une nouvelle version pour chaque mise à jour de Cloudformation, vous devez pirater le système.
J'ai résolu ce problème en créant une ressource personnalisée sauvegardée par Lambda qui est déclenchée pour chaque déploiement. Dans cette Lambda, je crée une nouvelle version pour la fonction Lambda donnée en paramètre.
Pour la source Lambda, vous pouvez vérifier http://serverless-Arch-eu-west-1.s3.amazonaws.com/serverless.Zip
Voici l'exemple de Cloudformation utilisant cette fonction Deployment Lambda (vous aurez peut-être besoin de modifications):
{
"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"
}
}
}
}
}
(Avertissement: ce code fait partie de mon livre. Pour plus d'informations sur Lambda & API Gateway, vous pouvez vérifier: https://www.Amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195 )
J'ai un cas d'utilisation similaire (ayant besoin d'utiliser CloudFormation pour gérer une fonction lambda à utiliser @Edge dans CloudFront, pour lequel une version spécifique de la fonction lambda est toujours requise, pas $LATEST
) et mes recherches m'ont d'abord amené à cette question, mais après Je suis heureux de constater qu'il existe désormais une prise en charge native du contrôle de version automatique lambda avec la nouvelle fonctionnalité AutoPublishAlias
de AWS Serverless Application Model (un ensemble supplémentaire facultatif de constructions de niveau supérieur pour vos modèles CloudFormation).
Annoncé ici: https://github.com/awslabs/serverless-application-model/issues/41#issuecomment-347723981
Pour plus de détails, voir:
En gros, vous incluez AutoPublishAlias
dans votre définition AWS::Serverless::Function
:
MyFunction:
Type: "AWS::Serverless::Function"
Properties:
# ...
AutoPublishAlias: MyAlias
Et ensuite, ailleurs dans le modèle CloudFormation, vous pouvez référencer la dernière version publiée en tant que !Ref MyFunction.Version
(syntaxe yaml).
La ressource AWS::Lambda::Version
représente uniquement une version de fonction Lambda publiée. Elle ne publiera pas automatiquement les nouvelles versions à chaque mise à jour de votre code. Pour ce faire, vous avez deux options:
Vous pouvez implémenter votre propre ressource Custom qui appelle PublishVersion
à chaque mise à jour.
Pour cette approche, vous devez toujours modifier au moins un paramètre à chaque mise à jour de votre pile afin de déclencher une mise à jour sur la ressource personnalisée qui déclenchera l'action PublishVersion. (Cependant, vous ne devrez pas réellement mettre à jour le modèle.)
Voici un exemple complet et fonctionnel:
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
Vous pouvez utiliser un préprocesseur de modèle tel que embedded Ruby (ou simplement mettre à jour manuellement votre modèle à chaque déploiement) pour publier une nouvelle version à chaque mise à jour de votre code en modifiant le ID logique de la ressource AWS::Lambda::Version
chaque fois que votre code est mis à jour.
Exemple:
# 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
Pour créer/mettre à jour la pile en passant template.yml
par le préprocesseur de modèle erb
, exécutez:
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
Réponse mise à jour pour février 2018
Vous pouvez utiliser AWS SAM (modèle d'application sans serveur) et ses commandes sam package
et sam deploy
pour mettre à jour Lambda. Elles sont similaires aux commandes aws cloudformation package
et aws cloudformation deploy
, mais vous permettent également de mettre à jour les versions Lambda automatiquement.
SAM peut conditionner votre code (ou prendre le paquet Zip que vous avez créé autrement), le télécharger sur S3 et mettre à jour la version $LATEST
de Lambda. (Si c'est tout ce dont vous avez besoin, vous pouvez également le faire avec aws cloudformation
, sans SAM; les exemples de code sont identiques à ceux décrits ci-dessous, mais utilisez uniquement les déclarations standard de CloudFormation
). Ensuite, avec SAM, s'il est configuré en conséquence, vous pouvez également publier automatiquement une version et mettre à jour un alias pour le désigner. Il peut également, en option, utiliser AWS CodeDeploy pour déplacer progressivement le trafic de la version précédente vers la nouvelle, et l'annuler en cas d'erreur. Tout cela est expliqué dans Déploiements Safe Lambda .
Techniquement, l'idée est que chaque fois que vous mettez à jour la pile, vous avez besoin de la variable Code
de votre AWS::Lambda::Function
pour pointer vers le package new dans S3. Cela garantira que lors de la mise à jour de la pile, la version $ LATEST de Lambda sera mise à jour à partir du nouveau paquet. Ensuite, vous pouvez également automatiser la publication de la nouvelle version et lui attribuer un alias.
Pour cela, créez un modèle SAM, similaire au modèle CloudFormation (un sur-ensemble). Il peut inclure des déclarations spécifiques à SAM, comme celle de AWS::Serverless::Function
ci-dessous. Pointez la Code
sur le répertoire du code source (ou un fichier Zip préemballé) et définissez la propriété 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
...
Courir:
$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket my-bucket
Ce package contient le contenu du répertoire source sous forme de fichier Zip (si Code
n'est pas déjà un fichier Zip), il est chargé sur S3 sous une nouvelle clé générée automatiquement et génère le modèle CloudFormation final dans packaged.yaml
, en y insérant la référence Code
appropriée. comme ça:
...
MyFunction:
Properties:
Code:
S3Bucket: my-bucket
S3Key: ddeeaacc44ddee33ddaaee223344
...
Vous pouvez maintenant utiliser packaged.yaml
généré avec SAM pour créer la fonction Version:
sam deploy --template-file packaged.yaml --stack-name my-stack [--capabilities ...]
Ceci mettra à jour la version $LATEST
de Lambda et, si AutoPublishAlias
a été défini, la publiera en tant que nouvelle version et mettra à jour l'alias pour qu'il pointe vers la version récemment publiée.
Voir les exemples dans le référentiel SAM GitHub pour un code de modèle complet.
Vous recherchez une fonctionnalité similaire qui fonctionne avec les fonctions Lambda déployées à partir de S3.
Mon cas d'utilisation était le suivant:
Non content de cela, j'ai cherché une alternative et suis tombé sur cette question. Aucune des réponses ne fonctionnant exactement pour moi, j’ai donc pris quelques idées et adapté les réponses ici pour en faire ma propre version écrite en Python.
Ce code est adapté de la réponse de @wjordan, alors portez-le à l'esprit pour l'idée et la réponse originale. Les différences sont:
Vous avez besoin d'un paramètre nonce. Vous modifiez la valeur de ce paramètre lorsque le code doit être republié vers Lambda. Cela permet de s'assurer que cloudformation mettra à jour votre ressource personnalisée. Lorsque la ressource personnalisée est mise à jour, elle exécute le code Python qui met finalement à jour votre code Lambda.
J'espère que ça aide quelqu'un.
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
Malheureusement, cela n'est pas possible avec CloudFormation. Vous devrez ajouter de nouvelles sections AWS::Lambda::Version
dans votre modèle CloudFormation pour chaque version.
La solution la plus proche serait de créer des modèles .erb et de générer des modèles CloudFormation avec toutes les versions.
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