服务器与大带宽专家 · 持牌IDC/CDN/ISP服务商
简米科技官网JIANMI TECH
资讯 2026-08-30 更新于 2026-08-30 简米科技 4,228 字 10 分钟阅读

滚动更新时旧副本连接怎么处理,draining机制详解

导读滚动更新期间旧副本的连接不会瞬间切断,而是通过Kubernetes的优雅终止机制(Terminating + PreStop钩子 + terminationGracePeriodSeconds)先摘流再销毁,但默认行为下处理不当会导致少量请求失败,核心解法是配置PreStop钩子延迟退出、调整terminati……

滚动更新期间旧副本的连接不会瞬间切断,而是通过Kubernetes的优雅终止机制(Terminating + PreStop钩子 + terminationGracePeriodSeconds)先摘流再销毁,但默认行为下处理不当会导致少量请求失败,核心解法是配置PreStop钩子延迟退出、调整terminationGracePeriodSeconds、配合readiness探针和drain命令的--grace-period参数。

为什么滚动更新时旧副本会被立刻杀掉

很多人在Kubernetes里做滚动更新,发现服务总会有几个请求报错,日志里出现connection reset或者upstream connect error,问题根源在于默认的滚动更新策略,Kubelet收到Pod删除指令后,会同时做两件事:把Pod状态改为Terminating,然后发送SIGTERM信号给容器主进程,如果你的应用没有监听SIGTERM做优雅退出,进程会直接退出,已经建立的TCP连接、正在处理的HTTP请求全部被掐断。

行业共识认为,优雅终止的关键是让旧副本在收到SIGTERM前先从Service的Endpoints里摘除,Kubernetes的Endpoint控制器会在Pod进入Terminating状态时把它从Endpoints中移除,但这个过程是异步的,kube-proxy刷新iptables或IPVS规则也需要时间,通常几秒到几十秒不等,如果Pod进程立刻死了,而负载均衡器还在往这个Pod转发新请求,这些请求必然失败。

优雅终止连接的完整处理链路

从Service Endpoints摘除旧副本

更新触发后,ReplicaSet控制器会创建一个新Pod,同时旧Pod收到删除指令,进入Terminating状态,此时Endpoint控制器检测到Pod正在删除,会把它从Endpoints对象中移除,但这个动作不等待任何东西,它只是修改etcd里的Endpoints资源,真正生效要等kube-proxy在各节点上更新转发规则,所以从"Pod开始终止"到"新连接完全不再进入"有一个时间窗口。

PreStop钩子:给连接一个缓冲期

最常用的解法是在Pod里加preStop钩子,让容器在收到SIGTERM前睡一会儿。

lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 10"]

滚动更新时旧副本连接怎么处理,draining机制详解

这个10秒的sleep让Kubelet在发送SIGTERM之前先等待10秒,目的很简单:给kube-proxy足够时间刷新规则,让新的连接不再路由到旧Pod,同时旧Pod在这10秒内仍在正常工作,继续处理已经建立的旧连接,10秒只是个经验值,如果你的节点数量多、规则刷新慢,可以加大到15秒或20秒,但要注意,terminationGracePeriodSeconds的总时长必须大于preStop的sleep时间加上应用自行优雅退出的时间。

应用层处理SIGTERM

光靠PreStop还不够,应用本身要能优雅退出,比如Go语言的HTTP服务器,需要监听SIGTERM后调用server.Shutdown(ctx),让正在处理的请求完成后再退出,Java Spring Boot应用要设置server.shutdown=graceful,如果应用不处理SIGTERM,Kubelet在terminationGracePeriodSeconds到期后会发送SIGKILL强杀,连接还是会被切断。

terminationGracePeriodSeconds 的合理取值

默认值是30秒,如果你只配了preStop sleep 10,应用处理请求最快也需要2秒,那总时长12秒就够,但如果你有长轮询或后台任务需要更长的时间,必须调整这个参数。

terminationGracePeriodSeconds: 60

注意,这个值是从Pod进入Terminating状态就开始计时的,包括preStop的sleep时间,行业内常用做法是设置成preStop时间 + 应用最大请求处理时间 + 5秒缓冲

draining参数怎么配才能不丢连接

这里说的draining,不是Kubernetes的kubectl drain,而是滚动更新场景下旧Pod的连接耗尽过程,核心是掌握三个维度的参数配合。

并行度与最大不可用Pod数

滚动更新的strategy配置决定了同时杀掉几个旧Pod。

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 0
    maxSurge: 1

