应用场景
在配置CloudFront的Distribution的时候,往往一个distribution下可能会有多个behavior。每个behavior下可能会用Lambda@Edge来实现修改请求或者边缘计算等功能。在整个Lambda@Edge的部署过程中,需要修改晚Lambda的代码后,在将代码推送到边缘节点,并且需要在CloudFront的每一个behavior的配置下面修改Lambda@Edge的version ARN。为了避免手动误操作的情况,本文主要讨论一个自动化的部署方案,在完成CI阶段后,仅需把程序包放置在对应的存储桶位置时,就可以实现CloudFront的Lambda@Edge的自动化部署方案
技术背景
本文主要应用到了以下AWS的服务及功能
1.S3 Bucket,并启用了版本控制:用于存储Lambda@Edge的代码
2.S3 存储桶触发Lambda:集成服务,实现上传Object后能够自动触发CloudFormation执行。并控制参数传入
(参考文档: https://docs.amazonaws.cn/lambda/latest/dg/with-s3-example.html)
3.CloudFormation:创建并负责更新CloudFront, Lambda@Edge, Lambda Version 等组件
事例代码
CloudFormation:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
FirstLaunch:
Type: String
Default: False
AllowedValues:
- True
- False
ConstraintDescription: True for Fist Launch the template, False for Update.
LambdaVersion:
Type: String
Default: NONE
Conditions:
FirstLaunch: !Equals [ !Ref FirstLaunch, True ]
Resources:
edgelambdarole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: edgelambda
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- edgelambda.amazonaws.com
testdistribuationupdate:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
DefaultCacheBehavior:
ViewerProtocolPolicy: allow-all
ForwardedValues:
QueryString: 'false'
TargetOriginId: MyOrigin
LambdaFunctionAssociations:
- EventType: origin-request
LambdaFunctionARN: !If [FirstLaunch, !Ref edgelambdaVersion, !Ref LambdaVersion]
Enabled: true
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: match-viewer
HTTPPort: 80
Id: MyOrigin
DomainName: aws.amazon.com
edgelambda:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: test-cf-lambda-code
S3Key: index.js.zip
FunctionName: !Join
- '-'
- - testlambda
Handler: index.handler
Role: !GetAtt
- edgelambdarole
- Arn
Timeout: 5
Runtime: nodejs12.x
edgelambdaVersion:
Type: 'AWS::Lambda::Version'
Properties:
FunctionName: !Ref edgelambda
Lambda:
部署函数之前,需要设置Template URL 为CloudFormation 模版存储的s3 地址
import boto3
def update_lambda(functionname, S3Bucket, S3Key, S3ObjectVersion):
client_lambda = boto3.client('lambda')
response_lambda = client_lambda.update_function_code(
FunctionName=functionname,
S3Bucket=S3Bucket,
S3Key=S3Key,
S3ObjectVersion=S3ObjectVersion,
Publish=True
)
lambdaversion = response_lambda['FunctionArn']
return lambdaversion
def lambda_handler(event, context):
# Get S3 Object Meta
S3Bucket = event['Records'][0]['s3']['bucket']['name']
S3Key = event['Records'][0]['s3']['object']['key']
S3ObjectVersion = event['Records'][0]['s3']['object']['versionId']
# The S3 Object key must start up with lambda@edge name
functionname = S3Key.split('.')[0]
lambdaversion = update_lambda(functionname, S3Bucket, S3Key, S3ObjectVersion)
client = boto3.client('cloudformation')
response_cloudformation = client.update_stack(
StackName='test',
TemplateURL='change to your cloudformation template located url',
Parameters=[
{
'ParameterKey': 'FirstLaunch',
'ParameterValue': 'false'
},
{
'ParameterKey': 'LambdaVersion',
'ParameterValue': lambdaversion
}
],
Capabilities=['CAPABILITY_NAMED_IAM']
)
Lambda@Edge json code:
exports.handler = (event, context, callback) => {
const response = {
status: '200',
statusDescription: 'OK',
body: "test",
};
callback(null, response);
};
工作原理
资源准备
1.创建S3 存储桶作为Lambda@Edge的代码artifacts。
2.创建S3 存储桶,作为CloudFormation 存储模版的仓库,并将模版上传至存储桶
3.创建deployed lambda执行时所需的IAM Role(具备AWSLambdaBasicExecutionRole 和 自定义策略如下)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"lambda:ListFunctions",
"lambda:InvokeFunction",
"lambda:GetFunction",
"cloudfront:CreateDistribution",
"lambda:EnableReplication",
"lambda:UpdateFunctionCode",
"cloudfront:GetDistribution",
"s3:GetObject",
"cloudformation:UpdateStack",
"lambda:PublishVersion",
"cloudfront:UpdateDistribution",
"s3:GetObjectVersion"
],
"Resource": "*"
}
]
}
示例场景及代码
场景一: 首次部署CloudFront和Lambda@Edge
CloudFormation Template解释:
在cloudfront_lambda.yaml,定义了一个参数 FirstLaunch, 并在后面用Conditions 设置一个判断条件。
Parameters:
FirstLaunch:
Type: String
Default: False
AllowedValues:
- True
- False
ConstraintDescription: True for Fist Launch the template, False for Update.
LambdaVersion:
Type: String
Default: NONE
Conditions:
FirstLaunch: !Equals [ !Ref FirstLaunch, True ]
在创建distribution的resource中,关联Lambda@Edge部分的时候会使用这个判断条件,如果FirstLaunch 为True的时候则这里的参数引用本模版中创建的Lambda Version,如果为False,则引用从Template外传入的参数。
testdistribuationupdate:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
DefaultCacheBehavior:
ViewerProtocolPolicy: allow-all
ForwardedValues:
QueryString: 'false'
TargetOriginId: MyOrigin
LambdaFunctionAssociations:
- EventType: origin-request
LambdaFunctionARN: !If [FirstLaunch, !Ref edgelambdaVersion, !Ref LambdaVersion]
创建资源
参考官方文档,创建堆栈
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/cfn-using-console.html
注意:在首次执行的时候,FirstLaunch 请选择true。
验证:
1.验证堆栈
在CloudFormation创建完毕后,以下资源被创建
2.登入CloudFront界面查看Distribution的Behavior
3.访问CloudFront的页面,并与Lambda@Edge功能核对
场景二: 更新CloudFront上的Lambda@Edge
在运行完首次创建后,在后期正常运维的情况下,可以直接将代码更新到S3 的Artifacts中,后续部分自动由deploy_lambda.py来执行。
deploy_lambda.py代码解释:
update_lambda() 负责根据event中的捕获的信息,完成对应的Lambda@Edge的更新,并生成新的版本。并作为更新CloudFormation的输入参数进行后续操作
def lambda_handler(event, context):
# Get S3 Object Meta
S3Bucket = event['Records'][0]['s3']['bucket']['name']
S3Key = event['Records'][0]['s3']['object']['key']
S3ObjectVersion = event['Records'][0]['s3']['object']['versionId']
# The S3 Object key must start up with lambda@edge name
functionname = S3Key.split('.')[0]
lambdaversion = update_lambda(functionname, S3Bucket, S3Key, S3ObjectVersion)
client = boto3.client('cloudformation')
response_cloudformation = client.update_stack(
StackName='test',
TemplateURL='your template url',
Parameters=[
{
'ParameterKey': 'FirstLaunch',
'ParameterValue': 'false'
},
{
'ParameterKey': 'LambdaVersion',
'ParameterValue': lambdaversion
}
],
Capabilities=['CAPABILITY_NAMED_IAM']
)
注意:
- py 的权限请参照文章资源准备中的权限部分
- 您上传的Lambda@Edge的zip包的名字需要跟Lambda@Edge函数的名字保持一致。
- StackName 要与CloudFormation的Stack名称一致,如果考虑参数联动,可以后期将py与API Gateway继承,进行参数传递
验证:
当您具备以上的前置条件和注意事项后
1.上传代码到S3 的artifacts中,并查看版本信息
2.检查CloudFormation是否处于更新状态,并等待更新完成(注意:因为更新CloudFront资源本身涉及到更新edge端的配置,因此可能耗时较长)
3.检查CloudFront状态,直至状态变更为“已部署”
4.检查Lambda@Edge效果(由于可能存在缓存,请清理缓存后验证)
总结
- 如果在管理多个Lambda@Edge场景中,可以将CloudFormation模版修改为Nested Stack方式执行。
- 在后续运维过程中,用户只需要将Lambda@Edge的code上传至S3即可,CD部分自动化实施
- CI部分可以集成Code系列工具,只需将代码成功上传至S3即可
本篇作者