如何使用 CloudFormation 创建新版本的 Lambda 函数?
How to create a new version of a Lambda function using CloudFormation?
我正在尝试使用 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 所述)还是我只是没有得到什么?
2017 年 1 月 11 日更新
亚马逊客服官方回复:
“...对于要发布的任何新版本,您需要定义一个附加 (sic) AWS::Lambda::Version 资源...”
AWS CloudFormation/Lambda 团队,如果您正在阅读本文 - 这是不可接受的。修复它。
不幸的是,使用 CloudFormation 无法做到这一点。您需要在每个版本的 CloudFormation 模板中添加新的 AWS::Lambda::Version
部分。
最接近的解决方案是创建 .erb 模板并让它生成包含所有版本的 CloudFormation 模板。
AWS::Lambda::Version
资源仅代表一个已发布的 Lambda 函数版本 - 它不会在您的代码每次更新时自动发布新版本。为此,您有两个选择:
1。自定义资源
您可以在每次更新时实施自己的 Custom Resource that calls 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 (or just manually updating your template on each deploy) to publish a new Version on each update of your code by changing the AWS::Lambda::Version
resource's 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
到create/update堆栈,同时通过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
AWS::Lambda::Version
没有用。您必须为每个 Lambda 版本添加一个新资源。如果你想为每个 Cloudformation 更新发布一个新版本,你必须破解系统。
我通过创建 Lambda 支持的自定义资源解决了这个问题,该资源会在每次部署时触发。在这个 Lambda 中,我正在为参数中给定的 Lambda 函数创建一个新版本。
对于 Lambda 的来源,您可以查看 http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip
以下是使用此 Deployment 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": "AWS::Lambda::Function",
## HERE DEFINE YOUR LAMBDA AS USUAL ##
},
"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 网关的更多信息,您可以查看:https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195)
我有一个类似的用例(需要使用 CloudFormation 来管理要在 CloudFront 中使用@edge 的 lambda 函数,为此始终需要特定的 lambda 函数版本,而不是 $LATEST
)和我的搜索首先让我想到了这个问题,但经过更多的挖掘,我很高兴地发现现在有对自动 lambda 版本控制的本地支持,具有 AWS 无服务器应用程序模型的新 AutoPublishAlias
功能(基本上是可选的额外一组更高的级构造您的 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
基本上您在 AWS::Serverless::Function
定义中包含 AutoPublishAlias
:
MyFunction:
Type: "AWS::Serverless::Function"
Properties:
# ...
AutoPublishAlias: MyAlias
然后在 CloudFormation 模板的其他地方,您可以将最新发布的版本引用为 !Ref MyFunction.Version
(yaml 语法)。
2018 年 2 月更新的答案
您可以使用 AWS SAM (Serverless Application Model) 及其 sam package
和 sam deploy
命令来更新 Lambda。它们类似于 aws cloudformation package
和 aws cloudformation deploy
命令,但还可以让您自动更新 Lambda 版本。
SAM 可以打包您的代码(或采用您创建的 ZIP 包),将其上传到 S3,并从中更新 Lambda 的 $LATEST
版本。 (如果这就是你所需要的,这也可以用 aws cloudformation
完成,没有 SAM;代码示例与下面相同,但只使用 CloudFormation
的标准声明)。然后,使用 SAM,如果相应配置,您还可以自动发布版本并更新别名以指向它。它还可以选择使用 AWS CodeDeploy 将流量从以前的版本逐渐移动到新版本,并在出现错误时回滚。 Safe Lambda deployments.
中解释了所有这些
从技术上讲,这个想法是每次更新堆栈时,您需要 AWS::Lambda::Function
的 Code
指向 new 包S3。这将确保当您更新堆栈时,Lambda 的 $LATEST 版本将从新包更新。然后,您还可以自动发布新版本并为其切换别名。
为此,创建一个 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
...
现在您可以将生成的 packaged.yaml
与 SAM 一起使用,以创建函数版本:
sam deploy --template-file packaged.yaml --stack-name my-stack [--capabilities ...]
这将更新 Lambda 的 $LATEST
版本,如果定义了 AutoPublishAlias
,则将其发布为新版本并更新别名以指向新发布的版本。
请参阅 examples in SAM GitHub repo 以获取完整的模板代码。
寻找与从 S3 部署的 Lambda 函数一起工作的类似东西。
我的用例是这样的:
- 您有一个从 S3 存储桶位置创建 Lambda 函数的 cloudformation 模板
- 您需要更新此函数,以便在本地更改代码并将更改推送到 S3
- 您现在想要将这些更改推送到 Lambda,因此您尝试更新堆栈,而 cloudformation 表示没有要更新的更改,因此您必须使用 AWS Lambda 控制台手动更新代码。
对此不满意,我寻找替代方案并遇到了这个问题。
None 的答案完全适合我,所以我采纳了一些想法并调整了这里的答案,并制作了我自己的 Python 版本。
此代码改编自@wjordan 的回答,因此感谢他提出的想法和原始答案。差异是:
- 这是写在Python
- 它适用于从 S3 存储桶部署的 Lambda 代码
- 更新代码并发布新版本
您需要一个随机数参数。当代码需要重新发布到 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
- 我们可以做一个Lambda部署包;
- 通过 Lambda
将版本作为 Cloud Formation 参数之一的包,例如
"LambdaPakcageNameWithVersion";
- 使用
"LambdaPakcageNameWithVersion" 作为 Lambda 代码 s3 密钥;
- 新的
运行 aws-cli 命令执行时将部署 Lamdba 包
更新 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
这有点 hack,取决于使用 gitlab-ci(或类似的东西),但我发现将提交哈希传递到 cloudformation 模板(通过模板的参数)非常有用。
(有点像@Jerry 的回答,但使用了提交哈希。)
在这种情况下,您可以这样做:
在您的模板中为提交哈希设置一个参数,例如:
AWSTemplateFormatVersion: '2010-09-09'
Description: Template for Lambda Sample.
Parameters:
ciCommitSha:
Type: String
s3Bucket:
Type: String
...
然后您可以在 lambda 资源中引用它,如下所示:
CFNLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: cfn_trigger_fn
Description: lambda which gets triggered by cloudformation
Runtime: python3.7
Code:
S3Bucket: !Ref s3Bucket
S3Key: !Join [ ".", [ !Ref ciCommitSha, "zip"]]
Handler: function.handler
...
你的 ci 管道需要看起来像(假设你调用你的 cloudformation 模板堆栈-template.yaml):
variables:
REGION: us-east-1
S3_BUCKET_NAME: my-bucket
stages:
- build
- push
- deploy
build-package:
stage: build
script:
- some code to produce a deployment package called function.zip
artifacts:
name: deployment_package
paths:
- function.zip
push-code:
stage: push
script:
- aws s3 cp function.zip s3://$S3_BUCKET_NAME/$CI_COMMIT_SHA.zip
deploy-trigger-stack:
stage: deploy
script:
- aws cloudformation deploy
--template-file stack-template.yaml
--stack-name my-stack
--region $REGION
--no-fail-on-empty-changeset
--capabilities CAPABILITY_NAMED_IAM
--parameter-overrides
ciCommitSha=$CI_COMMIT_SHA
s3Bucket=$S3_BUCKET_NAME
您也可以使用此技术在 EC2 元数据上触发 cfn-init..
我使用 CI/CD、一个 ant 脚本和 git 修订解决了这个问题,以便在 S3 存储桶中为每个提交创建一个唯一的 zip 名称。
ant 脚本由 CI/CD 调用以将 git 修订替换为 lambda 代码 zip 文件和 cloudformation 模板的名称。这些引用是在将代码和 cloudformation 脚本复制到 S3 之前进行的。这类似于 SAM 的工作方式,但它适用于普通的旧 Cloudformation Stacks,重要的是 Stack Sets 可能需要跨多个账户部署。在撰写本文时,SAM 与 CF 堆栈集不兼容
有两个文件:ant 文件和一个属性文件,它告诉 ant 文件要压缩哪些 lambda 源目录。
首先是 build.xml ant 文件:
<project basedir="." name="AWS Lambda Tooling Bucket Zip" default="ziplambdas">
<!-- this ant file is responsible for zipping up lambda source code that needs to be placed on an S3 bucket for deployment.
It reads a file `lambda-zip-build.properties` that contains a list of lambda folders and the corresponding zip names.
This allows a lambda to include source code and any required library packages into a single zip for deployment.
For further information refer to the comments at the top of the zip properties file.
-->
<property name="ant.home" value="${env.ANT_HOME}" />
<taskdef resource="net/sf/antcontrib/antlib.xml">
<classpath path="${ant.home}/lib/ant-contrib-1.0b3.jar" />
</taskdef>
<!-- <available file=".git" type="dir" property="git.present"/> -->
<available file="../../.git" type="dir" property="git.present"/>
<!-- get the git revision to make a unique filename on S3. This allows the zip key to be replaced, forcing an update if CloudFormation is deployed. Clunky,
AWS Support raised but advice was to use SAM, which is not compatible with StackSets ... *sigh* -->
<target name="gitrevision" description="Store git revision in ${repository.version}" if="git.present">
<exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
<arg value="describe"/>
<arg value="--tags"/>
<arg value="--always"/>
<arg value="HEAD"/>
</exec>
<condition property="repository.version" value="${git.revision}" else="unknown">
<and>
<isset property="git.revision"/>
<length string="${git.revision}" trim="yes" length="0" when="greater"/>
</and>
</condition>
<echo>git revision is ${git.revision} </echo>
</target>
<target name="replace.git.revision.in.files" depends="gitrevision" description="replace the git marker text in cloudformation files and zip properties file">
<replace dir="." token="@git.revision@" value="${git.revision}" summary="yes"/>
</target>
<property file="lambda.zip.build.properties"/>
<!-- zip the lambda code into a unique zip name based on the git revision -->
<target name="ziplambdas" description="Create Zip files based on the property list" depends="replace.git.revision.in.files">
<property file="lambda.zip.build.properties" prefix="zipme." />
<propertyselector property="zip.list" match="^zipme\.(.*)" select=""/>
<foreach list="${zip.list}" delimiter="," target="zip" param="folder"/>
</target>
<target name="zip">
<propertycopy property="zip.path" from="${folder}" />
<basename property="zip.file" file="${zip.path}" />
<echo message="${folder} is being zipped to ${zip.path}"/>
<zip destfile="${zip.path}">
<zipfileset dir="${folder}">
<exclude name="**/${zip.file}"/>
</zipfileset>
</zip>
</target>
</project>
lambda.zip.build.properties
文件如下所示:
# This property file contains instructions for CI/CD Build Process to zip directories containing lambda code to place on the S3 bucket.
# Lambda source code when deployed by CloudFormation must be available inside a Zip file in a S3 bucket.
# CI/CD runs an ant task that reads this file to create the appropriate zip files referenced by the CloudFormation scripts.
#
# Each property key value pair below contains a key of the top level directory containing the lambda code (in python, javascript or whatever),
# and a value of the path to the zip file that should be deployed to S3. The @git.revision@ tag is substituted with the actual git revision before copying to S3.
# This allows the lambda S3key to change for each deployment and forces a lambda code update.
#
# for example: myproject/lambda/src=myproject/lambda/my-src-@git.revision@.zip
# ^^ Directory ^^ Zip File
#
###################################################################################################################################################################################
myproject/lambda/src=myproject/lambda/lambda-code-@git.revision@.zip
# place your key value pairs above here...
然后是 CloudFormation 模板:
Resources:
MyLambda:
Type: AWS::Lambda::Function
Properties:
# git.revision is placed when code is zipped up by CI/CD and placed on S3 bucket. It allows a unique name for each commit and thereby forces
# lambda code to be replaced on cloudformation stackset redeployment.
Code:
S3Bucket: mybucket
S3Key: myproject/lambda/lambda-code-@git.revision@.zip
Handler: autotag-costcentre.lambda_handler
MemorySize: 128
Runtime: python3.7
Timeout: 10
.... etc
结果是一个具有唯一名称 lambda-code-0f993c3.zip
的 zip 文件和一个 S3Key
引用唯一名称的 Cloudformation 模板。
S3Key: myproject/lambda/lambda-code-0f993c3.zip
从 S3 位置部署模板,它会强制每次刷新现有的 lambda 代码。
为我工作了以下内容:
"LambdaAlias": {
"Type": "AWS::Lambda::Alias",
"DeletionPolicy" : "Retain",
"Properties": {
"FunctionName": {
"Ref": "LambdaFunction"
},
"FunctionVersion": {
"Fn::GetAtt": ["LambdaVersion","Version"]
},
"Name": "MyAlias"
}
这个 post 已经过时了。我在这里更新它,以便其他人可以看到从 2020 年 6 月 9 日起版本控制 Lambdas 的正确解决方案,而无需额外的自定义版本控制 Lambdas。
这个:
Description: Lambda Example
Resources:
Function:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Code:
ZipFile: |
'Example Code';
Runtime: nodejs12.x
Timeout: 5
变成这样:
Description: Lambda Example
Transform: AWS::Serverless-2016-10-31
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
AutoPublishAlias: live
Handler: index.handler
InlineCode: |
'Example Code';
Runtime: nodejs12.x
Timeout: 5
Transform:
允许 AWS::Serverless::Function
在 CloudFormation 模板内部,后者又支持 lambda 版本控制。
不要让上面过时的“最佳答案”——专为该人而写——像我一样把你扔进兔子洞。
不客气。
我对文件夹进行校验和,并将其附加到 lambda 的 S3Key
- lambda_src_version=$(find ${Lambda1} -type f -print0 | xargs -0 sha1sum | sha1sum )
- lambda_src_version =$(echo ${lambda_src_version//[[:blank:]]/})
- export S3Key="lambda_functions/${stage}/cloudformation-${lambda_src_version}.zip"
- zip - -r . -x '*.git*' | aws s3 cp - s3://${S3Bucket}/${S3Key}
- sam deploy --template-file cloudformation.yml --stack-name XXX --parameter-overrides Lambda1Bucket=${S3Bucket} Lambda1CodeZip="${S3Key}"
// cloudformation.yml 片段
Parameters:
Lambda1CodeZip:
Type: String
Lambda1Bucket:
Type: String
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Sub ${Lambda1Bucket}
S3Key: !Sub ${Lambda1CodeZip}
我有一个类似的问题并针对我的情况解决了它,但不适用于提到的其他一些问题。我的 Lambda 代码位于版本化存储桶中的一个 zip 文件中。要强制使用新版本,必须替换该功能。为了强制替换函数,我通过合并代码对象的版本 ID 来强制更改函数名称。对象版本 ID 中可以包含一个在函数名称中无效的句点,因此任何句点都会开始截断对象版本 ID。
rLambdaVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref rLambdaFunction
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: !Ref pProvisionedConcurrency
rLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
#FunctionName incorporates the code object version id so that code updates cause function version updates
#S3 object verion IDs can have a period in them which is invalid in a function name so any period is truncated
FunctionName: !Sub
- 'lambda-function-name-${CodeObjectVersionId}'
- CodeObjectVersionId:
Fn::Select: [0, !Split [".", !Ref pLambdaCodeS3ObjectVersion]]
Code:
S3Bucket: !Ref pS3Bucket
S3Key: !Ref pLambdaCodeS3Key
S3ObjectVersion: !Ref pLambdaCodeS3ObjectVersion
我正在尝试使用 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 所述)还是我只是没有得到什么?
2017 年 1 月 11 日更新 亚马逊客服官方回复: “...对于要发布的任何新版本,您需要定义一个附加 (sic) AWS::Lambda::Version 资源...”
AWS CloudFormation/Lambda 团队,如果您正在阅读本文 - 这是不可接受的。修复它。
不幸的是,使用 CloudFormation 无法做到这一点。您需要在每个版本的 CloudFormation 模板中添加新的 AWS::Lambda::Version
部分。
最接近的解决方案是创建 .erb 模板并让它生成包含所有版本的 CloudFormation 模板。
AWS::Lambda::Version
资源仅代表一个已发布的 Lambda 函数版本 - 它不会在您的代码每次更新时自动发布新版本。为此,您有两个选择:
1。自定义资源
您可以在每次更新时实施自己的 Custom Resource that calls 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 (or just manually updating your template on each deploy) to publish a new Version on each update of your code by changing the AWS::Lambda::Version
resource's 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
到create/update堆栈,同时通过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
AWS::Lambda::Version
没有用。您必须为每个 Lambda 版本添加一个新资源。如果你想为每个 Cloudformation 更新发布一个新版本,你必须破解系统。
我通过创建 Lambda 支持的自定义资源解决了这个问题,该资源会在每次部署时触发。在这个 Lambda 中,我正在为参数中给定的 Lambda 函数创建一个新版本。
对于 Lambda 的来源,您可以查看 http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip
以下是使用此 Deployment 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": "AWS::Lambda::Function",
## HERE DEFINE YOUR LAMBDA AS USUAL ##
},
"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 网关的更多信息,您可以查看:https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195)
我有一个类似的用例(需要使用 CloudFormation 来管理要在 CloudFront 中使用@edge 的 lambda 函数,为此始终需要特定的 lambda 函数版本,而不是 $LATEST
)和我的搜索首先让我想到了这个问题,但经过更多的挖掘,我很高兴地发现现在有对自动 lambda 版本控制的本地支持,具有 AWS 无服务器应用程序模型的新 AutoPublishAlias
功能(基本上是可选的额外一组更高的级构造您的 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
基本上您在 AWS::Serverless::Function
定义中包含 AutoPublishAlias
:
MyFunction:
Type: "AWS::Serverless::Function"
Properties:
# ...
AutoPublishAlias: MyAlias
然后在 CloudFormation 模板的其他地方,您可以将最新发布的版本引用为 !Ref MyFunction.Version
(yaml 语法)。
2018 年 2 月更新的答案
您可以使用 AWS SAM (Serverless Application Model) 及其 sam package
和 sam deploy
命令来更新 Lambda。它们类似于 aws cloudformation package
和 aws cloudformation deploy
命令,但还可以让您自动更新 Lambda 版本。
SAM 可以打包您的代码(或采用您创建的 ZIP 包),将其上传到 S3,并从中更新 Lambda 的 $LATEST
版本。 (如果这就是你所需要的,这也可以用 aws cloudformation
完成,没有 SAM;代码示例与下面相同,但只使用 CloudFormation
的标准声明)。然后,使用 SAM,如果相应配置,您还可以自动发布版本并更新别名以指向它。它还可以选择使用 AWS CodeDeploy 将流量从以前的版本逐渐移动到新版本,并在出现错误时回滚。 Safe Lambda deployments.
从技术上讲,这个想法是每次更新堆栈时,您需要 AWS::Lambda::Function
的 Code
指向 new 包S3。这将确保当您更新堆栈时,Lambda 的 $LATEST 版本将从新包更新。然后,您还可以自动发布新版本并为其切换别名。
为此,创建一个 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
...
现在您可以将生成的 packaged.yaml
与 SAM 一起使用,以创建函数版本:
sam deploy --template-file packaged.yaml --stack-name my-stack [--capabilities ...]
这将更新 Lambda 的 $LATEST
版本,如果定义了 AutoPublishAlias
,则将其发布为新版本并更新别名以指向新发布的版本。
请参阅 examples in SAM GitHub repo 以获取完整的模板代码。
寻找与从 S3 部署的 Lambda 函数一起工作的类似东西。
我的用例是这样的:
- 您有一个从 S3 存储桶位置创建 Lambda 函数的 cloudformation 模板
- 您需要更新此函数,以便在本地更改代码并将更改推送到 S3
- 您现在想要将这些更改推送到 Lambda,因此您尝试更新堆栈,而 cloudformation 表示没有要更新的更改,因此您必须使用 AWS Lambda 控制台手动更新代码。
对此不满意,我寻找替代方案并遇到了这个问题。 None 的答案完全适合我,所以我采纳了一些想法并调整了这里的答案,并制作了我自己的 Python 版本。
此代码改编自@wjordan 的回答,因此感谢他提出的想法和原始答案。差异是:
- 这是写在Python
- 它适用于从 S3 存储桶部署的 Lambda 代码
- 更新代码并发布新版本
您需要一个随机数参数。当代码需要重新发布到 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
- 我们可以做一个Lambda部署包;
- 通过 Lambda 将版本作为 Cloud Formation 参数之一的包,例如 "LambdaPakcageNameWithVersion";
- 使用 "LambdaPakcageNameWithVersion" 作为 Lambda 代码 s3 密钥;
- 新的 运行 aws-cli 命令执行时将部署 Lamdba 包 更新 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
这有点 hack,取决于使用 gitlab-ci(或类似的东西),但我发现将提交哈希传递到 cloudformation 模板(通过模板的参数)非常有用。
(有点像@Jerry 的回答,但使用了提交哈希。)
在这种情况下,您可以这样做:
在您的模板中为提交哈希设置一个参数,例如:
AWSTemplateFormatVersion: '2010-09-09'
Description: Template for Lambda Sample.
Parameters:
ciCommitSha:
Type: String
s3Bucket:
Type: String
...
然后您可以在 lambda 资源中引用它,如下所示:
CFNLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: cfn_trigger_fn
Description: lambda which gets triggered by cloudformation
Runtime: python3.7
Code:
S3Bucket: !Ref s3Bucket
S3Key: !Join [ ".", [ !Ref ciCommitSha, "zip"]]
Handler: function.handler
...
你的 ci 管道需要看起来像(假设你调用你的 cloudformation 模板堆栈-template.yaml):
variables:
REGION: us-east-1
S3_BUCKET_NAME: my-bucket
stages:
- build
- push
- deploy
build-package:
stage: build
script:
- some code to produce a deployment package called function.zip
artifacts:
name: deployment_package
paths:
- function.zip
push-code:
stage: push
script:
- aws s3 cp function.zip s3://$S3_BUCKET_NAME/$CI_COMMIT_SHA.zip
deploy-trigger-stack:
stage: deploy
script:
- aws cloudformation deploy
--template-file stack-template.yaml
--stack-name my-stack
--region $REGION
--no-fail-on-empty-changeset
--capabilities CAPABILITY_NAMED_IAM
--parameter-overrides
ciCommitSha=$CI_COMMIT_SHA
s3Bucket=$S3_BUCKET_NAME
您也可以使用此技术在 EC2 元数据上触发 cfn-init..
我使用 CI/CD、一个 ant 脚本和 git 修订解决了这个问题,以便在 S3 存储桶中为每个提交创建一个唯一的 zip 名称。
ant 脚本由 CI/CD 调用以将 git 修订替换为 lambda 代码 zip 文件和 cloudformation 模板的名称。这些引用是在将代码和 cloudformation 脚本复制到 S3 之前进行的。这类似于 SAM 的工作方式,但它适用于普通的旧 Cloudformation Stacks,重要的是 Stack Sets 可能需要跨多个账户部署。在撰写本文时,SAM 与 CF 堆栈集不兼容
有两个文件:ant 文件和一个属性文件,它告诉 ant 文件要压缩哪些 lambda 源目录。
首先是 build.xml ant 文件:
<project basedir="." name="AWS Lambda Tooling Bucket Zip" default="ziplambdas">
<!-- this ant file is responsible for zipping up lambda source code that needs to be placed on an S3 bucket for deployment.
It reads a file `lambda-zip-build.properties` that contains a list of lambda folders and the corresponding zip names.
This allows a lambda to include source code and any required library packages into a single zip for deployment.
For further information refer to the comments at the top of the zip properties file.
-->
<property name="ant.home" value="${env.ANT_HOME}" />
<taskdef resource="net/sf/antcontrib/antlib.xml">
<classpath path="${ant.home}/lib/ant-contrib-1.0b3.jar" />
</taskdef>
<!-- <available file=".git" type="dir" property="git.present"/> -->
<available file="../../.git" type="dir" property="git.present"/>
<!-- get the git revision to make a unique filename on S3. This allows the zip key to be replaced, forcing an update if CloudFormation is deployed. Clunky,
AWS Support raised but advice was to use SAM, which is not compatible with StackSets ... *sigh* -->
<target name="gitrevision" description="Store git revision in ${repository.version}" if="git.present">
<exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
<arg value="describe"/>
<arg value="--tags"/>
<arg value="--always"/>
<arg value="HEAD"/>
</exec>
<condition property="repository.version" value="${git.revision}" else="unknown">
<and>
<isset property="git.revision"/>
<length string="${git.revision}" trim="yes" length="0" when="greater"/>
</and>
</condition>
<echo>git revision is ${git.revision} </echo>
</target>
<target name="replace.git.revision.in.files" depends="gitrevision" description="replace the git marker text in cloudformation files and zip properties file">
<replace dir="." token="@git.revision@" value="${git.revision}" summary="yes"/>
</target>
<property file="lambda.zip.build.properties"/>
<!-- zip the lambda code into a unique zip name based on the git revision -->
<target name="ziplambdas" description="Create Zip files based on the property list" depends="replace.git.revision.in.files">
<property file="lambda.zip.build.properties" prefix="zipme." />
<propertyselector property="zip.list" match="^zipme\.(.*)" select=""/>
<foreach list="${zip.list}" delimiter="," target="zip" param="folder"/>
</target>
<target name="zip">
<propertycopy property="zip.path" from="${folder}" />
<basename property="zip.file" file="${zip.path}" />
<echo message="${folder} is being zipped to ${zip.path}"/>
<zip destfile="${zip.path}">
<zipfileset dir="${folder}">
<exclude name="**/${zip.file}"/>
</zipfileset>
</zip>
</target>
</project>
lambda.zip.build.properties
文件如下所示:
# This property file contains instructions for CI/CD Build Process to zip directories containing lambda code to place on the S3 bucket.
# Lambda source code when deployed by CloudFormation must be available inside a Zip file in a S3 bucket.
# CI/CD runs an ant task that reads this file to create the appropriate zip files referenced by the CloudFormation scripts.
#
# Each property key value pair below contains a key of the top level directory containing the lambda code (in python, javascript or whatever),
# and a value of the path to the zip file that should be deployed to S3. The @git.revision@ tag is substituted with the actual git revision before copying to S3.
# This allows the lambda S3key to change for each deployment and forces a lambda code update.
#
# for example: myproject/lambda/src=myproject/lambda/my-src-@git.revision@.zip
# ^^ Directory ^^ Zip File
#
###################################################################################################################################################################################
myproject/lambda/src=myproject/lambda/lambda-code-@git.revision@.zip
# place your key value pairs above here...
然后是 CloudFormation 模板:
Resources:
MyLambda:
Type: AWS::Lambda::Function
Properties:
# git.revision is placed when code is zipped up by CI/CD and placed on S3 bucket. It allows a unique name for each commit and thereby forces
# lambda code to be replaced on cloudformation stackset redeployment.
Code:
S3Bucket: mybucket
S3Key: myproject/lambda/lambda-code-@git.revision@.zip
Handler: autotag-costcentre.lambda_handler
MemorySize: 128
Runtime: python3.7
Timeout: 10
.... etc
结果是一个具有唯一名称 lambda-code-0f993c3.zip
的 zip 文件和一个 S3Key
引用唯一名称的 Cloudformation 模板。
S3Key: myproject/lambda/lambda-code-0f993c3.zip
从 S3 位置部署模板,它会强制每次刷新现有的 lambda 代码。
为我工作了以下内容:
"LambdaAlias": {
"Type": "AWS::Lambda::Alias",
"DeletionPolicy" : "Retain",
"Properties": {
"FunctionName": {
"Ref": "LambdaFunction"
},
"FunctionVersion": {
"Fn::GetAtt": ["LambdaVersion","Version"]
},
"Name": "MyAlias"
}
这个 post 已经过时了。我在这里更新它,以便其他人可以看到从 2020 年 6 月 9 日起版本控制 Lambdas 的正确解决方案,而无需额外的自定义版本控制 Lambdas。
这个:
Description: Lambda Example
Resources:
Function:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Code:
ZipFile: |
'Example Code';
Runtime: nodejs12.x
Timeout: 5
变成这样:
Description: Lambda Example
Transform: AWS::Serverless-2016-10-31
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
AutoPublishAlias: live
Handler: index.handler
InlineCode: |
'Example Code';
Runtime: nodejs12.x
Timeout: 5
Transform:
允许 AWS::Serverless::Function
在 CloudFormation 模板内部,后者又支持 lambda 版本控制。
不要让上面过时的“最佳答案”——专为该人而写——像我一样把你扔进兔子洞。
不客气。
我对文件夹进行校验和,并将其附加到 lambda 的 S3Key
- lambda_src_version=$(find ${Lambda1} -type f -print0 | xargs -0 sha1sum | sha1sum )
- lambda_src_version =$(echo ${lambda_src_version//[[:blank:]]/})
- export S3Key="lambda_functions/${stage}/cloudformation-${lambda_src_version}.zip"
- zip - -r . -x '*.git*' | aws s3 cp - s3://${S3Bucket}/${S3Key}
- sam deploy --template-file cloudformation.yml --stack-name XXX --parameter-overrides Lambda1Bucket=${S3Bucket} Lambda1CodeZip="${S3Key}"
// cloudformation.yml 片段
Parameters:
Lambda1CodeZip:
Type: String
Lambda1Bucket:
Type: String
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Sub ${Lambda1Bucket}
S3Key: !Sub ${Lambda1CodeZip}
我有一个类似的问题并针对我的情况解决了它,但不适用于提到的其他一些问题。我的 Lambda 代码位于版本化存储桶中的一个 zip 文件中。要强制使用新版本,必须替换该功能。为了强制替换函数,我通过合并代码对象的版本 ID 来强制更改函数名称。对象版本 ID 中可以包含一个在函数名称中无效的句点,因此任何句点都会开始截断对象版本 ID。
rLambdaVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref rLambdaFunction
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: !Ref pProvisionedConcurrency
rLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
#FunctionName incorporates the code object version id so that code updates cause function version updates
#S3 object verion IDs can have a period in them which is invalid in a function name so any period is truncated
FunctionName: !Sub
- 'lambda-function-name-${CodeObjectVersionId}'
- CodeObjectVersionId:
Fn::Select: [0, !Split [".", !Ref pLambdaCodeS3ObjectVersion]]
Code:
S3Bucket: !Ref pS3Bucket
S3Key: !Ref pLambdaCodeS3Key
S3ObjectVersion: !Ref pLambdaCodeS3ObjectVersion