缘起
目前在做的一个项目采用Kubernetes(简称K8S)来管理容器,然而,最近发现在做升级部署的时候,有很少一部分请求会失败。这就不符合规矩了啊,按照道理说,使用K8S做Rolling Update,应该会无痛切换,平稳从Version A 升级到 Version B。
来,看图:
解决问题前,先磨叽磨叽,说几句废话,心急的可以跳过。
点击直达
Rolling Update Zero-downtime 原理
K8S提供了一系列功能来支撑容器集群的自动化部署、扩容以及运维,其中一个功能便是今天要讲到的Rolling Update. 引用官方的一段话
To update a service without an outage, kubectl supports what is called rolling update, which updates one pod at a time, rather than taking down the entire service at the same time.
P.S: 目前官方不推荐使用“Replication Controllers”, 转而使用 Deployment,不过这里的原理是一样的。
在K8S集群里面,一个Pod里面的程序访问另一个Pod里面的程序,可以采用Service来实现,每一个Service都可以定义一个Selector来选择一系列Pod,当Service接收到请求后,它会转发给后面的Pod来处理。
K8S 执行 Rolling Update的时候,Service是不会改变的,Rolling Update只针对Replication Controllers。具体步骤如下:
- K8S 创建一个新的Replication Controllers
- K8S 创建一个新的Pod
- K8S 等待Pod进入Ready状态
- K8S 把新的Pod加入Service
- K8S 更改老的Pod状态为Terminating,并把老Pod从Service中移除
- K8S 结束老的Pod
其中K8S 等待Pod进入Ready状态是需要Pod提供一个Readiness探针,参见 Configure Liveness and Readiness Probes
看似完美,可是为什么出问题了呢?
故障分析
- 第一步,那一定是看日志,没毛病。
查看了一系列日志,发现在做音频转码的时候,报错 “connection refused”。 意识到转码服务在某一时刻是不工作的。 - 第二步,查看转码服务工作原理,转码服务提供两个端口,分别对应于命令通道和数据通道。
- 第三步,由于无法确认是在哪一个端口被拒绝了,于是启用Debug日志,发现是在连接数据通道的时候出问题了。
- 第四步,分析,理论上来说只有在Pod Ready后,K8S才会把流量转交给这个Pod。那么什么情况下会在K8S会把流量转交给一个没有Ready的Pod呢?
- 第五步,查看Readiness定义, 这个Component提供了两个Port,一个用于控制命令,一个用于数据命令。问题就出在这里了,Readiness probe采用了tcpSocket类型的probe, (详见) 但是这里只能配置一个端口,在某种极端情况下,两个端口的就绪时间可能不一致,导致了K8S探测到component已经就绪,但实际上有一个端口没有就绪。从而导致个别请求失败。
解决方案
- 内部实现 readiness http api并暴露给K8S
- 或者 采用exec + command 类型的probe, component内部检测到端口就绪后,写一个ready文件到磁盘。 详见
- 或者 延长 “initialDelaySeconds” 来等待component 就绪(不推荐)
p.s:
“initialDelaySeconds” 是指 Pod处于running状态后,K8S等待多少秒开始第一次执行readiness probe。
结论
自此,Rolling Update的问题完美解决了。Kubernetes 作为一个容器管理系统,还是很强大的。
成功图: