亚马逊AWS官方博客

使用 Readiness Gate 解决 EKS Pod 滚动升级产生的服务中断

背景介绍

AWS LoadBalancer Controller 是帮助一个管理 Kubernetes 集群的ELB的控制器。它可以通过 Kuberentes Server / Ingress 来控制 AWS NLB / ALB.

Amazon VPC CNI Plugin 是一个 AWS 原生 kubernetes 网络插件。它使用 AWS 上的弹性网络接口(ENI)在 Kubernetes 中用于 pod 网络,实现了 Pod IP 通过 ENI 落在 VPC CIDR 内。

在 EKS 中使用 AWS LoadBalancer Controller时,可以通过适当的 Annotations 启用 IP 模式。在这种模式下,AWS NLB / ALB 将流量直接转发到后端 Kubernetes Pods, 尔无须通过 NodePort 等进行额外跳转。

但这带了一个新的问题。

在 Kuberentes Server / Ingress 中启用 Instance 模式而非 IP 模式时,ELB targets 是 Kuberenetes Nodes 的 NodePort, 通过 iptables / IPVS 的方式转发到 Pod IP. 这种情况下升级 Pod 时,在 Kubernetes 内部通过滚动更新就可以完成切换,ELB 无感知。

但使用 IP 模式时,ELB Listener 是 Pod IP,及时 Kubernetes 进行了滚动更新,ELB 的初始化仍会带来服务中断。

测试

单副本模式

让我们进行一个简单的测试,部署一个示例 nginx 应用并更新它。在更新过程中按每秒一次的频率请求 Endpoint 查看状态码:

while true; do
echo -n "HTTP response status codes: " >> result.txt
curl -s -o /dev/null -w "%{http_code}" -m 1 k8s-readines-nginxweb-xxxxxxxx.elb.us-east-1.amazonaws.com >> result.txt
date '+ time: %H:%M:%S' >> result.txt
echo >> result.txt
sleep 1
done

可以看到从 16:11:43 到 16:13:51,中断了128秒:

这是因为新 Pod Running 之后,就会终止旧的 Pod. 此时在 ELB 中,新的 Pod 还在 initial,旧的 Pod 已经 draining, 此时 ELB 没有 healthy targets 提供服务。

多副本模式

一般情况下,用户都会选择多副本模式提高服务可用性,那么多副本模式会不会改善这个问题?

首先将 Deployment replicas 设置成2:

再进行更新:

可以看到,从 16:32:32 到 16:34:39 中断了 127 秒。多副本对这个问题并无改善。两个新 Pod 都在 initial 状态中,而旧 Pod 都在 draining。

使用 Readiness Gate

Kubernetes 提供了 Pod 就绪态(Readiness),保证 Pod 内业务就绪后才提供服务,减少了服务中断。在 Kubernetes 1.14 中,GA 了 Readiness Gate。通过Pod Readiness Gates,用户可以在 Pod 上设置自定义的ReadinessProbe探测方式,辅助kubernetes判断Pod是否真正到达服务可用状态Ready。

在使用 AWS LoadBalancer Controller 时,可以启用 Readiness Gate,使 EKS 持续监控 ELB Target 状态,直到状态为 healthy 才认为 Pod 已经启动,而非只通过 Pod 状态变为 Running. 这个功能仅适用于 IP 模式,而非 instance 模式,因为 instance 模式下,ELB 无法知道 pod/podReadiness 状态。

部署

想要启用 Readiness Gate 功能很简单,几乎无须额外配置,只需要在 namespace 中添加一个 label:

$ kubectl create namespace readiness
namespace/readiness created

$ kubectl label namespace readiness elbv2.k8s.aws/pod-readiness-gate-inject=enabled
namespace/readiness labeled

$ kubectl describe namespace readiness
Name:         readiness
Labels:       elbv2.k8s.aws/pod-readiness-gate-inject=enabled
Annotations:  <none>
Status:       Active

添加了 label 之后,新启动的 Pod 会自动注入 Readiness Gate 相关配置。对于已存在 pod 可以通过重启的方式自动注入配置。可以通过以下命令检查配置生效:

测试

此时进行同样的更新测试:

可以看到,新 Pod initial 时,旧 Pod 仍在提供服务

因为新 Pod readiness gate 没有就绪,Kubernetes 判断新 Pod 为非就绪状态,旧 Pod 不可以终止

在新 Pod 作为 ELB Target 已经 healthy 后,才会终止对应旧 Pod:

更新好多副本中的一个 Pod 后,再以同样的方式更新下一个 Pod:

最终每个新 Pod 都就绪后,才会完成所有的旧 Pod的删除:

检查一下更新过程中请求的状态码,可以看到状态码均为200,更新过程没有任何中断:

单副本模式测试

如果是单副本模式,readiness gate 仍会提供同样的性能:

我们先将 Deployment replicas 设置为 1,再进行更新。

我们可以看到同样的过程,在新 Pod 在 ELB 中作为 target 状态为 initial 时,会同时运行新旧两个 Pod:

在更新结束后,检查更新过程中的状态吗,均为200,更新过程没有任何中断:

结论

使用 readiness gate 可以在保证 EKS Server/Ingress 的性能的同时,有效提升服务的稳定性,减少服务中断的发生,而又无须复杂配置。建议在条件允许的情况下,使用 IP 模式 AWS LoadBalancer Controller 的用户都启用这个功能。

参考链接:

Pod Readiness Gate – AWS LoadBalancer Controller (kubernetes-sigs.github.io)

本篇作者

王逸飞

AWS解决方案架构师,负责基于 AWS 的云计算方案的咨询与架构设计,同时致力于 AWS 云服务知识体系的传播与普及。在微服务,容器等领域拥有多年经验。