随着项目中使用的持续部署(ContinouslyDeployment),之前的定期或者定时发布的节奏变成了随时高频发布。这就要求每次发布都应该是零停机部署(ZeroDowntimeDeployment),否则会引入bug。k8s中有一整套机制来保证我们的应用能够实现零宕机部署。本文将重点分析优雅退出部分。本文需要对k8s的架构和核心组件的职责有一定的了解。不明白的可以参考KubernetesComponents。发现问题对于KubernetesDeployment的每一个部署过程,都是一个创建新版本Pod和删除旧版本Pod的过程。如果在这个过程中不使用gracefulexit,会导致两个问题:问题1:Pod可能在处理完正在处理的请求之前就被删除了。如果请求不是幂等的,会导致Inconsistentstatebug。问题2:可能会出现Pod已经被删除,但是Kubernetes仍然将流量引向Pod,导致无法处理用户请求,导致用户体验不佳。分析问题在KubernetesPod的删除过程中,会同时出现两条并行的时间线,如下图所示,其中一条是网络规则的更新过程,另一条时间线是Pod的删除过程。当用户执行kubectldeletepod命令时,网络规则生效过程:Kube-apiserver会收到删除Pod的请求,更新Etcd中Pod的状态为Terminating;EndpointController从Endpoint对象中删除Pod的IP;Kube-proxy根据Endpoint对象的变化更新iptables规则,不再将流量路由到被删除的Pod。Pod删除流程:Kube-apiserver会收到删除Pod的请求,更新Etcd中Pod的状态为Terminating;kubelet清理节点上容器的相关资源,如存储、网络;Kubelet向容器发送SIGTERM进程。如果进程没有任何配置,容器立即退出;如果容器在默认的30秒内没有退出,Kubelet会向容器发送SIGKILL以强制容器退出。从删除Pod的过程中我们可以知道,如果不对容器中的进程进行任何配置,容器会立即退出,从而导致问题1的出现。由于网络规则的更新和pod的删除是并行的,不能保证网络规则的更新时间会早于pod的删除时间,所以可能会出现问题2。解决问题如果我们要解决以上两个问题,需要做如下配置:设置容器中进程的优雅退出;添加preStopHook;修改terminationGracePeriodSeconds。配置后的时间线如下图所示:设置容器中进程的优雅退出我们项目中使用的是Springboot,只需要添加配置server:shutdown:gracefulspring:lifecycle:timeout-per-shutdown在Springboot配置文件中-phase:30s这个配置后,Springboot会保证在收到SIGTERM后不再接受新的请求[^1],并在超时时间内处理所有的处理请求。如果无法完成处理,也会打印显示相应信息并强制退出。超时时间的具体值应该是指系统允许的最大请求时长,所以理论上所有的请求都应该在30s内处理完。对于30s内未处理的请求,我们可以监控日志并发送Alert。根据实际情况处理。加上这个配置,问题1就可以解决了。使用其他语言和框架的项目应该也有类似的配置。添加preStopHook解决问题2。需要保证网络规则更新,即在开始删除Pod前,不再有新的流量路由到待删除的Pod。所以需要在Kubernetes的yaml文件中加入preStopHook[^3],让Kubelet在收到Pod删除事件后等待一段时间,给Kube-proxy足够的时间去更新iptables网络规则,然后再开始删除豆荚。lifecycle:preStop:exec:command:["sh","-c","sleep10"]#setprestophook这里我们项目中设置的10s是指Springboot官网的配置[^2]。修改terminationGracePeriodSeconds以引用之前分析的Pod删除流程。Kubernetes会给容器最长30秒的删除时间[^3]。如果Spring中优雅退出的超时时间和Kubernetes中的preStopHook时间大于30s,则可能是Springboot没有处理完所有请求,Kubernetes开始强制删除容器。所以如果这个持续时间超过30秒,我们需要修改terminationGracePeriodSeconds,让它大于Springboot的优雅退出超时时间和preStopHookterminationGracePeriodSeconds:45之和。Kubernetes中最终的Yaml文件如下:apiVersion:apps/v1kind:Deploymentmetadata:name:graceful-shutdown-test-exit-graceful-30sspec:replicas:2selector:matchLabels:app:graceful-shutdown-test-exit-graceful-30stemplate:metadata:labels:app:graceful-shutdown-test-exit-graceful-30sspec:containers:-name:graceful-shutdonw-testimage:graceful-shutdown-test-exit-graceful-30s:latestports:-containerPort:8080lifecycle:preStop:exec:command:["sh","-c","sleep10"]#setprestophookterminationGracePeriodSeconds:45#terminationGracePeriodSeconds可以看出,通过设置Springboot的优雅退出,保证了处理请求能够完成,通过设置preStopHook,保证了Pod删除和网络规则更新的时序关系.通过配置terminationGracePeriodSeconds,给容器中的进程足够的时间来处理所有的请求。结合以上三步,可以解决之前发现的两个问题。总结本文结合了Kubernetes的优雅退出和Springboot的优雅退出机制,保证服务在任何时候都能在高频部署的情况下正确处理所有请求,减少bug的发生,提升用户体验。
