问题背景
Kubernetes使用Deployment来控制Pod的主要好处之一是能够执行滚动更新。
滚动更新允许你逐步更新Pod的配置,并且Deployment提供了许多选项来控制滚动更新的过程。
很多时候滚动更新过程,服务会很出现短暂的停机现象,期间访问服务会失败。
那么,如何做到滚动更新过程零停机?
原因分析
在给出方案之前,先看看为何会发生服务中断。
Deployment 滚动更新时会先创建新 pod,等待新 pod running 后再删除旧 pod。
- Pod 创建后进入 running 状态会被加入到 Endpoint 后端,默认情况下会直接进入 Ready 状态,开始接收流量。
- 在删除旧 pod 过程中需要对多个对象(如 Endpoint、ipvs /iptables、SLB)进行状态同步,并且这些同步操作是异步执行的,整体同步流程如下图所示。

pod 状态变更:
1.将 Pod 设置为 Terminating
状态,并从所有 Service 的 Endpoints 列表中删除。此时,Pod 停止获得新的流量,但在 Pod 中运行的容器不会受到影响;
2.执行 preStop Hook:Pod 删除时会触发 preStop Hook
,preStop Hook
支持 bash 脚本、TCP 或 HTTP 请求;
3.发送 SIGTERM 信号:向 Pod 中的容器发送 SIGTERM 信号;
4.等待指定的时间:terminationGracePeriodSeconds
字段用于控制等待时间,默认值为 30 秒。该步骤与 preStop Hook
同时执行,因此 terminationGracePeriodSeconds
需要大于 preStop
的时间,否则- 会出现 preStop
未执行完毕,pod 就被 kill 的情况;
5.发送 SIGKILL
信号:等待指定时间后,向 pod 中的容器发送 SIGKILL 信号,删除 pod。
简单说,服务中断原因在于:
- 创建Pod时,Pod 业务代码可能还未初始化完毕就开始接收流量,此时请求,服务会中断;
- 删除Pod时,上面1~4步骤同时进行,所以 Pod 收到 SIGTERM 信号并且停止工作后,可能还未从 Endpoints 中移除,此时请求,服务也会中断。
解决思路
1.配置滚动更新策略,保证始终有处于 Ready 状态的 pod 来提供服务。
2.使用 preStop 钩子,延迟 pod 的删除,给新创建的 pod 提供预热时间。
3.使用就绪探针,检查我们的应用程序是否已经准备好来处理流量了。
测试验证
工具:使用ab工具模拟持续请求,1000并发,重复10000次。
方法:在压测过程手动编辑部署实现重启,触发滚动更新。
说明:请求失败时每次运行结果都会有差异,下面是在多次运行中随机取一次结果。
现象:
等级1: 默认策略
如果你什么都没配置,k8s会有默认策略:滚动过程最大可超过预期副本25%,最大不可用副本数量为25%。
1 | strategy: |
单个副本下:

两个副本下:

等级2: 设置滚动更新策略
设置策略为滚动更新,且滚动过程最大可超过预期副本1个,最大不可用副本数量为0个,即保证至少有一个副本是Ready。
1 | spec: |
单个副本下:

两个副本下:

等级3: 设置pod生命周期钩子
我们这里使用 preStop 设置了一个 8s 的宽限期 (推荐5~10s),Pod 在真正销毁前会先 sleep 等待 8s,这就相当于留了时间给 Endpoints 控制器和 kube-proxy 更新去 Endpoints 对象和转发规则。
这段时间 Pod 虽然处于 Terminating 状态,即便在转发规则更新完全之前有请求被转发到这个 Terminating 的 Pod,依然可以被正常处理,因为它还在 sleep,没有被真正销毁。
1 | spec: |
单个副本下:

两个副本下:

等级4: 设置就绪探针
设置http就绪探针,需要应用代码支持,返回code=200~400则会被视为就绪,其他都会视为异常。
1 | spec: |
单个副本下:

两个副本下:

实践结论
滚动更新是k8s的一大特性,可以控制pod升级行为,目的是减少更新过程中出现服务中断的现象。
但滚动更新不等于不会停机,若想实现零停机滚动更新,需要通过各种手段来阻止流量打到被不可用的pod上的情况,任何一个漏洞的存在都可能出现断流现象。
因此,如何把这些漏洞“堵住”是一个技术活,针对不同项目有不同的配置,比如就绪探针和preStop钩子,需要针对项目实际情况来设置。
通常,有以下几种配置手段:
1、设置滚动更新策略,让应用始终保留有 Ready 状态的pod副本,但还是存在pod被删除,但Endpoint还没刷新,导致流量打到被终止的pod上造成断流。
2、设置proStop钩子,延迟pod的终止行为,腾出足够时间让控制器将其从Service删除,实现摘流(即要在Pod真正停止运行前,先把它从Service上拿掉),但流量还是可能进入未就绪但状态为Ready的pod上造成断流。
3、设置就绪探针,保证应用启动后能正常提供服务再变成Ready状态,避免pod被创建时的断流现象。
总的来说,滚动更新是一个服务升级方式,但具体策略需要我们控制好才能规避更新过程出现服务中断现象。