滚动更新本质是通过K8s的terminationGracePeriodSeconds、preStop钩子与Go应用优雅关闭逻辑协同实现无损发布;Go需监听SIGTERM并用http.Server.Shutdown()等待请求完成,且Shutdown超时须小于terminationGracePeriodSeconds。

滚动更新的本质是控制 Pod 生命周期而非“重启服务”
滚动更新不是让旧进程等新进程就绪后再退出,而是靠 Kubernetes 的 terminationGracePeriodSeconds 和 preStop 钩子配合应用自身优雅关闭逻辑来实现无感。Golang 程序若没处理 SIGTERM 或未等待 HTTP 连接 draining,哪怕 K8s 配置再标准,也会丢请求。
- K8s 默认发送
SIGTERM后立即删除 Pod(不等应用关完),必须显式配置terminationGracePeriodSeconds: 30 -
preStop钩子不能只做 sleep,应调用应用内部的 shutdown 接口或发信号触发 graceful shutdown - Golang 中需监听
os.Signal,收到syscall.SIGTERM后停止接收新连接、等待活跃请求完成(如用http.Server.Shutdown())
Go HTTP 服务必须用 Shutdown() 而非 Close()
http.Server.Close() 是暴力中断所有连接,Shutdown() 才是标准优雅退出方式——它会拒绝新请求、等待已有请求完成(可设超时),是滚动更新不丢请求的关键。
srv := &http.Server{Addr: ":8080", Handler: myHandler}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
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)
}
Deployment 配置中三个关键字段缺一不可
仅靠 Golang 代码优雅退出还不够,K8s 层必须配合:确保新 Pod 就绪后再删旧 Pod、给足关闭时间、避免就绪探针过早通过。
-
minReadySeconds: 10:新 Pod 启动后至少等待 10 秒才视为 ready,防止流量切过去时服务还没真正 ready -
strategy.rollingUpdate.maxSurge: 1和maxUnavailable: 0:保证更新过程中副本数不减(零不可用),适合核心服务 -
livenessProbe和readinessProbe的initialDelaySeconds必须大于应用冷启动耗时,否则 probe 失败会反复重启,阻断滚动更新
常见失败现象:502/503 和连接被 reset
这些错误基本都指向两个环节脱节:K8s 认为 Pod 可删了,但 Go 还在处理请求;或 K8s 还没把流量切走,Pod 就已退出。
立即学习“go语言免费学习笔记(深入)”;
- 现象:
upstream prematurely closed connection(Nginx Ingress 日志)→ Go 没等完请求就退出,或Shutdown()超时太短 - 现象:新 Pod 就绪后老 Pod 立即终止 → 缺少
minReadySeconds或readinessProbe配置过松 - 现象:滚动更新卡住,旧 Pod 一直 Terminating →
preStop钩子死锁,或 Go 的Shutdown()等待时间超过terminationGracePeriodSeconds
最易被忽略的是:Go 的 http.Server.Shutdown() 超时值必须小于 K8s 的 terminationGracePeriodSeconds,否则 K8s 强杀前 Go 根本没机会返回。










