Kubernetes平滑扩容依赖就绪探针(/readyz)与优雅退出(SIGTERM)协同:/readyz须检查DB、Redis、gRPC等依赖并设超时,返回非200即摘除流量;SIGTERM到来后需按序关闭gRPC、DB、HTTP服务并设足够超时,否则导致502、超时或数据丢失。

为什么平滑扩容不是Go代码里写个for循环就能解决
因为扩容动作发生在Kubernetes或Nomad这类调度器里,Go服务本身只负责“让新实例快点就绪、让旧实例慢点退场”。如果/readyz返回太早、/healthz检查太糙、或者SIGTERM来了不等请求结束就退出,调度器就会把流量打到半死不活的实例上,或在请求中途杀掉进程——结果就是502、超时、数据丢失。
就绪探针(/readyz)必须检查依赖,且不能阻塞
常见错误是把/readyz写成“只要HTTP能响应就返回200”,这会让Kubernetes把流量导给还没连上DB、没加载完配置、甚至gRPC server还没启动的实例。
-
/readyz要检查:DB连接池Ping()、Redisping、下游gRPC服务Connect()是否成功、本地配置监听是否已注册 - 必须加
context.WithTimeout(r.Context(), 2*time.Second),防止某个依赖卡住导致探针hang住 - 不要在
/readyz里做耗时操作(如全量缓存预热),改用启动后异步加载+状态标记 - 返回非200(比如503)时,Kubernetes会立刻把这个Pod从Service的Endpoints里摘除,但容器不会重启
优雅退出(Graceful Shutdown)必须按顺序清理资源
只调用http.Server.Shutdown()不够——它只管HTTP连接,而你的gRPC server、消息消费者、DB连接池、定时任务可能还在跑。
- 先关闭自定义资源:
grpcServer.GracefulStop()、db.Close()、consumer.Stop() - 再调用
http.Server.Shutdown(),并设足够超时(建议10–30秒,视最长请求而定) - 务必监听
syscall.SIGTERM(K8s默认发这个),而不是只监听os.Interrupt - 设置
srv.SetKeepAlivesEnabled(false),防止Shutdown期间新连接接入
RPC客户端如何感知新扩容节点
gRPC默认的DNS解析或static resolver只在首次连接时拉一次地址,扩容后新增Pod不会自动加入负载均衡列表——这是最常被忽略的“假扩容”原因。
立即学习“go语言免费学习笔记(深入)”;
- 必须用
grpc.WithResolvers()注册自定义resolver,且resolver要监听etcd/Consul/Nacos的watch事件 - watch回调里只做轻量转换,调用
cc.UpdateState()触发gRPC内部重平衡 - 避免resolver里阻塞或重试逻辑,否则整个gRPC连接池会卡死
- 客户端侧加“预热等待”:新实例注册后,主动发起一次健康探测,等至少2个心跳周期再上报ready
/readyz和SIGTERM之间的那几秒——依赖检查慢了、资源清理漏了、RPC客户端没刷新,都会让扩容变成抖动。


















