根本原因是livenessProbe探针失败,因Go服务未暴露/health或/livez等K8s要求的存活端点,导致kubelet请求404触发CrashLoopBackOff;需显式注册轻量级/livez(仅检查本地状态)和/readyz(并行检查依赖),严格匹配路径端口,并实现优雅终止。

为什么/health返回200但服务还是被反复重启
根本原因是livenessProbe探针失败,而Go服务没暴露符合K8s语义的存活端点。Gin或net/http默认不注册/health、/livez这类路径,kubelet发GET请求直接404,连续失败后触发CrashLoopBackOff。这不是代码bug,是集成缺失。
实操建议:
- 必须显式注册轻量级
/livez端点,只检查本地状态:HTTP listener是否已启动、goroutine数量是否突增、内存使用是否超阈值 - 禁止在
/livez里调用db.PingContext、http.Get、time.Sleep——任何外部依赖或阻塞操作都会导致超时失败 - 响应必须≤1秒:给
http.Server设ReadTimeout: 5 * time.Second和WriteTimeout: 10 * time.Second,防GC STW卡住探针 - 别用
http.DefaultServeMux,改用http.NewServeMux()创建独立实例,避免其他包注册冲突
readinessProbe失败却始终503,流量进不来
就绪探针失败意味着Pod没被加进Service的Endpoints,但常见误区是把/readyz写成和/livez一样的空检查,或者查DB时没做连接池复用和超时控制,结果每次探测都新建连接,压垮数据库后自己也挂掉。
实操建议:
-
/readyz必须并行检查所有关键依赖:DB用db.Stats().OpenConnections > 0快速过滤,再用db.QueryRowContext(ctx, "SELECT 1")轻量验证 - 所有依赖检查共用同一个带超时的
context.Context(如ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)),任一失败即整体返回503 - 别在handler里实时探测——启动后台goroutine每5秒检查一次,结果缓存在
sync.RWMutex保护的结构体中,/readyz只读快照 - 确保
failureThreshold≥3,periodSeconds≥10,避免DB瞬时抖动导致误摘流量
probe配置和Go代码端口/路径不一致
K8s探针发请求时,如果httpGet.path写成/health但Go只注册了/readyz,或httpGet.port写成8080但Go监听的是:80,就会直接连接拒绝或404。这类错误在日志里只显示Get "http://10.244.1.5:8080/health": dial tcp 10.244.1.5:8080: connect: connection refused,非常隐蔽。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
实操建议:
- Go代码里用独立端口暴露探针:比如业务走
:8080,/livez和/readyz走:8081,然后Deployment中声明两个containerPort - 确保
livenessProbe.httpGet.path和readinessProbe.httpGet.path严格匹配Go注册的路由,大小写、斜杠都不能错 - 检查Pod的
targetPort是否与容器内监听端口一致,常见错误是YAML写了targetPort: 8080但Go代码是http.ListenAndServe(":80", mux) - 用
kubectl port-forward pod/手动curl验证,比看Events更直接8081:8081
健康检查失败时Go进程没优雅退出
当/readyz持续失败,你希望服务自动下线并等请求处理完再停,而不是被kubelet暴力kill。但很多Go项目用log.Fatal或没监听SIGTERM,导致K8s判定为“非优雅终止”,下次调度可能立刻又打进来一批请求。
实操建议:
- 用
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)捕获信号,收到后先置isReady = 0让/readyz返回503,再调srv.Shutdown() - 所有顶层goroutine(如消息消费、定时任务)必须包一层
recover,防止panic后goroutine静默退出却不影响探针 -
http.Server.RegisterOnShutdown只在srv.Shutdown()被调用时触发,它不能替代信号监听 - 用
atomic.StoreInt32(&isReady, 0)控制就绪状态,/readyzhandler中用atomic.LoadInt32(&isReady) == 0判断并返回503
真正难的不是写一个返回200的handler,而是让/livez和/readyz在高并发、依赖抖动、GC暂停、信号中断等各种现实条件下,依然能稳定输出正确状态码——这要求它们完全脱离业务逻辑链,有自己独立的生命周期、超时控制和错误隔离。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










