之前的文章介绍了使用sam框架完成lambda函数的金丝雀发布,这里使用cdk创建lambda函数项目实现此功能
Building CI/CD pipelines for lambda canary deployments using AWS CDK
项目的结构如下图所示
lambda堆栈示例
应用程序和环境配置
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from aws_cdk_lambda.aws_cdk_lambda_stack import AwsCdkPythonStack
app = cdk.App()
# 获取cdk.out中context中的qa键值
environment_type = "qa"
environment_context = app.node.try_get_context(environment_type)
region = environment_context["region"]
account = app.node.try_get_context("account")
stack_name = f'{app.node.try_get_context("prefix")}-{environment_type}'
print(stack_name)
AwsCdkPythonStack(app, "AwsCdkPythonStack",
env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),
)
app.synth()
修改cdk.out,添加以下参数
context:{
"qa": {
"region": "cn-north-1",
"lambda": {
"name": "cdk-workshop-function-qa",
"alias": "live",
"stage": "qa"
},
"tags": {
"App":"cdk-workshop",
"Environment": "QA",
"IaC": "CDK"
}
}
}
创建lambda堆栈
from datetime import datetime
from aws_cdk import Stack, RemovalPolicy
from constructs import Construct
from aws_cdk.aws_lambda import Function, Runtime, Code, Alias, VersionOptions
from aws_cdk.aws_apigateway import LambdaRestApi, StageOptions
from aws_cdk.aws_cloudwatch import Alarm, ComparisonOperator
from aws_cdk.aws_codedeploy import LambdaDeploymentGroup, LambdaDeploymentConfig
class CdkWorkshopStack(Stack):
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
environment_type = self.node.try_get_context("environmentType")
context = self.node.try_get_context(environment_type)
self.alias_name = context["lambda"]["alias"]
self.stage_name = context["lambda"]["stage"]
current_date = datetime.today().strftime('%d-%m-%Y')
my_lambda = Function(
scope = self,
id = "MyFunction",
function_name= context["lambda"]["name"],
handler = "handler.lambda_handler",
runtime = Runtime.PYTHON_3_9,
code = Code.from_asset("lambda"),
current_version_options = VersionOptions(
description = f'Version deployed on {current_date}',
removal_policy = RemovalPolicy.RETAIN
)
)
# 每次cdk部署命令运行时自动创建一个新版本,修改版本的删除策略为保留
new_version = my_lambda.current_version
new_version.apply_removal_policy(RemovalPolicy.RETAIN)
alias = Alias(
scope = self,
id = "FunctionAlias",
alias_name = self.alias_name,
version = new_version
)
# LambdaRestApi(
# scope = self,
# id = "RestAPI",
# handler = alias,
# deploy_options = StageOptions(stage_name=self.stage_name)
# )
failure_alarm = Alarm(
scope = self,
id = "FunctionFailureAlarm",
metric = alias.metric_errors(),
threshold = 1,
evaluation_periods = 1,
alarm_description = "The latest deployment errors > 0",
alarm_name = f'{self.stack_name}-canary-alarm',
comparison_operator = ComparisonOperator.GREATER_THAN_THRESHOLD,
)
LambdaDeploymentGroup(
scope = self,
id = "CanaryDeployment",
alias = alias,
deployment_config = LambdaDeploymentConfig.CANARY_10_PERCENT_5_MINUTES,
alarms = [failure_alarm]
)
添加应用代码
$ mkdir lambda && touch lambda/handler.py
$ cat handler.py
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello from CDK!')
}
生成的cfn模板会创建一系列相关资源,示意图如下。主要创建的资源
- lambda,包括lambda执行角色,alias和version
- apigateway,通过deployment创建resource,在resource上创建method,还有apigateway调用resource,apigateway调用lambda的权限。对于lambda的部署可以省略此部分
- codedeploy,包括application和部署组他
- 当发生lambda函数更新时,cloudformation通过updatepolicy
对应堆栈资源如下
更新lambda函数重新部署cdk,查看堆栈事件,堆栈更新策略开始执行
部署触发
金丝雀部署开始转移流量
通过将alias指向不同version完成流量切换
pipeline实现部署
最后还有一个创建pipeline堆栈的示例
https://catalog.us-east-1.prod.workshops.aws/workshops/5195ab7c-5ded-4ee2-a1c5-775300717f42/en-US/cicd/cdk-pipelines/pipeline
有一个shellstep比较好奇是什么,创建出来看看
class PipelineStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
repository = Repository.from_repository_arn(
self,
"CodeCommitRepo",
'arn:aws-cn:codecommit:cn-north-1:xxxxxxx:temptest'
)
self.source_stage = CodePipelineSource.code_commit(repository,"master")
pipeline = CodePipeline(self, "Pipeline",
pipeline_name = "cdk-pipeline",
synth = ShellStep(
"Synth",
input = self.source_stage,
install_commands=[
"npm install -g aws-cdk",
"pip3 install -r requirements.txt",
"ACCOUNT=$(aws sts get-caller-identity | jq -r .Account)"
],
commands=[
"cdk synth -c account=$ACCOUNT -c environmentType=$ENV_TYPE"
],
)
)
shellstep实际上是个build阶段,command写到了buildspec.yaml
中