基于 Amazon ECS 的并行批处理任务解决方案

阅读数:31 2019 年 10 月 22 日 08:00

基于 Amazon ECS 的并行批处理任务解决方案

很多企业都会有运行批处理任务的需求,而且这些批处理任务可能都是不同类型的工作,比如有的是处理数据的,有的是进行媒体编码的,有的是进行机器学习模型训练的。这些批处理任务通常都是通过消息进行触发,也就是说需要处理数据或者其他任务的时候,程序发起方会发送一个或者多个消息到一个特定的队列里,然后由监控该队列的应用程序发起任务处理。

本文描述了一种基于容器化的,在 Amazon ECS 平台上搭建的、并行批处理任务的、通用的解决方案。

解决方案概述

该方案如下图所示。

基于 Amazon ECS 的并行批处理任务解决方案

在该方案中,我们会启动一个 Amazon ECS 集群,并关联 auto scaling 组。然后在该平台上,我们可以借助一个开源工具: mapbox/watchbot (以下简称 watchbot)来搭建任务处理平台。该平台与 Amazon ECS 无缝集成,结合 Amazon ECR 和 ECS 服务,使得用户只需要关注任务处理程序即可,其他的任务调度,高可用性管理等工作,都可以由 watchbot 来完成。Watchbot 的处理流程如下图所示。

基于 Amazon ECS 的并行批处理任务解决方案

在使用 watchbot 之前,用户把自己的任务处理程序(可以是任何语言编写的程序)封装到 docker 镜像中,并上传到 Amazon ECR 里。然后在部署了 watchbot 以后,会在 Amazon ECS 集群里启动一个 ECS 服务,该服务会启动 1 到多个 watcher 容器(具体几个 watcher 容器可以由用户自己配置)。该 watcher 容器会监控一个预先创建好的 Amazon SQS 队列,一旦该队列里被放入了消息,watcher 容器就会把该消息设置为不可见,然后启动预先配置好的 ECS 任务或者也可以叫做任务容器(也就是上图中的蓝色方块),任务容器就会从 Amazon ECR 里拉取包含了用户业务处理程序的 docker 镜像,并把消息作为参数传给任务容器,然后在任务容器里根据传入的消息参数,进行相应的逻辑处理。任务容器执行完以后,可以有 4 种退出状态:

  1. 退出代码为 0:表示任务容器执行结束并正常退出,对应的消息会被 watcher 容器从队列里删除。
  2. 退出代码为 3:表示该消息被任务容器拒绝,对应的消息会被 watcher 容器从队列里删除,同时发送一个通知到 SNS 主题。
  3. 退出代码为 4:表示该消息不被任务容器处理,对应的消息会被 watcher 容器转移到对应的 error 队列。
  4. 退出代码为其他值:表示任务容器在处理该消息时发生被捕获的异常并主动退出,对应的消息会被 watcher 容器转移到对应的 error 队列,并发送一个通知到 SNS 主题。

如果任务容器在执行过程中出现未被捕获的故障而导致异常退出,则 watcher 容器会再次启动该任务容器,如果任务容器反复执行失败导致异常退出,则 watcher 容器在尝试一定次数以后,会放弃并把该消息放入对应的 error 队列。

架构部署

我们以 us-east-1 region 为例,一步一步的说明如何部署整个并行批处理任务解决方案。注意在部署之前,需要在你的电脑上配置 AWS credentials,并具有管理 Amazon ECR 和 Amazon ECS 的权限。或者也可以在一台 EC2 实例上完成部署工作,那么可以在该 EC2 实例上关联一个角色,并为角色赋予管理 Amazon ECR 和 ECS 的权限。

  1. 创建 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 账号。

  2. 从 github 下载 watchbot 代码:git clone https://github.com/mapbox/ecs-watchbot.git

  3. 进入 ecs-watchbot 子目录,创建 watchbot 容器镜像,注意下面命令中别忘了最后一个“.”。在本地构建 ecs-watchbot 容器镜像以后,上传到第一步中创建的 ECR 仓库里。编辑 Dockerfile,内容如下:

    Bash

