简介
在之前有关 CICD 的文章中,我们利用 Lamdba 函数,把 Code Commit/Build/Deploy 连接起来,形成一个完整的 CICD 流水线。
图 52
在我们提交代码后,CICD 流水线会自动完成编译,生成镜像,推入仓库,部署的全过程。
但是在生产环境中,我们还需要加入人工审批这一环节,确保最后部署到生产环境前,相关利害方认可此次部署操作。
我们将分两篇文章介绍如何利用 AWS Step Function(SF)配合 Lambda 函数实现此功能。
本篇先介绍 AWS Step Function 及 SNS 服务,然后创建一个简单的 SF 例子,测试如何利用邮件进行审批,并把审批结果反馈回 SF。
本文还会用到 Lambda 函数和 ALB(Applicaiton Load Balancer)服务。
下一篇中我们把 Step Function 加入之前创建的 CICD 中,实现部署邮件审批功能。
目录
- 环境(配置)
- Step Function 简介
- SNS 简介
- 实战步骤
1. 新建 SNS
- 建 Topic
- 注册 Subscription
2. 新建 Lambda 函数
- tsSendMailforSF 函数
- tsALBForMailApproval 函数
3. 为 tsALBForMailApproval 函数设置 ALB
- 增加 Listener
4. 新建 Step Function
- 创建 Step Function
- 添加 policy
5. 测试邮件审批
- 总结
- 引申
- 后记
环境(配置)
- AWS 中国或 Global 帐号,可在官网申请,一年内使用指定资源免费
- AWS cli,Win10 + terminal
- 公网邮箱
Step Function 简介
Amazon Step Function(SF)是一项无服务器编排服务,可以简单理解为状态机。(注意不是容器编排服务)
状态机是一个工作流程,每个状态相当于一个任务。这个任务可以是 Lambda 函数,可以是 Batch 批处理,也可以是大数据 EMR 作业。
SF 通过内置的功能来检查每个状态(任务)的结果,然后可以通过分支(Choice)来选择下一步任务。
另外 SF 的 CallBack 功能,使得任务可以主动把结果返馈给 SF,我们下面配的邮件审批就是利用 CallBack 功能。
SF 有两种工作流
- 标准工作流 只有一个工作流,最多可运行一年,适合长时间运行的可审计工作流
- 快速工作流 至少有一个工作流,最长可运行 15 分钟,适合高事件率工作负载,例如流数据处理和 IoT 数据提取。
下面我们将用到标准工作流,实现以下的流程
图 1说明:
- SF 启动后,“Lamdba Callback”任务会调用 tsSendMailforSF Lambda 函数,向用户发送确认邮件并等待 Callback
- 用户点击邮件中的“Approve”或“Reject”链接后,tsALBForMailApproval Lambda 函数处理收到的用户反馈,然后把结果返回到 SF “Lamdba Callback” 任务
- 在“ManualApprovalChoiceState”中根据用户返回的结果选择下一步分支
- 执行 Approve 或 Reject 分支任务后结束整个 SF 流程
SNS 简介
Amazon Simple Notification Service (Amazon SNS) 是一项托管服务,可由发布者向订阅者发送消息。
我们通过把信息发送到 Topic(主题)来与 Subscription(订阅者)进行通信。
可以通过 SNS 向下列 Subscription 发送信息,本文我们利用 SNS 发送审批链接到 Email。
图 2
实战步骤
1. 新建 SNS
建 Topic
在 AWS 中控台,选择“SNS”,点击进入“Amazon SNS”界面,点击左边“Topic”后,点击“Create topic”
图 3
进入详细配置界面,选择“Standard”,添加 Topic 名称“tsSFTopic”,然后点击“Create topic”
图 4
Topic 建好后,我们注册一个 Subscription
注册 Subscription
在建好的 Topic 的页面下,点击“Create Subscription”
图 5
Protocol 选择“Email”,Endpoint 添入邮箱地址,然后点击“Create Subscription”
图 6
建好的 subscription 是处于“Pending confirmation”状态
图 7
注册的邮箱会收到一封确认订阅的邮件
图 8
点击“Confirm subscription”确认订阅
图 9
然后 subscription 状态会改为“Confirmed”,这时我们已经准备好接收邮件了
2. 新建 Lambda 函数
创建 Lambda 函数及调试的详细内容请参考“AWS Lambda 之 CodeDeploy 部署测试”一文。
tsSendMailforSF 函数
创建 tstsSendMailforSFforSF 函数,此函数用来把包含 taskToken 的 approve/reject 的链接发送给 Topic 中注册的邮箱。
图 10
lambda_function.py 代码如下
import json, boto3
from createUrlFunc import createUrl
def lambda_handler(event, context):
print(event)
step = 'SendApprovalRequest'
if step == 'SendApprovalRequest':
# TODO implement
input = {
"albUrl": event['APIGatewayEndpoint'],
"taskToken": event['ExecutionContext']['Task']['Token'],
"executionName": event['ExecutionContext']['Execution']['Name'],
"statemachineName": event['ExecutionContext']['StateMachine']['Name']
}
#call createrul function by sending data in json format
urls = json.loads(createUrl(json.dumps(input)))
print(urls)
# Compose email
email_subject = 'Step Functions example approval request'
email_body = """Hello {name},
Click below (these could be better in HTML emails):
Approve:
{approve}
Reject:
{reject}
""".format(
name= 'ts',
approve=urls['approve'],
reject=urls['reject']
)
print('Sending email:', email_body)
response = boto3.client('sns').publish(
TopicArn='arn:aws-cn:sns:cn-north-1:XXXX:tsSFTopic',
Subject=email_subject,
Message=email_body
)
print(response)
return { "status": 200}
说明:
- input 数据字典是准备创建审批 Url 的信息,这些信息从调用此函数的 Step Function 中传过来
- albUrl 是 approve 和 reject 的链接中的域名部分,这里用的 ALB 的 DNS
- taskToken 是 SF 中任务的 Token
- input 准备好的信息传给 createUrl 函数,生成 approve 和 reject 链接返回
- email_body 是发送邮件的内容,里面附加了 approve 和 reject 链接
- 最后利用 boto3 的 sns 服务发送邮件到 SNS Topic
createUrlFunc.py 代码如下
import json
def createUrl(event):
# TODO implement
input = json.loads(event)
approvalUrl=input['albUrl'] + "/execution?action=approve&ex=" + input['executionName'] + "&sm=" + input['statemachineName'] + "&taskToken=" + input['taskToken'];
rejectUrl=input['albUrl'] + "/execution?action=reject&ex=" + input['executionName'] + "&sm=" + input['statemachineName'] + "&taskToken=" + input['taskToken'];
urls = {
"approve": approvalUrl,
"reject": rejectUrl
}
#print(urls)
return json.dumps(urls)
说明:组装 approve/reject 链接并返回
tstsALBForMailApproval 函数
创建 tsALBForMailApproval 函数,此函数接收客户返回的信息,并把 Approve/reject 结果返回给 Step Function。
图 11
lambda_function.py 代码如下
import json, boto3
def lambda_handler(event, context):
print(event['queryStringParameters'])
if event['queryStringParameters']['action'] == 'approve':
userAction = 'approve'
message = {"Status": "Approved! Task approved"}
print('approve')
elif event['queryStringParameters']['action'] == 'reject':
userAction = 'reject'
message = {"Status": "Rejected! Task rejected"}
print('reject')
else:
return{}
client = boto3.client('stepfunctions')
response = client.send_task_success(
taskToken=event['queryStringParameters']['taskToken'],
output = json.dumps(message)
)
return {
'statusCode': 200,
'body': json.dumps(userAction)
}
说明:
- 客户点击 approve/reject 链接后,请求的结果会发送至 ALB,ALB 把请求转发到此 Lambda 函数
- 通过检查请求中 queryStringParameters 的 action 项,来判断 Approve 还是 reject
- 最后利用 Boto3 中 Step Function 包,把结果发回给等待中的 Step Function(CallBack)
- taskToken 从请求中获取(我们在 tsSendMailforSF 的 Lambda 中已经把 taskToken 装配在 approve/reject 中)
- 从上面的代码也可以看到,我们并没有指定 Step Function 的名称或者 ARN,只需要 taskToken 就可以唯一识别出 SF 及其具体任务
3. 为 tsALBForMailApproval 函数设置 ALB
我们利用之前 Fargate 用到的 ALB 接收 approve/reject 链接请求。如果需要单独创建 ALB 可参考“创建 ECS Fargate”一文。
我们给之前创建的 ALB 添加一个在端口 100 上的监听器,这样用户点击配有此 ALB DNS 的 Approve/reject 链接时,请求就会进入 ALB。
随后 ALB 把请求转发给 Lambda 函数“tsALBForMailApproval”,Lambda 函数从请求中提取 taskToken 和 Approve/reject 信息,然后返回给 SF 完成 Callback,接下来 SF 决定下一步分支是 Approve 还是 reject。
增加 Listener
在 EC2 界面,选择“Load Balancers”,选择我们的 ALB,点击“Add listener”
图 16
端口中写 100,在下面选择 Forward,然后选择“tsALBForMailApproval”,点击“Add listener”
图 17
添加完成
图 18
4. 新建 Step Function
创建 Step Function
下面我们创建 Step Function
在 AWS 中控台,选择“Step Functions”,点击进入“Step Function”界面,点击左边“State machines”后,点击“Create State machine”
图 12
选择“Author with code snippets”,在 Definition 部分粘贴以下代码,然后点击“Next”
图 13
{
"StartAt": "Lambda Callback",
"TimeoutSeconds": 600,
"States": {
"Lambda Callback": {
"Type": "Task",
"Resource": "arn:aws-cn:states:::lambda:invoke.waitForTaskToken",
"Parameters": {
"FunctionName": "arn:aws-cn:lambda:cn-north-1:XXXX:function:tstsSendMailforSFforSF",
"Payload": {
"ExecutionContext.$": "$$",
"APIGatewayEndpoint": "http://tstest-XXX.cn-north-1.elb.amazonaws.com.cn:100"
}
},
"Next": "ManualApprovalChoiceState"
},
"ManualApprovalChoiceState": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.Status",
"StringEquals": "Approved! Task approved",
"Next": "ApprovedPassState"
},
{
"Variable": "$.Status",
"StringEquals": "Rejected! Task rejected",
"Next": "RejectedPassState"
}
]
},
"ApprovedPassState": {
"Type": "Pass",
"End": true
},
"RejectedPassState": {
"Type": "Pass",
"End": true
}
}
}
流程图如下
图 1
说明:这段是 SF 的主体部分,定义了每个 state,包括起始和终结 state
- StartAt 指定起始 state
- TimeoutSeconds 10 分钟没有 callback 则放弃执行
- States 中具体定义每个 state
下面对每个 state 进行说明:
"Lambda Callback" state
- “Lambda Callback”是第一个 state,里面调用 Lambda 函数,并且需要等待 callback(arn:aws-cn:states:::lambda:invoke.waitForTaskToken)
- Parameters 中 FunctionName 指定“Lambda Callback” state 调用的 Lambda 函数“tstsSendMailforSFforSF”
- Payload 是传给“tstsSendMailforSFforSF”的参数,其中
"ExecutionContext.$": "$$",
代表把当前 SF 的自己的信息传给 Lambda,其中就包括了当前 state 的 taskToken
- APIGatewayEndpoint 是 approve/reject 的链接域名,这里配的是之前测试中新建的 ALB DNS 加端口
“ManualApprovalChoiceState” state
- 类型是 Choices,根据上一步 state 返回的信息决定下一步分支是“ApprovedPassState”还是“ApprovedPassState”
“ApprovedPassState” “RejectedPassState” state
- 类型是 pass,即这里没有其它内容直接进入与之相连的下一步,这里的 End 表明没有下一步,这里即是终点
进入下一页,添加 SF 名称“tsSFcallBack”,可以新建也可以选择已存在的 Role,后面会附上 Role 所需的 policy,点击“Create state machine”
图 14
创建成功
图 15
添加 policy
我们需要给 SF 的 role 添加调用 Lambda 函数的权限,点击“IAM role ARN”部分,打开 IAM Role 界面
图 19
点击“Add inline policy”
图 20
选择 Json,粘贴以下内容,点击“Review policy”
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws-cn:lambda:cn-north-1:XXXX:function:tsSendMailforSF:*"
]
},
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws-cn:lambda:cn-north-1:XXXX:function:tsSendMailforSF"
]
}
]
}
图 21
点击“Create policy”
图 22
5. 测试邮件审批
在 SF 界面,点击“Start execution”
图 23
这里可以给这次执行改个名称,或者不变,点击“Start execution”
图 24
跳转回 SF 界面,可以看到,正在运行
图 25
过一会儿,我们注册的邮箱会收到如下邮件,然后点击“Approve”的链接
图 26
回到 SF 界面,可以看到分支选择了“ApprovePassState”,然后成功结束
图 27
如果没有点击邮件中 approve 或者 reject 链接超过 10 分钟,则 SF 中止,并以“Timed out”状态结束。
总结
我们再把整个邮件审批流程总结一下
图 1
- SF 状态机启动后,进入“Lambda Callback”状态
- “Lambda Callback”状态类型为 Task,调用 Lamdba 函数“tsSendMailforSF”并传递 taskToken 等参数,然后进入等待 Callback 状态
- tsSendMailforSF 把 taskToken 及 ALB 的 DNS 信息组合成 approve/reject 链接邮件信息发送给 SNS Topic
- SNS Topic 把邮件发送给订阅者的邮箱
- 订阅者收到邮件后点击 approve 或 reject 链接,请求发送至 ALB。
- ALB 转发请求到 lambda 函数“tsALBForMailApproval”,Lambda 从请求中提取 taskToken 及结果,发送给 SF(callback)
- SF 收到 callback 结果,进入下一步状态,选择 approve 或者 reject
- 执行 approve 或者 reject 后结束 SF 此次执行
引申
- 上面以邮件审批为例,简单介绍了 SF 的功能。SF 因其可以控制状态,检查每个状态的信息,所以配合其它 AWS 服务可以实现很多复杂的功能。之前做过的 DataLake 项目中主要就是用 SF 来控制各个服务的协同工作。
- Topic 中的每个 subscription 只能添加一个邮箱地址,如果要发送给多个邮箱,需要在 Topic 下注册多个 subscription。
- Topic 支持把消息发送给各种订阅者,包括 AWS 的服务,比如 SQS,Firehose 或者其它 Http/Https 接口。利用不同订阅者,我们不但可以实现邮件审批,还实现诸如手机审批等其它功能。
资源下载
相关代码及 policy 文件可在下列链接中下载https://github.com/tansong0091/realCrapForAWS/tree/main/StepFunction1
后记
官网中审批的例子写的比较简单,里面用的是 Api gateway 和 SQS,并且也没提供详细代码。
我把官网的例子稍稍改了一下,用之前讲到过的 ALB 代替 Api gateway,并且去掉了 SQS 这个服务,并提供了全部 Python3.8 环境的代码,这样可以使整个 SF 部署测试更加简单。
在下一篇中,我们把这篇用到的技术加到之前创建的 CICD 流水线中,实现部署审批功能。
喜欢请点赞,欢迎转发
微信公众号“全是 AWS 干货”