ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

K8s部署XXL-JOB实战:YAML、镜像与执行器避坑指南

K8s部署XXL-JOB实战:YAML、镜像与执行器避坑指南 简介面向需要在 Kubernetes 集群中落地定时任务调度的运维与后端开发人群这份 XXL-JOB 容器化部署 YAML 可作为直接应用的部署清单重点解决集群环境下任务调度组件编排与快速验证的问题。压缩包内仅含 1 个 YAML 文件类型为 yaml体积约 782B内容紧凑便于按需修改镜像、命名空间与资源配置后提交集群。已有 647 人学习下载说明其在实际部署场景中具备一定参考价值。读者可直接获得一份经过验证的集群部署配置用于理解 XXL-JOB 在 K8S 中的服务、容器与调度组件组织方式并作为环境搭建、参数对照和部署排错的起点适合具备基础 K8S 操作经验、希望缩短验证路径的读者使用。1. 为什么先在 K8s 里跑通 XXL-JOB一个版本坑换来的教训上个月接手一个 XXL-JOB K8s 集群部署的烂摊子调度中心 Pod 反复 CrashLoopBackOff执行器在管理页面里挂着一排“离线”业务方以为任务在跑实际三天没触发。排查两小时根因是 YAML 里镜像 tag 写成了 latest节点上缓存的旧镜像和新配置对不上。这类 xxljob 容器化部署的活儿最怕的就是“YAML 文件直接部署成功”这句话被当成结论——apply 返回 created 不等于系统能跑。XXL-JOB 的结构不复杂一个调度中心 admin、若干执行器 executor、一个 MySQL。但搬到 K8s 之后注册地址、时区、探针路径、镜像拉取策略每一项都能单独翻车。这篇装过的记录适合正准备把调度中心塞进集群的人也适合已经 apply 完但页面打不开、执行器注册不上的情况。2. 部署前把环境列清楚K8s 版本、镜像 tag 和五个 YAML 的分工动手之前先把版本和资源对一遍这一步省掉后面所有报错都会变成黑匣子。2.1 集群版本与命名空间的硬性检查XXL-JOB 本身的 YAML 只用到 Deployment、Service、ConfigMap 这几类基础资源对集群版本不挑。真正会卡人的是 Ingress 和 RBAC 这两块K8s 1.22 之后extensions/v1beta1和networking.k8s.io/v1beta1的 Ingress 全部下线一份两三年前下载的 YAML 直接 apply 会报no matches for kind Ingress1.25 之后 PodSecurityPolicy 被移除如果你的 YAML 里还挂着 PSP 引用整个 apply 会中断。我一般先跑这四条确认底子# 客户端与服务端版本重点看 Minor 是否 22 kubectl version --short # 节点状态和容器运行时确认没有 NotReady 节点 kubectl get nodes -o wide # 确认 Ingress API 用的是 v1 kubectl api-resources | grep ingress # 建独立命名空间别和业务挤在 default kubectl create namespace xxl-job kubectl config set-context --current --namespacexxl-job参数上没什么可调的重点是把--namespace固定下来。后面所有命令都默认落在这个命名空间能避免“资源建到默认命名空间去了”这种低级但高频的问题。如果集群开了 NetworkPolicy 默认拒绝还要提前放通 admin 的 8080 和执行器的 9999否则注册心跳会被静默丢掉页面上就是一直离线。资源配额也要看一眼。调度中心本身不重但我见过给 256Mi limit 的JVM 堆加元空间直接顶到 OOMKilled重启循环起来比配置错误更难查。2.2 镜像 tag 的选择别用 latest也别每次重启都拉这是踩过最疼的一脚。latest配imagePullPolicy: Always意味着每次 Pod 重建都要去仓库拉一次仓库一抖动Pod 就ImagePullBackOff而滚动更新时因为 tag 没变K8s 认为镜像没变化新配置根本不会生效——现象是“我改了 YAML怎么还是老行为”。正确的做法是 tag 写死到具体版本拉取策略用IfNotPresent私有仓库提前把凭据建好# 私有仓库凭据只需建一次 kubectl -n xxl-job create secret docker-registry registry-cred \ --docker-serverregistry.example.com \ --docker-usernamerobot_deploy \ --docker-password********spec: containers: - name: admin image: registry.example.com/middleware/xxl-job-admin:TAG # TAG 换成你实际验证过的版本 imagePullPolicy: IfNotPresent imagePullSecrets: - name: registry-credTAG这块别照抄网上的看你下载的这套 YAML 里写的是哪个版本就以哪个为准。admin 有官方镜像执行器没有——执行器是你自己的业务镜像这一点后面单独讲很多人卡在这儿以为执行器也能拉一个现成的。2.3 YAML 清单拆解五个文件各管什么、按什么顺序 apply一套能直接跑的部署文件通常拆成五份按依赖顺序落地序号文件资源类型作用依赖0100-namespace.yamlNamespace隔离环境无0201-mysql-init.sqlSQL 脚本建库建表MySQL 实例0302-configmap.yamlConfigMap数据源、token 等参数无0403-admin.yamlDeployment Service调度中心数据库、ConfigMap0504-executor.yamlDeployment Service业务执行器admin 的 Service顺序错了不会立刻报错但会浪费时间。比如先起 admin 再建库Pod 会一直重启你在日志里看到的是连接拒绝得从头猜。我习惯先把 01 的 SQL 在数据库里跑完再一次性apply -f .最后rollout status等就绪。2.4 数据库这一层不能省版本、字符集和那张锁表XXL-JOB 的元数据全在 MySQL 里表不多但一张都不能少。MySQL 5.7 和 8.0 都能跑差别在驱动类名和默认排序规则5.7 用com.mysql.jdbc.Driver8.0 必须用com.mysql.cj.jdbc.Driver写错就是启动时ClassNotFoundException。字符集统一到utf8mb4排序规则显式指定utf8mb4_general_ci。8.0 默认是utf8mb4_0900_ai_ci如果建库时用了默认值后面和 5.7 的环境做数据迁移会撞上Illegal mix of collations这个错在跨环境同步任务配置时特别常见。还有一张容易被忽略的表xxl_job_lock。调度中心多副本部署时靠它做数据库层面的分布式锁保证同一个任务不会被两个 admin 同时触发。有人“清理冗余表”把它删了结果就是任务重复执行日志里还看不出异常——这张表是集群模式的后悔药别动。3. 从 apply 到三个 Pod 起来XXL-JOB 的 YAML 按什么顺序落地3.1 先建库导表再谈部署数据库没准备好后面全是白搭。建库语句显式写字符集和排序规则CREATE DATABASE IF NOT EXISTS xxl_job DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; -- 建一个专用账号别用 root 跑调度中心 CREATE USER xxl_job% IDENTIFIED BY ********; GRANT ALL PRIVILEGES ON xxl_job.* TO xxl_job%; FLUSH PRIVILEGES;然后导入随包的tables_xxl_job.sql。如果 MySQL 跑在集群内用一次性的 Job 或直接 exec 进去导# 从本机把 SQL 灌进集群里的 MySQL Pod kubectl -n middleware exec -i mysql-0 -- \ mysql -uxxl_job -p******** xxl_job tables_xxl_job.sql # 确认关键表都在 kubectl -n middleware exec -i mysql-0 -- \ mysql -uxxl_job -p******** -e use xxl_job; show tables;期望看到xxl_job_info、xxl_job_log、xxl_job_group、xxl_job_registry、xxl_job_lock这些表。少一张admin 启动时就会在初始化那一步抛异常日志里是一长串 MyBatis 的映射错误看着吓人其实回去补表就行。3.2 admin 的 DeploymentPARAMS、探针和资源三处必须改调度中心的镜像启动脚本支持用PARAMS环境变量透传 Spring Boot 参数这是最省事的做法不用自己打包 configmap 挂载。核心片段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: admin image: registry.example.com/middleware/xxl-job-admin:TAG imagePullPolicy: IfNotPresent ports: - containerPort: 8080 env: - name: TZ value: Asia/Shanghai - name: PARAMS value: - --spring.datasource.urljdbc:mysql://mysql.middleware.svc.cluster.local:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue --spring.datasource.usernamexxl_job --spring.datasource.password******** --xxl.job.accessToken******** resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi readinessProbe: httpGet: path: /xxl-job-admin/toLogin port: 8080 initialDelaySeconds: 40 periodSeconds: 10 failureThreshold: 6 livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 90 periodSeconds: 20几处细节值得说PARAMS里的连接串用-折叠写法拼成一行注意在 YAML 里别裸写在行首放在字符串中间没问题但保险起见整段加引号或折叠块。探针路径必须是/xxl-job-admin/toLogin不是根路径/。admin 是传统 Spring MVC 应用根路径返回 302 到登录页有些探针配置不跟随跳转就会判定失败Pod 永远不 ReadyService 后面一个可用端点都没有你以为是服务挂了其实是探针写错了。initialDelaySeconds给到 40 秒以上是因为首次启动要初始化数据库连接池和调度线程太快探活会把正常启动判死。liveness 用 tcpSocket 而不是 httpGet是为了避免数据库短时抖动导致探针失败引发重启——重启解决不了数据库问题只会让启动更慢。3.3 执行器不是官方镜像业务镜像怎么接进 YAML这是新手最容易误解的一环。执行器没有现成镜像可拉它是你自己的 Spring Boot 应用引入xxl-job-core依赖后打包再写一个配置类把执行器注册上去Configuration public class XxlJobConfig { Value(${xxl.job.admin.addresses}) private String adminAddresses; Value(${xxl.job.accessToken}) private String accessToken; Value(${xxl.job.executor.appname}) private String appName; Value(${xxl.job.executor.port:9999}) private int port; Bean public XxlJobSpringExecutor xxlJobExecutor() { XxlJobSpringExecutor executor new XxlJobSpringExecutor(); executor.setAdminAddresses(adminAddresses); // 调度中心地址多个用逗号分隔 executor.setAppname(appName); // 执行器名要和 admin 页面里录入的一致 executor.setPort(port); // 内置 Netty 端口默认 9999 executor.setAccessToken(accessToken); // 必须和 admin 完全一致 executor.setLogPath(/data/applogs/xxl-job/jobhandler); // 任务日志落盘路径 executor.setLogRetentionDays(7); // 日志保留天数按磁盘容量调 return executor; } }setAppname的值是执行器和调度中心之间的约定键admin 页面里“执行器管理”新增的那条记录AppName 必须和这里一字不差大小写、横线都算。setPort对应的容器端口要一起暴露YAML 里containerPort: 9999加上 Service 的 9999 映射少一个注册就上不去。setLogPath指向的目录建议挂emptyDir或 PVC。容器重启后日志丢失任务失败时你连执行输出都看不到只剩一个失败状态排查全靠猜。执行器的 Deployment 本体和 admin 差不多主要是PARAMS换成执行器自己的参数env: - name: PARAMS value: - --xxl.job.admin.addresseshttp://xxl-job-admin:8080/xxl-job-admin --xxl.job.executor.appnamexxl-job-executor-demo --xxl.job.executor.port9999 --xxl.job.accessToken********地址里用的是 admin 的 Service 名xxl-job-admin同命名空间内可直接解析不必写完整域名。跨命名空间就补上.xxl-job.svc.cluster.local。3.4 对外暴露ClusterIP、NodePort、Ingress 三选一内部使用只需要 ClusterIP业务方通过集群内域名访问。要从办公网打开管理页面再选 NodePort 或 Ingress。Ingress 的写法在 1.22 之后必须是 v1apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: xxl-job-admin namespace: xxl-job spec: ingressClassName: nginx rules: - host: xxl-job.example.com http: paths: - path: /xxl-job-admin pathType: Prefix backend: service: name: xxl-job-admin port: number: 8080注意path要带上应用上下文/xxl-job-admin只写/的话静态资源和接口路径会对不上页面能打开但登录请求 404。如果必须在根路径访问就得在 admin 启动参数里改server.servlet.context-path但改了之后执行器注册地址也要跟着改牵一发动全身不如路径带前缀。4. 配置项逐个过数据源、accessToken、执行器地址这三处最容易写错4.1 数据源URL 里四个参数不能少连接串看着简单实际有四个参数直接影响能不能连上、连上之后行为对不对spring.datasource.urljdbc:mysql://mysql.middleware.svc.cluster.local:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltruerewriteBatchedStatementstrue spring.datasource.usernamexxl_job spring.datasource.password******** spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.connection-timeout30000 spring.datasource.hikari.idle-timeout600000serverTimezone不写MySQL 8.0 驱动会按 UTC 解释时间任务的下次触发时间整体偏 8 小时useSSLfalse在集群内部通常直接关掉开着但没配证书会建立连接失败allowPublicKeyRetrievaltrue是 8.0 默认加密方式下的必需项少了报Public Key Retrieval is not allowedrewriteBatchedStatementstrue对日志批量写入有帮助调度中心的日志表增长很快这个参数能让批量插入真正合并。连接池上限别拍脑袋写大。调度中心是单实例长连接为主20 个连接足够设成 100 反而可能把数据库的max_connections吃满影响同实例上的其他库。4.2 accessToken两端必须完全一致空值也不行1.9 之后版本默认启用通信校验admin 的xxl.job.accessToken和执行器的setAccessToken必须是同一串字符。实践里有三种写法会导致注册失败admin 设了、执行器没设两边设了但一个有空格用了特殊字符在PARAMS透传时被 shell 解释。建议用纯字母数字的 32 位串两边都从同一个配置源取。如果 admin 用 ConfigMap、执行器用环境变量记得改的时候一起改。校验失败的表现很隐蔽执行器日志里是 401 或者“registry fail”admin 页面上看不到机器任务调度时报“执行器地址为空”。临时排查可以先把两边都置空验证链路是否通确认后再填回去。但生产环境不要长期空着等于把注册接口完全敞开。4.3 执行器注册地址自动注册在 K8s 里到底靠不靠谱执行器默认走自动注册向 admin 上报自己的 IP 和端口admin 把这些地址存进xxl_job_registry表调度时直接连。在 K8s 里这套逻辑能不能work取决于 admin 到执行器的网络路径同集群同命名空间Pod IP 直接可达自动注册没问题。跨命名空间也通常可以前提是没有 NetworkPolicy 拦住 9999。但如果 admin 部署在集群外或者执行器用了hostNetwork上报的地址就对不上了需要在执行器侧显式指定xxl.job.executor.ip10.0.0.21 xxl.job.executor.addresshttp://10.0.0.21:9999/address优先级高于ipport填了它就以它为准。手工录入模式也常用在 admin 页面“执行器管理”里把地址写死成 Service 名或 NodePort代价是扩缩容后要手动维护。自动注册还有个副作用Pod 重建后 IP 变化旧地址会在注册表里残留约 90 秒靠心跳超时清理。这期间调度可能打到已经销毁的 IP 上任务日志显示“连接超时”。滚动更新时如果流量敏感宁可先缩到 1 副本再更新。4.4 JVM 参数与时区任务慢 8 小时的那个坑时区要三处对齐容器TZ、JVM-Duser.timezone、数据库连接串serverTimezone。缺一处cron 表达式就可能按 UTC 解析你配的0 0 2 * * ?实际在北京时间上午 10 点触发。env: - name: TZ value: Asia/Shanghai - name: JAVA_OPTS value: - -Xms512m -Xmx512m -XX:UseG1GC -XX:MaxGCPauseMillis200 -Duser.timezoneAsia/Shanghai -Dfile.encodingUTF-8堆给 512m 起步调度中心的对象不多瓶颈在数据库不在堆。-Dfile.encodingUTF-8是为了任务参数里有中文时不出现乱码——这个错很难定位因为任务本身执行成功了只是参数变了样。执行器侧同样要配尤其是日志文件里的中文输出。5. 部署避坑五个卡住过我的现象和它们的解法5.1 Pod 一直 CrashLoopBackOff日志停在数据源初始化现象kubectl logs只看到 Spring Boot 启动 banner 之后就没了或者报Communications link failure。原因数据库不可达或者账号权限不足。常见于数据库和 admin 不在同一命名空间但用了短域名DNS 解析失败。解决先进 admin Pod 里用nc -zv mysql.middleware.svc.cluster.local 3306验证连通性再用同样的账号密码从 Pod 内跑一次mysql -h ... -u ... -p确认权限。短域名不通就换成完整 FQDN。另外检查数据库的白名单有些云数据库只允许特定网段。5.2 执行器列表里显示离线机器 registries 为空现象admin 页面“执行器管理”能看到执行器记录但“OnLine 机器地址”是空的。原因三种可能——accessToken 不一致、9999 端口不通、AppName 拼错。解决先看执行器日志里有没有注册成功的记录再在 admin Pod 里curl http://执行器IP:9999/试探端口最后逐字符比对 AppName。三条都排除后检查 NetworkPolicy 和 Service 的 targetPort 是否指到了 9999 而不是 8080。5.3 任务到点没触发调度日志里一条记录都没有现象任务配置看起来正常cron 也到了但“调度日志”列表是空的。原因调度中心的时间不对或者任务状态是“停止”而不是“运行”。还有一种情况是 cron 表达式用了 Quartz 格式但写错了位数。解决先看 admin 容器内的date和宿主机对一下再确认任务状态最后把 cron 换成0/30 * * * * ?这种 30 秒一次的做快速验证。如果日志里出现“调度成功但执行失败”那就是执行器侧的问题看handleCode。5.4 ImagePullBackOff节点上明明有镜像现象kubectl describe pod显示Failed to pull image但docker images在节点上能看到同名镜像。原因imagePullPolicy: Always配合私有仓库凭据失效或者 tag 是latest导致每次都拉。解决把策略改成IfNotPresenttag 写死版本。凭据过期就重建 Secret注意 Secret 必须和 Pod 同命名空间。还有一个隐蔽情况节点打了污点或走了特定的镜像加速配置实际拉取路径和你以为的不一样。5.5 滚动更新后任务重复执行现象一次更新之后同一个任务在同一分钟被触发两次。原因admin 起了两个副本但没配数据库锁相关参数或者xxl_job_lock表被删了。解决确认xxl_job_lock表存在且有数据多副本部署时确保所有副本连的是同一个库。如果只是单副本需求replicas保持 1用strategy: Recreate避免新旧 Pod 并存期间重复调度。6. 装完怎么验证跑通从 Pod 状态到调度日志的四层自检apply 完看到created不算数我固定按四层往下走每层不过就不往下看。第一层看 Pod 状态和重启次数kubectl -n xxl-job get pods -o wide kubectl -n xxl-job rollout status deploy/xxl-job-admin --timeout180sREADY 1/1、RESTARTS 0才算过。如果重启次数是 1 但当前 Running别放过去看上一次的日志多半是启动初期探针太急。第二层看 HTTP 接口能不能返回 200绕过浏览器直接打kubectl -n xxl-job exec deploy/xxl-job-admin -- \ curl -s -o /dev/null -w %{http_code}\n http://127.0.0.1:8080/xxl-job-admin/toLogin返回200说明应用上下文、端口、探针路径三者一致。返回302一般是路径少了前缀返回000是端口没起。第三层登录页面确认执行器在线。默认账号admin密码123456进去第一件事是改密码第二件事是看“执行器管理”里的 OnLine 机器数是不是等于你起的副本数。第四层做一次端到端触发这是唯一能证明“调度链路通”的验证检查项操作期望结果执行器在线执行器管理页OnLine 机器数 ≥ 1任务可调度新建 GLUE(Shell) 任务内容echo hello-xxl-job保存成功手动触发点“执行一次”调度日志出现一条记录执行回传查看日志详情handleCode200执行备注含hello-xxl-job定时触发cron 设0/30 * * * * ?等一分钟连续出现两条调度记录四层全过这套 YAML 才算真的部署成功。任何一层卡住回到第 5 章对号入座基本都在那五条里。从那以后我每次部署完 XXL-JOB不管多急都强制把echo hello-xxl-job这个 GLUE 任务跑一遍——页面能打开不代表调度能通只有日志里那条handleCode200才是真的。这套按顺序编号的 YAML 和初始化 SQL 我整理在一起了把镜像前缀和密码换成自己的就能直接 apply希望你一次跑通不用在 CrashLoop 里耗一晚上。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表