亚马逊AWS官方博客
基于 Amazon ECS 的并行批处理任务解决方案
很多企业都会有运行批处理任务的需求,而且这些批处理任务可能都是不同类型的工作,比如有的是处理数据的,有的是进行媒体编码的,有的是进行机器学习模型训练的。这些批处理任务通常都是通过消息进行触发,也就是说需要处理数据或者其他任务的时候,程序发起方会发送一个或者多个消息到一个特定的队列里,然后由监控该队列的应用程序发起任务处理。
本文描述了一种基于容器化的,在 Amazon ECS 平台上搭建的、并行批处理任务的、通用的解决方案。
解决方案概述
该方案如下图所示。
在该方案中,我们会启动一个 Amazon ECS 集群,并关联 auto scaling 组。然后在该平台上,我们可以借助一个开源工具:mapbox/watchbot(以下简称 watchbot)来搭建任务处理平台。该平台与 Amazon ECS 无缝集成,结合 Amazon ECR 和 ECS 服务,使得用户只需要关注任务处理程序即可,其他的任务调度,高可用性管理等工作,都可以由 watchbot 来完成。Watchbot 的处理流程如下图所示。
在使用 watchbot 之前,用户把自己的任务处理程序(可以是任何语言编写的程序)封装到 docker 镜像中,并上传到 Amazon ECR 里。然后在部署了watchbot 以后,会在 Amazon ECS 集群里启动一个 ECS 服务,该服务会启动1到多个 watcher 容器(具体几个 watcher 容器可以由用户自己配置)。该 watcher 容器会监控一个预先创建好的 Amazon SQS 队列,一旦该队列里被放入了消息,watcher 容器就会把该消息设置为不可见,然后启动预先配置好的 ECS 任务或者也可以叫做任务容器(也就是上图中的蓝色方块),任务容器就会从 Amazon ECR 里拉取包含了用户业务处理程序的 docker 镜像,并把消息作为参数传给任务容器,然后在任务容器里根据传入的消息参数,进行相应的逻辑处理。任务容器执行完以后,可以有4种退出状态:
- 退出代码为0:表示任务容器执行结束并正常退出,对应的消息会被 watcher 容器从队列里删除。
- 退出代码为3:表示该消息被任务容器拒绝,对应的消息会被 watcher 容器从队列里删除,同时发送一个通知到 SNS 主题。
- 退出代码为4:表示该消息不被任务容器处理,对应的消息会被 watcher 容器转移到对应的 error 队列。
- 退出代码为其他值:表示任务容器在处理该消息时发生被捕获的异常并主动退出,对应的消息会被 watcher 容器转移到对应的 error 队列,并发送一个通知到 SNS 主题。
如果任务容器在执行过程中出现未被捕获的故障而导致异常退出,则 watcher 容器会再次启动该任务容器,如果任务容器反复执行失败导致异常退出,则 watcher 容器在尝试一定次数以后,会放弃并把该消息放入对应的 error 队列。
架构部署
我们以 us-east-1 region 为例,一步一步的说明如何部署整个并行批处理任务解决方案。注意在部署之前,需要在你的电脑上配置 AWS credentials,并具有管理 Amazon ECR 和 Amazon ECS 的权限。或者也可以在一台 EC2 实例上完成部署工作,那么可以在该 EC2 实例上关联一个角色,并为角色赋予管理 Amazon ECR 和 ECS 的权限。
- 创建 Amazon ECS 集群,具体步骤参考:
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/create_cluster.html。
该 ECS 集群可以与Auto scaling 组结合实现根据业务负载自动伸缩。有关 Auto scaling 的内容请参考官方文档:
https://docs.aws.amazon.com/autoscaling/plans/userguide/what-is-aws-auto-scaling.html。
然后根据官方文档创建 ECR 仓库:
https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-create.html。
假设该仓库的名字叫做 ecs-watchbot,则该 ECR 的 URI 为:123456789012.dkr.ecr.us-east-1.amazonaws.com/ecs-watchbot
,这里的 123456789012 为用户的 AWS 账号。 - 从 github下载 watchbot 代码:git clone https://github.com/mapbox/ecs-watchbot.git
- 进入 ecs-watchbot 子目录,创建 watchbot 容器镜像,注意下面命令中别忘了最后一个“.”。在本地构建 ecs-watchbot 容器镜像以后,上传到第一步中创建的 ECR 仓库里。编辑 Dockerfile,内容如下:
- 在部署基于 watchbot 的 ECS 服务的时候,mapbox 在 github 上提供了一个样例 mapbox/ecs-telephone:https://github.com/mapbox/ecs-telephone。我们可以基于该样例进行修改,使其满足实际的需求。在安装该样例之前,先在当前主机上安装 js 以及其他组件:
- 该方案会使用 KMS 对 Amazon Cloudformation 的相关资源进行加密,比如环境变量等,因此需要先部署 KMS。从 github 下载 cloudformation-kms 的代码:
git clone https://github.com/mapbox/cloudformation-kms.git
进入cloudformation-kms
目录,执行下面的命令生成 Amazon Cloudformation 模板:然后使用下面的命令创建 Amazon Cloudformation 堆栈:
堆栈创建完毕以后,会有一个输出,其 export name 为
cloudformation-kms-production
,这对应到生成的 KMS key 的 ARN,这个 ARN 会在ecs-telephone
模板中被使用到。 - 从 github 下载 ecs-telephone 代码:
git clone
https://github.com/mapbox/ecs-telephone.git进入 ecs-telephone 子目录,可以看到一个 Dockerfile 文件,使用该 Dockerfile 构建出的容器镜像里就包含了用户的业务逻辑。用户可以使用任何编程语言编写相应的业务逻辑。比如下面的例子使用 python 写了一段很简单的逻辑,该逻辑把传入当前容器的环境变量:message 的内容输出到 Amazon Cloudwatch log 里,保存该代码名为domsg.py
。然后编写一个
Dockerfile
,如下所示:运行下面的命令构建用户的任务容器镜像,tag 为123:
创建 ECR 仓库,假设名为
ecs-telephone
,对应的 URI 为012345678901.dkr.ecr.us-east-1.amazonaws.com/ecs- telephone
。然后把用户的任务容器镜像上传到 ECR 里: - 进入 ecs-telephone/cloudformation 子目录,可以看到里面有一个 javascript 文件:
ecs-telephone.template.js
,可以利用该文件生成 Amazon Cloudformation 模板。打开该文件,其内容如下所示:这里有三个输入参数:
GitSha
:表示包含用户任务的 docker 镜像的 tag 值。Cluster
:表示整个任务平台所在的 Amazon ECS 集群的 ARN,注意这里需要输入 ARN 而不是集群的名称。该 ARN 可以使用 awscli 命令来获得:aws ecs list-clusters
Family
:表示该批处理任务平台的名称,可以根据需要输入一个字符串,其内容没有特定要求。
还可以在这里根据实际需要修改相应的参数,包括:
service
:表示使用该模板创建的 ECS 服务的名字。workers
:表示 watcher 在发现队列里有消息的时候,启动的包含用户的业务逻辑的容器。缺省为1个。reservation
:任务容器在运行时所需要的 CPU 和内存资源。notificationEmail
:各种通知产生以后,会发送到该邮箱里。
参数都修改完毕并保存以后,使用下面的命令创建 Amazon Cloudformation 模板:
打开该模板,找到包含
Watchbot-worker-ecs-telephone
字样的部分,可以看到如果使用该模板部署 ECS 服务,则会去找名为ecs-telephone
的 Amazon ECR 仓库,用户可以根据实际情况修改该 ECR 仓库名。如果要在中国区运行该模板的话,需要手工进行修改。具体修改的地方包括:
- 对该 Cloudformation 模板修改完毕以后,使用下面的命令运行模板从而创建 Amazon ECS 服务:
- 当 Amazon Cloudformation 堆栈部署完毕以后,进入 ECS 集群,可以看到创建了一个 ECS 服务,点击该服务,进入服务配置以后,有一个始终保持运行状态的 ECS 任务,该任务就是 watcher 容器。还可以通过控制台进入 Amazon SQS 界面,找到三个队列,其后缀名分别为
WatchbotDeadLetterQueue
,WatchbotTaskEventQueue
以及WatchbotQueue
。触发任务执行的消息需要发送到后缀为WatchbotQueue
的队列,如果任务执行失败,则消息会放入后缀为 WatchbotDeadLetterQueue 的队列。在向WatchbotQueue
发送消息的时候,必须遵循下面的格式:该消息中的 Message 内容:“Your random message” 就会被上面第六步中的
domsg.py
输出到 Amazon CloudWatch 的 log group 里。我们可以进入 Amazon CloudWatch 控制台界面上,点击右边的 Logs,然后会看到包含 watchbot 的 log group,点击进去以后找到最新的 log stream 并点击进入,会看到该消息,说明整个流程正常运行起来了:
结论
本文详细描述了如何基于 Amazon ECS 构建并行批处理任务平台。在该方案中,用户只需要关心业务逻辑的代码即可,其他启动,停止,重试任务容器以及自动扩展等操作都交由平台本身完成。另外,我们在部署任务容器时,任务处理的输出以及状态等信息应该保存到 Amazon S3 或者其他存储服务里(比如 RDS),确保任务容器在异常终止的时候,数据不会丢失。