滚动更新本质是通过K8s的terminationGracePeriodSeconds、preStop钩子与Go应用优雅关闭逻辑协同实现无损发布;Go需监听SIGTERM并用http.Server.Shutdown()等待请求完成,且Shutdown超时须小于terminationGracePeriodSeconds。
滚动更新不是让旧进程等新进程就绪后再退出,而是靠 Kubernetes 的 terminationGracePeriodSeconds 和 preStop 钩子配合应用自身优雅关闭逻辑来实现无感。Golang 程序若没处理 SIGTERM 或未等待 HTTP 连接 draining,哪怕 K8s 配置再标准,也会丢请求。
SIGTERM 后立即删除 Pod(不等应用关完),必须显式配置 terminationGracePeriodSeconds: 30
preStop 钩子不能只做 sleep,应调用应用内部的 shutdown 接口或发信号触发 graceful shutdownos.Signal,收到 syscall.SIGTERM 后停止接收新连接、等待活跃请求完成(如用 http.Server.Shutdown())Shutdown() 而非 Close()
http.Server.Close() 是暴力中断所有连接,Shutdown() 才是标准优雅退出方式——它会拒绝新请求、等待已有请求完成(可设超时),是滚动更新不丢请求的关键。
srv := &http.Server{Addr: ":8080", Handler: myHandler}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClose
d {
log.Fatal(err)
}
}()
// 收到 SIGTERM 后触发 Shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("HTTP server shutdown error: %v", err)
}
仅靠 Golang 代码优雅退出还不够,K8s 层必须配合:确保新 Pod 就绪后再删旧 Pod、给足关闭时间、避免就绪探针过早通过。
minReadySeconds: 10:新 Pod 启动后至少等待 10 秒才视为 ready,防止流量切过去时服务还没真正 readystrategy.rollingUpdate.maxSurge: 1 和 maxUnavailable: 0:保证更新过程中副本数不减(零不可用),适合核心服务livenessProbe 和 readinessProbe 的 initialDelaySeconds 必须大于应用冷启动耗时,否则 probe 失败会反复重启,阻断滚动更新这些错误基本都指向两个环节脱节:K8s 认为 Pod 可删了,但 Go 还在处理请求;或 K8s 还没把流量切走,Pod 就已退出。
upstream prematurely closed connection(Nginx Ingress 日志)→ Go 没等完请求就退出,或 Shutdown() 超时太短minReadySeconds 或 readinessProbe 配置过松preStop 钩子死锁,或 Go 的 Shutdown() 等待时间超过 terminationGracePeriodSeconds
最易被忽略的是:Go 的 http.Server.Shutdown() 超时值必须小于 K8s 的 terminationGracePeriodSeconds,否则 K8s 强杀前 Go 根本没机会返回。