可能存在的宕机原因分析
当应用在滚动更新期间重新路由流量时,从旧的 Pod 实例到新的实例究竟会发生什么,首先让我们先看看 Kubernetes 是如何管理工作负载连接的。
如果我们执行测试的客户端直接从集群内部连接到Service,那么首先会通过 集群的 DNS 服务解析到 Service 的 ClusterIP,然后转发到 Service 后面的 Pod 实例,这是每个节点上面的 kube-proxy 通过更新 iptables 规则来实现的。
Kubernetes 会根据 Pods 的状态去更新 Endpoints 对象,这样就可以保证 Endpoints 中包含的都是准备好处理请求的 Pod。
但是 Kubernetes Ingress 连接到实例的方式稍有不同,这就是为什么当客户端通过 Ingresss 连接到应用程序的时候,我们会在滚动更新过程中查看到不同的宕机行为。
大部分 Ingress Controller,比如 nginx-ingress、traefik 都是通过直接 watch Endpoints 对象来直接获取 Pod 的地址的,而不用通过 iptables 做一层转发了。
无论我们如何连接到应用程序,Kubernetes 的目标都是在滚动更新的过程中最大程度地减少服务的中断。一旦新的 Pod 处于活动状态并准备就绪后,Kubernetes 就将会停止就的 Pod,从而将 Pod 的状态更新为 “Terminating”,然后从 Endpoints 对象中移除,并且发送一个 SIGTERM 信号给 Pod 的主进程。SIGTERM 信号就会让容器以正常的方式关闭,并且不接受任何新的连接。Pod 从 Endpoints 对象中被移除后,前面的负载均衡器就会将流量路由到其他(新的)Pod 中去。
这个也是造成我们的应用可用性差距的主要原因,因为在负载均衡器注意到变更并更新其配置(比如说nginx-ingress的会自动去修改upstream,然后进行reload的操作)之前,终止信号就会去停用 Pod,而这个重新配置过程又是异步发生的,所以并不能保证正确的顺序,所以就可能导致很少的请求会被路由到终止的 Pod 上去。
如何实现零宕机?
首先,要实现这个目标的先决条件是我们的容器要正确处理终止信号,在 SIGTERM 信号上实现优雅关闭。下一步需要添加 readiness 可读探针,来检查我们的应用程序是否已经准备好来处理流量了。
可读探针只是我们平滑滚动更新的起点,为了解决 Pod 停止的时候不会阻塞并等到负载均衡器重新配置的问题,我们需要使用 preStop 这个生命周期的钩子,在容器终止之前调用该钩子。
生命周期钩子函数是同步的,所以必须在将最终终止信号发送到容器之前完成,,然后 SIGTERM 信号将停止应用程序进程。同时,Kubernetes 将从 Endpoints 对象中删除该 Pod,所以该 Pod 将会从我们的负载均衡器中排除,基本上来说我们的生命周期钩子函数等待的时间可以确保在应用程序停止之前重新配置负载均衡器。
我们这里使用 preStop 设置了一个 20s 的宽限期,Pod 在真正销毁前会先 sleep 等待 20s,这就相当于留了时间给 Endpoints 控制器和 kube-proxy 更新去 Endpoints 对象和转发规则,这段时间 Pod 虽然处于 Terminating 状态,即便在转发规则更新完全之前有请求被转发到这个 Terminating 的 Pod,依然可以被正常处理,因为它还在 sleep,没有被真正销毁。
lifecycle:
preStop:
exec:
command: ["/bin/bash", "-c", "sleep 20"]
复制代码
测试钩子可用性
资源添加了上面的规则,在销毁这个pod之前调用这个钩子等待20s,查看效果
可以看到,当这个pod的状态为terminating的时候,我们通过curl工具访问这个pod的ip还是可以正常访问的,直到20s以后这个pod的才会被销毁。
`#重新启动这个deploy查看效果`
[root@dm01 ~]# kubectl rollout restart deploy nginx-app
deployment.apps/nginx-app restarted
[root@dm01 ~]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hpa-demo-6c655c5458-6hdgd 1/1 Running 10 53d 10.244.1.62 dm02 <none> <none>
nginx-6c79994d64-zf746 1/1 Running 0 59m 10.244.0.208 dm01 <none> <none>
nginx-app-69d45d4d89-8qhk5 1/1 Terminating 0 26m 10.244.0.215 dm01 <none> <none>
nginx-app-d8585cb66-mwv9h 1/1 Running 0 7s 10.244.0.216 dm01 <none> <none>
nginx-test-5f4tf 1/1 Running 6 6d17h 10.244.0.200 dm01 <none> <none>
nginx-test-jkqcj 1/1 Running 3 6d17h 10.244.2.85 dm03 <none> <none>
nginx-test-n98lc 1/1 Running 4 6d17h 10.244.1.56 dm02 <none> <none>
`#可以看到pod在terminating的状态下面还是可以访问到`
[root@dm01 ~]# curl 10.244.0.215
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
复制代码