XXLJOB在K8S上的容器化部署实践:从YAML到Pod注册全攻略

XXLJOB在K8S上的容器化部署实践:从YAML到Pod注册全攻略 简介XXL-JOB是常用的分布式任务调度中间件在Kubernetes集群中部署时YAML配置的编写与校验往往需要反复调试这也是不少入门者容易卡住的地方这份资源面向负责xxljob容器化部署的开发或运维人员提供一份经过实际集群环境验证的YAML文件可直接用于测试或生产环境减少因配置项错误导致的部署失败。压缩包为zip格式内部仅包含1个YAML配置文件整体约782B结构十分精简便于快速查看、修改与复用该资源已有645人浏览学习具备一定的参考热度。使用者拿到后可以直接基于该YAML创建相关资源对象快速拉起XXL-JOB服务也可以将其作为自定义部署模板结合自身集群的命名空间、存储、访问方式等配置做调整从而缩短部署周期、降低上手门槛对于正在规划xxljob容器化落地的技术团队或者希望快速验证调度中心功能的个人开发者而言这是一份轻量却实用的部署参考。 XXLJOB这个调度平台老一批搞Java的人应该都不陌生。以前做定时任务要么用quartz裸写要么在spring里自己封装到了微服务时代任务调度这块如果不统一治理确实容易乱成一锅粥。XXLJOB自带的admin调度中心可视化操作、动态调整cron、执行日志追踪这些功能在传统部署模式下已经很成熟了。但这两年容器化、K8S普及之后还有个痛点一直卡着——调度中心后端执行器注册的IP是Pod IPPod一旦重建就变了任务会直接失联。所以这次我直接把XXLJOB整套搬到K8S集群里用YAML文件一次性部署成功整个过程踩了不少坑也积累了些经验下面把这套验证过的部署方案完整分享出来适合正在做K8S化改造的运维、对容器化调度方案有需求的Java开发也适合在K8S集群里规划定时任务体系的架构同学。先说结论XXLJOB这套东西往K8S上搬难度真的不高核心就两块——调度中心admin做成Deployment暴露服务执行器executor做成Deployment让Pod自动注册。真正费心思的是网络通信模式和注册IP的处理方式这两个点想明白了YAML文件里填参数就不会再犹豫。1. 部署方案选型与整体思路1.1 为什么把XXLJOB搬上K8S传统部署XXLJOB的方式是打jar包扔到服务器上配个systemd或者用shell脚本启动。这种方式在机器少、服务固定的时候没什么问题但一旦执行器分布在多台机器上扩容缩容全靠人工登录服务器去操作admin上看到的执行器列表经常跟实际机器不一致。K8S化之后执行器变成Pod扩容就是改一下replicas让Deployment滚动重建Pod起来后自动向admin注册这才是调度平台该有的交付形态。还有一点很实际——容器化部署能直接复用K8S的资源隔离和健康检查机制。XXLJOB的admin如果被OOM或者线程卡死K8S的livenessProbe探针会直接干掉重启不需要再搞外部监控脚本去守护进程。这类能力对任务调度的稳定性非常重要YAML文件里配好探针参数相当于白捡了一个轻量级的HA机制。1.2 XXLJOB架构在K8S下的映射关系官方架构其实很清晰一个admin调度中心多个executor执行器。调度中心负责任务的编排、触发、日志收集执行器负责接收调度指令跑具体的业务逻辑代码。传统模式下两者通过HTTP接口通信执行器启动时把自己注册到admin到了K8S里这个模型没有本质变化只是执行器的“注册地址”变成了Pod IP或者Service地址。我这次采用的是“admin一主executor多副本”的布局。admin用Deployment部署副本数设为1因为是验证版先不开多副本避免admin集群注册表冲突executor用Deployment部署副本数可以直接调到2到3个。执行器Pod挂掉之后K8S会自动拉起新的Pod而admin里的执行器列表会短暂显示失联等新的Pod注册进来后自动恢复。这个机制在生产环境的价值在于——就算某个执行器Pod被OOM Kill任务也不会永远卡死新的Pod起来后调度会自动续上。1.3 为什么用纯YAML而不是Helm社区里其实有人做了XXLJOB的Helm Chart但我个人建议验证阶段先用纯YAML。原因很简单——Helm相当于套了一层模板出问题时要排查的层面多了一层而且很多Chart的默认参数是为生产环境设计的对我们验证场景来说反而产生干扰。纯YAML的好处是每一行配置都能看懂改一个参数只需要搜索替换对应字段执行kubectl apply之后反馈立竿见影。这套YAML是我在测试环境反复apply验证过的直接从零到部署成功能跑通整个调度流程文件里没有多余的OwnerReferences、ServiceAccount等复杂对象复制下来改几个环境变量就能用。2. 部署前置工作与镜像策略2.1 镜像选择与版本踩坑我这次选的是xuxueli/xxl-job-admin这个官方镜像版本用的2.3.1。当时也纠结过要不要上2.4.0的最新版后来查了官方Release说明2.3.1的容器化部署资料最全网上踩坑记录也多有问题好搜。2.4.0主要是新增了部分功能但底层调度模型没大变所以验证阶段先求稳。这里有个非常关键的坑xxl-job-admin的官方镜像里内置了调度中心的代码但执行器并没有打进去。如果我们不想单独写一个executor服务可以先用admin镜像自带的“内置执行器”来做任务验证。admin项目里自带了一个名为xxl-job-executor-sample-springboot的示例执行器镜像启动时会一并拉起。验证部署时可以直接用这个内置执行器跑任务测试后面再替换成自己业务项目的执行器镜像。2.2 数据库初始化是第一步XXLJOB必须在MySQL里先建好库和表admin启动时才能正常连接。官方提供了一份tables_xxl_job.sql脚本里面是调度表、日志表、锁表等近十张表。我刚开始部署时偷懒没执行脚本结果admin启动后一直在报SQL语法错误日志刷得飞快。我这次用的是一个独立的MySQL实例提前创建了xxl_job数据库并执行了官方SQL脚本。如果你手头没有现成MySQL也可以直接在K8S里部署一个单节点MySQL用简单YAML即可但要注意Pod重启后数据会丢验证场景勉强可以生产环境必须挂PV存储。数据库连接串我是直接放在环境变量里的没上Secret毕竟验证版图个省事。2.3 命名空间与资源规划K8S集群里强烈建议单开一个namespace来隔离任务调度这套东西别跟业务应用混在一起不然Pod列表里全是别人的服务查日志都费劲。我建了xxl-job这个命名空间所有资源都用namespace隔离。资源配额上admin的requests配了512Mi内存、200m CPUlimits配了1Gi内存、500m CPU执行器因为实际业务逻辑可能吃内存更多limits我放宽到2Gi。K8S的QoS机制下requests和limits不一致会分配到Burstable优先级Pod被系统驱逐的概率比Guaranteed高一些。验证环境无所谓生产环境如果对稳定性要求苛刻建议把requests和limits设成一致。3. 核心YAML文件逐个拆解3.1 Admin部署文件admin-deployment.yaml这是整个部署的最核心文件我直接先贴出来apiVersion: apps/v1 kind: Deployment metadata: name: xxl-job-admin namespace: xxl-job spec: replicas: 1 selector: matchLabels: app: xxl-job-admin template: metadata: labels: app: xxl-job-admin spec: containers: - name: xxl-job-admin image: xuxueli/xxl-job-admin:2.3.1 ports: - containerPort: 8080 name: http env: - name: PARAMS value: - --spring.datasource.urljdbc:mysql://your-mysql-host:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai --spring.datasource.usernamexxljob --spring.datasource.passwordyourpass --xxl.job.accessTokendefault_token livenessProbe: httpGet: path: /xxl-job-admin/ port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /xxl-job-admin/ port: 8080 initialDelaySeconds: 20 periodSeconds: 5 resources: requests: cpu: 200m memory: 512Mi limits: cpu: 500m memory: 1Gi这个文件有几个细节要重点说第一PARAMS环境变量是官方镜像预留的参数透传入口。镜像里的启动脚本会把PARAMS的内容拼接到java -jar命令后面所以Spring Boot的全部配置项都可以通过这里传进去。如果你的k8s网络环境不需要特殊配置直接用这格式改成你自己的数据库地址和账号密码就能跑。第二探针路径不是根路径。xxl-job-admin默认的context-path是/xxl-job-admin所以探针请求的路径必须带上这个前缀。我第一次配的时候写的是/结果readinessProbe一直失败Pod处于未就绪状态排查了半小时才发现是context-path的问题。探针的initialDelaySeconds建议给到20到30秒等Spring Boot完全启动后再探测不然前几次探测失败会残留warning记录。第三accessToken要记下来。这个token是admin和执行器握手用的凭证如果改了执行器那边的配置必须同步。我自己差点被这个坑到——admin侧写的是default_tokenexecutor侧忘记了结果一直报401。3.2 Admin对外服务暴露admin调度中心必须让外界能访问到不然没法打开管理页面配置任务。有几种选择NodePort、LoadBalancer、Ingress。我这里用的NodePort直接编辑Service定义apiVersion: v1 kind: Service metadata: name: xxl-job-admin-svc namespace: xxl-job spec: type: NodePort selector: app: xxl-job-admin ports: - name: http port: 8080 targetPort: 8080 nodePort: 30080NodePort的取值范围是30000-32767我选了30080好记。访问方式就是http://任意节点IP:30080/xxl-job-admin。生产环境用Ingress更规范可以做域名绑定和HTTPS接入但验证阶段NodePort简单粗暴。这里有个注意点如果你集群有多个节点访问任意一个节点的30080端口都能到admin如果节点有防火墙或者安全组记得放行这个TCP端口不然外部访问不了。3.3 执行器的注册与网络方案xxl-job的执行器配置里最关键的是xxl.job.executor.appname和xxl.job.executor.address。K8S环境下Pod IP会变所以有两种常见方案方案一配置K8S Service域名。给执行器创建一个无头服务Pod通过statefulset或者controller-revision-hash保证名称固定时可以拿到稳定的DNS域名。但XxlJob官方文档不推荐直接填域名做回调地址——因为调度中心admin在触发任务时是通过HTTP访问执行器接口的如果填的是域名依赖的是K8S集群内DNS解析最后解析出来的还是Pod IP多绕了一层。方案二配置注册IP为Pod IP。我在executor的容器启动参数里直接写--xxl.job.executor.ip${POD_IP}然后通过环境变量注入当前Pod IPenv: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP这样执行器启动时会拿着自己的Pod IP去admin注册。因为executor和admin在同一个集群内网admin访问Pod IP是通的所以这个方案最直接。缺点就是Pod重建后IP变了admin那边会出现一个失联的执行器记录但新的Pod注册成功后任务调度不受影响只是后台列表里会有短暂重复记录。我这次用的方案二实测2个副本部署后admin执行器列表里能看到两个IP不同的执行器任务调度轮询到哪个都能正常跑。3.4 执行器Deployment文件apiVersion: apps/v1 kind: Deployment metadata: name: xxl-job-executor namespace: xxl-job spec: replicas: 2 selector: matchLabels: app: xxl-job-executor template: metadata: labels: app: xxl-job-executor spec: containers: - name: xxl-job-executor image: your-registry/your-executor:latest ports: - containerPort: 9999 name: executor env: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: XXL_JOB_ADMIN_ADDRESSES value: http://xxl-job-admin-svc:8080/xxl-job-admin - name: XXL_JOB_ACCESS_TOKEN value: default_token - name: XXL_JOB_EXECUTOR_APPNAME value: xxl-job-executor - name: XXL_JOB_EXECUTOR_PORT value: 9999 command: [java, -jar] args: - /app/executor.jar - --spring.profiles.activek8s resources: requests: cpu: 200m memory: 512Mi limits: cpu: 500m memory: 1Gi如果你还没有自己业务的执行器镜像可以直接用admin镜像启动内置执行器的方式替代env: - name: PARAMS value: --xxl.job.executor.appnamexxl-job-executor但这个方式的可控性差一点生产环境一定是你自己的业务项目打包成executor镜像接入xxl-job的starter然后再去注册。4. 执行部署与功能验证4.1 应用YAML的完整顺序配置全部准备好后部署顺序很重要。我按下面的顺序执行kubectl create ns xxl-job kubectl apply -f admin-deployment.yaml -n xxl-job kubectl apply -f admin-service.yaml -n xxl-job kubectl apply -f executor-deployment.yaml -n xxl-job kubectl get pod -n xxl-job -w先启动admin因为executor启动时需要拿admin的地址去注册。虽然执行器有失败重试机制但如果admin没起来日志里会一直打连接失败的异常看着心烦。执行完apply后用kubectl get pod -w观察Pod状态等所有Pod都变成Running和Ready之后再去浏览器访问管理页面。4.2 验证步骤一确认admin页面正常浏览器输入http://节点IP:30080/xxl-job-admin会跳转到登录页。默认账号是admin密码是123456。登录进去后左侧菜单能看到执行器管理、任务管理、调度日志、GLUE IDE等模块说明admin部署成功。这个页面能打开意味着数据库连接正常、初始化SQL执行正确、Spring Boot容器启动无异常。如果登录页打不开第一反应不是看K8S日志而是直接访问Pod的日志kubectl logs -f deployment/xxl-job-admin -n xxl-jobSpring Boot的启动日志里如果有Started XxlJobApplication字样说明应用起来了问题大概率出在Service或NodePort上如果没有启动成功就看异常堆栈里的Root Cause多半是数据库连不上或者端口被占。4.3 验证步骤二执行器注册确认在admin后台的“执行器管理”页面新增一个执行器AppName填xxl-job-executor必须和executor配置里的appname保持一致名称随意注册方式选自动注册。保存后稍等几秒执行器列表就会刷新出两个运行中的实例显示的是两个Pod IP。如果列表一直是空的先去executor Pod日志里查关键报错。最常见的报错是Connection refused或者Fail to register这说明executor到admin的地址不通。检查executor里配置的XXL_JOB_ADMIN_ADDRESSES是否指向了http://xxl-job-admin-svc:8080/xxl-job-admin并且xxl-job-admin-svc这个Service是否真的创建了。还有accessToken配置是否两侧一致。4.4 验证步骤三新建定时任务并跑一次进入“任务管理”页面新增任务。JobHandler选择“内置示例”——我记得官方示例里有个名字叫demoJobHandler的处理器直接在运行模式里选BEAN然后JobHandler填demoJobHandlercron表达式填0 * * * * ?每分钟执行一次调度类型选Cron。保存后点击“执行一次”按钮这时候去“调度日志”页面看结果。调度日志里如果出现“调度成功”并且“执行成功”说明整个链路已经完全打通了admin从数据库读取任务配置 - 根据执行器注册表选择Pod - HTTP回调executor的9999端口 - 业务逻辑执行 - 结果上报。这个流程验证通过说明整个K8S部署已经没有任何问题了。我在这步还特意做了一个实验手动删除一个executor Pod执行kubectl delete pod xxl-job-executor-xxx -n xxl-job观察Deployment自动拉起新Pod然后去admin执行器列表看IP有没有变化。新的IP出现后再手动触发一次任务确认调度正常。这个验证很有价值它证明了K8S的Pod生命周期管理与XXLJOB的自动注册机制能够协同工作——这就是K8S化部署相比传统方式的核心优势。5. 常见问题与踩坑实录5.1 执行器地址变了怎么办这个问题是我被问得最多的。在K8S里Pod删除重建后IP发生变化是常态而xxl-job的admin端会把执行器地址缓存在数据库表中。如果Pod IP变了旧地址会一直显示“失联”新地址自动注册后正常使用。解决方案第一不要慌失联记录不影响新任务的调度第二想清理旧记录可以在admin后台删除或者等系统自动清理第三如果想彻底避免这个问题可以使用K8S的StatefulSet部署执行器给每个Pod分配稳定的网络标识但XxlJob官方对每个Pod只注册一个地址StatefulSet场景下需要给每个副本分别配置注册地址反而繁琐。我这边验证下来DeploymentPod IP注册的方式是最简单且够用的——除非你的调度任务必须绑定到某个固定执行器实例上否则没必要折腾固定地址。5.2 容器时区问题导致调度延迟经典的坑。容器默认时区是UTC而XXLJOB的调度时间是按照服务器本地时间计算的。如果你直接跑官方镜像不管时区会发现任务在8点整执行时实际cron会晚8小时。解决方案在Deployment里挂载时区env: - name: TZ value: Asia/Shanghai volumeMounts: - name: timezone mountPath: /etc/localtime volumes: - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai上面的两个方案我建议都用上TZ环境变量是Java层面读取的hostPath是系统层面的。只配TZ的话有些镜像的内置时区数据可能没装全挂载localtime是最保险的。5.3 内存限制与OOM默认Java应用启动时JVM会读取宿主机内存来设置堆大小在K8S里如果不显式配置-XmxJVM可能直接申请到Pod limits之外的内存然后被OOM Kill。解决方案在executor启动命令里显式限制内存command: [java, -Xms256m, -Xmx512m, -jar, /app/executor.jar]同时把K8S的limits内存设置比Xmx大一点比如Xmx512mlimits设1Gi留点余量给JVM的非堆内存和metaspace。这个问题在admin上也一样建议admin的Xmx也显式配置。5.4 ScheduledExecutorService自研调度 vs XXLJOB最后说个题外话。有些项目组从最开始就是用Spring自带的Scheduled或者自己用ScheduledExecutorService写调度结果业务多了之后发现没有统一的任务管理页面、没有失败告警、手动触发也麻烦。搬到K8S里更尴尬——如果你自己写调度K8S的HPA一扩容就会出现多个实例同时执行同一个定时任务的问题。XXLJOB天然就是分布式调度同一个任务只会被调度中心派发给一个执行器不会重复执行。这也是我为什么在K8S化改造中强烈推荐直接用XXLJOB这类专门做分布式调度的框架。5.5 常见问题速查表现象原因解决办法admin页面打不开Service或NodePort配置错误、探针失败检查Service端口映射、Pod日志、context-path路径执行器注册不上admin地址不通、accessToken不一致检查XXL_JOB_ADMIN_ADDRESSES、accessToken两侧配置任务调度失败执行器地址失联、JobHandler名称错误删除失联执行器记录、核对JobHandler名称日志乱码容器编码问题启动命令加-Dfile.encodingUTF-8任务延迟8小时容器时区UTCTZ环境变量hostPath挂载localtime调度日志显示失败但无明文执行器异常堆栈没打出来去executor Pod日志里看详细堆栈6. 最终部署验证脚本与后续扩展建议这套YAML我已经在测试环境跑了快两个月期间手动重建过admin和executor Pod也故意杀过任务所在Pod制造故障系统都能自动恢复。如果你也准备在K8S里部署建议先把这套验证版的YAML跑通再把executor镜像替换成你自己的业务项目最后根据实际业务把配置管理迁移到ConfigMap和Secret里。最后再分享一个小技巧升级XXLJOB版本时不要直接在原Deployment上修改镜像版本先另起一个临时YAML文件apply到相同命名空间确认新版本启动无异常后再修改原Deployment。这样如果新版本有兼容性问题你还有时间回滚。我们线上就遇到过升级2.3.1到2.4.0后数据库多了一张表配置中心也变了直接在原Deployment上回滚特别麻烦——这是真金白银换来的教训。本文还有配套的精品资源点击获取