k8s离线环境部署DolphinScheduler+SeaTunnel,这个组合我前前后后折腾了一个多月,踩了不少坑,也沉淀了不少经验。先说清楚这套东西是干嘛的:DolphinScheduler做任务编排调度,SeaTunnel做数据同步抽取,两者搭在一起,就是一个完整的、可视化的数据集成调度平台。很多公司内部环境是隔离的,没有外网,这就逼着你必须走离线部署这条路。这篇文章就是把我在离线环境里从零到一部署这套组合的完整过程、关键配置和排坑记录都写出来,给同样被困在内网的同学一个参考。
1. 整体方案设计与前置准备
1.1 “离线”到底离到什么程度
做离线部署,第一件事不是下载安装包,而是搞清楚“离线”的边界。我这次的环境是:k8s集群本身是好的,但是所有节点都不能访问外网,内部有一个私有的镜像仓库,可以接收我上传的镜像。软件包、依赖、镜像,全部要靠u盘或者内部文件服务器搬运进去。
这里有个很容易忽略的问题:离线不代表完全不让动网络,而是分三种情况:
- 纯离线:所有节点无外网,内部有私有镜像仓库和yum源
- 半离线:部分节点有外网,可以下载,但生产环境隔离
- 仅k8s离线:应用层完全隔离,但镜像仓库、对象存储等都是内网服务
确认清楚边界再动手,不然方案设计会跑偏。我这次属于第一种,纯离线,所有安装包都得靠自己的“人肉快递”。
先列一下我这次环境的基本信息:
- k8s版本:1.24.x
- 存储方案:NFS作为默认StorageClass
- 镜像仓库:Harbor私有仓库
- DolphinScheduler版本:3.1.9
- SeaTunnel版本:2.3.5
- 数据库:使用已有的MySQL 8.0
为啥选这两个版本?DolphinScheduler 3.x和SeaTunnel 2.3.x是可以稳定兼容的,SeaTunnel 2.3.5支持MySQL CDC,和DolphinScheduler的Workflow配合度高,而且这两个版本都有现成的Docker镜像可以直接拿来改造。
1.2 为什么选择DolphinScheduler + SeaTunnel这套组合
市面上的调度和数据同步工具不少,比如DolphinScheduler对标的还有Apache Airflow,SeaTunnel对标的还有DataX、Flink CDC,为啥我偏偏选了这两个?
DolphinScheduler的优势在于:
- 自带工作流可视化编排界面,业务人员也能看懂依赖关系
- 支持定时调度、依赖触发、失败重跑,这些是生产级调度平台的基本盘
- 部署方式灵活,可以源码部署也可以k8s部署
SeaTunnel的优势在于:
- 不用写代码,通过配置文件就能定义数据源和同步逻辑
- 支持MySQL、PostgreSQL、Oracle、ClickHouse、Hive、Kafka等几十种数据源
- 基于Apache Zeta引擎,批流一体,性能比第一代版本有了明显提升
这两个组合起来,DolphinScheduler负责“什么时候干什么活”,SeaTunnel负责“怎么把数据从A挪到B”,边界清晰,互不干扰,比在一个系统里强行揉在一起要好维护得多。
1.3 离线安装包清单
这一步非常关键,做少了后面就得反复折腾,做多了u盘装不下。我列一下我整理的清单:
- DolphinScheduler镜像:apache/dolphinscheduler:3.1.9及相关组件镜像
- SeaTunnel镜像:apache/seatunnel:2.3.5,以及seatunnel-web镜像
- SeaTunnel连接器包:seatunnel-connectors.tar.gz,包含MySQL、JDBC、Kafka、Hive等常用连接器
- DolphinScheduler源码包:apache-dolphinscheduler-3.1.9-src.tar.gz,用于生成初始化SQL和修改配置
- SeaTunnel客户端包:apache-seatunnel-2.3.5-bin.tar.gz
- 基础依赖:JDK 1.8,MySQL驱动jar包
- 网络调优工具包:比如iproute2、telnet等排查工具,离线环境装这些很麻烦,提前备好
镜像怎么拿?有外网的时候提前pull下来,然后通过docker save打成tar包,到内网后用docker load加载,再retag到私有仓库。如果是arm架构的机器,还需要注意镜像的架构匹配,这个很多人容易踩坑,后面我会专门说。
2. 核心原理与架构拆解
2.1 DolphinScheduler在k8s中的部署逻辑
DolphinScheduler在k8s里跑,和传统的主机部署有本质的区别。它在主机上部署时,会拆成MasterServer、WorkerServer、ApiServer、AlertServer、LoggerServer这几个进程,各司其职:
- MasterServer:负责任务的调度、DAG切分、任务分发
- WorkerServer:负责实际执行任务,可以水平扩展
- ApiServer:提供Rest API,给前端页面调用
- AlertServer:负责告警通知
- LoggerServer:负责日志服务
在k8s上部署时,这些进程变成了一个个Deployment或者StatefulSet,但逻辑架构不变。DolphinScheduler官方提供了Helm Chart,所以最稳妥的方式是用Helm部署,而不是自己手写一堆yaml。Helm Chart里已经把Master、Worker、Api、Alert拆成了不同的组件,并且支持配置副本数、资源配额、持久化存储、环境变量。
Master和Worker在k8s里的区别还在于:Master需要的是稳定的状态,不适合随便重启,建议用Deployment配合反亲和性,保证Master实例分散在不同节点上;Worker是可以随时弹的,业务负载大就多拉几个Worker实例。
2.2 SeaTunnel在k8s中的运行方式
SeaTunnel本身设计成独立运行的引擎,它不感知k8s,所以要在k8s环境里跑起来,有几种主流方式:
第一种是直接用SeaTunnel镜像启动一个Pod,Pod里通过命令行执行配置好的同步作业,这是最简单的方式,适合一次性的数据同步任务。
第二种是把SeaTunnel整合到DolphinScheduler对应的Worker中,通过DolphinScheduler的Shell任务类型去调用Seatunnel的提交命令。这种方式适合把同步任务纳入DolphinScheduler的调度体系,形成统一的“调度+数据同步”流程。
第三种是SeaTunnel Zeta引擎的Cluster模式,作为一个常驻的分布式服务运行在k8s中,Job通过Client发送给Cluster执行。
我这次采用的是第二种方式。原因很简单:通过Shell调用SeaTunnel客户端,虽然看起来有点“土”,但胜在稳定可控。DolphinScheduler负责重试、告警、定时,SeaTunnel负责真正的数据搬运,出了问题边界清楚。第一种方式脱离调度体系,生产上没法用;第三种方式架构更复杂,对内网环境来说需要维护Zeta集群本身的健康状态,反而增加负担。
2.3 数据同步任务的执行链路
搞清一次数据同步任务从触发到落地的完整链路,对排查问题非常有帮助。任务在DolphinScheduler里被触发后,链路是这样的:
DolphinScheduler Master根据DAG分发的状态,把任务发给对应的Worker节点,Worker节点会找到配好的SeaTunnel客户端路径,执行提交命令,SeaTunnel启动Zeta引擎(或者连接已运行的引擎),读取配置文件,初始化Source、Transform、Sink三个阶段的插件,然后开始拉数据、处理数据、写目标库,最后返回执行状态给DolphinScheduler Worker。
这里最常出问题的点有两个:
- Worker找不到SeaTunnel客户端路径,或者环境变量没配置好
- SeaTunnel配置文件里的数据库连接串、账号密码写得有问题
链路长,排错就要一层一层来,先把DolphinScheduler的日志看透,再到SeaTunnel的日志里去找真正的报错原因。
3. 离线部署实操全流程
3.1 准备离线仓库
这一步是整个离线部署的“地基”。我用的方案是先把Harbor装在一台可以访问外网的机器上(这台机器后面再搬到内网),或者直接在已经有Harbor的内网环境里,通过跳板机把镜像传进去。
具体步骤如下:
- 在能联网的机器上,用docker pull拉取需要的官方镜像
- 用docker save把镜像打成tar包,最好是gzip压缩,体积更小
- 把tar包拷贝到内网跳板机
- 在内网跳板机上用docker load把镜像加载进来
- 用docker tag打上内网Harbor的地址标签
- 用docker push推送到内网Harbor
- 所有k8s节点配置/etc/docker/daemon.json,把Harbor地址加入insecure-registries
注意一下:很多人在第4步docker load之后,直接忘了打tag,导致推送的时候就镜像名还是原来的docker.io/apache/dolphinscheduler:3.1.9,不是harbor地址/项目名/镜像名,这样推送必然失败。这个细节值得留意。
3.2 初始化MySQL数据库
DolphinScheduler使用关系型数据库存储元数据,包括工作流定义、任务实例、调度计划等。首次部署必须执行初始化脚本。
我从源码包里找到了dolphinscheduler_mysql.sql这个初始化脚本。脚本执行前需要先建库:
CREATE DATABASE dolphinscheduler DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; GRANT ALL PRIVILEGES ON dolphinscheduler.* TO 'dolphinscheduler'@'%' IDENTIFIED BY '你的密码'; FLUSH PRIVILEGES;执行初始化脚本:
mysql -h mysql主机地址 -u dolphinscheduler -p 你的密码 < dolphinscheduler_mysql.sql需要注意,如果MySQL的sql_mode设置得比较严格,比如开启了ONLY_FULL_GROUP_BY,初始化脚本可能报错。我遇到过一次,后来把sql_mode调整了一下,去掉ONLY_FULL_GROUP_BY,重新导入就好了。这个属于MySQL本身的兼容问题,不是DolphinScheduler的bug。
3.3 修改Helm Chart的values.yaml
Helm Chart是部署的“说明书”,values.yaml则是这份说明书的“可调参数页”。这一步我花了不少时间,因为官方默认的values.yaml是按在线环境写的,里面很多镜像地址默认指向docker.io,必须全部改掉。
我挑几个关键配置项说一下:
# 镜像仓库地址 image: registry: harbor.internal.com tag: 3.1.9 # 数据库连接配置 common: database: host: mysql.internal.com port: "3306" user: dolphinscheduler password: "我的密码" database: dolphinscheduler # Worker副本数 worker: replicas: 2 resources: limits: cpu: 2 memory: 4Gi requests: cpu: 500m memory: 2Gi根据我自己的经验,这里有三个地方非常容易出错:
一是数据库的时区配置。DolphinScheduler默认用的是UTC时区,如果MySQL也是UTC,没问题,但如果你MySQL用的是CST(中国标准时间,其实这里是UTC+8),就得在jdbc连接串里加上serverTimezone=Asia/Shanghai,否则调度时间会和预计时间差8个小时,这个坑非常隐蔽。
二是Worker的存储配置。Worker在跑任务时会产生日志和临时文件,默认是挂在容器的临时目录里,Pod一重启数据就没了。日志丢了还能忍,但如果SeaTunnel的配置文件和临时下载的连接器也放在临时目录里,Pod重建后任务必然失败。所以需要把日志目录、临时目录都挂到持久化存储上。
三是资源配额。官方默认的requests很低,但如果你把requests设得太高,k8s调度时会因为节点资源不足而pending。我一开始没经验,把Worker的requests设成了2C/4G,结果两个Worker pod起不来,一直Pending。后来改成500m/2G,调度就正常了。requests和limits要拉开差距,requests是调度依据,limits是资源上限。
3.4 使用Helm部署DolphinScheduler
改完values.yaml就可以正式部署了。如果你在内网环境没有Helm仓库,需要先把Helm Chart包下载好,放到内网中。具体操作:
# 在有外网的机器上 helm repo add dolphin https://dolphinscheduler.apache.org/helm-chart/ helm pull dolphin/dolphinscheduler --version 3.1.9拉下来的chart包拷贝到内网后,直接使用:
helm install dolphinscheduler ./dolphinscheduler-3.1.9.tgz -f values.yaml -n>kubectl get pods -n>kubectl describe pod xxx -n>export SEATUNNEL_HOME=/opt/seatunnel export PATH=$PATH:/opt/seatunnel/bin为了让这个环境变量自动生效,我在Worker容器的启动脚本里加了对应的source语句,或者在DolphinScheduler的Workflow里直接用绝对路径调用。用绝对路径更稳,因为不同Pod的环境变量初始化时机不同,有时候会因为找不到变量导致任务执行失败。
3.6 打通DolphinScheduler和SeaTunnel的调用
在DolphinScheduler中新建一个工作流,添加一个Shell任务,内容写:
/opt/seatunnel/bin/seatunnel.sh --config /opt/seatunnel/config/mysql_to_mysql.conf -e local这个mysql_to_mysql.conf就是我们的数据同步作业配置。为了方便管理,我把所有SeaTunnel的配置文件也放到了共享存储里,DolphinScheduler通过Shell任务直接读取这些配置文件。
为了让整个流程更“DolphinScheduler化”,我同时也做了几件事:
- 在DolphinScheduler里配置了告警组,任务失败时发钉钉或者邮件通知
- 设置了任务超时时间,防止数据量大的同步任务卡死
- 配置了失败重试次数,解决临时性的网络抖动问题
这样下来,整个数据平台就是真正“活”的调度系统了,定时、触发、重试、告警,全都在DolphinScheduler面面俱到。
4. 常见问题与排查技巧实录
4.1 镜像拉取失败
离线环境最常见的就是镜像拉取失败,报错信息通常是image pull back off。先排除一下是哪个节点在拉镜像,然后手动在那台节点上执行docker pull试试,看能不能拉到。如果手动拉取没问题,说明是k8s的安全策略问题,检查daemon.json里的insecure-registries是否配置正确,然后重启docker服务。
还有一个小坑:Harbor里配置的项目如果是私有的,k8s节点需要配置imagePullSecret,否则同样拉不下来。在Helm的values.yaml里,有一个imagePullSecrets配置项,要填上对应的secret名字。
4.2 MySQL连接失败
这个报错在各阶段都遇到过,表现形式五花八门:
- DolphinScheduler初始化时报Exception connecting to database
- Pod启动时报Connection refused
- 任务执行时报Communications link failure
排查思路是:先从DolphinScheduler的Pod里用telnet测一下数据库端口通不通,然后看数据库账号密码是否正确,再看MySQL是否允许远程连接。内网环境还有个很隐蔽的问题:MySQL的bind-address如果配置成了127.0.0.1,那其他机器肯定连不上,必须改成0.0.0.0。
4.3 时区错乱
我刚才提过时区问题,这里详细展开。如果你发现DolphinScheduler里设置的定时调度时间和实际执行时间差8个小时,基本就是时区不对。
解决方法就是在values.yaml里数据库连接串加serverTimezone=Asia/Shanghai,同时DolphinScheduler本身的时区也要设置成Asia/Shanghai。有的老版本问题更多,需要在启动参数里加-Duser.timezone=GMT+8,好在新版本里已经默认处理好了。
4.4 SeaTunnel任务执行后无响应
SeaTunnel任务在DolphinScheduler里显示运行中,但一直不结束,这种情况多半是SeaTunnel的Zeta引擎启动有问题,或者配置里的任务名称和实际提交任务不一致。
我在排查时用的方法是:先到Worker节点上手动执行一下同样的命令,看看能不能正常跑完,如果可以,说明问题出在DolphinScheduler和SeaTunnel的交互上,比如环境变量、用户权限;如果手动也卡住,就把SeaTunnel的日志级别调到DEBUG,看具体卡在哪个环节。
调试日志通常在SeaTunnel的logs目录下,文件名类似seatunnel-engine-xxx.log。看到卡在Source阶段,一般是数据库连接问题;卡在Sink阶段,一般是写入目标库有问题;卡在Transform阶段,就要看看是不是字段类型转换出错了。
4.5 ARM架构镜像问题
公司内部可能有arm架构的服务器,如果你拿的是x86架构的镜像,Pod会一直CrashLoopBackOff,日志里报exec format error。这个报错很直接:我的AppArmor,哦不是,是CPU架构不匹配。
解决方法有两种:
- 在联网机器上docker pull时,用--platform指定arm64
- 用manifest工具把多架构镜像打包成fat manifest,这样k8s会根据节点架构自动选择对应架构的镜像
第二种方案更通用,因为如果你部署的集群是混合架构,x86和arm节点都有,用fat manifest就不会出现“镜像只支持一种架构”的尴尬。
4.6 共享存储权限问题
SeaTunnel放在NFS共享存储里,如果NFS的权限设置不对,Worker容器没有读写权限,执行任务时就会报Permission denied。
这个问题排查起来也简单:
- 查看NFS目录的属主和属组
- 确认Worker容器运行的用户ID
- 将NFS目录的属主调整为该用户ID
但这里有个麻烦:DolphinScheduler官方镜像里的Worker进程默认是用uid 1000跑的,而NFS的root_squash机制可能不认这个uid。我是直接把NFS目录的属主改成了uid 1000,再配合mount option里加入no_root_squash,搞定。这种底层文件系统权限的坑,不遇到一次很难想到。
5. 配置最佳实践与调优建议
5.1 DolphinScheduler调度参数调优
DolphinScheduler的Master和Worker线程数、心跳超时、任务重试机制等配置,在生产环境需要结合实际并发量来调。
我总结的调优思路如下:
- 任务量多,并发高,加大worker.execution.threads,同时提高Worker的内存限制
- 如果Master频繁失联,调大master.heartbeat.interval和监听端口超时时间
- 失败重试次数设置:普通离线任务设为2-3次,实时性要求高的任务设为0次,因为重试只会让数据更乱
对于调度,我一般会设置一个“重跑保护”:工作日跑批的任务,失败重试2次,如果还失败,就不自动重跑,而是发告警出来人工介入。这样避免出现“任务失败然后自动重跑,重跑又占用资源导致其他任务失败”的连锁雪崩,这个在大数据平台里特别容易发生。
5.2 SeaTunnel资源并发度设置
SeaTunnel的配置文件里有一个execution.parallelism参数,直接影响同步任务的并发度。并行度并不是越大越好,因为目标数据库的写入能力是有限的。如果你并行度设为10,但目标MySQL只能承受5个并发写入,那么性能和稳定性都会出问题。
我这里的一个推荐做法是:先按同步数据量的复杂度估算,初始并行度设为2或3,然后通过任务运行时间对比,逐步往上加,直到找到一个稳定的并发上限。你还得注意source端的连接池,SeaTunnel的MySQL Source会创建多个连接,连接数不要超过数据库max_connections的一半,不然会影响其他业务。
5.3 数据一致性保障
离线同步最怕的是数据重复或者数据丢失。SeaTunnel的JDBC Sink支持写入模式:append、upsert和delete+insert。业务上如果要求数据不重复,一定要用upsert模式,并且指定主键字段。如果目标表没有主键,建议加一个自增主键或者业务主键,否则同步数据出现问题后,很难做幂等恢复。
我自己在跑订单表同步时,就是用了upsert模式,source端是MySQL的CDC,sink端还是MySQL,主键设为订单号。就算同一个订单被更新了多次,同步结果也是最终一致的,不会出现重复数据。这一个坑我踩得很深,最开始用的append模式,结果源库update一条订单,目标库多出一行,统计报表全错了。
5.4 监控与告警配置
一个数据平台如果只有调度没有监控,那就是在裸奔。DolphinScheduler自带的告警机制已经很完善了,支持邮件、钉钉、企业微信等。我建议配置两套告警:
- 任务级告警:每个关键工作流都绑一个告警组,任务失败、超时都要通知
- 全局告警:DolphinScheduler本身的服务状态异常,也要有Master、ApiServer的存活探针,配合k8s自身的Prometheus监控
我实际的经验是:任务级告警里,邮件和钉钉都发,邮件是给领导看的日报,钉钉是给开发实时看的。这样生产有问题,开发能第一时间感知,日报也能留档。
6. 扩展思考与二次开发建议
6.1 如何对接更多数据源
SeaTunnel的连接器本身已经覆盖了很多主流数据源,但在实际工作中,总会遇到一些“有点冷门”的数据源,或者需要对接内部自研系统的情况。
我的建议是:先看SeaTunnel连接器列表里有没有对应的连接器;没有的话,考虑是否可以用JDBC通用连接器加自定义SQL解决;还不行,就需要基于SeaTunnel的API做二次开发,写自己的Source或者Sink插件。SeaTunnel的插件机制设计得还是很清晰的,写一个自定义插件,本质上就两件事:实现接口,打jar包放到connectors目录。做一次之后,后面扩展就容易了。
6.2 从离线到云原生的演进路径
DolphinScheduler和SeaTunnel这套组合,跑在k8s上,其实已经算是云原生架构了。但你可以做得更进一步:
- 通过KEDA或HPA,让SeaTunnel的Worker数量根据同步任务的积压量自动伸缩
- 用Argo Workouts或者GitOps的方式管理DolphinScheduler的工作流定义
- 考虑到DolphinScheduler API Server已经是无状态的,直接水平扩展即可
这些扩展方向,内网环境也是完全可以推进的,只要前期把基础镜像和依赖下载好,后续滚动升级的风险总体可控。
6.3 团队协作规范
数据平台的稳定运行,靠的不仅仅是部署,还有日常的规范。我和团队后来约定了几条不占纸面的“规矩”:
- SeaTunnel配置文件的命名统一:数据源_目标源_业务名称.conf,比如mysql_mysql_order.conf
- 同步作业提交时,必须写清owner和告警组
- DolphinScheduler工作流命名按业务域隔离,前缀统一,比如ods_、dwd_、ads_
- 每次修改SeaTunnel配置,必须先在测试环境跑一次,确认没问题才能上生产
这些规矩能避免很多低级失误。特别是在多人协作的时候,一个命名不规范的配置文件,正好又在你凌晨排查问题时出现,那真的会让人血压升高。
6.4 离线部署包版本管理
最后一件事,建议把部署包和配置文件纳入版本管理,用Git维护。很多人觉得内网环境不需要Git,但问题恰恰出在这里:如果哪天同事改了values.yaml,或者换了SeaTunnel连接器包,你根本不知道改了什么,排错的时候非常被动。
我把部署相关的文件全部放在一个自建的Git仓库里,包括:
- Helm Chart的values.yaml
- SeaTunnel配置文件
- 初始化SQL脚本
- 部署操作文档
每次改动都有记录,回滚非常方便。配合一个简单的发布检查单(先计划、再变更、后验证),整个数据平台的迭代会轻松非常多。
7. 最后的小建议
这套组合在内网环境里跑稳定之后,其实还可以多做一些事情。比如把数据同步任务的执行数据收集起来,做一个简单的数据血缘页面,让业务看到数据从哪来到哪去。或者把SeaTunnel的同步任务做成标准的模板,业务需求来了,直接填参数就能生成配置,不用每次从头写。
我个人的体会是:离线部署最大的难点从来不在技术本身,而在于环境的不确定性。同一套镜像,在不同节点组上拉取结果可能不同;同一个脚本,在不同库版本上行为也可能不同。所以关键的配置和操作,一定要记录下来,形成一个团队内部的“离线部署手册”,后续再有人接手就不用重新踩一遍坑。