首页 / 资讯中心 / 文章详情

K8s 集群部署 XXL-JOB:YAML、执行器注册与调度验证

K8s 集群部署 XXL-JOB:YAML、执行器注册与调度验证 ★ FEATURED ARTICLE
简介一份面向需要在 Kubernetes 集群中部署 XXL-JOB 的开发者与运维人员的已验证 YAML 清单重点解决从零编写编排文件、快速验证容器化部署流程的问题适合实验环境搭建、部署方案对照及后续二次调整。压缩包内共 1 个文件类型为 YAML体积约 782B内容集中、轻量便于下载后纳入版本管理或直接在集群中应用。已有 647 人学习下载说明它在 XXL-JOB 容器化部署场景中具有一定参考价值。读者可从中获得经过实际部署验证的清单参考理解 XXL-JOB 在 Kubernetes 中的资源组织与配置要点减少自行拼装部署文件的时间也可将其作为集群部署验证的起点对照应用结果排查配置差异并在此基础上按命名空间、镜像和资源规格进行适配。整体更适合具备 Kubernetes 基础、希望快速完成部署验证的中级读者。1. 集群里跑 XXL-JOB先别急着 kubectl apply上周帮一个团队查他们的定时任务环境kubectl get pod一屏 Running唯独xxl-job-admin在 CrashLoopBackOff 里打转。翻日志数据库连接串少写了serverTimezone容器里是 UTC任务触发时间全偏了八小时。这类问题在把 XXL-JOB 往 Kubernetes 上搬的时候特别集中——YAML 谁都会 apply能不能跑起来是另一回事。最新这版 xxl-job 的 K8s 集群部署说到底就是把调度中心、执行器、MySQL 三件事在集群里对齐镜像和 JDK 对不上、ConfigMap 里拼错一个参数、执行器注册地址写成 127.0.0.1任何一个都能让整套环境变成摆设。这篇记录的是用 YAML 直接部署、能跑通一次真实调度的完整路径从版本选择、镜像准备、YAML 拆分到执行器注册和验证装过一遍的人可以对照排查第一次上手的人照着走能少翻几次车。2. 装前对版本XXL-JOB 2.3.1、JDK 与 MySQL 的搭配账2.1 先把三个版本号定下来XXL-JOB 的镜像不是随便挑一个 tag 就能跑。2.3.x 和 2.4.x 两条线在包结构和调度链路上有差别社区里流传的 YAML 大多是 2.3.1 那一套直接套在 2.4.x 上不一定报错但执行器注册、日志清理这些边角行为会不一样。我一般按下面这张表把版本钉死再动手组件建议版本说清楚为什么Kubernetes1.20 及以上networking.k8s.io/v1的 Ingress 从 1.19 起稳定低于这个版本 YAML 里的 apiVersion 得回退XXL-JOB2.3.1 或 2.4.12.3.1 资料全、镜像成熟2.4.x 是新项目可以上的线升级前先读 release notesMySQL5.7 或 8.0库和表必须 utf8mb48.0 要显式关 SSL、指定时区JDK8 或 112.3.x 按 JDK8 编译2.4.x 用 JDK11 跑没问题别混用高版本编译低版本运行版本账里最容易翻车的是 JDK 和 MySQL 时区这两个隐藏项。镜像里 JDK 大版本不对启动时直接UnsupportedClassVersionErrorMySQL 8.0 不给serverTimezone驱动会按 UTC 解释trigger_time页面上看到的触发时间和你本地时间差八小时排查半天以为是 cron 写错了。这两个参数我一般提前写进配置模板不给自己留后悔药。还有一点调度中心的镜像 tag 要和xxl-job-core依赖版本对齐。业务服务里引的是 2.3.1 的 core调度中心跑的是 2.4.1 的 admin协议层不一定兼容症状是执行器显示在线但一调度就超时。原则上整套环境同一个小版本别搞混搭。2.2 集群侧的前置条件命名空间、存储、入口XXL-JOB 本身是无状态的但它依赖的东西在集群里得有。我一般先建独立命名空间不和业务 Pod 挤在 default 里后面做 NetworkPolicy 和资源配额都方便# 独立命名空间资源隔离后面 NetworkPolicy 好写 kubectl create namespace xxl-job # 确认集群有默认 StorageClassMySQL 用得上外部库可跳过 kubectl get storageclass # 确认 Ingress Controller 已装class 名称按你集群实际的写 kubectl get ingressclass第一条命令建命名空间第二条确认有没有默认可用的 StorageClass如果 MySQL 走集群外部的实例这一步可以跳过第三条看 Ingress Controller 的 class 名后面 Ingress YAML 里的ingressClassName要和它一致写错了 Ingress 一直 Pending页面打不开还找不到原因。前置条件里还有镜像仓库。私有集群拉不了公网镜像得提前把xxl-job-admin推到自己的仓库并在命名空间里配好imagePullSecrets。我见过有人 YAML 里 image 写的是公网地址apply 之后 Pod 卡在 ImagePullBackOff查半天以为是网络问题其实是集群节点根本出不去。2.3 镜像从哪来官方镜像与自建镜像的取舍两条路。第一条直接用官方镜像仓库里的xxl-job-admintag 跟版本号走它的启动脚本预留了PARAMS环境变量值会拼到java -jar后面当程序参数改数据库连接很方便env: - name: PARAMS value: - --spring.datasource.urljdbc:mysql://mysql.middleware.svc.cluster.local:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai --spring.datasource.usernamexxl_jobPARAMS里是一串空格分隔的 Spring Boot 启动参数--spring.datasource.url覆盖配置文件里的同名项。这种写法省事代价是密码会明文出现在 Deployment 里而且改一个参数就得重新 apply。第二条自己打镜像把 jar 包塞进openjdk:8-jre-slim好处是时区、字体、启动参数全在自己手里坏处是每次升级都要重新构建FROM openjdk:8-jre-slim # 容器时区必须显式设置否则 cron 触发时间按 UTC 算 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone COPY xxl-job-admin-2.3.1.jar /app/app.jar WORKDIR /app EXPOSE 8080 # exec 形式启动K8s 里 args 才能正确追加到 java 命令后面 ENTRYPOINT [java, -jar, /app/app.jar]ENV TZ加软链那两行是必须的slim 镜像默认没有时区数据不装 tzdata 的话ln会失败ENTRYPOINT必须用 exec 形式中括号数组用 shell 形式的话 K8s 的args会被吞掉你在 Deployment 里写的--spring.config.additional-location根本不生效。我一般选第二条路镜像推到registry.local/middleware/xxl-job-admin:2.3.1版本可控。3. 拆 YAMLDeployment、Service、ConfigMap 与 Ingress 的分工3.1 ConfigMap 与 Secret把数据库口令从镜像里拿出来配置和镜像分离是这套部署的底线。我习惯把非敏感配置放 ConfigMap密码单独放 Secret靠 Spring Boot 的环境变量优先级覆盖——环境变量高于配置文件所以 Secret 注入的SPRING_DATASOURCE_PASSWORD会盖掉文件里的值apiVersion: v1 kind: ConfigMap metadata: name: xxl-job-admin-config namespace: xxl-job data: application.properties: | server.port8080 server.servlet.context-path/xxl-job-admin spring.datasource.urljdbc:mysql://mysql.middleware.svc.cluster.local:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8autoReconnecttrueserverTimezoneAsia/ShanghaiuseSSLfalse spring.datasource.usernamexxl_job spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # 调度中心访问令牌执行器侧要填一样的值 xxl.job.accessTokenxxl-job-token-2024 # 日志保留天数按磁盘容量调 xxl.job.logretentiondays30context-path决定了后面 Ingress 的路径和登录页地址写死/xxl-job-admin是默认值改了它所有访问地址都要跟着改不建议动。useSSLfalse是给 MySQL 8.0 准备的不关的话驱动会尝试 TLS 握手内网环境直接连不上。accessToken这一项两边必须一致调度中心配了执行器没配注册请求会被拒表现是执行器列表一直空着。Secret 用命令建别手写 base64kubectl create secret generic xxl-job-admin-secret \ --from-literalmysql-passwordXxlJob2024 \ -n xxl-job这个 Secret 只有一个 keymysql-passwordDeployment 里用secretKeyRef引成环境变量SPRING_DATASOURCE_PASSWORD。密码换了只需要更新 Secret 再滚动重启不用碰镜像和 ConfigMap。3.2 Deployment副本数、探针与 PARAMS 参数Deployment 是整套 YAML 里参数最密的一块我一般按下面这个模板改apiVersion: apps/v1 kind: Deployment metadata: name: xxl-job-admin namespace: xxl-job labels: app: xxl-job-admin spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: xxl-job-admin template: metadata: labels: app: xxl-job-admin spec: containers: - name: admin image: registry.local/middleware/xxl-job-admin:2.3.1 imagePullPolicy: IfNotPresent args: - --spring.config.additional-locationfile:/config/ ports: - containerPort: 8080 name: http env: - name: TZ value: Asia/Shanghai - name: SPRING_DATASOURCE_PASSWORD valueFrom: secretKeyRef: name: xxl-job-admin-secret key: mysql-password volumeMounts: - name: config mountPath: /config readinessProbe: httpGet: path: /xxl-job-admin/toLogin port: http initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: tcpSocket: port: http initialDelaySeconds: 60 periodSeconds: 20 resources: requests: cpu: 500m memory: 1Gi limits: cpu: 1 memory: 2Gi volumes: - name: config configMap: name: xxl-job-admin-configargs里的--spring.config.additional-locationfile:/config/是让 Spring Boot 额外加载挂载进来的application.properties不写这行 ConfigMap 等于白挂。replicas: 2是有讲究的调度中心多副本靠数据库里的xxl_job_lock行锁避免重复调度两副本能扛住单点故障但副本数别超过 3行锁竞争会拖慢触发。探针这块readinessProbe打登录页/xxl-job-admin/toLogin返回 200 才算就绪livenessProbe用 TCP 探 8080 就够了别用 HTTP 探活页页面依赖数据库数据库抖一下 liveness 失败会把健康 Pod 重启掉反而放大故障。initialDelaySeconds给 30 秒以上调度中心启动要初始化数据源和线程池太激进会反复被杀。3.3 Service 与 Ingress调度中心怎么对外Service 保持 ClusterIP 就行执行器和业务服务在集群内通过服务名访问apiVersion: v1 kind: Service metadata: name: xxl-job-admin namespace: xxl-job spec: type: ClusterIP selector: app: xxl-job-admin ports: - name: http port: 8080 targetPort: httptargetPort: http引的是容器里那个命名端口比写 8080 更抗改。执行器配置的xxl.job.admin.addresses就填http://xxl-job-admin.xxl-job.svc.cluster.local:8080/xxl-job-admin注意末尾带 context-path少了它执行器会往/api上打全部 404。对外访问用 Ingress不需要 rewriteapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: xxl-job-admin namespace: xxl-job spec: ingressClassName: nginx rules: - host: xxl-job.internal http: paths: - path: /xxl-job-admin pathType: Prefix backend: service: name: xxl-job-admin port: number: 8080因为context-path本身就是/xxl-job-adminIngress 路径直接对上写rewrite-target反而会把前缀削掉登录页能打开但静态资源全 404。pathType用Prefix用Exact的话只有精确匹配的那一个路径能进。4. 库先建好再启动MySQL 初始化与首次拉起4.1 建库建号与导表调度中心启动时会去连库库不存在直接启动失败。先在 MySQL 上把库和账号建好-- 字符集必须是 utf8mb4任务参数里有中文或 emoji 才存得下 CREATE DATABASE xxl_job DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 账号放开到 %K8s 里的 Pod IP 不固定 CREATE USER xxl_job% IDENTIFIED BY XxlJob2024; GRANT ALL PRIVILEGES ON xxl_job.* TO xxl_job%; FLUSH PRIVILEGES;utf8mb4这一项不要省用utf8建库的话任务描述里带四字节字符插入直接报错症状是新建任务保存失败但页面没提示。账号 host 用%Pod 重建后 IP 会变写死某一个 IP 过两天就连不上。建库之后导表结构SQL 文件在源码的doc/db/tables_xxl_job.sql8 张表表名作用xxl_job_info任务定义cron、路由策略、执行器组都在这里xxl_job_log调度和执行日志排查问题的第一现场xxl_job_log_report日志统计报表xxl_job_logglueGLUE 模式的任务代码xxl_job_registry执行器注册表执行器列表空不空看这张xxl_job_group执行器组AppName 和地址xxl_job_user登录账号xxl_job_lock调度锁多副本调度中心靠它防重复触发# 从源码包导入库名和账号替换成上一步建的 mysql -h mysql.middleware.svc.cluster.local -u xxl_job -p xxl_job tables_xxl_job.sql # 导完核对表数量应该是 8 张 mysql -h mysql.middleware.svc.cluster.local -u xxl_job -p -e USE xxl_job; SHOW TABLES;xxl_job_lock表里有一行schedule_lock记录多副本调度中心启动时会先抢这把锁缺了这行两个副本会同时触发任务症状是每个任务一次触发跑两遍日志里两条一样的记录。导完 SQL 顺手查一眼这张表有没有数据。4.2 首次启动看哪三行日志kubectl apply之后别只看 Pod 状态进容器看日志kubectl logs -n xxl-job -l appxxl-job-admin --tail200 -f启动成功的日志里这三处是关键信号HikariPool-1 - Starting... HikariPool-1 - Start completed. Started XxlJobAdminApplication in 12.345 secondsStart completed说明数据源通了Started XxlJobAdminApplication说明 Spring 上下文起来了。反过来如果卡在HikariPool之后没有Start completed基本是连接串或账号问题常见的是serverTimezone没写导致驱动抛The server time zone value is unrecognized或者 Secret 里的密码和 MySQL 里的对不上。日志里出现Communications link failure是网络层不通从 Pod 里nc -zv mysql.middleware.svc.cluster.local 3306试一下通了再查账号授权。还有一种静默失败Pod 显示 Running但日志里没有Started只是反复刷Retrying这种多半是 ConfigMap 挂载路径写错容器起来但配置没加载。4.3 页面首次登录与账号安全Ingress 配好之后浏览器访问http://xxl-job.internal/xxl-job-admin默认账号admin密码123456。登录进去第一件事改密码xxl_job_user表里的初始口令是公开的暴露在公网入口上等于开门。改完密码顺手看看「执行器管理」的列表这时候应该是空的等业务侧执行器注册上来才会有数据。如果这里一直是空的先别怀疑调度中心去查执行器侧的xxl.job.admin.addresses配置八成是那个地址写错了。5. 执行器接入与排查注册不上的几种典型情况5.1 执行器的两种部署形态与注册地址执行器有两种放法。一是嵌在业务 Spring Boot 服务里加xxl-job-core依赖启动时自动向调度中心注册二是独立部署成一个执行器 Pod专门跑定时任务。前一种省资源后一种隔离性好任务把业务服务打挂的风险小。不管哪种注册都靠执行器主动上报配置项是# 调度中心地址末尾必须带 context-path xxl.job.admin.addresseshttp://xxl-job-admin.xxl-job.svc.cluster.local:8080/xxl-job-admin # 执行器 AppName要和调度中心执行器组里的一致 xxl.job.executor.appnameorder-job-executor # 执行器监听端口注册时上报的就是这个端口 xxl.job.executor.port9999 # 与调度中心一致的访问令牌 xxl.job.accessTokenxxl-job-token-2024appname是执行器和调度中心之间的唯一约定执行器组表xxl_job_group里的app_name必须和它一致不一致的表现是执行器在线但挂在「默认」组下面调度时找不到。port默认 9999同一台机器跑多个执行器要错开。5.2 AppName、端口与网络策略执行器注册上报的地址是它自己的 IP 加端口调度中心拿到这个地址后反向调执行器。跨命名空间部署时这条反向链路经常被 NetworkPolicy 拦住症状是执行器显示在线点「执行一次」报connect timed out。我一般给调度中心命名空间和执行器命名空间之间开白名单方向是两个执行器到调度中心 8080 做注册调度中心到执行器 9999 做触发。只开一个方向的话注册成功但调度失败排查起来很容易误判成调度中心的问题。端口这块还有个坑执行器的 9999 端口没有暴露在容器ports列表里K8s 不会自动帮你转发Pod 之间直连是用 Pod IP 的这个没问题但如果中间隔了 Service就得在 Service 里把 9999 也声明出来。5.3 五条踩坑记录现象一Pod 一直 CrashLoopBackOff。原因多半是数据库连不通或时区参数缺失Spring 启动时数据源初始化失败直接退出。进容器看kubectl logs如果抛的是server time zone value在连接串补serverTimezoneAsia/Shanghai如果是Access denied检查 Secret 里的密码和xxl_job_user表的账号是否一致改完kubectl rollout restart deployment/xxl-job-admin -n xxl-job。现象二调度中心页面能打开执行器列表一直空。原因在执行器侧xxl.job.admin.addresses写成了127.0.0.1或容器内的localhost执行器把注册请求发给了自己。在集群内注册地址必须写调度中心的 Service 全名http://xxl-job-admin.xxl-job.svc.cluster.local:8080/xxl-job-admin改完重启执行器等 30 秒左右注册表刷新。现象三执行器显示在线手动执行报connection refused。原因是xxl_job_registry里存的是执行器旧 Pod 的 IPPod 重建后 IP 变了但旧记录没清。调度中心按旧 IP 去调自然连不上。解决是等注册表过期默认 90 秒自动清理急着恢复就在页面「执行器管理」里手动删掉旧地址然后重启执行器重新注册。现象四每个任务一次触发跑了两遍。原因是调度中心副本数大于 1 但xxl_job_lock表里没有schedule_lock这条记录行锁失效两个副本同时触发。查库SELECT * FROM xxl_job_lock WHERE lock_nameschedule_lock没有就补上或者把副本数先降到 1 确认逻辑。现象五页面登录后跳到 404。原因是 Ingress 配了rewrite-target把/xxl-job-admin前缀削掉了而后端 context-path 还带着这个前缀。去掉 rewrite 注解或者把 context-path 改成/并同步调整执行器的addresses。改完之后清一次浏览器缓存302 跳转会被缓存住不清的话看起来像没生效。6. 装完怎么验证从 Pod Running 到一次真实调度Pod 显示 Running 只能说明进程起来了离「能跑任务」还有距离。我按四层往下验每一层都有明确的可观察信号。第一层Pod 和端点。kubectl get pods -n xxl-job要看到READY 1/1READY显示0/1说明 readiness 探针没过大概率是登录页打不开。再看 Service 有没有把 Pod 挂上kubectl get pods -n xxl-job -o wide kubectl get endpoints xxl-job-admin -n xxl-jobENDPOINTS那列应该是8080/TCP加两个 Pod IP为空说明 selector 对不上或者探针没过。第二层HTTP 可达。从集群外打 Ingress从集群内打 Service# 期望返回 200返回 404 查 Ingress 路径返回 503 查 Service 后端 curl -s -o /dev/null -w %{http_code}\n http://xxl-job.internal/xxl-job-admin/toLogin # 集群内直接打 Service验证服务发现 kubectl run curl-test --rm -it --imagecurlimages/curl -n xxl-job -- \ curl -s -o /dev/null -w %{http_code}\n \ http://xxl-job-admin.xxl-job.svc.cluster.local:8080/xxl-job-admin/toLogin第三层页面和配置。登录后能看到执行器组说明数据库读写正常改一次密码能保存成功说明xxl_job_user表可写。第四层真实调度。建一个 GLUE(Shell) 任务脚本写echo hello-xxl-job路由策略选「第一个」点「执行一次」然后查库-- trigger_code200 表示调度成功handle_code200 表示执行成功 SELECT id, job_id, trigger_time, trigger_code, trigger_msg, handle_code, handle_msg FROM xxl_job_log ORDER BY id DESC LIMIT 5;trigger_code不是 200看trigger_msg一般是执行器地址不通handle_code不是 200看handle_msg那是脚本本身的错误。两个都 200这套环境才算真跑通。从那以后我每次上完一套调度中心不管 YAML 多顺、Pod 多绿都强制走一遍手动触发加查库这两步。页面打开成功不代表调度链路通任务真跑过一次、日志里能查到记录才算交付。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站