maxUnavailable: 0很关键,它保证在任何时间点,可用的Pod数量不会低于期望副本数,更新时会先启动一个新Pod,等它Ready后才开始终止一个旧Pod,这样旧Pod的draining压力最小。

滚动更新时旧副本连接怎么处理,draining机制详解

maxSurge: 1允许更新期间比期望多一个Pod,给draining留出空间,如果你追求更快的更新速度,可以把maxUnavailable设为1,但那样就不会等新Pod Ready,直接杀旧Pod,连接风险陡增,对线上高可用服务,行业共识是maxUnavailable: 0更稳。

Readiness探针和滚动更新的关系

很多人忽视readiness探针,滚动更新时,新Pod必须通过readiness探针才会被加入Endpoints,开始接收流量,如果你没有配readiness探针,新Pod启动后立即被当成可用,一旦应用内部还没准备好(比如连接池未创建、缓存未加载),请求照样会失败,更关键的是,readiness探针的periodSecondsfailureThreshold会影响draining的判断,当Pod处于Terminating状态时,探针仍然在运行,但kubelet最终会忽略探针结果直接发送SIGTERM,所以readiness主要影响新Pod的准入,对旧Pod的draining帮助有限。

kubectl drain在实际运维中的应用

除了滚动更新,日常运维中把节点下线时也会触发Pod驱逐。kubectl drain命令会先标记节点不可调度,然后驱逐该节点上的Pod,驱逐过程同样走优雅终止逻辑。

kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --grace-period=60

--grace-period会覆盖Pod的terminationGracePeriodSeconds,给所有被驱逐的Pod统一设置终止宽限期,如果节点上有Local Storage的Pod,需要加--delete-emptydir-data,如果Pod有专属的PDB(PodDisruptionBudget),drain会等待PDB允许的不可用数量范围内的Pod被驱逐,否则会阻塞,运维时要先看PDB的配置,避免drain卡死。

场景对比:不同部署环境下的draining差异

滚动更新时旧副本连接怎么处理,draining机制详解

环境/场景 常见问题 推荐配置
自建K8s + Nginx Ingress Ingress Controller的Lua缓存导致连接复用失效 preStop sleep 10,keep-alive-requests调低
云厂商托管集群(如ACK) SLB后端连接耗尽慢,时长达30秒 preStop sleep 15,SLB健康检查间隔调小
多节点大集群(100+节点) kube-proxy iptables规则刷新慢 增大preStop到20秒,或改用IPVS模式
长连接服务(WebSocket) 客户端不感知断连,需要应用层心跳重连 设置terminationGracePeriodSeconds: 45,应用监听SIGTERM后发送关闭帧

Q&A:滚动更新时旧副本连接处理常见疑问

问:preStop里sleep会不会拖慢整个滚动更新进度?

会,但影响有限,每个旧Pod的draining时间多了sleep的秒数,如果maxUnavailable: 0,更新N个副本的总时间会明显增加,如果你有100个副本,sleep 10秒意味着多出1000秒的等待时间,不过这些时间不是全部串行的,因为滚动更新是分批进行的,且新Pod是并行创建的。

问:连接在draining期间被强制断开,客户端该怎么做?

这是客户端容错的范畴,最直接的办法是设置重试机制,对于HTTP请求,客户端遇到连接错误时自动重试一次,但对于写操作,重试要小心幂等性,更好的办法是客户端使用连接池时主动检测失效连接,比如Go的http.Transport设置MaxIdleConnsPerHost,并在获取连接前用http.Client.Timeout约束,服务端即使做了优雅终止,客户端仍需要有重试的底气才能保证100%不报错。

根据百度搜索的常见疑问整理,很多用户遇到的"滚动更新 连接断开 解决方法"基本都是因为只加了preStop sleep,却忘了调整terminationGracePeriodSeconds,如果preStop时间超过terminationGracePeriodSeconds,Kubelet会在宽限期到点时强制杀进程,preStop还没执行完就被SIGKILL终止,这个坑相当隐蔽。

核心结论一句话:滚动更新不丢连接,不是靠某个单一参数,而是preStop钩子、terminationGracePeriodSeconds、maxUnavailable、应用优雅退出四个环节协同工作,缺一个都可能丢请求。 下次配置滚动更新前,先用kubectl get pod -o yaml检查当前Pod的lifecycle和terminationGracePeriodSeconds,然后对照应用的连接处理逻辑调整。

分享本文
本文为 简米科技官网 原创,已由运维技术专家审核。转载请注明来源:原文链接
售前咨询 服务热线 售后 邮箱