一、问题
当我们将某应用的新版本发布到 K8s 的时候,经常会出现这样一个场景:
Pod 已经运行起来了(READY=1/1,STATUS=Running),但是 Pod 中的应用(例如:springboot)还在处于启动中的阶段。此时,如果有客户端的请求被转发到了该 Pod 上,那最终的结果将会是客户端收到一个类似“连接被拒绝”的返回。
二、原因分析
默认情况下(在我们未在 Pod 中未设置 ReadinessProbe 相关信息的时候),Kubelet 会认为 Pod 的 ReadinessProbe 探针永远返回 Success,即 Pod 一直都处于可以对外提供服务的状态,该 Pod 会立刻成为一个服务端点接收客户端的请求。由于此时内部的服务正处于启动中,所以就很有可能会出现本文开头出现的那个问题。
三、解决方案
在原因分析当中其实已经提到了解决方案,就是利用 K8s 提供的 ReadinessProbe(就绪探针)。通过该探针是否返回 SUCCESS, Kubelet 可以知道当前 Pod 是否已经准备好接收请求了。只有已经准备好了的 Pod 才会被安排对外接客(接收请求)。
试想下,我们对「服务A」加上了 ReadinessProbe,且「服务A」有三个 Pod 副本(A1,A2,A3)正在对外提供服务。这个时候,我们重新发布一下「服务A」,若新建的副本 A4 还未准备就绪,则之前的A1,A2,A3 还将继续对外提供服务。当 A4 已经准备就绪,此时 Ta 才会替换掉 A1,A2,A3 其中一个副本。这样,就避免了出现 A4 还没准备好,就开始接客的尴尬时刻了。
四、新问题(迷惑)
到现在为止是不是一切看起来都很美好?ReadinessProbe 很完美的解决了开始提到的那个问题。
可是,但是,但可是 … 现实还是狠狠的抽了我一个嘴巴。
先看下我们系统结构的一张简单示意图:
- annoroad-xxx-front:前端应用
- annoraod-xxx-server:对应前端的服务(BFF),有 3 个 Pod 副本,annoraod-xxx-server-1、annoraod-xxx-server-2、annoraod-xxx-server-3
- springcloud-gateway:网关,有 3 个 Pod 副本,springcloud-gateway-1、springcloud-gateway-2、springcloud-gateway-3
- 微服务A:提供实际的业务服务,有 3 个 Pod 副本,微服务A-1、微服务A-2、微服务A-3
请求的路径如下:
- 用户通过浏览器访问前端应用 annoroad-xxx-front
- 前端应用(annoroad-xxx-front)通过 SVC 路由到一个前端服务(annoroad-xxx-server-1)
- 前端服务(annoroad-xxx-server-1)通过 SVC 路由到一个 gateway(springcloud-gateway-3)
- 最后由该 gateway(springcloud-gateway-3) 路由到一个微服务A(微服务A-1)
我们分别在 annoroad-xxx-server、springcloud-gateway、微服务A、微服务B …. 上加上了ReadinessProbe(就绪探针),这样 服务准备就绪后再对外提供服务 的目标就能实现了吗?
让我来直接公布试验后的最终结果:
- annoroad-xxx-server、springcloud-gateway 可以达到预期的效果
- 微服务A、微服务B …… 不行!!!
之所以造成这个结果的原因是:
首先,annoroad-xxx-server、springcloud-gateway 的路由走的是 K8s 的 SVC,SVC 中的 Endpoint 描述了有关联的 Pod ,如果 Pod 的 Ready 状态变为 False,则 K8s 会自动将其从 Service 的 Endpoint 列表中删除,后续如果 Pod 的 Ready 状态又恢复了,那会将该 Pod 重新加到 Service 的 Endpoint 列表当中。所以, annoroad-xxx-server、springcloud-gateway 可以达到预期的效果。
我们再来说下从 gateway 到微服务。从 gateway 路由到 微服务A、微服务B …… 走的是 gateway 的 ribbon 路由,拉取的是 eureka 上的节点列表,也就是说只要节点已经注册到 eureka 上了,从 gateway就能路由到这个节点上了,即使这个节点在 K8s 中的还处于未就绪的状态。
为了更好的加以证明,我做了一个简单的实验,将某微服务的 ReadinessProbe 的 initialDelaySeconds 设置的时间长一点儿(模拟一直处于未就绪的状态)。然后,观察处于未就绪状态的该服务,启动完成后是否被注册到 eureka 上了,结果如下图:
从上图,我们可以看到 annoroad-alpha 的一个副本已经注册到 eureka 上了。但是在 K8s 容器内,该副本的 Ready 状态为0/1(未就绪)。此时,通过 gateway 访问该微服务(annoroad-alpha),是能够成功访问(完全绕过了 K8s 的就绪状态)。
四、最后
如果想再多了解些 ReadinessProbe(就绪探针)的相关概念,可以移步到我写的另外一篇文章《Pod健康检查和服务可用性检查》