ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

用Docker搭建Azkaban+Spark调度学习环境的完整指南

用Docker搭建Azkaban+Spark调度学习环境的完整指南 这篇技术文我琢磨了有一阵子起因其实挺简单公司在搞数据平台相关的技术分享平时大家搭建学习环境最头疼的就是那堆依赖——JDK版本、Hadoop配置、Spark参数、调度器环境变量本地机器装一次要折腾小半天各种踩坑。后来我把整套环境做成了 Docker 镜像用一条 docker run 就能把 Azkaban Solo 模式 Spark 调度的学习环境跑起来省心很多。今天把具体过程完整复盘一遍给同样需要快速搭一套调度 Spark 任务学习环境的同学做个参考。1. 整体环境设计与思路拆解1.1 为什么选 Docker 承载这套学习环境先说结论Docker 不是用来装样子的它是这套学习环境能快速交付的核心。Azkaban 的 Solo 模式虽然号称单机就能跑但实际配过的人都知道它依赖 JDK、MySQL或 H2、Hadoop 客户端、Spark 发行包四者之间还有版本兼容关系。在裸机上装系统是自己搞的Java 路径不对MySQL 初始化脚本跑不通任何一个环节报错排查时间都以小时计。Docker 的价值在于把环境烘焙进镜像里只要镜像构建成功任何一台装了 Docker 的机器Windows、Mac、Linux都能跑出相同环境。对学习场景来说意味着从零搭建时间压缩到分钟级——pull 镜像、起容器、连页面三步。实验环境可反复销毁重建——配置乱了就删容器重新跑不污染宿主机系统。方便横向对比——同样的调度环境想换 Spark 版本改镜像重新构建即可不必担心系统残留干扰。网上有不少 docker-compose 整合 Hadoop Spark Azkaban 全分布式的方案但对大多数学习 Azkaban 调度的场景这种重量级方案反而多余资源占用高启动慢甚至很多人的电脑根本扛不动三四个 Java 进程同时跑。我最终选型是Docker 单容器 Azkaban Solo 模式 Spark local 模式够用、稳定、起步门槛最低。1.2 Azkaban Solo 模式适合谁Azkaban 有三种部署模式solo server、two-server、multi-executor。简单来说solo serverAzkaban 的 web 服务和执行服务跑在同一个进程里使用内嵌的 H2 数据库无需额外配置存储开箱即用。two-server分成 web server 和 executor server 两个进程适合模拟生产环境但配置项翻倍。multi-executor多个执行器负载均衡属于生产规模方案。学习阶段强烈建议 Solo 模式。原因很直接two-server 和 multi-executor 大部分配置如数据库表初始化、executor 端口通信、心跳超时在学习阶段用不到还容易在配置环节劝退新手。Solo 模式麻雀虽小五脏俱全调度的核心概念——project、flow、job、schedule、dependency——全部能体验到等理解透了再切换到 multi-executor 模式思路是平滑过渡的。1.3 Spark 选用 local 模式的考量在 Azkaban 里提交 Spark 任务生产环境一般是 spark-submit --master yarn但在 Docker 单容器学习环境里撑起一个完整的 YARN 集群太重了。我这里采用 Spark local 模式即 --master local[*]。local 模式用本地线程模拟分布式执行Spark 的数据分片、RDD 转换、Action 触发这些核心机制一样能跑通只是没有跨节点的网络 shuffle。对学习调度流程来说重点在“任务能不能按预期被调度、依赖顺序对不对、失败重试逻辑是否生效”而不在分布式执行性能local 模式完全可以覆盖需求。实测 stage 划分、shuffle 落盘、血缘容错这些在 local 下都有完整表现。注意这里讲的 local 模式不是让你绕过分布式概念而是用一个轻量方式先把调度链路打通。等后续要真正学 Spark 的 executor 调度细节再单独搭集群不迟。2. 基础镜像与容器网络方案2.1 基础镜像选型centos7 还是 ubuntu我最终选择了 centos:7 作为基础镜像理由一是个人用得多、踩坑经验相对集中二是网上绝大多数 Azkaban 和 Spark 的博客教程都基于 CentOS 系照抄命令能减少很多认知负担。当然你用 ubuntu:18.04 也可以本质区别只在包管理器和个别依赖库名核心流程一致。选版本时要注意几点openjdk 版本选 8。Spark 2.x 和 Azkaban 3.x 对 Java 8 的支持最成熟JDK 11 以上在某些老版本 Azkaban 上跑会报模块访问异常对新手很不友好。Spark 发行版直接选官方预编译的 hadoop2.7 或 2.6 版本二进制包。虽然这个 Hadoop 版本老但它内部捆绑的 jar 正好兼容 Azkaban 3.x 默认环境实测最省事。Azkaban 3.x 发布包里有不同编译版本记得选 azkaban-solo-server 那个 tarball不是 azkaban-web-server 的。2.2 容器网络与端口映射Azkaban Solo 模式默认 web 端口是 8081容器内 spark 提交任务不需要外部暴露额外端口local 模式不走网络。所以 docker run 时只需映射 8081 端口即可。例如docker run -d -p 8081:8081 --name azkaban-spark-learn your-image-name这里有一点容易被忽略Azkaban 页面跳转后用的是容器内 URL 还是宿主机 URL。Solo 模式默认会以容器的 hostname 生成地址直接点击执行历史里的任务链接会跳到容器内部 IP本地浏览器无法访问。解决办法是在 azkaban.properties 中显式指定 azkaban.webserver.external.hostname 为主机 IP 或 localhost后面配置环节会详细说。2.3 镜像构建的完整 Dockerfile 思路为了可复现我采用 Dockerfile 构建而不是直接 docker commit 在容器里手工装。整个 Dockerfile 的核心步骤是基础镜像 centos:7安装 JDK8、git、wget、unzip 等工具下载并解压 Spark 到 /opt/spark下载并解压 Azkaban Solo Server 到 /opt/azkaban配置环境变量 JAVA_HOME、SPARK_HOME、PATH暴露端口 8081设置启动入口脚本初始化时首次自动配置并启动 Azkaban一个关键点是不要直接在 Dockerfile 里用 RUN 去启 Azkaban。因为容器启动docker run会执行 CMD/ENTRYPOINT而不是构建时命令而且 Azkaban 的运行需要一些初始化动作例如创建 H2 数据目录写到入口脚本里更可控。3. Azkaban Solo 模式安装与核心配置3.1 Azkaban Solo Server 的下载与目录结构Azkaban 的官方 GitHub Releases 页面里有编译好的 tarball。如果是学习用途建议锁定一个稳定版本我用的是 3.90.0 左右的版本功能完整且踩坑经验丰富。下载后解压到 /opt/azkaban目录大概是/opt/azkaban/ ├── bin/ │ ├── azkaban-solo-start.sh │ ├── azkaban-solo-shutdown.sh │ └── ... ├── conf/ │ ├── azkaban.properties │ ├── log4j.properties │ ├── ... ├── lib/ ├── plugins/ ├── data/ (首次启动后生成H2 数据库文件所在) └── ...Solo 模式的执行器executor和 web server 共用 /opt/azkaban 目录启动后会自动生成 H2 数据库文件 project.db 和执行的临时工程文件。3.2 azkaban.properties 中的关键参数azkaban.properties 是 Solo 模式的灵魂。默认配置已经能跑但要贴合 Docker 环境需要改几个地方# 数据库Solo模式默认H2无需MySQL database.typeh2 h2.path./data/h2 jdbc.urljdbc:h2:file:./data/h2;IGNORECASETRUE jdbc.usernameazkaban jdbc.passwordazkaban # Solo模式必须为true azkaban.use.multiple.executorsfalse azkaban.executorselector.filtersStaticRemainingFlowSize azkaban.solo.modetrue # 时间相关默认使用本地时区 azkaban.tz.defaultAsia/Shanghai # Web服务器端口 azkaban.webserver.port8081 # 如果不设置这个生成的URL会用容器hostname azkaban.webserver.external.hostnamelocalhost其中 azkaban.webserver.external.hostname 必须改成 localhost 或宿主机 IP否则在浏览器里打开 execution 页面时里面的超链接会指向容器内hostname类似一串容器ID导致点击后打不开。这是 Solo 模式在 Docker 里最容易踩的坑且没有报错只能通过对比 URL 发现。3.3 首次启动与初始化过程启动脚本是 bin/azkaban-solo-start.sh默认前台输出日志。Docker 里建议用后台方式跑并把日志打到 stdout方便 docker logs 查看bin/azkaban-solo-start.sh logs/azkaban-solo.log 21首次启动会做这些事初始化 H2 数据库建好 azkaban 所需的各类表projects、execution_flows、execution_jobs 等。在当前目录生成 data 目录。根据 conf 里的配置绑定 8081 端口启动 web server 和 executor。启动成功的标志是日志里出现Starting Azkaban Server... Azkaban Server started on port 8081如果启动失败90% 是数据库初始化报错最典型的是 h2.path 目录权限不足导致无法创建数据库文件。Docker 里容易遇到 root 用户 vs 容器内用户对挂载目录的权限冲突建议直接在容器内用 root 跑学习环境没必要折腾用户权限隔离。3.4 通过 Web UI 验证环境浏览器访问 http://localhost:8081 默认会跳到登录界面。Solo 模式的默认用户密码是 azkaban/azkaban。登录之后看到创建工程Create Project按钮说明 Solo Server 状态正常。到这一步环境和调度器本身已经通了。真正需要花心思的是怎么把 Spark 任务挂进去跑起来这部分我单独作为一个章节细讲。4. 把 Spark 任务交给 Azkaban 调度4.1 理解 Project、Flow、Job 三层关系Azkaban 的调度模型不复杂用一句话概括Project 是工程的根一个 Project 下可以挂多个 Flow一个 Flow 由多个 Job 按依赖关系组成。Job 是真正被执行的单元可以是一条 shell 命令、一个 Spark 任务、一个 Hive 查询甚至一个 HTTP 请求。在 Azkaban 里描述 Flow 和 Job 的是一组 .job 文件。例如一个包含两个 job 的 flow 可以写成# spark_etl.job typecommand command/opt/spark/bin/spark-submit --master local[2] --class com.example.WordCount /data/spark-demo.jar# spark_report.job typecommand dependenciesspark_etl command/opt/spark/bin/spark-submit --master local[2] --class com.example.Report /data/spark-report.jar将这两个 .job 文件打包成 zip 上传到 Azkaban Project 后Azkaban 会自动生成一个 Flow默认是 spark_etl - spark_report。这里要注意一个 Flow 可以包含任意多个 jobjob 之间的依赖通过 dependencies 指定多个 job 引用同一个 .job 文件名时则视为同一个 job如果你定义了两个独立 job名称就必须不同。4.2 用 command 类型 job 提交 Spark 任务Azkaban 自带多种 job type但最通用、最不容易出幺蛾子的是 typecommand。它就是在执行节点上跑一条 shell 命令。我推荐的 Spark 相关 job 写法是typecommand command/opt/spark/bin/spark-submit \ --master local[4] \ --driver-memory 1g \ --class org.example.SparkWordCount \ /data/jars/spark-demo.jar \ /data/input /data/output几个细节路径全部写绝对路径。Azkaban 执行作业时的工作目录未必是项目目录不同版本还可能有偏差绝对路径最稳妥。Java 进程内存不要超过容器可用内存。Docker 容器如果没有限制内存默认可以吃满宿主机但学习环境建议加 -m 2g 限制避免 Spark 的 driver 和 Azkaban 抢内存导致异常。Spark 任务如果跑在 local 模式无需提供 HDFS 路径可以直接用本地文件路径。但如果后续切换成 YARN 模式job 里就得改成 hdfs:// 路径这点心里要有数。4.3 常见的中文乱码与 shell 环境变量问题我踩过最典型的两个坑第一job 里 echo 中文或者 Spark 日志里带中文在 Azkaban 执行页面显示乱码。原因不是 Azkaban 问题而是容器的 locale 没有设为 UTF-8。解决办法是在启动脚本或者 base image 里加上ENV LANGen_US.UTF-8 ENV LC_ALLen_US.UTF-8或者在 .job 文件中先执行 export LANGen_US.UTF-8再跑 spark-submit。第二Azkaban 执行的 command job 默认不会加载 /etc/profile 里的环境变量所以 SPARK_HOME、JAVA_HOME 这类变量在 shell 里明明有但在 Azkaban 执行环境里却可能丢失。保险做法是在每个需要 Spark 的 job 里显式写commandexport JAVA_HOME/opt/jdk8; export SPARK_HOME/opt/spark; $SPARK_HOME/bin/spark-submit ...或者把环境变量写在一个 shell 脚本里job 里只调这个脚本。我自己的习惯是写一个 /data/bin/run-spark.sh 统一管理环境变量和提交参数这样改参数只需要改一个地方。4.4 一个完整的 Spark 定时调度示例假设我们要实现一个简单的“定时计算某个目录下文件词频然后输出到另一个目录”的任务。整个流程是写一个简单的 Spark 应用比如 WordCount打包成 spark-demo.jar。准备输入数据 /data/input/words.txt。写 job 文件typecommand commandexport SPARK_HOME/opt/spark; $SPARK_HOME/bin/spark-submit --master local[2] --class com.example.WordCount /data/jars/spark-demo.jar /data/input /data/output在 Azkaban 页面上传包含该 job 文件的 zip 包执行一次确认输出目录生成。在 Flow 页面点击 Schedule配置 Cron 表达式例如每天凌晨 2 点执行0 0 2 * * ?保存后Azkaban 会在 scheduled flow 列表里显示该任务按预定时间触发。这里有个比较容易误导的操作Schedule 里填 Cron 表达式时Azkaban 默认按服务器时区解析。如果在 azkaban.properties 里没有配置 azkaban.tz.defaultAsia/Shanghai那么定时触发时间可能和你的本地时间差 8 小时。我当时排查了很久才发现是时区问题建议一开始就配好。4.5 单节点 Docker 下 Spark 执行资源评估学习环境里 Spark local 模式的并发度就是 --master local[N] 中的 N代表用 N 个线程执行任务。比如 local[4] 表示四个线程一般对应 4 个 CPU core。如果容器所在机器只有 2 核硬设 local[8] 不仅不会更快反而会因为线程切换增加开销。我的经验值是本地开发机 4 核 8G 内存的情况下local[2] 跑常见的 ETL 学习任务完全够用CPU 不会打满同时 Azkaban 和 H2 也能稳定运行。每个 Spark job 的 driver memory 设 512m 到 1g 即可给太多也跑不起来。5. 常见问题与排查技巧实录5.1 启动容器后页面打不开现象docker run 成功但浏览器访问 http://localhost:8081 无响应。排查思路看容器状态是否存活docker ps -a如果容器 Exited用 docker logs 看退出时的错误日志。若容器存活但页面打不开进容器确认进程是否存在docker exec -it ps -ef | grep azkaban。确认端口映射docker port 看 8081 是否真的映射到宿主机。确认防火墙部分 Linux 发行版默认开着 firewalld需要放行 8081 端口。最常见的还是 Azkaban 启动失败后容器直接退出这类问题日志里会有明确堆栈按提示处理即可。5.2 上传项目后 Flow 为空现象zip 包上传成功但打开 Project 后看不到任何 Flow。原因一般是 .job 文件没有放在 zip 包的根目录。Azkaban 不会递归扫描 zip 内所有目录的 job 文件它只寻找根目录下的 .job 文件目录层级会作为 Flow 的命名空间。解决方法是重新压缩确认 .job 文件在 zip 根目录。另外 .job 文件编码如果是 GBKAzkaban 解析属性时可能报错统一存为 UTF-8 无 BOM。5.3 Spark 任务执行失败日志显示 ClassNotFoundException这个报错通常是用户自己写的类在 Spark Submit 时没被正确加载。Azkaban command job 里执行 spark-submit 时--class 指定的主类必须存在于 jar 中并且需要确认 jar 路径被正确传递。排查时可以手动在容器里跑一遍docker exec -it container bash /opt/spark/bin/spark-submit --master local[2] --class com.example.WordCount /data/jars/spark-demo.jar /data/input /data/output如果手动跑没问题那大概率是 job 环境变量或工作目录问题尽量做成绝对路径即可。5.4 任务调度了但没到点触发如果 Schedule 已经保存但到点任务没跑第一件事看 Azkaban 调度线程是否工作。Solo 模式下调度器和执行器在同一进程理论上不存在通信故障最常见原因还是时区。确认方式在 Azkaban 页面的 Scheduling 列表看任务下一次调度时间如果显示的时间和实际期望的时间差 8 小时说明时区没配对。这时回看确认 azkaban.properties 里 azkaban.tz.default 是否为 Asia/Shanghai并且重启 Azkaban 让配置生效。5.5 容器重启后物料文件丢失如果只是 docker stop/start容器内的文件是保留的但如果 docker rm 再重新 run容器内 /data 下所有 jar、输入文件、Azkaban 工程数据都会丢失。为了防止辛辛苦苦传的数据消失建议把关键目录通过 docker run -v 挂载到宿主机docker run -d -p 8081:8081 -v /host/data:/data -v /host/azkaban-data:/opt/azkaban/data --name azkaban-learn your-image-name这样无论容器怎么重建数据还在宿主机上。6. 个人总结与扩展建议Azkaban Solo 模式跑 Spark 这套链路核心价值在于让人用最小成本理解工作流调度的全貌。它在生产环境也许有更复杂的形态但“metadata 管理、依赖解析、触发调度、任务执行、状态追踪”这五件事是相通的。你在这套环境里看到的 Flow 执行图、失败重试机制、调度时间表达式和在大规模集群上用的版本没有本质区别。扩展方向有三个把 Spark local 模式升级成 YARN 或 Kubernetes 模式这一步会引出更多的资源管理、日志聚合、动态资源分配等概念。接入 MySQL 替换 H2体验 two-server 模式下数据库表结构的变化。把 .job 文件从手写改为通过 API 动态生成实现业务侧自助提交调度任务。我个人在实际操作中的体会是学习调度框架最忌一步到位搭生产级集群因为几乎所有时间都会花在基础设施排查上而不是调度逻辑本身。先用 Docker 把环境固定下来再在稳定环境里快速迭代学习内容等到概念清透了再去碰复杂部署模式效率会高很多。如果你也准备搭这样一套学习环境记住关键词固定版本、绝对路径、时区先行、数据挂载。这四件事做好了基本不会遇到大坑。
返回列表