{"value":"#### **背景**\n\nAmazon Step Functions 是一项低代码、可视化的工作流服务,让开发人员可通过 Amazon 服务轻松构建分布式应用程序、自动化 IT 和业务流程并构建数据和机器学习管道。通过嵌套 Step Functions 工作流程,您可以将大型和复杂的工作流程构建为更小型和简单的多个工作流程。但是,嵌套 Step Functions 需要您在同一个账号上部署。而在很多实际使用场景中,需要在多个 Amazon 账号下进行工作流的编排。\n\n文章提供了一个通过 Amazon API Gateway 来实现跨账号工作流服务的解决方案,并且提供 Amazon 管理控制台和 Amazon CDK 基础架构即代码(IaC)两种部署方式作为参考。\n\n#### **Amazon Step Functions**\n\nAmazon Step Functions 可以与其他 Amazon 服务进行集成,在工作流中直接调用其他服务的的 API。例如:\n\n- 调用 Amazon Lambda function\n- 插入或者读取 Amazon DynamoDB 的数据\n- 运行一个 Amazon Elastic Container Service (Amazon ECS)任务,并等待它运行结束。\n- 在 Amazon Simple Queue Service (Amazon SQS)发送消息\n- 运行其他的 Amazon Step Functions 工作流\n- 向 Amazon API Gateway 发送请求\n\n更多请参考 [Call other Amazon services](https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-services.html)\n\n#### **架构设计**\n\n以下架构图展示了上游账号 A 的工作流通过 API Gateway,异步调用下游账号 B 的工作流,并传递有效业务信息。其通过资源策略来对 API 和 Step Functions 工作流进行访问权限控制,防止匿名访问。此架构展示的是两个 Amazon 账号间的跨账号工作流服务,你也可以扩展为跨多个区域、多个账号的工作流服务。\n\n![image.png](https://dev-media.amazoncloud.cn/dcc4548d28b24d03ad694d48be597a4b_image.png)\n\n##### **工作流程**\n\n1. 上游账号 A 的 Step Functions 状态机向下游账号 B 的 API Gateway 端点发起请求,请求中包含下游账号B的 Step Functions 状态机 ARN 及其他业务信息。\n2. 下游账号 B 的 API Gateway 判断工作流流向,异步调用所需下游账号 B 的 Step Functions 状态机并传递业务信息。\n3. 下游账号 B 的 Step Functions 状态机运行其工作流。\n\n#### **配置步骤**\n\n文章会先展示在 Amazon 管理控制台进行无代码的部署,如果您需要参考 CDK 代码,请至文章的 CDK 部署模块。\n\n##### **上游账号 A**\n\n在 Step Functions 中建立状态机,用于向托管在不同账号上的 API 发起请求。\n\n###### **创建 Step Functions 状态机所需 IAM Role**\n\n使用控制台或者 CDK 创建 IAM Role,命名为 SenderStateMachineRole,满足以下条件:\n\n1. Permissions\n\n```\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"execute-api:Invoke\",\n ],\n \"Resource\": \"arn:aws:execute-api:*:*:*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:*\",\n ],\n \"Resource\": \"*\"\n }\n ]\n}\n\n```\n2. Trust Relationships 选择 Amazon Step Functions\n\n```\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"states.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n```\n\n\n###### **创建 Step Functions 状态机**\n\n\n![image.png](https://dev-media.amazoncloud.cn/bd6bbe0e106b4c208ccb5960dd59b679_image.png)\n\n\n上游账号 A 运行状态机工作流的任务,向下游账号 B 的 API 发送请求。\n\n```\n{\n \"Comment\": \"A description of my state machine\",\n \"StartAt\": \"API Gateway Invoke\",\n \"States\": {\n \"API Gateway Invoke\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::apigateway:invoke\",\n \"Parameters\": {\n \"ApiEndpoint.$\": \"<下游账号B的API端点>\",\n \"Method\": \"POST\",\n \"Stage\": \"dev\",\n \"Path\": \"/execution\",\n \"Headers\": {},\n \"RequestBody\": {\n \"input.$\": \"$.body\",\n \"stateMachineArn.$\": \"$.stateMachineArn\"\n },\n \"AuthType\": \"RESOURCE_POLICY\"\n },\n \"End\": true\n }\n }\n}\n```\n\n##### **下游账号 B**\n\n\n###### **创建 DynamoDB Table**\n\n创建 Table 命名为 ReceiverTable,Partition Key 为message。此 Table 会储存账号 A 状态机工作流向账号 B 发送的信息。\n\n![image.png](https://dev-media.amazoncloud.cn/05d34530080243afadc905390f56a024_image.png)\n\n###### **创建 Step Functions 状态机所需 IAM Role**\n\n```\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"dynamodb:GetItem\",\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:DeleteItem\"\n ],\n \"Resource\": [\n \"arn:aws:dynamodb:<region>:<账号B>:table/ReceiverTable\"\n ]\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:*\",\n ],\n \"Resource\": \"*\"\n },\n ]\n}\n```\n\n###### **创建 Step Functions 状态机**\n\n创建一个简单的状态机 ReceiverStateMachine,可以执行 DynamoDB的PutItem。该 Task 可以将 API Gateway 传来的参数存储进 DynamoDB ReceiverTable。\n\n![image.png](https://dev-media.amazoncloud.cn/ea768e6693d14ef9987c5c62b8b63850_image.png)\n\n```\n{\n \"Comment\": \"A description of my state machine\",\n \"StartAt\": \"PutItem\",\n \"States\": {\n \"PutItem\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::dynamodb:putItem\",\n \"Parameters\": {\n \"TableName\": \"ReceiverTable\",\n \"Item\": {\n \"message\": {\n \"S.$\": \"$.inputValue\"\n }\n }\n },\n \"End\": true\n }\n }\n}\n```\n\n###### **创建 API Gateway 所需 IAM Role**\n\n使用控制台或者 CDK 创建 IAM Role,命名为 ReceiverApiRole,满足以下条件:\n\n1. Permissions\n\n```\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:*\"\n ],\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"states:*\"\n ],\n \"Resource\": \"<Receiver state machine ARN>\"\n }\n ]\n}\n\n```\n\n2. Trust relationships\n\n```\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"apigateway.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n```\n\n###### **创建 API Gateway RestAPI**\n\n1. 创建 RestAPI,命名为 ReceiverApi。\n2. 编辑 Resource Policy,让 API 仅接受来自账号 A 的状态机 SenderStateMachine 的 API 请求。\n\n```\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"states.amazonaws.com\"\n },\n \"Action\": \"execute-api:Invoke\",\n \"Resource\": \"arn:aws:execute-api:<region>:<账号B>:<RestAPI ID>/*/*/*\",\n \"Condition\": {\n \"StringEquals\": {\n \"aws:SourceArn\": \"arn:aws:states:<region>:<账号A>:stateMachine:SenderStateMachine\"\n }\n }\n }\n ]\n}\n```\n\n3. 建立 Resources 为 /execution,再建立 method 为 POST。\n4. 设置 Integration Request,使 API 在被请求时,向 Step Functions 发送 StartExecution 的命令\n\n![image.png](https://dev-media.amazoncloud.cn/86dd92a37a8146a38d35b78157f12805_image.png)\n\n5. 设置 Method Request Authorization 为 Amazon IAM\n\n![image.png](https://dev-media.amazoncloud.cn/f8095255cf454f5da813e352b9a404c2_image.png)\n\n6. Deploy API,选择[New Stage] ,输入dev为Stage name。\n\n##### **运行工作流**\n\n执行上游账号 A 的工作流,输入以下参数。\n\n```\n{\n \"stateMachineArn\": \"<ReceiverStateMachineArn>\",\n \"body\": \"{\\\"inputValue\\\":\\\"some message\\\"}\"\n}\n```\n\n##### **运行结果**\n\n\n1. 上游账号 A 状态机 SenderStateMachine\n\n![image.png](https://dev-media.amazoncloud.cn/cf40bbb491a34d2d96978664f3ca9a29_image.png)\n\n2. 下游账号 B 状态机 ReceiverStateMachine\n\n![image.png](https://dev-media.amazoncloud.cn/4e7b83bea8044675a33a0476b2ce2f4d_image.png)\n\n3. DynamoDB 插入数据\n\n![image.png](https://dev-media.amazoncloud.cn/f9ca49a8c1b240e8994ef874979fb005_image.png)\n\n\n#### **Amazon CDK部署(参考代码)**\n\n##### **上游账号 A**\n\n```\nimport { Construct, Stack, StackProps } from '@aws-cdk/core'\nimport { PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'\nimport { JsonPath, StateMachine, Succeed, TaskInput } from \"@aws-cdk/aws-stepfunctions\";\nimport { AuthType, CallApiGatewayRestApiEndpoint, HttpMethod } from \"@aws-cdk/aws-stepfunctions-tasks\";\nimport { LogGroup } from \"@aws-cdk/aws-logs\";\nimport { RestApi } from '@aws-cdk/aws-apigateway'\n```\n\n```\nexport class SenderStack extends Stack {\n constructor(scope: Construct, id: string, props?: StackProps) {\n super(scope, id, props);\n \n // sender state machine Cloudwatch log group\n const logGroup = new LogGroup(this, \"LogGroup\", {\n logGroupName: \"SenderStateMachineLogGroup\"\n })\n \n // sender state machine IAM role\n const senderStateMachineRole = new Role(this, \"SenderStateMachineRole\", {\n roleName: \"SenderStateMachineRole\",\n assumedBy: new ServicePrincipal(\"states.amazonaws.com\"),\n inlinePolicies: {\n stateMachinePolicy: new PolicyDocument({\n statements: [\n // api invoke permission\n new PolicyStatement({\n actions: [\"execute-api:Invoke\"],\n resources: [\"arn:aws:execute-api:*:*:*\"]\n }),\n new PolicyStatement({\n actions: [\"logs:*\"],\n resources: [logGroup.logGroupArn]\n })\n ]\n })\n }\n })\n // sender Step Functions state machine to call api from account B\n const senderStateMachine = new StateMachine(this, 'SenderStateMachine', {\n stateMachineName: \"SenderStateMachine\",\n definition: new CallApiGatewayRestApiEndpoint(this, \"CallApiGatewayRestApiEndpoint\", {\n api: <ReceiverStateMachine arn>,\n stageName: \"dev\",\n method: HttpMethod.POST,\n requestBody: TaskInput.fromObject({\n input: JsonPath.stringAt('$.body'),\n stateMachineArn: JsonPath.stringAt('$.receiverStateMachineArn'),\n }),\n authType: AuthType.RESOURCE_POLICY\n })\n .next(new Succeed(this, \"TaskCompleted\")),\n role: receiverStateMachineRole,\n logs: {\n destination: logGroup\n }\n });\n\n }\n}\n```\n\n##### **下游账号 B**\n\n```\nimport { Stack, Construct, StackProps } from '@aws-cdk/core'\nimport { Table, AttributeType } from '@aws-cdk/aws-dynamodb'\nimport { Role, Effect, PolicyDocument, PolicyStatement, ServicePrincipal, Condition } from '@aws-cdk/aws-iam'\nimport { LogGroup } from '@aws-cdk/aws-logs'\nimport { StateMachine, JsonPath, Succeed } from '@aws-cdk/aws-stepfunctions'\nimport { DynamoPutItem, DynamoAttributeValue } from \"@aws-cdk/aws-stepfunctions-tasks\";\nimport { RestApi, AwsIntegration, AuthorizationType, Stage, Deployment } from '@aws-cdk/aws-apigateway'\n```\n\n```\nexport class ReceiverStack extends Stack {\n constructor(scope: Construct, id: string, props?: StackProps) {\n super(scope, id, props);\n \n // DynamoDB table\n const receiverTable = new Table(this, \"ReceiverTable\", {\n tableName: \"ReceiverTable\",\n partitionKey: { name: 'message', type: AttributeType.STRING }\n });\n \n \n // receiver state machine Cloudwatch log group\n const logGroup = new LogGroup(this, \"LogGroup\", {\n logGroupName: \"ReceiverStateMachineLogGroup\"\n })\n```\n\n ```\n// receiver state machine IAM role\n const receiverStateMachineRole = new Role(this, \"ReceiverStateMachineRole\", {\n roleName: \"ReceiverStateMachineRole\",\n assumedBy: new ServicePrincipal(\"states.amazonaws.com\"),\n inlinePolicies: {\n stateMachinePolicy: new PolicyDocument({\n statements: [\n // DynamoDB permissions\n new PolicyStatement({\n actions:[\n \"dynamodb:GetItem\",\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:DeleteItem\"],\n resources: [receiverTable.tableArn],\n effect: Effect.ALLOW\n }),\n new PolicyStatement({\n actions: [\n \"logs:*\"\n ],\n resources: [logGroup.logGroupArn],\n effect: Effect.ALLOW\n })\n ]\n })}})\n```\n\n```\n// receiver Step Functions state machine to put item in DynamoDB\n const receiverStateMachine = new StateMachine(this, 'ReceiverStateMachine', {\n stateMachineName: \"ReceiverStateMachine\",\n definition: new DynamoPutItem(this, \"DynamodbPutItemTask\", {\n item: {\n message: DynamoAttributeValue.fromString(JsonPath.stringAt('$.inputValue')),\n },\n table: receiverTable\n }).next(new Succeed(this, \"TaskCompleted\")),\n role: receiverStateMachineRole,\n logs: {\n destination: logGroup\n }\n });\n```\n\n ```\n// API Gateway IAM role \n const apiRole = new Role(this, 'ReceiverApiRole', {\n roleName: \"ReceiverApiRole\",\n assumedBy: new ServicePrincipal(\"apigateway.amazonaws.com\"),\n inlinePolicies: {\n apiPolicy: new PolicyDocument({\n statements: [\n new PolicyStatement({\n actions: [\"logs:*\"],\n resources: [\"*\"]\n }),\n // execute state machine permissions\n new PolicyStatement({\n actions: [\"states:*\"],\n resources: [receiverStateMachine.stateMachineArn]\n })\n ]\n })\n }\n })\n```\n\n```\n// API Gateway RestAPI\n const api = new RestApi(this, \"ReceiverApi\", {\n restApiName: \"ReceiverApi\"\n })\n// resource policy to restrict access\n const resourcePolicy = new PolicyDocument({\n statements: [\n new PolicyStatement({\n actions: [\"execute-api:Invoke\"],\n resources: [api.arnForExecuteApi(\"POST\", \"/execution\")],\n conditions: {\n StringEquals: {\n <SenderStateMachine arn>\n }\n }\n })\n ]\n })\n // API resource to start state machine execution with AWS Integration\n api.root.addResource('execution').addMethod('POST',\n new AwsIntegration({\n service: \"states\",\n action: \"StartExecution\",\n region: this.region,\n options: {\n credentialsRole: apiRole\n }\n }),\n {\n authorizationType: AuthorizationType.IAM\n })\n \n // deploy api\n const deployment = new Deployment(this, 'Deployment', {api});\n const stage = new Stage(this, 'dev', {deployment});\n\n }\n\n}\n```\n\n#### **总结**\n\n文章提供了部署基于 API Gateway 和 Step Functions 的跨账号工作流的详细步骤,并提供了 CDK 部署的参考代码。上游账号的 Step Functions 工作流通过 API Gateway 作为前端,调用下游账号的工作流。此方案可以进行扩展,通过一个上游工作流集中管理账号,管理多区域和多账号的工作流服务,避免了在不同账号进行重复工作,\n\n更多的了解无服务器架构及 Step Functions,请参考 [Amazon Step Functions](https://docs.aws.amazon.com/step-functions/index.html)\n\n#### **本篇作者**\n\n![image.png](https://dev-media.amazoncloud.cn/31dad8cc50454add9c48f6140b691a3f_image.png)\n\n#### **刘红雨**\n\n云原生应用工程师,负责基于 Amazon 的云计算方案架构的设计和实施开发。拥有丰富的互联网产品的开发经验,负责多个项目的搜索功能的设计和开发,熟悉搜索的性能优化,对公有云、机器学习、DevOps、基于云原生的微服务架构、敏捷加速研发效能等有深入的研究和热情。\n\n![image.png](https://dev-media.amazoncloud.cn/2d9168c306634da8bde807723e5acdbc_image.png)\n\n#### **许和风**\n\nAmazon 云原生应用工程师,负责基于 Amazon 的云计算方案架构的设计和实施。对公有云、DevOps、微服务、容器化、Serverless、全栈开发等有深入的研究,同时致力于推广云原生应用,帮助客户利用云原生来实现业务需求。","render":"<h4><a id=\"_0\"></a><strong>背景</strong></h4>\n<p>Amazon Step Functions 是一项低代码、可视化的工作流服务,让开发人员可通过 Amazon 服务轻松构建分布式应用程序、自动化 IT 和业务流程并构建数据和机器学习管道。通过嵌套 Step Functions 工作流程,您可以将大型和复杂的工作流程构建为更小型和简单的多个工作流程。但是,嵌套 Step Functions 需要您在同一个账号上部署。而在很多实际使用场景中,需要在多个 Amazon 账号下进行工作流的编排。</p>\n<p>文章提供了一个通过 Amazon API Gateway 来实现跨账号工作流服务的解决方案,并且提供 Amazon 管理控制台和 Amazon CDK 基础架构即代码(IaC)两种部署方式作为参考。</p>\n<h4><a id=\"Amazon_Step_Functions_6\"></a><strong>Amazon Step Functions</strong></h4>\n<p>Amazon Step Functions 可以与其他 Amazon 服务进行集成,在工作流中直接调用其他服务的的 API。例如:</p>\n<ul>\n<li>调用 Amazon Lambda function</li>\n<li>插入或者读取 Amazon DynamoDB 的数据</li>\n<li>运行一个 Amazon Elastic Container Service (Amazon ECS)任务,并等待它运行结束。</li>\n<li>在 Amazon Simple Queue Service (Amazon SQS)发送消息</li>\n<li>运行其他的 Amazon Step Functions 工作流</li>\n<li>向 Amazon API Gateway 发送请求</li>\n</ul>\n<p>更多请参考 <a href=\"https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-services.html\" target=\"_blank\">Call other Amazon services</a></p>\n<h4><a id=\"_19\"></a><strong>架构设计</strong></h4>\n<p>以下架构图展示了上游账号 A 的工作流通过 API Gateway,异步调用下游账号 B 的工作流,并传递有效业务信息。其通过资源策略来对 API 和 Step Functions 工作流进行访问权限控制,防止匿名访问。此架构展示的是两个 Amazon 账号间的跨账号工作流服务,你也可以扩展为跨多个区域、多个账号的工作流服务。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/dcc4548d28b24d03ad694d48be597a4b_image.png\" alt=\"image.png\" /></p>\n<h5><a id=\"_25\"></a><strong>工作流程</strong></h5>\n<ol>\n<li>上游账号 A 的 Step Functions 状态机向下游账号 B 的 API Gateway 端点发起请求,请求中包含下游账号B的 Step Functions 状态机 ARN 及其他业务信息。</li>\n<li>下游账号 B 的 API Gateway 判断工作流流向,异步调用所需下游账号 B 的 Step Functions 状态机并传递业务信息。</li>\n<li>下游账号 B 的 Step Functions 状态机运行其工作流。</li>\n</ol>\n<h4><a id=\"_31\"></a><strong>配置步骤</strong></h4>\n<p>文章会先展示在 Amazon 管理控制台进行无代码的部署,如果您需要参考 CDK 代码,请至文章的 CDK 部署模块。</p>\n<h5><a id=\"_A_35\"></a><strong>上游账号 A</strong></h5>\n<p>在 Step Functions 中建立状态机,用于向托管在不同账号上的 API 发起请求。</p>\n<h6><a id=\"_Step_Functions__IAM_Role_39\"></a><strong>创建 Step Functions 状态机所需 IAM Role</strong></h6>\n<p>使用控制台或者 CDK 创建 IAM Role,命名为 SenderStateMachineRole,满足以下条件:</p>\n<ol>\n<li>Permissions</li>\n</ol>\n<pre><code class=\"lang-\">{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Effect": "Allow",\n "Action": [\n "execute-api:Invoke",\n ],\n "Resource": "arn:aws:execute-api:*:*:*"\n },\n {\n "Effect": "Allow",\n "Action": [\n "logs:*",\n ],\n "Resource": "*"\n }\n ]\n}\n\n</code></pre>\n<ol start=\"2\">\n<li>Trust Relationships 选择 Amazon Step Functions</li>\n</ol>\n<pre><code class=\"lang-\">{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Sid": "",\n "Effect": "Allow",\n "Principal": {\n "Service": "states.amazonaws.com"\n },\n "Action": "sts:AssumeRole"\n }\n ]\n}\n</code></pre>\n<h6><a id=\"_Step_Functions__86\"></a><strong>创建 Step Functions 状态机</strong></h6>\n<p><img src=\"https://dev-media.amazoncloud.cn/bd6bbe0e106b4c208ccb5960dd59b679_image.png\" alt=\"image.png\" /></p>\n<p>上游账号 A 运行状态机工作流的任务,向下游账号 B 的 API 发送请求。</p>\n<pre><code class=\"lang-\">{\n "Comment": "A description of my state machine",\n "StartAt": "API Gateway Invoke",\n "States": {\n "API Gateway Invoke": {\n "Type": "Task",\n "Resource": "arn:aws:states:::apigateway:invoke",\n "Parameters": {\n "ApiEndpoint.$": "<下游账号B的API端点>",\n "Method": "POST",\n "Stage": "dev",\n "Path": "/execution",\n "Headers": {},\n "RequestBody": {\n "input.$": "$.body",\n "stateMachineArn.$": "$.stateMachineArn"\n },\n "AuthType": "RESOURCE_POLICY"\n },\n "End": true\n }\n }\n}\n</code></pre>\n<h5><a id=\"_B_120\"></a><strong>下游账号 B</strong></h5>\n<h6><a id=\"_DynamoDB_Table_123\"></a><strong>创建 DynamoDB Table</strong></h6>\n<p>创建 Table 命名为 ReceiverTable,Partition Key 为message。此 Table 会储存账号 A 状态机工作流向账号 B 发送的信息。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/05d34530080243afadc905390f56a024_image.png\" alt=\"image.png\" /></p>\n<h6><a id=\"_Step_Functions__IAM_Role_129\"></a><strong>创建 Step Functions 状态机所需 IAM Role</strong></h6>\n<pre><code class=\"lang-\">{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Effect": "Allow",\n "Action": [\n "dynamodb:GetItem",\n "dynamodb:PutItem",\n "dynamodb:UpdateItem",\n "dynamodb:DeleteItem"\n ],\n "Resource": [\n "arn:aws:dynamodb:<region>:<账号B>:table/ReceiverTable"\n ]\n },\n {\n "Effect": "Allow",\n "Action": [\n "logs:*",\n ],\n "Resource": "*"\n },\n ]\n}\n</code></pre>\n<h6><a id=\"_Step_Functions__158\"></a><strong>创建 Step Functions 状态机</strong></h6>\n<p>创建一个简单的状态机 ReceiverStateMachine,可以执行 DynamoDB的PutItem。该 Task 可以将 API Gateway 传来的参数存储进 DynamoDB ReceiverTable。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/ea768e6693d14ef9987c5c62b8b63850_image.png\" alt=\"image.png\" /></p>\n<pre><code class=\"lang-\">{\n "Comment": "A description of my state machine",\n "StartAt": "PutItem",\n "States": {\n "PutItem": {\n "Type": "Task",\n "Resource": "arn:aws:states:::dynamodb:putItem",\n "Parameters": {\n "TableName": "ReceiverTable",\n "Item": {\n "message": {\n "S.$": "$.inputValue"\n }\n }\n },\n "End": true\n }\n }\n}\n</code></pre>\n<h6><a id=\"_API_Gateway__IAM_Role_186\"></a><strong>创建 API Gateway 所需 IAM Role</strong></h6>\n<p>使用控制台或者 CDK 创建 IAM Role,命名为 ReceiverApiRole,满足以下条件:</p>\n<ol>\n<li>Permissions</li>\n</ol>\n<pre><code class=\"lang-\">{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Effect": "Allow",\n "Action": [\n "logs:*"\n ],\n "Resource": "*"\n },\n {\n "Effect": "Allow",\n "Action": [\n "states:*"\n ],\n "Resource": "<Receiver state machine ARN>"\n }\n ]\n}\n\n</code></pre>\n<ol start=\"2\">\n<li>Trust relationships</li>\n</ol>\n<pre><code class=\"lang-\">{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Sid": "",\n "Effect": "Allow",\n "Principal": {\n "Service": "apigateway.amazonaws.com"\n },\n "Action": "sts:AssumeRole"\n }\n ]\n}\n</code></pre>\n<h6><a id=\"_API_Gateway_RestAPI_233\"></a><strong>创建 API Gateway RestAPI</strong></h6>\n<ol>\n<li>创建 RestAPI,命名为 ReceiverApi。</li>\n<li>编辑 Resource Policy,让 API 仅接受来自账号 A 的状态机 SenderStateMachine 的 API 请求。</li>\n</ol>\n<pre><code class=\"lang-\">{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Effect": "Allow",\n "Principal": {\n "Service": "states.amazonaws.com"\n },\n "Action": "execute-api:Invoke",\n "Resource": "arn:aws:execute-api:<region>:<账号B>:<RestAPI ID>/*/*/*",\n "Condition": {\n "StringEquals": {\n "aws:SourceArn": "arn:aws:states:<region>:<账号A>:stateMachine:SenderStateMachine"\n }\n }\n }\n ]\n}\n</code></pre>\n<ol start=\"3\">\n<li>建立 Resources 为 /execution,再建立 method 为 POST。</li>\n<li>设置 Integration Request,使 API 在被请求时,向 Step Functions 发送 StartExecution 的命令</li>\n</ol>\n<p><img src=\"https://dev-media.amazoncloud.cn/86dd92a37a8146a38d35b78157f12805_image.png\" alt=\"image.png\" /></p>\n<ol start=\"5\">\n<li>设置 Method Request Authorization 为 Amazon IAM</li>\n</ol>\n<p><img src=\"https://dev-media.amazoncloud.cn/f8095255cf454f5da813e352b9a404c2_image.png\" alt=\"image.png\" /></p>\n<ol start=\"6\">\n<li>Deploy API,选择[New Stage] ,输入dev为Stage name。</li>\n</ol>\n<h5><a id=\"_270\"></a><strong>运行工作流</strong></h5>\n<p>执行上游账号 A 的工作流,输入以下参数。</p>\n<pre><code class=\"lang-\">{\n "stateMachineArn": "<ReceiverStateMachineArn>",\n "body": "{\\"inputValue\\":\\"some message\\"}"\n}\n</code></pre>\n<h5><a id=\"_281\"></a><strong>运行结果</strong></h5>\n<ol>\n<li>上游账号 A 状态机 SenderStateMachine</li>\n</ol>\n<p><img src=\"https://dev-media.amazoncloud.cn/cf40bbb491a34d2d96978664f3ca9a29_image.png\" alt=\"image.png\" /></p>\n<ol start=\"2\">\n<li>下游账号 B 状态机 ReceiverStateMachine</li>\n</ol>\n<p><img src=\"https://dev-media.amazoncloud.cn/4e7b83bea8044675a33a0476b2ce2f4d_image.png\" alt=\"image.png\" /></p>\n<ol start=\"3\">\n<li>DynamoDB 插入数据</li>\n</ol>\n<p><img src=\"https://dev-media.amazoncloud.cn/f9ca49a8c1b240e8994ef874979fb005_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"Amazon_CDK_297\"></a><strong>Amazon CDK部署(参考代码)</strong></h4>\n<h5><a id=\"_A_299\"></a><strong>上游账号 A</strong></h5>\n<pre><code class=\"lang-\">import { Construct, Stack, StackProps } from '@aws-cdk/core'\nimport { PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'\nimport { JsonPath, StateMachine, Succeed, TaskInput } from "@aws-cdk/aws-stepfunctions";\nimport { AuthType, CallApiGatewayRestApiEndpoint, HttpMethod } from "@aws-cdk/aws-stepfunctions-tasks";\nimport { LogGroup } from "@aws-cdk/aws-logs";\nimport { RestApi } from '@aws-cdk/aws-apigateway'\n</code></pre>\n<pre><code class=\"lang-\">export class SenderStack extends Stack {\n constructor(scope: Construct, id: string, props?: StackProps) {\n super(scope, id, props);\n \n // sender state machine Cloudwatch log group\n const logGroup = new LogGroup(this, "LogGroup", {\n logGroupName: "SenderStateMachineLogGroup"\n })\n \n // sender state machine IAM role\n const senderStateMachineRole = new Role(this, "SenderStateMachineRole", {\n roleName: "SenderStateMachineRole",\n assumedBy: new ServicePrincipal("states.amazonaws.com"),\n inlinePolicies: {\n stateMachinePolicy: new PolicyDocument({\n statements: [\n // api invoke permission\n new PolicyStatement({\n actions: ["execute-api:Invoke"],\n resources: ["arn:aws:execute-api:*:*:*"]\n }),\n new PolicyStatement({\n actions: ["logs:*"],\n resources: [logGroup.logGroupArn]\n })\n ]\n })\n }\n })\n // sender Step Functions state machine to call api from account B\n const senderStateMachine = new StateMachine(this, 'SenderStateMachine', {\n stateMachineName: "SenderStateMachine",\n definition: new CallApiGatewayRestApiEndpoint(this, "CallApiGatewayRestApiEndpoint", {\n api: <ReceiverStateMachine arn>,\n stageName: "dev",\n method: HttpMethod.POST,\n requestBody: TaskInput.fromObject({\n input: JsonPath.stringAt('$.body'),\n stateMachineArn: JsonPath.stringAt('$.receiverStateMachineArn'),\n }),\n authType: AuthType.RESOURCE_POLICY\n })\n .next(new Succeed(this, "TaskCompleted")),\n role: receiverStateMachineRole,\n logs: {\n destination: logGroup\n }\n });\n\n }\n}\n</code></pre>\n<h5><a id=\"_B_364\"></a><strong>下游账号 B</strong></h5>\n<pre><code class=\"lang-\">import { Stack, Construct, StackProps } from '@aws-cdk/core'\nimport { Table, AttributeType } from '@aws-cdk/aws-dynamodb'\nimport { Role, Effect, PolicyDocument, PolicyStatement, ServicePrincipal, Condition } from '@aws-cdk/aws-iam'\nimport { LogGroup } from '@aws-cdk/aws-logs'\nimport { StateMachine, JsonPath, Succeed } from '@aws-cdk/aws-stepfunctions'\nimport { DynamoPutItem, DynamoAttributeValue } from "@aws-cdk/aws-stepfunctions-tasks";\nimport { RestApi, AwsIntegration, AuthorizationType, Stage, Deployment } from '@aws-cdk/aws-apigateway'\n</code></pre>\n<pre><code class=\"lang-\">export class ReceiverStack extends Stack {\n constructor(scope: Construct, id: string, props?: StackProps) {\n super(scope, id, props);\n \n // DynamoDB table\n const receiverTable = new Table(this, "ReceiverTable", {\n tableName: "ReceiverTable",\n partitionKey: { name: 'message', type: AttributeType.STRING }\n });\n \n \n // receiver state machine Cloudwatch log group\n const logGroup = new LogGroup(this, "LogGroup", {\n logGroupName: "ReceiverStateMachineLogGroup"\n })\n</code></pre>\n<pre><code class=\"lang-\">// receiver state machine IAM role\n const receiverStateMachineRole = new Role(this, "ReceiverStateMachineRole", {\n roleName: "ReceiverStateMachineRole",\n assumedBy: new ServicePrincipal("states.amazonaws.com"),\n inlinePolicies: {\n stateMachinePolicy: new PolicyDocument({\n statements: [\n // DynamoDB permissions\n new PolicyStatement({\n actions:[\n "dynamodb:GetItem",\n "dynamodb:PutItem",\n "dynamodb:UpdateItem",\n "dynamodb:DeleteItem"],\n resources: [receiverTable.tableArn],\n effect: Effect.ALLOW\n }),\n new PolicyStatement({\n actions: [\n "logs:*"\n ],\n resources: [logGroup.logGroupArn],\n effect: Effect.ALLOW\n })\n ]\n })}})\n</code></pre>\n<pre><code class=\"lang-\">// receiver Step Functions state machine to put item in DynamoDB\n const receiverStateMachine = new StateMachine(this, 'ReceiverStateMachine', {\n stateMachineName: "ReceiverStateMachine",\n definition: new DynamoPutItem(this, "DynamodbPutItemTask", {\n item: {\n message: DynamoAttributeValue.fromString(JsonPath.stringAt('$.inputValue')),\n },\n table: receiverTable\n }).next(new Succeed(this, "TaskCompleted")),\n role: receiverStateMachineRole,\n logs: {\n destination: logGroup\n }\n });\n</code></pre>\n<pre><code class=\"lang-\">// API Gateway IAM role \n const apiRole = new Role(this, 'ReceiverApiRole', {\n roleName: "ReceiverApiRole",\n assumedBy: new ServicePrincipal("apigateway.amazonaws.com"),\n inlinePolicies: {\n apiPolicy: new PolicyDocument({\n statements: [\n new PolicyStatement({\n actions: ["logs:*"],\n resources: ["*"]\n }),\n // execute state machine permissions\n new PolicyStatement({\n actions: ["states:*"],\n resources: [receiverStateMachine.stateMachineArn]\n })\n ]\n })\n }\n })\n</code></pre>\n<pre><code class=\"lang-\">// API Gateway RestAPI\n const api = new RestApi(this, "ReceiverApi", {\n restApiName: "ReceiverApi"\n })\n// resource policy to restrict access\n const resourcePolicy = new PolicyDocument({\n statements: [\n new PolicyStatement({\n actions: ["execute-api:Invoke"],\n resources: [api.arnForExecuteApi("POST", "/execution")],\n conditions: {\n StringEquals: {\n <SenderStateMachine arn>\n }\n }\n })\n ]\n })\n // API resource to start state machine execution with AWS Integration\n api.root.addResource('execution').addMethod('POST',\n new AwsIntegration({\n service: "states",\n action: "StartExecution",\n region: this.region,\n options: {\n credentialsRole: apiRole\n }\n }),\n {\n authorizationType: AuthorizationType.IAM\n })\n \n // deploy api\n const deployment = new Deployment(this, 'Deployment', {api});\n const stage = new Stage(this, 'dev', {deployment});\n\n }\n\n}\n</code></pre>\n<h4><a id=\"_505\"></a><strong>总结</strong></h4>\n<p>文章提供了部署基于 API Gateway 和 Step Functions 的跨账号工作流的详细步骤,并提供了 CDK 部署的参考代码。上游账号的 Step Functions 工作流通过 API Gateway 作为前端,调用下游账号的工作流。此方案可以进行扩展,通过一个上游工作流集中管理账号,管理多区域和多账号的工作流服务,避免了在不同账号进行重复工作,</p>\n<p>更多的了解无服务器架构及 Step Functions,请参考 <a href=\"https://docs.aws.amazon.com/step-functions/index.html\" target=\"_blank\">Amazon Step Functions</a></p>\n<h4><a id=\"_511\"></a><strong>本篇作者</strong></h4>\n<p><img src=\"https://dev-media.amazoncloud.cn/31dad8cc50454add9c48f6140b691a3f_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"_515\"></a><strong>刘红雨</strong></h4>\n<p>云原生应用工程师,负责基于 Amazon 的云计算方案架构的设计和实施开发。拥有丰富的互联网产品的开发经验,负责多个项目的搜索功能的设计和开发,熟悉搜索的性能优化,对公有云、机器学习、DevOps、基于云原生的微服务架构、敏捷加速研发效能等有深入的研究和热情。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/2d9168c306634da8bde807723e5acdbc_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"_521\"></a><strong>许和风</strong></h4>\n<p>Amazon 云原生应用工程师,负责基于 Amazon 的云计算方案架构的设计和实施。对公有云、DevOps、微服务、容器化、Serverless、全栈开发等有深入的研究,同时致力于推广云原生应用,帮助客户利用云原生来实现业务需求。</p>\n"}