livenessprobe 和 readinessprobe 的 timeoutseconds 不应过小,否则会因探针超时导致 pod 重启循环;建议 initialdelayseconds 设为冷启动时间加2~3秒缓冲,timeoutseconds 不低于3秒(推荐5秒),periodseconds 不宜过小(推荐10秒),且需区分 liveness(如 /healthz)与 readiness(如 /readyz)端点,go 应用须显式暴露初始化状态并避免 probe 中阻塞操作。

livenessProbe 和 readinessProbe 的 timeoutSeconds 不能设太小
Pod 启动慢时,探针超时会直接触发重启循环,不是应用没起来,是探针把应用“杀”了。K8s 默认 timeoutSeconds 是 1 秒,而 Go 应用加载模块、连 DB、预热缓存常需数秒——尤其带 gRPC 注册、Prometheus 指标初始化或大体积 embed 文件的场景。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
-
initialDelaySeconds至少设为预估冷启动时间 + 2~3 秒缓冲(比如本地测出 6s 启动,这里填 9) -
timeoutSeconds不要低于 3,Go HTTP 探针在 GC 停顿或 mmap 大文件时可能卡住,设 5 更稳妥 - 避免把
periodSeconds设成 2 —— 高频探测+低 timeout 容易误判,10 是更安全的起点 - 不要复用同一端点做 liveness 和 readiness:liveness 只查进程存活(如
/healthz返回 200),readiness 查依赖就绪(如/readyz检 DB 连接)
Go 应用内建健康检查端点必须区分启动阶段和运行阶段
很多团队只写一个 /health 返回 200,结果 Pod 还在 load config、初始化 Redis client,探针已通过,流量进来就 panic。Go 里没有“自动预热感知”,得自己暴露状态机。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
sync.Once标记关键初始化完成,比如initDBOnce.Do(connectDB),然后在 handler 里检查dbReady.Load() - HTTP handler 中避免阻塞调用:
/readyz不该同步 ping MySQL,而应查本地dbReady原子变量 + 最近一次连接池健康检查时间戳 - 对耗时预热(如加载大模型权重、warm up JIT 编译器),单独起 goroutine 异步做,主 handler 立即返回状态,别等它
- 加个
/startupz端点专供 initContainer 或 postStart hook 轮询,只返回{"phase": "loading_config"}这类字符串,不走完整中间件链
PostStart hook 执行失败会导致 Pod 卡在 ContainerCreating
有人想用 postStart 触发 Go 应用内部预热,但 hook 是 shell 命令,无法直接调用 Go 函数;若写成 curl http://localhost:8080/warmup,又大概率因端口未监听而失败,Pod 永远起不来。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 绝不在
postStart里调用本容器的 localhost 接口——容器网络栈尚未 ready,curl 必败 - 如果真需要外部触发预热,改用 InitContainer:先跑一个 busybox 容器 sleep 5s,再启动主容器,靠主容器自己的
init()或main()开始时延时+重试逻辑来等依赖 - 更可靠的方式是让 Go 应用启动后主动轮询自身 readiness 端点(比如用
http.Get("http://localhost:8080/readyz")),成功才退出 main,这样 K8s 不会提前发流量 - 注意:InitContainer 里不能依赖 ConfigMap/Secret 尚未挂载完成的状态,
ls /config可能报错,要用stat+ 重试判断
Probe 使用 exec 方式调用 Go 二进制会放大启动延迟
有些团队为“精确控制”改用 exec 探针,比如 command: ["sh", "-c", "/app/myapp healthcheck"],结果每次探测都 fork 新进程、加载 runtime、初始化 goroutine 调度器——单次耗时从毫秒级跳到 300ms+,还可能触发 cgroup CPU throttle。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 一律用
httpGet探针,哪怕只是最简http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) - 避免在 probe handler 里打日志(
log.Println)、写磁盘、或调用time.Now().UnixNano()—— 高频探测下 syscall 开销明显 - 如果必须用 exec(比如检查某个 socket 文件是否存在),命令务必用
stat -c "%s" /tmp/sock 2>/dev/null这类轻量操作,禁用ps aux | grep或任何管道 - Go 编译时加
-ldflags="-s -w"减小二进制体积,降低 exec fork 成本,但这只是补救,不如换 httpGet
预热不是加个探针就能解决的事,它本质是把“应用状态”显式暴露给调度系统。Go 程序里那些隐式的 init 函数、包级变量赋值、sync.Once 初始化,全得变成可查询的 HTTP 状态,否则 K8s 看不见,只会按固定节奏杀与启。










