"山寨"一个 AWS Batch

2017-05-27  本文已影响216人  haitaoyao

去年参加了 AWS re:Invent 2016, 最喜欢也最需要的新产品莫过于 AWS Batch. 回来几个月了发现中国区依旧没有上线, 然后就手痒痒"山寨"了一个, 跑着还很稳定. 记录一下"山寨"过程中的思考.

0x00 AWS Batch 拆解

什么是 AWS Batch? 官方介绍:

AWS Batch 让开发人员、科学家和工程师能够轻松高效地在 AWS 上运行成千上万项批处理计算任务。AWS Batch 可根据提交的批处理任务的数量和特定资源要求,动态预置计算资源 (CPU 或 内存优化型实例) 的最佳数量和类型。借助 AWS Batch,您无需安装和管理运行您的任务所使用的批处理计算软件或服务器群集,从而使您能够专注于分析结果和解决问题。AWS Batch 可以跨多种 AWS 计算服务和功能 (如 Amazon EC2 和竞价型实例) 计划、安排和执行您的批处理计算工作负载。

AWS Batch

简单来说就是一个 Producer-Consumer 的异步任务执行系统. 拆解下来有如下几个部分组成:

拆解完成后, 来看看我们的场景

0x01 业务场景

有一个业务, 每天凌晨需要执行多个批处理任务(任务耗时从几分钟到几个小时不等), 白天基本上没有任务执行. 遇到的问题就是: 想让任务尽快执行完成, 又想节省成本.

之前的办法是, 把任务挤在一个大 instance 上, 调整好并发(最多并发四个执行), 白天空闲.

那么, 如何改造呢?

0x02 可选方案

第一个想法: 上 Yarn 等资源调度

公司现有的资源调度框架, Yarn 是一个选择, 但想想要写 Yarn application 我瞬间就放弃了, 不值当.

第二个想法: autoscaling + ec2

当前在 AWS 中国区, 最简单的方式就是直接使用 ec2 作为独立执行单元调度了(没有 ECS), 既然是耗时耗资源(不是简单的函数执行, 需要几个 GB 内存的 batch 任务), 直接使用 ec2 对应的 instance type, 也不算浪费. 那么参照 AWS Batch 的拆解, 对应的模块如何实现?

系统就变成了这个样子:


系统结构

0x03 如何"无损" scale in?

由于任务是定时开始的(凌晨), 扩容使用 scheduled action; 但如何在没有任务执行的时候关闭 ec2 节点而不影响正在执行的任务?

一种方式是根据 SQS 队列中的任务数量, 可以参见 AWS 官方文档: Scaling Based on Amazon SQS. 但是我的任务是比较大的批处理任务, 队列中没有任务, 很有可能任务正在执行中, 如果直接关闭 ec2 instance 进行 scale in 的话, 会导致我的任务执行到一半就失败.

那么有没有其他办法呢? 翻了翻 AWS autoscaling 文档, 发现 AWS 的新功能: Instance Protection for Auto Scaling, 试了一下, 发现 autoscaling 会一直重试 scale-in 的操作, 直到你的 ec2 instance 把 scale-in 保护关闭. 正合我意!

0x04 最终实现

事情想清楚了, 行动就显得简单了.

首先, 给 worker 节点的 IAM 要加上对应的 autoscaling 权限

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:SetInstanceProtection"
            ],
            "Resource": "*"
        }
    ]
}

其次, worker 代码中的关键在于:

  1. 每次从 master 获取任务前, 必须先打开 scale-in 保护
  2. 强烈建议累计 N 次获取不到任务时, 才关闭 scale-in 保护, 避免任务队列中依然有任务, 但 worker 节点在上一个任务执行完毕到获取到下一个任务的间隙, 被 autoscaling 关闭
# 记录连续没有获取到 task 的次数
task_poll_miss_count = 0
while True:
    # 打开 scale-in 保护
    protect_from_scalein()
    try:
        # 从 master 节点获取任务
        task = get_task_from_master()
        if task is not None:
            # 如果任务不为空, 则执行任务
            task_poll_miss_count = 0
            execute_task(task)
        else:
            task_poll_miss_count += 1
    finally:    
            if task_poll_miss_count >= 10:
                # 连续 10 次没有获取到任务, 应该是系统空闲, 直接关闭 scale-in 保护, 留足够时间给 autoscaling 关闭该节点
                LOG.info("task poll miss count > 10, unlock scale-in protection")
                no_protect_from_scalein()
                task_poll_miss_count = 0
                time.sleep(random.randint(50, 100))

由于任务执行的周期性, 直接使用 autoscaling 的 schedued action 提前在任务执行前启动 worker, 预估一个执行时间后直接将整个 autoscaling group 的节点数量设置成 0.

scale in 效果

0x05 总结

使用 autoscaling + scale-in 保护, 轻松实现山寨了一个 "AWS Batch". 虽然简陋, 但很使用, 优点就是大大提高了批处理任务并行度的同时, 还降低了成本. 但缺点也很明显:

总之, 作为一个"山寨的 AWS Batch", 够用就好. 如果 AWS Batch 中国区再不发布, 我就要考虑一下, 把这个山寨货更完善一下, 毕竟批处理任务执行系统是刚需.

-- EOF --

上一篇 下一篇

猜你喜欢

热点阅读