资讯详情

资讯详情

XXL-JOB 容器化部署验证:K8s 调度中心、执行器与优雅停机避坑

简介面向需要在 Kubernetes 集群中落地 XXL-JOB 的运维与开发人员这份 YAML 部署清单可用于快速验证容器化任务调度服务的集群部署流程减少从零编写资源清单、反复排查配置错误的时间成本。压缩包共 1 个文件为 YAML 类型的 Kubernetes 资源定义体积约 782B内容集中精炼便于直接应用、对照修改或纳入版本管理。已有 647 人学习下载说明该部署方式在同类场景中具有一定参考价值。读者可从中获取 XXL-JOB 在 K8s 环境下的资源组织示例理解镜像、服务暴露及部署参数的基本配置思路并以此为基础完成部署验证、环境适配和后续扩展对于正在搭建测试集群、需要一份可运行 YAML 模板的读者也能提供直接的起点与排错参照。1. XXL-JOB 部署到 K8sPod 全绿只是开始验证要走完三步清单 apply 下去三个 Pod 全绿浏览器能打开调度中心登录页很多人到这儿就收工了。可第一次点“执行一次”控制台弹出java.net.ConnectException执行器管理页里机器那一栏是空的——这才是 Kubernetes 上跑 XXL-JOB 真正的分水岭。这份验证版 YAML 的价值不在于“能启动”而在于把调度中心、执行器、库表初始化、探针路径、时区、优雅停机这些默认值提前固定下来让一套集群部署可以重复 apply、可以被别人接手复现。它适合已经在虚拟机上跑过 XXL-JOB、准备迁到集群的团队也适合接手别人清单、需要快速判断“这套东西到底能不能直接用”的从业者。2. 拆开这份 YAML 清单调度中心、执行器、数据库各自占哪一格拿到一套号称“直接部署成功”的清单我一般不会先 apply而是先数 kind。数完你就知道这套东西的边界在哪它替你处理了哪些默认值哪些还得自己填。XXL-JOB 在容器化场景下被拆成三个进程它们对 K8s 的诉求完全不同混在一个清单里写后面排查会非常难受。2.1 三个进程与三类 K8s 资源的对应关系先把对应关系摆清楚后面的所有参数才有落点。进程典型 K8s 资源状态性关键约束xxl-job-admin 调度中心Deployment Service按需加 Ingress无状态可多副本多副本必须共享同一个 MySQL注册信息走库不走内存执行器内嵌 xxl-job-core 的业务应用Deployment Service无状态但注册地址必须可预期端口要能被 admin 直连注册上去的地址不能随 Pod 重建而漂移MySQL外部实例或 StatefulSet PVC有状态库丢了任务配置、执行器分组、调度日志全丢调度中心是整个体系里唯一“看起来无状态、实际上强依赖数据库”的组件。它自己不做任务存储所有执行器分组、任务定义、调度记录都落在xxl_job_group、xxl_job_info、xxl_job_log这几张表里。所以 admin 扩容到 2 个副本是安全的前提是两个副本连的是同一个库并且xxl.job.accessToken完全一致。执行器这块最容易出问题。xxl-job-core 启动时会向调度中心注册自己注册内容是一个 HTTP 地址。在虚拟机上这个地址就是固定的内网 IP写死也行在 K8s 里Pod 每次重建 IP 都变如果让框架自动取本机 IP注册表里留下的就是一条会过期的记录。这也是后面第 4 章要重点讲的地方。数据库放哪决定了这份清单是一份还是两份。用云上的托管实例清单里就只剩 admin 和执行器用集群内自建就得多一个 StatefulSet、一个 headless Service 和一份初始化 SQL。2.2 部署前必须定死的常量以下这几个值在写第一行 YAML 之前就要定下来。中途改一个往往要连带改探针、Service、Ingress、执行器注册地址四处。常量建议值影响面admin 端口8080Service targetPort、探针、Ingress backendadmin context-path/xxl-job-admin探针路径、注册地址、Ingress path三个地方必须一字不差执行器端口9999Service port、注册地址、admin 侧连通性执行器日志目录/data/applogs/xxl-job/jobhandlerPVC 挂载点、磁盘容量规划、日志清理策略时区Asia/Shanghaicron 触发时间、调度日志时间戳accessToken自定义强随机串admin 与所有执行器必须完全一致context-path 这一项坑最多。XXL-JOB 的 admin 默认带/xxl-job-admin前缀很多人写探针时按习惯写/或/actuator/health容器起来后探针一直 404kubelet 判定不健康Pod 被反复重启日志里却干干净净写着Started XxlJobAdminApplication。这不是应用的问题是探针路径的问题。时区同样别省。容器基础镜像默认 UTCJVM 跟着系统时区走你配的0 0 2 * * ?会在北京时间上午十点触发而且调度日志里的时间戳全部差 8 小时排查的时候会以为自己看错了表。提示这几个常量建议直接写进清单顶部的注释块改的时候一眼能看到影响范围比散落在十几个文件里强。2.3 数据库为什么不建议和执行器挤在同一份清单有人图省事把 MySQL、admin、执行器写成一个大 YAMLapply 一次全起来。演示环境没问题一旦进入滚动更新就会出事MySQL 的 Pod 重启期间admin 的数据库连接池全部失效健康检查失败被判定为不健康执行器此时心跳注册也失败注册表里的记录在清理周期后被摘掉等数据库恢复调度中心那边已经认为“这台机器下线了”。更合理的组织方式是按职责拆文件并且固定 apply 顺序# 按依赖顺序 apply顺序错了会出现 admin 连不上库、执行器注册到不存在的 admin kubectl apply -f 00-namespace.yaml kubectl apply -f 01-configmap.yaml # admin 与执行器的 application.properties kubectl apply -f 02-secret.yaml # 数据库密码、accessToken kubectl apply -f 03-mysql.yaml # 用外部托管库时跳过这一步 kubectl apply -f 04-admin.yaml # Deployment Service kubectl apply -f 05-executor.yaml # 业务应用 Service00到05这个编号本身就是文档。新人接手时不用问“先起哪个”按编号敲下去就行。这套顺序里唯一可以乱的是 ConfigMap 和 Secret它们只是被挂载不参与启动依赖但 admin 必须晚于数据库就绪执行器必须晚于 admin 的 Service 可解析这两条不能违背。另外把数据库和执行器放同一份清单还有个隐性代价kubectl delete -f的时候会一起删。生产环境里手滑一次任务定义全没。分开写至少多一道确认。3. 调度中心落地ConfigMap 拆配置、Job 灌库表、Deployment 配探针调度中心是这套体系的门面它的部署质量直接决定了后面执行器能不能顺利挂上来。这一章按“配置外置 → 库表初始化 → 工作负载与探针”三步走每一步都给可直接抄的片段。3.1 配置外置把 application.properties 拆进 ConfigMap 与 Secret不建议把配置打进镜像。同一个镜像要在测试、预发、生产三套环境跑唯一的差别就是数据库地址和 token打进镜像意味着要构建三次。apiVersion: v1 kind: ConfigMap metadata: name: xxl-job-admin-config namespace: xxl-job data: application.properties: | server.port8080 # 这个前缀会同时影响探针路径、注册地址和 Ingress 转发规则 server.servlet.context-path/xxl-job-admin spring.datasource.urljdbc:mysql://mysql.xxl-job.svc.cluster.local:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse spring.datasource.usernamexxl_job spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver xxl.job.accessTokenchange-me-please xxl.job.i18nzh_CN # 调度日志保留天数配合调度中心自身的清理线程 xxl.job.logretentiondays30数据库密码不进 ConfigMap走 SecretapiVersion: v1 kind: Secret metadata: name: xxl-job-admin-secret namespace: xxl-job type: Opaque stringData: # 键名按 Spring 的宽松绑定规则写容器里会自动映射到 spring.datasource.password SPRING_DATASOURCE_PASSWORD: replace-with-your-password MYSQL_ROOT_PASSWORD: replace-with-root-password这里有两个细节值得说。第一属性文件名必须是application.properties挂到容器的/config目录后还得通过SPRING_CONFIG_ADDITIONAL_LOCATIONfile:/config/application.properties显式告诉 Spring Boot 去哪儿读否则它会按默认的 classpath 顺序找你挂进去的文件根本不会被加载。第二xxl.job.accessToken在 ConfigMap 里是明文这在实际项目里通常不能接受稳妥做法是同样放进 Secret用环境变量XXL_JOB_ACCESSTOKEN注入——Spring 的宽松绑定会把xxl.job.accessToken映射到这个变量名配置文件里就不用再写一行。注意改完 ConfigMap 后 Pod 不会自动重启。要么kubectl rollout restart deployment/xxl-job-admin要么在 Deployment 的 podTemplate 上打个配置版本号的 annotation 触发滚动更新。3.2 库表初始化用一个一次性 Job 灌 SQL而不是塞进 admin 的启动脚本XXL-JOB 的库表结构来自官方提供的tables_xxl_job.sql。容器化部署里常见的两种做法塞进 MySQL 镜像的/docker-entrypoint-initdb.d或者用 initContainer。前者只在数据目录为空时执行一次云上托管库根本用不上后者要求 admin 和 MySQL 在同一个 Pod 里也不现实。这两种做法都有个共同风险那份 SQL 脚本开头是DROP TABLE IF EXISTS重复执行会把线上任务配置清空。所以初始化必须是一次性的、幂等的、可人工确认的。apiVersion: batch/v1 kind: Job metadata: name: xxl-job-dbinit namespace: xxl-job spec: backoffLimit: 0 # 失败不重试避免反复执行 DROP template: spec: restartPolicy: Never containers: - name: dbinit image: mysql:8.0 command: [/bin/sh, -c] args: - mysql -h mysql.xxl-job.svc.cluster.local -u root -p$MYSQL_ROOT_PASSWORD -e CREATE DATABASE IF NOT EXISTS xxl_job DEFAULT CHARACTER SET utf8mb4; mysql -h mysql.xxl-job.svc.cluster.local -u root -p$MYSQL_ROOT_PASSWORD xxl_job /sql/tables_xxl_job.sql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: xxl-job-admin-secret key: MYSQL_ROOT_PASSWORD volumeMounts: - name: sql mountPath: /sql volumes: - name: sql configMap: name: xxl-job-init-sqlSQL 文件本身通过 ConfigMap 挂进去建 ConfigMap 的命令只有一行# 把官方库表脚本挂成 ConfigMap注意单文件不能超过 1MiB kubectl -n xxl-job create configmap xxl-job-init-sql \ --from-filetables_xxl_job.sql./tables_xxl_job.sqlbackoffLimit: 0是这里最关键的一个参数。默认值是 6一旦 SQL 执行到一半失败Job 会重试六次而脚本里的DROP TABLE每次都会先执行如果库中已经有数据就是六次清空。设成 0 之后失败就停在那里人上去看日志再决定手动重跑。跑完之后记得把 Job 删掉kubectl -n xxl-job delete job xxl-job-dbinit。留着它不影响运行但下次有人kubectl apply -f整个目录时会重新跑一遍那就真出事了。我一般会把这份 Job YAML 单独放在init/子目录里不跟常规清单混在一起。3.3 Deployment 与 Service探针路径必须带 context-path工作负载这一块新手最容易翻车的是探针。先看清单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 # 换成你自己仓库里的镜像地址不要直接引用公网 tag image: your-registry/xxl-job/xxl-job-admin:2.x ports: - containerPort: 8080 env: - name: TZ value: Asia/Shanghai - name: JAVA_OPTS value: -Xms512m -Xmx512m -Duser.timezoneAsia/Shanghai - name: SPRING_CONFIG_ADDITIONAL_LOCATION value: file:/config/application.properties - name: SPRING_DATASOURCE_PASSWORD valueFrom: secretKeyRef: name: xxl-job-admin-secret key: SPRING_DATASOURCE_PASSWORD volumeMounts: - name: config mountPath: /config - name: logs mountPath: /data/applogs # 启动慢的应用先用 startupProbe 兜底避免 liveness 过早介入 startupProbe: httpGet: path: /xxl-job-admin/ port: 8080 failureThreshold: 30 periodSeconds: 5 readinessProbe: httpGet: path: /xxl-job-admin/ port: 8080 periodSeconds: 10 timeoutSeconds: 3 livenessProbe: httpGet: path: /xxl-job-admin/ port: 8080 periodSeconds: 20 failureThreshold: 3 volumes: - name: config configMap: name: xxl-job-admin-config - name: logs persistentVolumeClaim: claimName: xxl-job-logs --- apiVersion: v1 kind: Service metadata: name: xxl-job-admin namespace: xxl-job spec: selector: app: xxl-job-admin ports: - name: http port: 8080 targetPort: 8080逻辑说明三种探针分工不同。startupProbe只管启动阶段成功一次之后就不再执行failureThreshold: 30配合periodSeconds: 5给了 150 秒启动窗口JVM 冷启动加上数据库连接池初始化通常 40 到 90 秒够用。readinessProbe决定 Pod 是否接流量探针失败只是从 Endpoints 里摘掉不会重启。livenessProbe才会重启容器所以它的阈值要放宽failureThreshold: 3配 20 秒间隔意味着连续 60 秒不可用才动手。如果镜像的启动脚本不吃JAVA_OPTS上面的 JVM 参数会被忽略。验证办法很简单kubectl -n xxl-job exec deploy/xxl-job-admin -- ps -ef | grep java看进程命令行里有没有带上-Duser.timezone。没有的话用args直接覆盖 entrypoint或者把参数写进JAVA_TOOL_OPTIONS环境变量——后者被 JVM 无条件读取兼容性最好。Service 这一层没什么花样port和targetPort都写 8080 就行。真正要注意的是名字xxl-job-admin这个 Service 名会被执行器写进注册地址里改名字等于改所有执行器的配置。4. 执行器接入注册地址写 Service DNS还是让框架自己取 IP调度中心起来只是把台子搭好执行器注册不上一切都是空转。这一章是整套部署里信息密度最高的部分也是“虚拟机思维”和“集群思维”差别最大的地方。4.1 执行器的三个必填参数执行器本质上是你自己的业务应用加一个xxl-job-core依赖。它的核心配置项不多但每一项都有默认值和优先级理解优先级比记住字段名更重要。apiVersion: v1 kind: ConfigMap metadata: name: xxl-job-executor-config namespace: xxl-job data: application.properties: | server.port8081 # 调度中心地址多副本时用逗号分隔多个地址 xxl.job.admin.addresseshttp://xxl-job-admin.xxl-job.svc.cluster.local:8080/xxl-job-admin # 必须与调度中心完全一致不一致会报 access token 校验失败 xxl.job.accessTokenchange-me-please # 执行器分组名调度中心的任务按这个名字找机器 xxl.job.executor.appnamexxl-job-executor-sample # 关键显式声明对外暴露地址不要让框架自己取 Pod IP xxl.job.executor.addresshttp://xxl-job-executor.xxl-job.svc.cluster.local:9999/ xxl.job.executor.port9999 xxl.job.executor.logpath/data/applogs/xxl-job/jobhandler xxl.job.executor.logretentiondays30参数作用不填的后果xxl.job.admin.addresses注册目标多个用逗号分隔执行器完全不注册调度中心看不到机器xxl.job.executor.appname执行器分组标识任务绑定的分组找不到机器报“执行器地址为空”xxl.job.executor.address显式对外地址优先级最高退回 ip:port再退回自动取本机 IPxxl.job.executor.port内置 Netty 服务端口默认 9999与 Service 端口不一致会导致连不通xxl.job.accessToken双向校验报 “The access token is wrong”注册被拒address、ip、port三者的优先级是固定的address非空就直接用address否则用ip:port拼接两者都没有才走NetUtils.getIp()自动获取。K8s 里 Pod IP 是动态分配的自动获取等于把一个会过期的值写进注册表。4.2 为什么要把地址钉在 Service DNS 上注册表xxl_job_registry里存的是registry_key执行器分组、registry_value注册地址、update_time心跳时间。执行器约 30 秒发一次心跳调度中心有个清理线程把超时未更新的记录删掉。整个机制在虚拟机上是成立的因为 IP 不变在 K8s 里就变成了“和清理线程赛跑”。设想一个典型场景执行器 Deployment 有 3 个副本Pod IP 分别是 10.244.1.7、10.244.1.9、10.244.2.3。某次发布后三个 Pod 全部重建新 IP 是 10.244.2.11 到 10.244.2.13。如果执行器配置的是自动取 IP注册表里很快会变成新地址看起来没事——但如果调度中心在旧记录还没过期的那一刻下发任务请求就会打到已经不存在的 IP 上报连接超时。更糟的情况是 Pod IP 被复用旧 IP 被分配给了集群里另一个服务任务就发到完全不相干的地方去了。把xxl.job.executor.address写成 Service 的全限定域名等于让注册表存一个永久有效的逻辑地址http://service-name.namespace.svc.cluster.local:port/Service 的 ClusterIP 在整个生命周期内不变kube-proxy 负责把请求转发到当前健康的 Pod。执行器重建多少次注册表里的值都是同一个调度中心的视角里这台机器只是在持续心跳而已。注意这种写法要求调度中心和执行器在同一个集群内且能解析集群 DNS。跨集群或者执行器部署在集群外时得换成 NodePort、Ingress 或专线可达的地址别硬套.svc.cluster.local。还有一个边界address写成一个 Service 名之后注册表里就只能看到一个地址即使你有 10 个执行器副本。这对调度来说是好事——调度中心会按地址做负载均衡但对排查来说是坏事——你没法从控制台的“在线机器”列表里区分是哪个 Pod 在跑。折中方案是用 headless Service 配合xxl.job.executor.address留空、改配xxl.job.executor.ip指向固定网络标识代价是运维复杂度上升一般项目用不到。4.3 滚动更新与优雅停机terminationGracePeriodSeconds 怎么给默认的优雅停机时间是 30 秒。执行器收到 SIGTERM 后会停止接受新任务但正在跑的任务线程不会立刻中断——它没有强制熔断机制JVM 退出时线程直接被杀调度中心那边看到的就是任务执行到一半失联日志表里状态卡在“运行中”。apiVersion: apps/v1 kind: Deployment metadata: name: xxl-job-executor namespace: xxl-job spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 # 保证发布期间至少有一个可用副本 maxSurge: 1 template: metadata: labels: app: xxl-job-executor spec: # 给足时间让在跑的任务收尾按你最长任务的耗时来定 terminationGracePeriodSeconds: 90 containers: - name: executor image: your-registry/your-app/xxl-job-executor:2.x ports: - containerPort: 8081 # 业务 HTTP 端口 - containerPort: 9999 # xxl-job 内置服务端口 lifecycle: preStop: exec: # 先从 Service 上摘流量再让应用自己停 command: [/bin/sh, -c, sleep 20]三段逻辑要配合起来看。maxUnavailable: 0保证滚动更新时始终有副本在服务preStop里的 20 秒是给 kube-proxy 更新 Endpoints 留的窗口避免新请求还打到正在关闭的 PodterminationGracePeriodSeconds: 90是从发出 SIGTERM 到 SIGKILL 的总时长preStop的 20 秒也算在这个总量里所以实际留给业务收尾的是 70 秒。这个数字不是拍脑袋定的。先看调度中心里配置的任务超时时间再取最长任务的正常执行耗时往上加 30 秒缓冲。如果某个任务本身就设了 300 秒超时那 90 秒肯定不够得往上调反过来说所有任务都在 5 秒内结束给 90 秒只是让发布变慢没必要。提示调度中心侧还有一个保险——任务配置里可以开“失败重试”配合“路由策略”里的故障转移能在执行器被 kill 后重新路由到别的副本。但重试是补偿手段不能代替优雅停机重复执行带来的幂等问题更麻烦。5. 避坑与排查上线后最容易翻车的五个现场下面这五条是我在实际环境里遇到过的、也是验证版清单最容易漏掉的地方。每条按现象、原因、解决三段写遇到的时候可以直接对照。5.1 执行器列表显示在线任务点下去还是连接失败现象调度中心的“执行器管理”页面能看到地址和在线机器点“执行一次”后控制台报ConnectException日志表里记录状态为失败。原因注册表是有缓存的。执行器心跳失败后注册记录并不会立刻消失清理线程按周期扫超时项这中间有个窗口期。Pod 重建后 IP 变了旧记录还在表里调度中心按旧地址下发自然连不上。另一种情况是xxl.job.executor.address只写了ip:port而没写协议头调度中心拼出来的 URL 不合法。解决先把address改成 Service 全限定域名并加上http://前缀和结尾的/然后手动查一下残留记录确认清理线程在正常工作-- 找出心跳超过 90 秒没更新的注册记录正常情况应该为空 SELECT registry_key, registry_value, update_time FROM xxl_job_registry WHERE update_time DATE_SUB(NOW(), INTERVAL 90 SECOND);5.2 探针把健康的 Pod 反复重启现象kubectl get pods里 RESTARTS 一路涨到几十kubectl logs里明明是正常启动的日志没有异常堆栈。原因九成是探针路径不对。admin 的 context-path 是/xxl-job-admin有人按习惯写/或/actuator/health。Spring Boot 默认不暴露 actuator 端点health 路径直接 404路径写成/则返回 302 跳转kubelet 把 3xx 当作失败处理。剩下的一成是initialDelaySeconds太短JVM 还没起完 liveness 就开始探测。解决探针路径统一写/xxl-job-admin/末尾的斜杠别省liveness 前面挂一个 startupProbe如果临时排查可以先把 liveness 整段注释掉确认应用本身没问题再加回来。5.3 cron 配的凌晨两点实际上午十点才触发现象任务配置0 0 2 * * ?日志显示的执行时间是 10:00 左右调度日志的时间戳也整体偏移。原因容器基础镜像是 UTC 时区JVM 启动时读系统时区Spring 的Scheduled和 Quartz 表达式都按这个时区解释。TZ环境变量没设或者设了但 JVM 在之前就读过默认时区都可能导致偏移。解决环境变量TZAsia/Shanghai和 JVM 参数-Duser.timezoneAsia/Shanghai两个都加前者影响系统调用后者影响 JVM 内部时间。数据库连接串里的serverTimezoneAsia/Shanghai也补上否则写入xxl_job_log的时间戳还会再偏一次。改完在容器里执行date和kubectl exec ... -- jshell -q -e System.out.println(java.time.ZoneId.systemDefault())双向确认。5.4 日志把节点磁盘写满Pod 被驱逐现象节点状态变成DiskPressure其他不相干的 Pod 被驱逐kubectl describe node显示镜像和日志占满了根分区。原因xxl.job.executor.logpath落在容器可写层也就是/data/applogs/xxl-job/jobhandler没挂卷。日志按天分目录写每个任务每次执行一个文件高频任务一天能写几个 G。容器重启后日志虽然在但节点上的空间已经被占了而且logretentiondays的清理逻辑依赖执行器自己的线程容器被删掉后这些文件没人管。解决把/data/applogs挂到 PVC 或 hostPath并在节点上配一层日志轮转做双保险xxl.job.executor.logretentiondays按业务频率调小高频任务建议 7 天如果日志要长期留存加个 sidecar 把文件投到日志平台本地只留三天。5.5 滚动更新期间部分任务中断现象发布过程中调度日志里出现几条失败记录错误信息是连接被重置或者响应超时重试后成功。原因默认 30 秒优雅期太短preStop没配kube-proxy 更新 Endpoints 有延迟SIGTERM 发出去的时候应用还在处理任务。另外maxUnavailable默认 25%两副本的 Deployment 在更新时可能同时摘掉一个剩余容量不够扛。解决按第 4.3 节的配置改terminationGracePeriodSeconds、preStop和maxUnavailable: 0同时在调度中心给任务打开失败重试把重试次数设成 1业务侧保证幂等。这三件事缺一件都还会出问题。6. 验证闭环三条命令确认这次部署真的成了Pod 全绿是必要条件不是充分条件。我习惯用三步确认每一步都能独立证伪。验证项操作位置期望结果调度中心可达浏览器或 curl 访问/xxl-job-admin/返回 200登录页正常渲染网络路径通畅从 admin 容器内 curl 执行器地址返回 200 或 405不是超时注册记录新鲜查xxl_job_registry表update_time在 60 秒内第二步是最有价值的一步因为它把“服务发现”和“应用逻辑”隔离开了# 从调度中心容器里反向验证执行器地址通不通 # 这一步能排除 DNS 解析、Service 选择器、NetworkPolicy 三类问题 kubectl -n xxl-job exec deploy/xxl-job-admin -- \ curl -s -o /dev/null -w %{http_code}\n \ http://xxl-job-executor.xxl-job.svc.cluster.local:9999/ # 确认注册表里的心跳是新鲜的而不是几天前的残留记录 kubectl -n xxl-job exec -it mysql-0 -- \ mysql -uxxl_job -p$MYSQL_PASSWORD xxl_job \ -e SELECT registry_key, registry_value, update_time FROM xxl_job_registry;如果第二步返回 000说明根本没连上去查 Service 的 selector 是否匹配 Pod 标签返回 404 说明连上了但路径不对检查xxl.job.executor.address结尾有没有斜杠返回 200 就说明网络链路完全打通剩下的问题只可能在 token 校验或者分组名上。第三步经常被忽略。注册表里的旧记录不会自己消失得那么快看到一条记录就以为注册成功实际可能是几天前留下的。update_time必须落在心跳周期内才是真的活着。最后说一个习惯从那以后我每次改完 YAML都会先把 admin 的 Deployment 缩到 0 再拉起来模拟一次完整的冷启动因为滚动更新掩盖了太多初始化顺序问题——数据库连接、注册表重建、日志目录创建这些只在冷启动时才会暴露。真正的验证不是“它能跑”而是“把它全删了重来一遍它还能跑”。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →