web-dev-qa-db-fra.com

Comment créer une nouvelle version d'une fonction Lambda à l'aide de CloudFormation?

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.

23
boris

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 )

15

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).

14
Rob Peters

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:

1. Ressource personnalisée

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:

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. préprocesseur de modèle

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

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.

6
Timur

Vous recherchez une fonctionnalité similaire qui fonctionne avec les fonctions Lambda déployées à partir de S3.

Mon cas d'utilisation était le suivant:

  • Vous avez un modèle cloudformation qui crée une fonction Lambda à partir d'un emplacement de compartiment S3.
  • Vous devez mettre à jour cette fonction pour pouvoir modifier le code localement et transmettre les modifications à S3.
  • Vous souhaitez maintenant appliquer ces modifications à Lambda afin que vous tentiez de mettre à jour la pile et que cloudformation indique qu'aucune modification ne soit mise à jour. Vous devez donc mettre à jour manuellement le code à l'aide de la console AWS Lambda.

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:

  • Ceci est écrit en Python
  • Cela fonctionne avec le code Lambda déployé à partir d'un compartiment S3
  • Il met à jour le code et publie une nouvelle version

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 
2
CarlR

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.

1
Sinan Gedik
  1. Nous pouvons créer un package de déploiement Lambda; 
  2. Transmettez le package Lambda Avec la version en tant que paramètre de formation dans le cloud, par exemple . "LambdaPakcageNameWithVersion"; 
  3. Utilisez "LambdaPakcageNameWithVersion" comme clé s3 du code Lambda; 
  4. Le nouveau package Lamdba sera déployé lors de l'exécution de la commande aws-cli pour mettre à jour La mise à jour de la pile d'informations cloud ou l'exécution du pipeline 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

0
Jerry