复制代码
FROM ubuntu:16.04
# Setup
RUN apt-get update -qq && apt-get install -y curl
# Install node.js
RUN curl -s https://s3.amazonaws.com/mapbox/apps/install-node/v2.0.0/run | NV=4.4.2 NP=linux-x64 OD=/usr/local sh
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get install -y nodejs
# Setup application directory
RUN mkdir -p /usr/local/src/watchbot
WORKDIR /usr/local/src/watchbot
# npm installation
COPY ./package.json ./
RUN npm install --production
# Copy files into the container
COPY ./index.js ./
COPY ./lib ./lib
COPY ./bin ./bin
# Logging onto the host EC2
VOLUME /mnt/log
# Run the watcher
CMD ["/bin/sh", "-c", "npm start"]
复制代码
然后构建容器镜像:
Bash
复制代码
sudo docker build -t ecs-watchbot:v3.5.1.
复制代码
登录 ECR:
Bash
复制代码
eval "sudo $(aws ecr get-login --region us-east-1 --no-include-email )"
复制代码
上传容器镜像到 ECR:
Bash
复制代码
sudo docker tag ecs-watchbot:v3.5.1 123456789012.dkr.ecr.us-east-1.amazonaws.com/ecs-watchbot:v3.5.1
sudo docker push 123456789012.dkr.dkr.ecr.us-east-1.amazonaws.com/ecs-watchbot:v3.5.1
  1. 在部署基于 watchbot 的 ECS 服务的时候,mapbox 在 github 上提供了一个样例 mapbox/ecs-telephone: https://github.com/mapbox/ecs-telephone。我们可以基于该样例进行修改,使其满足实际的需求。在安装该样例之前,先在当前主机上安装 js 以及其他组件:

    Bash

复制代码
sudo yum -y erase npmcurl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -sudo yum -y install nodejs
sudo npm install -g @mapbox/cfn-config
sudo npm install -g @mapbox/cloudfriend
sudo npm install -g @mapbox/watchbot
  1. 该方案会使用 KMS 对 Amazon Cloudformation 的相关资源进行加密,比如环境变量等,因此需要先部署 KMS。从 github 下载 cloudformation-kms 的代码:
    git clone https://github.com/mapbox/cloudformation-kms.git
    进入 cloudformation-kms 目录,执行下面的命令生成 Amazon Cloudformation 模板:

    Bash

复制代码
export NODE_PATH=/usr/lib/node_modules/@mapboxvalidate-template cloudformation-kms.template.jsbuild-template cloudformation-kms.template.js >> cloudformation-kms.template
复制代码
然后使用下面的命令创建 Amazon Cloudformation 堆栈:
Bash
复制代码
aws cloudformation create-stack \
--stack-name kms-production \
--capabilities CAPABILITY_IAM \
--template-body file://cloudformation-kms.template \
--region us-east-1
复制代码
堆栈创建完毕以后,会有一个输出,其 export name 为 `cloudformation-kms-production`,这对应到生成的 KMS key 的 ARN,这个 ARN 会在 `ecs-telephone` 模板中被使用到。
  1. 从 github 下载 ecs-telephone 代码:git clone https://github.com/mapbox/ecs-telephone.git 进入 ecs-telephone 子目录,可以看到一个 Dockerfile 文件,使用该 Dockerfile 构建出的容器镜像里就包含了用户的业务逻辑。用户可以使用任何编程语言编写相应的业务逻辑。比如下面的例子使用 python 写了一段很简单的逻辑,该逻辑把传入当前容器的环境变量:message 的内容输出到 Amazon Cloudwatch log 里,保存该代码名为 domsg.py

    Bash

复制代码
import boto3
import os
message = os.environ["Message"]
print (message)
复制代码
然后编写一个 `Dockerfile`,如下所示:
Bash
复制代码
FROM ubuntu:16.04
WORKDIR /usr/local/src/ecs-telephone
RUN apt-get update -qq
RUN apt-get install -y curl
RUN apt-get install -y python-pip python-dev build-essential
RUN pip install boto3
COPY domsg.py ./
CMD ["python","domsg.py"]
复制代码
运行下面的命令构建用户的任务容器镜像,tag 为 123
Bash
复制代码
sudo docker build -t ecs-telephone:123 .
复制代码
创建 ECR 仓库,假设名为 `ecs-telephone`,对应的 URI 为 `012345678901.dkr.ecr.us-east-1.amazonaws.com/ecs- telephone`。然后把用户的任务容器镜像上传到 ECR 里:
Bash
复制代码
eval "sudo $(aws ecr get-login --region us-east-1 --no-include-email )"
sudo docker tag ecs-telephone:123 012345678901.dkr.ecr.us-east-1.amazonaws.com/ecs-telephone:123
sudo docker push 012345678901.dkr.ecr.us-east-1.amazonaws.com/ecs-telephone:123
  1. 进入 ecs-telephone/cloudformation 子目录,可以看到里面有一个 javascript 文件:ecs-telephone.template.js,可以利用该文件生成 Amazon Cloudformation 模板。打开该文件,其内容如下所示:

    Java

