基于Docker部署Azkaban工作流调度系统:从核心架构到生产实践
1. 项目概述:为什么我们需要Azkaban?
在数据开发、运维自动化乃至日常的脚本任务管理中,我们经常会遇到这样的场景:一个复杂的业务处理流程,需要按顺序执行多个脚本或任务。比如,每天凌晨1点,先要运行一个Python脚本从数据库A拉取昨天的日志数据,清洗转换后,再调用一个Java程序进行聚合计算,最后将结果通过一个Shell脚本推送到报表系统,并发送一封邮件通知。如果中间某个步骤失败,我们可能希望整个流程暂停,并收到告警;如果成功,则希望自动触发下一个任务。
手动去管理这一系列任务,不仅效率低下,而且极易出错。今天要聊的Azkaban,就是为了解决这类问题而生的一个开源工作流调度系统。它由LinkedIn开发并开源,核心目标就是让复杂的任务依赖关系变得可视化、可调度、可监控。简单来说,你可以把它想象成一个“任务管家”,你只需要告诉它任务之间谁先谁后、什么时候执行,它就能帮你自动、可靠地按计划跑起来。
最近,随着容器化技术的普及,“docker+安装azkaban”也成了一个热门组合。确实,用Docker来部署Azkaban,能极大地简化环境配置的复杂度,避免“在我的机器上能跑”的尴尬。这篇文章,我就结合自己多次在生产环境部署Azkaban的经验,从零开始,带你彻底搞懂Azkaban的核心概念,并手把手完成一次基于Docker的“干净”安装。无论你是数据工程师、运维工程师,还是对自动化任务调度感兴趣的开发者,这篇内容都能给你提供一份可直接复现的实操指南。
2. Azkaban核心架构与设计思路拆解
在动手安装之前,我们必须先理解Azkaban是怎么工作的。知其然,更要知其所以然,这样在后续配置和排错时,你才能心中有数。
2.1 核心组件:三驾马车驱动工作流
Azkaban的架构非常清晰,主要由三个核心组件构成,理解它们各自的分工是掌握Azkaban的关键。
Azkaban Web Server:这是整个系统的“大脑”和“门面”。它提供了一个基于Web的用户界面,我们所有的工作流定义、项目管理、任务调度、权限设置、历史日志查看等操作,都是通过它与系统交互。同时,它也是调度逻辑的核心,负责解析我们提交的工作流文件(比如.job文件),计算任务之间的依赖关系,并决定何时触发哪个任务。Web Server本身不执行具体任务,它只负责“指挥”。
Azkaban Executor Server:这是系统的“四肢”,负责实际干活。当Web Server决定要运行一个任务时,它会将这个任务分派给一个可用的Executor Server。Executor Server接收到任务后,会启动一个独立的进程(例如一个JVM进程来运行Java作业,或一个Shell进程来运行脚本),并监控其执行状态(成功、失败、运行中),最后将状态汇报回Web Server。一个Azkaban集群可以配置多个Executor Server,以实现负载均衡和高可用。
关系型数据库:这是系统的“记忆中枢”。Azkaban重度依赖数据库来存储一切状态信息,包括:项目元数据、工作流定义、所有历史执行记录、日志、用户权限、调度计划等。Web Server和Executor Server都是无状态的,它们的状态都持久化在数据库中。这意味着,只要数据库完好,即使Web Server或Executor Server重启,整个系统的状态也不会丢失。Azkaban官方支持MySQL,这也是生产环境最常用的选择。
这三个组件协同工作的流程可以概括为:用户在Web UI上定义并调度一个工作流 -> Web Server将工作流信息和触发事件存入数据库 -> 触发时间到达,Web Server从数据库加载工作流,解析依赖,生成待执行任务队列 -> Web Server从数据库的executors表中选取一个活跃的Executor,将任务分配给它 -> Executor执行任务,并将状态实时写回数据库 -> Web Server从数据库读取状态,更新UI展示给用户。
2.2 设计哲学:简单、可依赖
Azkaban的设计有几个鲜明的特点,这些特点也决定了它适合的场景。
1. 基于文件的工作流定义:这是Azkaban的一大特色。你不需要在复杂的UI上拖拽连线(虽然它也提供了简单的可视化),而是通过编写纯文本的.job文件来定义任务和依赖。每个.job文件定义一个任务,通过dependencies参数指明它依赖哪些其他任务。最后,将这些.job文件打包成一个ZIP包上传。这种方式虽然看起来“原始”,但带来了巨大的好处:版本控制友好。你可以用Git来管理你的工作流定义,清晰地看到每次变更;易于批量创建和修改;便于CI/CD集成。
2. 调度与执行分离:Web Server和Executor Server的分离,使得系统扩展性非常好。当任务量增长时,你可以单独横向扩展Executor Server的数量,而无需改动调度逻辑。同时,这种分离也提高了系统的稳定性,一个Executor的崩溃不会直接影响调度中心。
3. “Solo”与“Cluster”模式:为了适应不同规模的需求,Azkaban提供了两种部署模式。Solo模式将Web Server和Executor Server打包在一个进程中,使用内嵌的H2数据库,开箱即用,适合个人学习或测试。Cluster模式则是生产级部署,三个组件独立部署,通常使用MySQL数据库,支持多Executor。我们今天的Docker安装,本质上是在容器环境下实现一个简化但结构清晰的Cluster模式。
注意:虽然Solo模式简单,但H2数据库在生产环境下有诸多限制(如并发性能、稳定性),严禁将其用于任何正式环境。即使是测试,也建议从一开始就使用MySQL,以保持环境一致性。
3. 基于Docker的安装环境准备
理解了架构,我们就可以开始动手了。用Docker安装,能让我们快速获得一个干净、隔离、可复现的Azkaban环境。这里我选择使用社区维护度较高的镜像,并结合docker-compose进行编排,这样服务之间的依赖和网络配置一目了然。
3.1 工具与镜像选型解析
首先,你需要确保你的机器上已经安装了Docker和docker-compose。这是前提,安装过程不再赘述。
对于镜像的选择,经过对比,我推荐使用azkaban/azkaban-web-server和azkaban/azkaban-executor-server这两个官方相关的镜像(由社区在官方代码基础上构建)。为什么不自己从头构建?因为这两个镜像已经帮我们做好了基础环境的配置,比如Java版本、目录结构等,省去了大量繁琐的步骤。数据库方面,我们直接使用官方的mysql:8.0镜像。
版本选择考量:我选择Azkaban 3.x系列的最新稳定版(例如3.90.0)。3.x系列相较于更老的2.x系列,在UI、API、多Executor支持等方面有显著改进,是目前的主流。MySQL选择8.0而非5.7,主要是为了获得更好的性能和安全性,同时确保与最新软件的兼容性。
3.2 编写docker-compose.yml编排文件
接下来是核心步骤:编写docker-compose.yml文件。这个文件定义了三个服务(MySQL, Web Server, Executor Server)以及它们之间的关系。
version: '3.8' services: # 1. MySQL 数据库服务 azkaban-db: image: mysql:8.0 container_name: azkaban-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: AzkabanRootPass123! # 请务必修改为强密码 MYSQL_DATABASE: azkaban MYSQL_USER: azkaban MYSQL_PASSWORD: AzkabanUserPass123! # 请务必修改为强密码 volumes: - ./mysql_data:/var/lib/mysql # 持久化数据库数据,避免容器删除后数据丢失 - ./init-sql:/docker-entrypoint-initdb.d:ro # 初始化SQL脚本目录 ports: - "3306:3306" # 将主机3306端口映射到容器,方便本地工具连接查看 networks: - azkaban-net healthcheck: # 健康检查,确保数据库就绪后再启动其他服务 test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uazkaban", "-pAzkabanUserPass123!"] interval: 10s timeout: 5s retries: 10 # 2. Azkaban Executor Server azkaban-executor: image: azkaban/azkaban-executor-server:3.90.0 container_name: azkaban-executor restart: unless-stopped depends_on: azkaban-db: condition: service_healthy # 依赖数据库健康状态 environment: AZKABAN_DB_HOST: azkaban-db # 使用Docker服务名进行网络通信 AZKABAN_DB_PORT: 3306 AZKABAN_DB_NAME: azkaban AZKABAN_DB_USER: azkaban AZKABAN_DB_PASSWORD: AzkabanUserPass123! AZKABAN_EXECUTOR_PORT: 12321 # Executor服务端口 volumes: - ./executor-conf:/azkaban-exec-server/conf # 挂载配置文件目录,便于自定义 - ./executor-logs:/azkaban-exec-server/logs # 挂载日志目录,便于查看 - ./jobs:/azkaban-exec-server/jobs:ro # 挂载一个公共任务目录(可选) ports: - "12321:12321" networks: - azkaban-net # 3. Azkaban Web Server azkaban-webserver: image: azkaban/azkaban-web-server:3.90.0 container_name: azkaban-webserver restart: unless-stop depends_on: - azkaban-db - azkaban-executor environment: AZKABAN_DB_HOST: azkaban-db AZKABAN_DB_PORT: 3306 AZKABAN_DB_NAME: azkaban AZKABAN_DB_USER: azkaban AZKABAN_DB_PASSWORD: AzkabanUserPass123! AZKABAN_WEBSERVER_PORT: 8081 # Web Server服务端口 AZKABAN_EXECUTOR_HOST: azkaban-executor # 告知Web Server Executor的地址 AZKABAN_EXECUTOR_PORT: 12321 volumes: - ./web-conf:/azkaban-web-server/conf - ./web-logs:/azkaban-web-server/logs ports: - "8081:8081" # 将Web UI映射到主机8081端口 networks: - azkaban-net networks: azkaban-net: driver: bridge关键配置解读与避坑点:
- 网络(networks):我们创建了一个名为
azkaban-net的桥接网络。三个服务都加入这个网络,这样它们之间可以通过容器名(如azkaban-db)直接通信,无需知道IP地址,这是Docker容器间通信的最佳实践。 - 数据持久化(volumes):我们将MySQL的数据目录、Azkaban的配置和日志目录都挂载到了主机当前目录下的子文件夹中。这样做有两个巨大好处:一是数据不会随容器销毁而丢失;二是方便我们直接在主机上查看日志、修改配置,无需进入容器。
- 依赖与启动顺序(depends_on + healthcheck):我们定义了启动顺序:数据库先启动并健康(通过
mysqladmin ping检测),然后启动Executor,最后启动Web Server。这确保了服务启动时,它所依赖的服务已经就绪,避免连接失败。 - 密码安全:示例中的密码是弱密码,在实际生产或任何暴露在公网的环境下,必须使用复杂的强密码,并通过Docker Secrets或环境变量文件(
.env)来管理,不要硬编码在yml文件中。 - 端口映射:我们将MySQL的3306和Web Server的8081映射到了主机,方便我们用本地客户端(如Navicat)连接数据库,用浏览器访问Web UI。Executor的端口12321通常不需要对外暴露,仅供内部通信。
4. 数据库初始化与核心配置详解
编排文件写好了,但Azkaban需要的数据库表还没有创建。我们需要提供初始化SQL脚本。
4.1 准备数据库初始化脚本
在项目根目录下创建init-sql文件夹,并在其中创建create-all-sql-3.90.0.sql文件(可以从Azkaban官方GitHub仓库的sql目录下找到对应版本的脚本)。这里我列出最核心的几个表创建语句,并解释其作用。
-- 在`azkaban`数据库中执行 CREATE TABLE `executors` ( `id` int(11) NOT NULL AUTO_INCREMENT, `host` varchar(64) NOT NULL, `port` int(11) NOT NULL, `active` tinyint(1) DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `host_port` (`host`,`port`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 这个表用于注册和管理所有活跃的Executor Server。Web Server通过读取这个表来分配任务。 CREATE TABLE `projects` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `description` mediumtext, `create_time` bigint(20) NOT NULL, `last_modified_time` bigint(20) NOT NULL, `version` int(11) DEFAULT NULL, `enc_type` tinyint(4) DEFAULT NULL, `settings_blob` longblob, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 存储所有上传的项目元信息。 CREATE TABLE `project_versions` ( `project_id` int(11) NOT NULL, `version` int(11) NOT NULL, `upload_time` bigint(20) NOT NULL, `uploader` varchar(64) NOT NULL, `file_type` varchar(16) DEFAULT NULL, `file_name` varchar(128) DEFAULT NULL, `md5` varchar(128) DEFAULT NULL, `num_chunks` int(11) DEFAULT NULL, `resource_id` varchar(512) DEFAULT NULL, PRIMARY KEY (`project_id`,`version`), CONSTRAINT `project_versions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 存储项目每个版本的上传信息,Azkaban支持项目多版本管理。 -- 还有 `project_flows`, `execution_flows`, `execution_jobs`, `project_permissions` 等数十张表,用于存储工作流定义、执行历史、权限等。将完整的SQL文件放入init-sql目录后,Docker会在MySQL容器首次启动时自动执行这些脚本,完成数据库的初始化。
4.2 自定义Azkaban配置文件
虽然镜像提供了默认配置,但为了更贴合我们的需求(比如时区、邮件报警),我们需要自定义配置。我们在docker-compose.yml中已经将容器的conf目录挂载到了主机的web-conf和executor-conf。
Web Server配置 (./web-conf/azkaban.properties):
# Azkaban Personalization Settings azkaban.name=MyAzkabanCluster azkaban.label=My Local Cluster azkaban.color=#FF6600 # Azkaban Server Settings azkaban.webserver.port=8081 azkaban.webserver.ssl.port=8443 web.resource.dir=web/ default.timezone.id=Asia/Shanghai # 设置时区为上海 # Azkaban Executor Settings azkaban.executor.port=12321 executor.port=12321 # Database Settings (与docker-compose环境变量对应) database.type=mysql mysql.port=3306 mysql.host=azkaban-db mysql.database=azkaban mysql.user=azkaban mysql.password=AzkabanUserPass123! mysql.numconnections=100 # Mail Settings (如果需要邮件通知,需配置) mail.sender=your-email@gmail.com mail.host=smtp.gmail.com mail.user=your-email@gmail.com mail.password=your-app-password # 注意使用应用专用密码 mail.tls=true job.failure.email=receiver@example.com job.success.email=receiver@example.com # User Manager (使用默认的基于数据库的用户管理) user.manager.class=azkaban.user.XmlUserManager user.manager.xml.file=conf/azkaban-users.xmlExecutor Server配置 (./executor-conf/azkaban.properties):
# Executor Server azkaban.executor.port=12321 executor.port=12321 # Database Settings (必须与Web Server配置一致) database.type=mysql mysql.port=3306 mysql.host=azkaban-db mysql.database=azkaban mysql.user=azkaban mysql.password=AzkabanUserPass123! mysql.numconnections=100 # Executor specific executor.max_threads=50 # 最大并发线程数 executor.flow.threads=30 # 执行工作流的线程数实操心得:配置文件中,数据库连接信息、Executor端口等必须与
docker-compose.yml中的环境变量以及容器间的网络可达性保持一致,这是最常见的启动失败原因。另外,邮件配置中的密码,对于Gmail等现代邮箱,需要使用“应用专用密码”,而不是你的登录密码。
5. 启动服务与验证安装
配置完成后,我们就可以启动整个Azkaban集群了。
5.1 一键启动与日志观察
在包含docker-compose.yml的目录下,执行命令:
docker-compose up -d-d参数表示在后台运行。Docker会依次拉取镜像(如果本地没有)、创建网络、启动容器。
启动后,立即查看日志,确认服务是否正常启动:
# 查看所有容器日志的尾部 docker-compose logs -f # 或者分别查看 docker-compose logs -f azkaban-webserver docker-compose logs -f azkaban-executor docker-compose logs -f azkaban-db你需要关注日志中是否有ERROR字样。正常的启动日志会显示数据库连接成功、服务在指定端口监听等信息。
5.2 核心验证步骤
服务启动后,需要通过几步来验证安装是否真正成功。
第一步:检查容器状态
docker-compose ps所有服务的状态(State)都应该是Up。
第二步:验证数据库表用MySQL客户端(如DBeaver、命令行)连接主机的3306端口(用户azkaban,密码是你设置的),查看azkaban数据库。应该能看到一系列以executors,projects,execution_等开头的表被创建出来。这证明初始化SQL脚本已成功执行。
第三步:验证Web UI打开浏览器,访问http://你的服务器IP:8081。你应该能看到Azkaban的登录界面。
- 默认用户名密码:在首次使用XmlUserManager且未配置
azkaban-users.xml时,默认用户是azkaban,密码是azkaban。 - 如果能看到登录页,说明Web Server运行正常。
第四步:验证Executor注册登录Web UI后(默认密码可能需要先修改),点击顶部导航栏的Executor菜单。你应该能看到一个状态为Active的Executor,它的地址就是我们在docker-compose.yml中配置的azkaban-executor:12321。这证明Web Server和Executor Server之间的通信是正常的,并且Executor已经成功在数据库注册。
第五步:创建并运行一个测试工作流这是最终的验收测试。在Web UI上创建一个新项目(例如叫TestProject)。然后,在本地准备两个简单的.job文件:
first.job:# first.job type=command command=echo "This is the first job"second.job:# second.job type=command dependencies=first command=echo "This is the second job, running after first"
将这两个文件打包成test-flow.zip,在项目页面上传。上传后,你可以在Flows页面看到名为test-flow的工作流,它包含两个有依赖关系的任务。点击Execute Flow立即运行它。如果一切正常,你会看到两个任务依次变成绿色(成功),并可以查看每个任务的控制台输出日志。
6. 常见问题与排查技巧实录
即使按照步骤操作,你也可能会遇到一些问题。这里我总结几个最常见的坑和解决方法。
6.1 数据库连接失败
问题现象:Web Server或Executor启动日志中大量报错,提示Communications link failure,Access denied for user等。
排查思路:
- 检查数据库容器是否健康:
docker-compose logs azkaban-db查看MySQL启动日志,确认无错误且初始化完成。 - 检查连接参数:这是最常见的原因。请逐一核对:
docker-compose.yml中environment部分的MYSQL_*环境变量。- 挂载的
azkaban.properties文件中的mysql.host,mysql.port,mysql.user,mysql.password。 - 特别注意:在配置文件中,
mysql.host必须填写Docker服务名(azkaban-db),而不是localhost或127.0.0.1,因为是从Azkaban容器内部去连接MySQL容器。
- 手动测试连接:进入Azkaban Web Server容器内部,用命令行工具测试。
如果能成功连接,说明网络和认证是通的。docker exec -it azkaban-webserver bash apt-get update && apt-get install -y mysql-client # 如果容器内没有mysql客户端 mysql -hazkaban-db -uazkaban -pAzkabanUserPass123! azkaban
6.2 Executor未出现在Web UI中
问题现象:Web UI可以访问,但Executor页面为空,或者Executor状态为Inactive。
排查思路:
- 检查Executor日志:
docker-compose logs azkaban-executor,看是否有数据库连接错误或启动错误。 - 检查数据库
executors表:直接查询数据库,看是否有记录插入。
如果表中有记录但状态不是USE azkaban; SELECT * FROM executors;active=1,可能是Executor与Web Server心跳通信有问题。 - 检查网络互通:在Web Server容器内,尝试ping或telnet到Executor的端口。
如果不通,检查docker exec -it azkaban-webserver bash telnet azkaban-executor 12321 # 或者用 ncdocker-compose.yml中的网络配置,确保两个服务在同一个自定义网络下,并且AZKABAN_EXECUTOR_HOST环境变量设置正确。 - 检查端口冲突:确认主机和容器内部都没有其他进程占用
12321端口。
6.3 任务执行失败,日志显示Cannot run program
问题现象:工作流任务执行失败,点击日志看到类似Cannot run program “python”: error=2, No such file or directory的错误。
原因与解决:Azkaban Executor执行命令时,是在它自己的容器环境里。默认的azkaban/azkaban-executor-server镜像是一个精简的Java环境,可能没有安装你任务所需的运行时,如python、bash(实际上bash是有的)、hadoop命令等。
解决方案:
- 自定义Executor镜像(推荐):编写Dockerfile,基于官方镜像,安装你需要的所有依赖。
然后修改FROM azkaban/azkaban-executor-server:3.90.0 USER root RUN apt-get update && apt-get install -y python3 python3-pip curl # 安装所需工具 USER azkaban # 切换回非root用户docker-compose.yml中azkaban-executor的image为你构建的新镜像。 - 使用容器外执行:配置Azkaban使用远程的SSH或其它执行器,但这增加了复杂度。对于Docker部署,自定义镜像是最干净、可复现的方式。
6.4 时区问题
问题现象:Web UI上显示的任务调度时间、执行时间与本地时间不符。
解决:确保三个地方的时间一致:
- 宿主机时区。
- Docker容器时区:在
docker-compose.yml中每个服务的配置里可以添加:
或者通过挂载environment: - TZ=Asia/Shanghai/etc/localtime文件。 - Azkaban配置:在Web Server的
azkaban.properties中设置default.timezone.id=Asia/Shanghai。
6.5 Web UI访问缓慢或无法加载
问题现象:页面打开很慢,或者静态资源(CSS, JS)加载失败。
排查:
- 检查主机端口
8081是否被防火墙阻止。 - 检查Web Server容器的日志,看是否有资源加载错误。
- 如果是内网环境,可能是镜像拉取慢。可以尝试提前拉取镜像:
docker-compose pull。
7. 生产环境部署的进阶考量
我们上面搭建的是一个用于学习和开发测试的“标准”Docker环境。如果要用于生产,还需要考虑更多因素。
1. 高可用(HA)部署:
- Web Server高可用:可以部署多个Web Server实例,前面通过Nginx等负载均衡器做代理。多个Web Server连接同一个数据库,它们之间通过数据库来同步状态。需要确保上传的项目文件存储在共享文件系统(如NFS)或对象存储(如S3)中,以便所有Web Server都能访问。
- Executor高可用:这是Azkaban的天然优势。你只需要启动多个Executor容器,它们会自动注册到数据库。Web Server会从池中选取可用的Executor来执行任务。在
docker-compose.yml中,你可以使用docker-compose up -d --scale azkaban-executor=3来快速启动3个Executor实例。
2. 数据持久化与备份:
- MySQL数据:我们通过
volumes挂载了./mysql_data,务必确保这个目录所在的主机磁盘有足够的容量和备份策略。 - 项目文件与日志:Azkaban上传的ZIP包默认存储在数据库的
project_versions表中(resource_id指向文件系统路径)。在生产环境中,强烈建议配置外部存储。修改azkaban.properties中的azkaban.storage.type、azkaban.storage.hdfs.path或s3.bucket等参数,将项目文件存储在HDFS或S3上,实现持久化和共享。
3. 安全加固:
- 修改默认密码:第一时间修改默认的
azkaban/azkaban用户密码,并创建具有不同权限的用户。 - 启用HTTPS:为Web Server配置SSL证书,避免密码在网络上明文传输。
- 网络隔离:将Azkaban集群部署在内网,通过跳板机访问。Executor可能需要访问数据仓库(如Hadoop集群),需要规划好网络策略。
- 使用专业的用户管理:考虑集成LDAP或Active Directory,替代默认的XML文件管理。
4. 监控与告警:
- 服务健康监控:监控Web Server、Executor、MySQL容器的运行状态(CPU、内存、端口存活)。
- 业务监控:利用Azkaban的API,监控关键工作流的执行状态、耗时。可以编写脚本,当工作流失败或超时时,通过更强大的告警系统(如Prometheus Alertmanager)发送通知。
- 日志收集:将Azkaban的日志(
./web-logs,./executor-logs)接入ELK或类似日志平台,便于集中查询和分析。
最后,我个人在多次部署中最大的体会是:一定要先在小规模环境(比如一台开发机)上,用Docker把整个流程完全跑通,形成一套稳定的配置模板和启动脚本。然后再将这套模板复制到生产服务器上,根据生产环境的需求(网络、存储、权限)进行微调。这样能最大程度减少环境差异带来的“玄学”问题。Azkaban本身是一个稳定且强大的工具,只要理解了它的运作原理,并且保证各个组件之间的连接和配置正确无误,它就能成为你数据流水线上最可靠的调度指挥官。