kubernetes健康检查需返回http 200状态码且逻辑轻量,/health路径须独立、无中间件;panic须用recover捕获,避免静默崩溃;优雅关闭需监听sigterm并调用srv.shutdown()。

Go HTTP 服务健康检查接口怎么写才不被 Kubernetes 杀掉
健康检查失败是容器被反复重启的最常见原因,不是因为代码没写,而是 livenessProbe 和 readinessProbe 对响应内容、状态码、超时的要求和你写的 http.HandleFunc 默认行为不匹配。
- 必须返回 HTTP 200,哪怕内部有警告——K8s 不看响应体,只认状态码;返回 503 或 404 就触发重启
- 路径要独立,别复用业务路由(比如
/health别写成/api/v1/health再套中间件),中间件里加日志、鉴权、耗时统计都会让探针超时 - 检查逻辑必须轻量:不查 DB 连接、不调下游服务、不读大文件;真要查依赖,单独用
/readyz,并配更长的timeoutSeconds和periodSeconds - 示例:
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) })
Go 错误没传给上层,panic 却在容器里静默崩溃
容器进程 exit code 非 0 但日志空空如也,大概率是 panic 没被捕获,而 Go 默认把 panic 输出到 os.Stderr —— Docker/K8s 日志采集器(如 fluentd)可能没配置捕获 stderr,或者程序启动时重定向了标准流。
- 所有顶层 goroutine 必须包一层
recover,尤其是http.Server.Serve、定时任务、消息消费协程 - 别依赖
log.Fatal做错误退出:它直接调os.Exit(1),绕过 defer 和 shutdown hook,K8s 认为“非优雅终止” - 用
http.Server.RegisterOnShutdown做资源清理,但注意:它只在srv.Shutdown()被显式调用时触发,SIGTERM 信号需自己监听并调用 - 示例启动逻辑:
srv := &http.Server{Addr: ":8080", Handler: mux} go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Printf("server error: %v", err) } }() signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) <-sigChan log.Println("shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(ctx)
错误上报只打日志,监控平台收不到指标
日志 ≠ 可观测性。Prometheus 抓不到 log.Printf("failed to write to cache: %v", err) 这种语句,它需要暴露 /metrics 端点 + 结构化指标。
- 用
prometheus.NewCounterVec定义带 label 的计数器,比如errors_total{service="auth",type="redis_timeout"} - 错误发生时,先上报指标再打日志:“先 metric 后 log”,避免日志刷屏掩盖指标突增
- 别在 defer 里上报错误指标——
err可能是 nil,或已被上层处理,重复计数会误导 SLO 计算 - HTTP handler 中典型模式:
func handleUser(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { errorsTotal.WithLabelValues("user_handler", "panic").Inc() log.Printf("panic in user handler: %v", r) } }() if err := doSomething(); err != nil { errorsTotal.WithLabelValues("user_handler", "db_query").Inc() http.Error(w, "internal error", http.StatusInternalServerError) return } }
Docker 构建后 panic 信息全变成 runtime·panicwrap 错误
这是静态链接缺失导致的典型现象:CGO_ENABLED=0 时,某些依赖(如 SQLite、某些 crypto 库)会在运行时报 runtime·panicwrap: not implemented,但真实错误被包装掉了。
立即学习“go语言免费学习笔记(深入)”;
- 构建前确认是否真需要禁用 cgo:如果用了
net.Resolver或database/sql的 mysql 驱动(非纯 Go 版),必须设CGO_ENABLED=1 - 交叉编译镜像时,优先用
golang:alpine配合apk add --no-cache ca-certificates,而不是盲目追求 scratch 镜像——后者缺证书、缺 resolver 配置,DNS 解析失败就 panic - 本地复现方式:用
docker run --rm -it your-image /bin/sh进去,手动跑二进制,看是否输出完整 panic stack - 临时调试技巧:构建时加
-ldflags="-extldflags '-static'"并确保所有依赖都支持静态链接,否则不如老实用debian:slim
main() 第一行就耦合在一起的——一个没处理的 panic、一次没设对的 probe timeout、一条没结构化的错误日志,都会在 K8s 里放大成服务不可用。真正的难点不在写对某段代码,而在理解每个环节的边界:谁负责超时、谁负责重试、谁负责记录、谁负责告警。这些边界线,往往藏在 yaml 文件的字段名里,而不是 Go 源码里。