复制代码
const watchbot = require('@mapbox/watchbot');const cf = require('@mapbox/cloudfriend');const Parameters = {
GitSha: { Type: 'String' },
Cluster: { Type: 'String' },
Family: { Type: 'String' }
};
const watcher = watchbot.template({
cluster: cf.ref('Cluster'),
service: 'ecs-telephone',
family: cf.ref('Family'),
serviceVersion: cf.ref('GitSha'),
workers: 1,
reservation: { cpu: 256, memory: 128 },
env: { StackRegion: cf.region },
notificationEmail: 'devnull@mapbox.com'
});
复制代码
这里有三个输入参数:
* `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 模板:
Bash
复制代码
export NODE_PATH=/usr/lib/node_modules
validate-template ecs-telephone.template.js
build-template ecs-telephone.template.js >> ecs-telephone.template
复制代码
打开该模板,找到包含 `Watchbot-worker-ecs-telephone` 字样的部分,可以看到如果使用该模板部署 ECS 服务,则会去找名为`ecs-telephone` 的 Amazon ECR 仓库,用户可以根据实际情况修改该 ECR 仓库名。
如果要在中国区运行该模板的话,需要手工进行修改。具体修改的地方包括:
* 找到 EcrRegion 部分,在其中添加 `"cn-north-1": { "Region": "cn-north-1" }`
* 搜索 amazonaws.com,除了 ecs-tasks.amazonaws.com 以外,其他都要添加 .cn,也就是改为 amazonaws.com.cn
* 找到下面这一段,将其删除。
![.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/aws-ecs-parallel(5).png)
  1. 对该 Cloudformation 模板修改完毕以后,使用下面的命令运行模板从而创建 Amazon ECS 服务:

    Bash

复制代码
aws cloudformation create-stack \--stack-name ecs-telephone \--capabilities CAPABILITY_IAM \
--template-body file://ecs-telephone.template \
--parameters \
ParameterKey=GitSha,ParameterValue=123 \
ParameterKey=Cluster,ParameterValue=arn:aws:ecs:us-east-1:123456789012:cluster/production-cluster-ECSCluster\
ParameterKey=Family,ParameterValue=ecs-telephone \
--region us-east-1
  1. 当 Amazon Cloudformation 堆栈部署完毕以后,进入 ECS 集群,可以看到创建了一个 ECS 服务,点击该服务,进入服务配置以后,有一个始终保持运行状态的 ECS 任务,该任务就是 watcher 容器。还可以通过控制台进入 Amazon SQS 界面,找到三个队列,其后缀名分别为 WatchbotDeadLetterQueueWatchbotTaskEventQueue 以及 WatchbotQueue。触发任务执行的消息需要发送到后缀为 WatchbotQueue 的队列,如果任务执行失败,则消息会放入后缀为 WatchbotDeadLetterQueue 的队列。在向 WatchbotQueue 发送消息的时候,必须遵循下面的格式:

    Bash

复制代码
{
"Subject": "Your subject",
"Message": "Your random message"
}
复制代码
该消息中的 Message 内容:“Your random message” 就会被上面第六步中的 `domsg.py` 输出到 Amazon CloudWatch 的 log group 里。我们可以进入 Amazon CloudWatch 控制台界面上,点击右边的 Logs,然后会看到包含 watchbot 的 log group,点击进去以后找到最新的 log stream 并点击进入,会看到该消息,说明整个流程正常运行起来了:
![.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/aws-ecs-parallel(7).png)

结论

本文详细描述了如何基于 Amazon ECS 构建并行批处理任务平台。在该方案中,用户只需要关心业务逻辑的代码即可,其他启动,停止,重试任务容器以及自动扩展等操作都交由平台本身完成。另外,我们在部署任务容器时,任务处理的输出以及状态等信息应该保存到 Amazon S3 或者其他存储服务里(比如 RDS),确保任务容器在异常终止的时候,数据不会丢失。

作者介绍:
韩思捷
亚马逊 AWS 解决方案架构师,曾负责大企业客户在 AWS 上的售后技术支持工作,目前负责基于 AWS 的云计算方案架构咨询和设计。在加入 AWS 之前,在中国医药集团,Oracle 以及 EMC 研发中心工作,有多年开发和运维经验,并对各种数据库以及存储应用的高可用架构,方案及性能调优有深入研究。

本文转载自 AWS 技术博客。

原文链接:
https://amazonaws-china.com/cn/blogs/china/amazon-ecs-parallel-solution/

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布