发现问题
对于Kubernetes Deployment的每次部署过程,都是创建新版本的Pod后,再删除老版本的Pod的过程。在这个过程中如果不实现优雅的退出,则会引起两个问题:
- 会出现旧的Pod还未将正在处理的请求处理完成就被删除了,如果该请求不是幂等性的,那么就会导致状态不一致的问题。
- 会出现旧的Pod已经被删除,kube-proxy仍然将流量导向该Pod,从而出现用户请求处理失败。
分析问题
在Kubernetes delete pod 的过程中会有两个时间线,其中一条时间线是网络规则的更新过程,另一条时间线是pod的删除过程。
当执行 kubectl delete pod 命令时,
网络规则更新:
- kube-apiserver 收到Pod删除的请求,在ETCD中更新Pod的状态为Terminating;
- Endpoint Controller将该Pod的IP从Endpoint对象中删除;
- kube-proxy根据Endpoint对象的改变更新iptables规则,不再将流量路由到被删除的Pod。
Pod删除过程
- kube-apiserver 会收到Pod删除的请求,在ETCD中更新Pod的状态为Terminating;
- kubelet 在节点上清理容器的相关资源,例如存储,网络;
- Kubelet 发送SIGTERM进程给容器,如果容器中的进程未做任何配置,则容器立即退出;
- 如果容器未在默认的30秒时间内退出,Kubelet发送SIGKILL给容器,强制让容器退出。
从Pod的删除过程可以知道,如果不对容器内的进程进行任何配置,容器会立即退出,会导致问题1出现。
由于网络规则的更新和Pod的删除是并行的,并不能保证网络规则的更新时间一定会早于Pod的删除时间,有可能出现问题2。
解决问题
-
进程的优雅退出
Springboot-2.3.0开始提供了官方的优雅停机方案,在配置文件中配置优雅停机:
server:
shutdown: graceful ## 开启优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 60s ## 优雅停机等待时间,默认30s
-
设置preStopHook
在yaml文件中增加 preStopHook,当kubelet接收到Pod删除事件后sleep一段时间,给Kube-proxy足够的时间去更新iptables网络规则后,再开始删除Pod。
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
-
延长terminationGracePeriodSeconds
修改 terminationGracePeriodSeconds使其大于Springboot的优雅退出超时时间和preStopHook sleep时间之和
terminationGracePeriodSeconds: 70