exec探针无法检测定时任务真实活性,因其仅确认进程存在,而无法感知goroutine阻塞、连接泄漏、消费者断连等静默失效;必须通过/livez端点验证主循环心跳(如检查最近执行时间戳),并用/readyz独立探测下游依赖,配合合理探针参数避免误判。

不能只用 exec 探针,必须暴露 /healthz 或 /livez 端点——Kubernetes 无法从“进程存在”推断“定时任务还在健康运行”。
为什么 exec 探针对定时任务容器基本无效
exec 探针常写成 ps aux | grep myjob 或 kill -0 $PID,但这只能确认主 goroutine 没退出。实际中,定时任务容器常见失效模式是:
- goroutine 卡死在 select/case 或 channel 阻塞,进程仍在但任务永不触发
- 数据库连接泄漏导致后续 cron 执行时 panic,但进程未崩溃(被 recover 捕获)
- 消息队列消费者连接断开后未重连,goroutine 空转不报错
- 配置加载失败,但初始化阶段没 panic,后续每次 run 都跳过逻辑
这些情况 exec 都检测不到。Kubernetes 会持续认为 Pod “healthy”,流量或依赖方照常调用,结果静默失败。
/livez 必须检查主循环是否仍在推进
/livez 不是“进程活着就行”,而是要验证定时逻辑的“心跳”是否真实跳动。核心是记录并暴露最近一次成功执行的时间戳,由 handler 对比当前时间判断是否超期。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 用
sync/atomic更新一个int64类型的lastRunAt,每次 cron 成功完成时调用atomic.StoreInt64(&lastRunAt, time.Now().Unix()) -
/livezhandler 中读取该值:if time.Since(time.Unix(atomic.LoadInt64(&lastRunAt), 0)) > 2 * time.Minute { http.Error(w, "stale", http.StatusInternalServerError); return } - 不要查 goroutine 数量或内存——它们和定时任务活性无直接关系;也不要调用
db.Ping(),那是/readyz的事 - 响应必须在 100ms 内完成,禁止任何 I/O、锁竞争或日志写入
/readyz 要反映下游依赖是否可支撑下一次执行
定时任务能否跑下去,取决于它下次执行前依赖是否 ready。比如 Kafka 消费者需确保 group coordinator 可达,数据库备份任务需确认 PG 连接池可用。
-
/readyz中做轻量探测:用context.WithTimeout(ctx, 800*time.Millisecond)调用kafkaClient.ListTopics()或db.PingContext() - 每个依赖单独探测、独立超时,用
errgroup.Group并发执行,避免单点拖慢整体响应 - 失败时不返回 500,而用
http.StatusServiceUnavailable——这样 readinessProbe 失败只会摘流量,不会重启容器 - 别缓存探测结果超过 5 秒:Kubernetes 默认每 10 秒探一次,缓存太久会导致状态滞后
Kubernetes 探针参数必须匹配定时周期
如果任务每 5 分钟跑一次,livenessProbe 的 periodSeconds 设成 10 就太激进——网络抖动一次就可能误判。
-
initialDelaySeconds至少设为任务首次加载+预热耗时 + 30s 宽裕(比如配置解析、DB 连接池填充) -
periodSeconds建议设为定时周期的 1/3~1/2,例如任务间隔 5min,则设periodSeconds: 120 -
failureThreshold设为 3,容忍连续两次探测失败(比如 GC STW 或短暂网络分区) -
timeoutSeconds必须 ≤periodSeconds,否则探测堆积;建议固定为 2,与 handler 内部超时一致
最易忽略的是:定时任务容器往往没有 HTTP server,但 livenessProbe.httpGet 仍要求端口开放。务必启动一个最小化 http.Server 仅服务 /livez 和 /readyz,别试图复用业务端口——那会把健康检查和业务请求耦合在一起,放大风险。

















