AWS CICD Step Function 上篇 邮件审批_json




简介

在之前有关 CICD 的文章中,我们利用 Lamdba 函数,把 Code Commit/Build/Deploy 连接起来,形成一个完整的 CICD 流水线。

图 52AWS CICD Step Function 上篇 邮件审批_状态机_02

在我们提交代码后,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 数据提取。

下面我们将用到标准工作流,实现以下的流程

图 1AWS CICD Step Function 上篇 邮件审批_状态机_03说明:


  1. SF 启动后,“Lamdba Callback”任务会调用 tsSendMailforSF Lambda 函数,向用户发送确认邮件并等待 Callback
  2. 用户点击邮件中的“Approve”或“Reject”链接后,tsALBForMailApproval Lambda 函数处理收到的用户反馈,然后把结果返回到 SF “Lamdba Callback” 任务
  3. 在“ManualApprovalChoiceState”中根据用户返回的结果选择下一步分支
  4. 执行 Approve 或 Reject 分支任务后结束整个 SF 流程

SNS 简介

Amazon Simple Notification Service (Amazon SNS) 是一项托管服务,可由发布者向订阅者发送消息。

我们通过把信息发送到 Topic(主题)来与 Subscription(订阅者)进行通信。

可以通过 SNS 向下列 Subscription 发送信息,本文我们利用 SNS 发送审批链接到 Email。

图 2AWS CICD Step Function 上篇 邮件审批_json_04

实战步骤

1. 新建 SNS

建 Topic

在 AWS 中控台,选择“SNS”,点击进入“Amazon SNS”界面,点击左边“Topic”后,点击“Create topic”

图 3AWS CICD Step Function 上篇 邮件审批_json_05

进入详细配置界面,选择“Standard”,添加 Topic 名称“tsSFTopic”,然后点击“Create topic”

图 4AWS CICD Step Function 上篇 邮件审批_官网_06

Topic 建好后,我们注册一个 Subscription

注册 Subscription

在建好的 Topic 的页面下,点击“Create Subscription”

图 5AWS CICD Step Function 上篇 邮件审批_json_07

Protocol 选择“Email”,Endpoint 添入邮箱地址,然后点击“Create Subscription”

图 6AWS CICD Step Function 上篇 邮件审批_状态机_08

建好的 subscription 是处于“Pending confirmation”状态

图 7AWS CICD Step Function 上篇 邮件审批_json_09

注册的邮箱会收到一封确认订阅的邮件

图 8AWS CICD Step Function 上篇 邮件审批_官网_10

点击“Confirm subscription”确认订阅

图 9AWS CICD Step Function 上篇 邮件审批_官网_11

然后 subscription 状态会改为“Confirmed”,这时我们已经准备好接收邮件了

2. 新建 Lambda 函数

创建 Lambda 函数及调试的详细内容请参考“AWS Lambda 之 CodeDeploy 部署测试”一文。

tsSendMailforSF 函数

创建 tstsSendMailforSFforSF 函数,此函数用来把包含 taskToken 的 approve/reject 的链接发送给 Topic 中注册的邮箱。

图 10AWS CICD Step Function 上篇 邮件审批_json_12

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。

图 11AWS CICD Step Function 上篇 邮件审批_状态机_13

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”

图 16AWS CICD Step Function 上篇 邮件审批_状态机_14

端口中写 100,在下面选择 Forward,然后选择“tsALBForMailApproval”,点击“Add listener”

图 17AWS CICD Step Function 上篇 邮件审批_json_15

添加完成

图 18AWS CICD Step Function 上篇 邮件审批_官网_16

4. 新建 Step Function

创建 Step Function

下面我们创建 Step Function

在 AWS 中控台,选择“Step Functions”,点击进入“Step Function”界面,点击左边“State machines”后,点击“Create State machine”

图 12AWS CICD Step Function 上篇 邮件审批_状态机_17

选择“Author with code snippets”,在 Definition 部分粘贴以下代码,然后点击“Next”

图 13AWS CICD Step Function 上篇 邮件审批_json_18

{
  "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
    }
  }
}

流程图如下

图 1AWS CICD Step Function 上篇 邮件审批_状态机_03

说明:这段是 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”

图 14AWS CICD Step Function 上篇 邮件审批_状态机_20

创建成功

图 15AWS CICD Step Function 上篇 邮件审批_状态机_21

添加 policy

我们需要给 SF 的 role 添加调用 Lambda 函数的权限,点击“IAM role ARN”部分,打开 IAM Role 界面

图 19AWS CICD Step Function 上篇 邮件审批_状态机_22

点击“Add inline policy”

图 20AWS CICD Step Function 上篇 邮件审批_官网_23

选择 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"
            ]
        }
    ]
}

图 21AWS CICD Step Function 上篇 邮件审批_json_24

点击“Create policy”

图 22AWS CICD Step Function 上篇 邮件审批_json_25

5. 测试邮件审批

在 SF 界面,点击“Start execution”

图 23AWS CICD Step Function 上篇 邮件审批_状态机_26

这里可以给这次执行改个名称,或者不变,点击“Start execution”

图 24AWS CICD Step Function 上篇 邮件审批_json_27

跳转回 SF 界面,可以看到,正在运行

图 25AWS CICD Step Function 上篇 邮件审批_官网_28

过一会儿,我们注册的邮箱会收到如下邮件,然后点击“Approve”的链接

图 26AWS CICD Step Function 上篇 邮件审批_json_29

回到 SF 界面,可以看到分支选择了“ApprovePassState”,然后成功结束

图 27AWS CICD Step Function 上篇 邮件审批_官网_30

如果没有点击邮件中 approve 或者 reject 链接超过 10 分钟,则 SF 中止,并以“Timed out”状态结束。

总结

我们再把整个邮件审批流程总结一下

图 1AWS CICD Step Function 上篇 邮件审批_状态机_03


  1. SF 状态机启动后,进入“Lambda Callback”状态
  2. “Lambda Callback”状态类型为 Task,调用 Lamdba 函数“tsSendMailforSF”并传递 taskToken 等参数,然后进入等待 Callback 状态
  3. tsSendMailforSF 把 taskToken 及 ALB 的 DNS 信息组合成 approve/reject 链接邮件信息发送给 SNS Topic
  4. SNS Topic 把邮件发送给订阅者的邮箱
  5. 订阅者收到邮件后点击 approve 或 reject 链接,请求发送至 ALB。
  6. ALB 转发请求到 lambda 函数“tsALBForMailApproval”,Lambda 从请求中提取 taskToken 及结果,发送给 SF(callback)
  7. SF 收到 callback 结果,进入下一步状态,选择 approve 或者 reject
  8. 执行 approve 或者 reject 后结束 SF 此次执行

引申


  1. 上面以邮件审批为例,简单介绍了 SF 的功能。SF 因其可以控制状态,检查每个状态的信息,所以配合其它 AWS 服务可以实现很多复杂的功能。之前做过的 DataLake 项目中主要就是用 SF 来控制各个服务的协同工作。
  2. Topic 中的每个 subscription 只能添加一个邮箱地址,如果要发送给多个邮箱,需要在 Topic 下注册多个 subscription。
  3. 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 干货”

AWS CICD Step Function 上篇 邮件审批_官网_32