本文翻译自Azkaban官网文档 Azkaban Containerized Executions - Design Doc
作者/关键贡献者: Arvind Pruthi , Janki Akhani , Shardool , Deepak Jaiswal , Aditya Sharma , Abhishek Nath
老架构(裸金属架构)背景/概览
- 每一套Azkaban集群都包含如下组件:一个
web server
,一组部署在物理机上的executor server
,一个mysql(用于保存job
状态和历史记录) - 每一个
executor server
大约可以并行执行10个Flow
(任务流),具体取决于每个Flow
的消耗的资源 executor server
定期(一般1秒)从mysql拉取处于待执行状态的Flow
,mysql相当于是一个队列
Azkaban Executor Server的职责
分发任务
Executor Server
定期从mysql拉取flows- 构建flow:解析所有的配置、在内存中构建一个图、下载依赖文件、分配计算资源(线程池、执行目录等)
- 触发调度
调度
- 为了保证任务并行执行,每一个
executor server
为每一个flow
都维护了一个独立的线程池 - 从hadoop name node获取hadoop tokens以便提交YARN应用
- 每一个
job
都是用admin账号独立加载 - 在内存中维护
flow
的状态机,并及时更新到mysql中,最后把flow
产生的日志都写到mysql中
管理Flow
executor server
对外暴露AJAX API,提供暂停、强制停止、恢复等操作,超过10天的flow
会被强制停止。
管理日志
AJAX API支持流式日志,可以实时观察执行中的flow
/job
,当flow
/job
结束后,所有的日志都会以15MB每块的大小保存到数据库中。
部署
- 部署
executor server
,先把新版代码准备好,启动前会先完成一些必要的测试,以便验证环境符合预期 - 测试通过后,把
executor server
置为不可用模式,确保不会从数据库拉取flow
(particular executor
除外) - 启动新版代码,状态变为可用,恢复任务的执行
裸金属架构的问题
互相影响(无资源隔离)
Azkaban支持多种Job类型,也允许用户自己上传代码,所以很容易发生部分任务占用过多的资源(CPU、内存、磁盘),进而拖垮整个executor server
,影响此server上的所有任务
弹性伸缩/维护问题(僵化的架构)
网站可靠性对弹性伸缩能力具有强烈的诉求,数十年前的架构就已经存储了。但是裸金属架构并不能从最近的新技术上获益,这在云计算领域已经司空见惯了。
缺少金丝雀发布系统
Azkaban的executor server
是所有计算的门户,除了它自身的功能代码、配置、任务类型之外,还屏蔽了hadoop、安全管理器、spark等基础计算设施。当前缺少金丝雀机制以支持细粒度的新功能验证。 根据Azkaban在Linkedin的使用的经验看,在缺少合适的金丝雀系统的时候,每次滚动发布代码是多么的痛苦。
无视YARN内部的任务队列
Azkaban有自己的任务队列和分发机制,这样可以最大限度的发挥executor server
的能力,但是它自己的队列与YARN的队列并不匹配,这经常导致YARN集群过载。
发布的问题
发布过程中的环境验证环节可能要持续10天,在这期间executor server
都是不可用的,并且CPU和内存是闲置的。一次发布如果有问题,需要executor server
带病运行的情况下,不断的尝试修复,可能导致GC暂停或者OOM,并且会污染其他的指标。
容器化的关键需求
- Azkaban的
web server
动态的为每一个flow
创建一个容器,也就是为每一个flow
提供一套完全独立的运行环境。 - 快速响应激增的资源需求(可伸缩的架构)
- 提供一种机制以支持Azkaban各个组件各自独立进化
- 把发版的控制权交给对应的用户:运行平台、Azkaban本身、用户Job
- 让用户自己选择Azkaban或者
JobType
的版本(在更新基础设施的时候尤其有用)
- 提供一套垂直的金丝雀系统以支持
Azkaban/jobtypes
和运行平台完全控制各自的代码更新
未来的扩展
- 为多个组件开发细粒度的金丝雀系统以支持各自独立发版
架构概览
- Azkaban采用
Disposable Container
(一次性容器)的模式,这意味着每当flow
被调度之前都会创建一个新的POD,并且在flow
结束之后销毁。 - 资源隔离体现在
flow
级别(而非job
),jobs
或subflows
都是flow
的一部分;Job
级别的资源也探索过,但是最终放弃了:
- 它会极大的破快原来的架构,为了实现
job
级别的资源隔离,大部分的代码都需要重写; - 为每一个
job
创建一个pod会造成过多的资源消耗,还有一种做法是在一个POD里面包含多个容器,但是这也会导致大部分flow
和job
相关的代码被重写;也许未来会重新考虑这个设计;
- 创建POD的时候使用默认的cpu、内存资源,也可以通过参数指定需要申请的资源
- 在这样的设计下,
web server
必须部署在k8s之外,这样做并不妨碍它与flow
和log
之间的通信,它们之间的通信通过Ingress Controller
实现,这样就不需要导出Flow
的POD了; - 为了实现上述第三点需求,
flow
的pod的执行环境必须动态创建:
- 在任务分发的环节,会提供动态选择组件版本的功能,以便提供
executor server
的运行环境 - 一系列的容器初始化工作将通过众多组件的不同版本的组合来完成
- 动态选择的功能可以用来实现组件的金丝雀发布
- 需要一些Admin API来完成镜像的管理工作
详细设计
镜像管理
- 使用docker镜像来创建
flow
的执行环境,为了实现上述第三个需求,要使用预制的container模板来创建POD,下文的分发逻辑
一节会详述; - Azkaban的运行环境由以下依赖组成:
依赖类型 | 描述 |
---|---|
平台依赖 | Hadoop、Hive、Spark、Pig、Dali、Ksudo等 |
Azkaban核心 | Azkaban代码包、配置、安全项,由Azkaban管理 |
JobTypes | JobType开发人员开发的代码/配置,由Azkaban管理,如KafkaPushJob , SparkJob 等 |
- Azkaban核心在基础镜像(RHEL7)上加一层,形成
Azkaban基础镜像
- 其他的平台依赖、JobTypes各自在
Azkaban基础镜像
之上新增独立的层。为了保持镜像体积小,节约下载时间,可以使用busybox
或者alpine
技术来实现 - 有的开发者的
job-types
镜像需要特别定制,不依赖Azkaban。比如Kafka Push Job
FROM container-image-registry.mycorp.com/rhel7-base-image/rhel7-base-image:0.16.9
ARG KPJ_URL=https://artifactory.mycorp.com/kafka-push-job/kafka-push-job/0.2.61/kafka-push-job-0.2.61.jar
RUN curl $KPJ_URL --output ~/kafka-push-job-0.2.61.jar
- 每个
job-type
都会基于一个公共的预制镜像,这个预制镜像会把所有的代码包和配置文件都放到容器的外部卷上,此外部卷也会被挂载到应用容器(基于Azkaban基础镜像
) job-type
开发者使用镜像管理API
构造job-type
镜像,构造出来的镜像可以作为默认的job-type
镜像,flow
的开发者可以使用DSL指定job-type
镜像的版本flow
执行的过程中使用version-set
和version-number
来唯一标识它的依赖的组件的状态;这也有助于在测试环境复现失败的flow
,便于排查问题
镜像管理API
API使用流程图
数据库ER图
分发逻辑
状态流程图
kubernets的安全性
初始化容器
运行Flow的容器
Ingress控制器
日志
状态页面
使用裸金属架构解决上述问题如何?
在kubernets上debug Azkaban
待解决事项
- 搁置了通过参数指定
flow
使用的镜像的版本 - 搁置了通过参数设置版本
- 搁置了通过参数指定
flow
容器需要的CPU和内存 - debug问题
- 更多关于配置的技巧