go http handler需用context.withtimeout控制超时、避免panic和goroutine泄漏,liveness与readiness应分离且逻辑精简,shutdown前需主动摘除endpoint并协调k8s信号流。

Go HTTP handler 怎么写才符合 Kubernetes 的 /healthz 要求
Kubernetes 的 livenessProbe 和 readinessProbe 默认用 HTTP GET 检查,但 Go 默认的 http.HandleFunc 不会自动处理超时、panic 或阻塞,容易让探针误判为失败。
必须确保 handler 在几秒内返回且不 panic。常见错误是直接在 handler 里调用数据库连接检查或长耗时逻辑,导致 probe 超时(默认 1 秒),K8s 连续失败后重启 Pod。
- 用
context.WithTimeout包裹所有外部依赖调用,超时时间设为 probetimeoutSeconds的 60% 左右(比如 probe 设 3s,代码里用 1.8s) - handler 内不要做
log.Fatal或未捕获的 panic,否则整个进程挂掉;改用recover()+ 返回 500 - 避免在 handler 中新建 goroutine 后不等结果就返回 —— 探针只看 HTTP 状态码和响应体,不等后台完成
示例:
func healthz(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 1800*time.Millisecond)
defer cancel()
if err := db.PingContext(ctx); err != nil {
http.Error(w, "db unreachable", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
为什么 readinessProbe 返回 200 但 Pod 还是不进 Service
不是返回 200 就算就绪 —— Kubernetes 要求 endpoint 必须稳定通过 probe 若干次(由 initialDelaySeconds 和 periodSeconds 共同决定),且 endpoint 列表需同步更新。
典型问题:Go 应用启动快,但 gRPC server 或消息队列消费者还没 ready,此时 readiness handler 却已返回 200,Service 流量进来后请求失败。
立即学习“go语言免费学习笔记(深入)”;
- 把 readiness 拆成多层检查:基础 HTTP 可达 → 本地依赖(如 config 加载)→ 关键远程依赖(如 Redis、下游 API)
- 关键依赖用「缓存上次检查结果 + 异步刷新」策略,避免每次 probe 都重连;例如用
sync.Once初始化,probe 时只读状态变量 - 确认 kube-proxy 或 CNI 插件没卡住 endpoint 同步 —— 查
kubectl get endpoints <svc-name></svc-name>,看 IPs 是否及时出现
livenessProbe 失败重启后反复 CrashLoopBackOff 怎么定位
根本原因往往是 liveness handler 自身不稳定,或者 probe 配置和代码逻辑冲突。比如 handler 里检查了磁盘空间,但磁盘满导致 handler panic,Pod 重启后又立刻满,形成死循环。
更隐蔽的是:liveness 和 readiness 共用同一个 handler,而 readiness 应该容忍短暂抖动,liveness 却要求绝对稳定 —— 二者逻辑不该混用。
- 给 liveness 单独写 handler,只检查最核心项(如进程是否还在运行、HTTP server 是否 accept 连接),别碰外部依赖
- 加日志:在 liveness handler 开头打 timestamp,在结尾打「liveness ok」,方便查是不是卡在中间某步
- 临时把
livenessProbe.failureThreshold调大(比如从 3 改成 10),再看日志 —— 如果连续 10 次都失败,基本能排除偶发网络抖动,聚焦代码逻辑
Go net/http Server Shutdown 与 probe 的竞态问题
应用收到 SIGTERM 后调用 srv.Shutdown(),但 Kubernetes 在发送 SIGTERM 前已停止发 readiness probe;如果 shutdown 期间还接受新连接,旧连接又没及时 close,probe 可能打到正在关闭的 server 上,返回 404 或 connection refused,触发误杀。
这不是配置问题,是 Go HTTP server 生命周期和 K8s 信号协作没对齐。
- 在
srv.Shutdown()前,先关掉 readiness handler(比如用原子开关控制 handler 返回 503),让 endpoint 从 Service 中快速摘除 - 设置
readinessProbe.initialDelaySeconds大于应用冷启动时间,避免 probe 在 server listen 前就发起请求 - 用
http.Server.IdleTimeout防止长连接拖慢 shutdown —— 否则Shutdown()可能等几十秒才返回
真正麻烦的从来不是怎么写 handler,而是 handler 的生命周期如何跟 K8s 的信号流、endpoint 同步节奏咬合上。少一个 context.WithTimeout,或多一次没受控的 goroutine,都可能让健康检查从保护机制变成定时炸弹。










