文章目录

  • 背景
  • Deployment滚动更新策略
  • 容器探针
  • 检查机制
  • 探测结果
  • 探测类型
  • 删除和新建Pod情况分析
  • 【1】新Pod启动时请求处理
  • 【2】删除pod时请求处理
  • 参考


背景

在部署项目版本升级过程中发现每次都会出现2分钟左右的502停机情况,在进行问题和排查过程中发现如下配置中存在一些可以优化的点,本文是这次操作过程的总结以及一些简单的扩展。

docker 部署 rocketmq Docker 部署无感升级_docker


分析下上面配置,使用是没有问题的,但是没有很好的利用K8s的一些特性机制来提升升级的效率,首先我们使用了Deployment声明式组件,该组件为 Pods 和 ReplicaSets 提供声明式的更新能力,提供了默认的滚动更新机制,如果按照下面所示先执行kubectl delete操作那么会将所有pod删除,等待删除后再依次创建新的pod,那么中间必然存在一个较大的空档期,即副本数被删除完毕,开始创建第一个Pod副本时,此时访问应用就会出现502网关问题,更友好的一种方式是使用RollingUpdate 滚动更新方式,这也是默认的配置。其次,最后俩行的配置是重复了,直接执行kubectl apply是可以完成修改升级操作的。

修改完成之后再次部署发现整个发布过程还是存在一些问题,多次请求下间断性出现一些502和404的错误,经过排查发现是因为Pod启动时未完全启动成功就接入流量导致4XX问题,还有在Pod已经删除后还依然有流量接入导致出现502问题.

Deployment滚动更新策略

疑问:有了Pod组件为什么还需要Deployment组件?

分析

【1】Pod是K8S调度的最小单元。但是Pod中容器出现异常终止了是不会重启的。

【2】Deployment拥有更加灵活强大的升级、回滚功能,并且支持滚动更新

【3】使用Deployment升级Pod只需要定义Pod的最终状态,k8s会为你执行必要的操作

【4】pod是k8s里调度的最小单位,而deployment是更高一层的定义。纯pod我们是无法确保其运行,无法对其异常做出处理的。

docker 部署 rocketmq Docker 部署无感升级_docker 部署 rocketmq_02


Deployment提供了俩个升级机制RollingUpdate滚动更新,这也是默认的机制。即先创建一个新的 Pod,并待其成功运行以后,再删除旧的。另外一个策略是Recreate,即先删除现有的 Pod,再创建新的。关于 Deployment,还可以设置最大不可用(maxUnavailable)和最大增量(maxSurge),来更精确地控制滚动更新操作,在RollingUpdate滚动更新策略下,Deployment 控制器会不断对 ReplicaSet 进行调和,滚动更新的最大好处就是零停机,整个更新过程始终有副本在运行,从而保证了业务的连续性。

容器探针

Kubernetes 中提供了一系列的健康检查,可以定制调用,来帮助解决类似的问题,我们称之为 Probe(探针)。
probe 是由 kubelet 对容器执行的定期诊断。 要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。

检查机制

使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种:
exec
在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
grpc
使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC健康检查。 如果响应的状态是 “SERVING”,则认为诊断成功。 gRPC 探针是一个 alpha 特性,只有在你启用了 “GRPCContainerProbe” 特性门控时才能使用。
httpGet
对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
tcpSocket
对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

探测结果

每次探测都将获得以下三种结果之一:
Success(成功)
容器通过了诊断。
Failure(失败)
容器未通过诊断。
Unknown(未知)
诊断失败,因此不会采取任何行动。

探测类型

针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success。
readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success。
startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器,而容器依其 重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success。

删除和新建Pod情况分析

【1】新Pod启动时请求处理

默认情况下,一旦某个 pod 中的所有容器全部启动,k8s 就会认为该 pod 处于就绪状态,从而将流量发往该 pod。但某些应用启动后,还需要完成数据或配置文件的加载工作才能对外提供服务,因此通过容器是否启动来判断其是否就绪并不严谨。通过为容器配置就绪探针,能让 k8s 更准确地判断容器是否就绪,从而构建出更健壮的应用。K8s 保证只有 pod 中的所有容器全部通过了就绪探测,才允许 service 将流量发往该 pod。一旦就绪探测失败,k8s 会停止将流量发往该 pod。
对于Java语言程序来说,可能还需要启动spring boot框架,连接数据库等待,注册服务等等。这样就会导致短暂的服务不可用。尤其是对于启动耗时的应用来说更是如此,就拿参与项目为例,平均启动时间都在30s以上,在升级过程中经常会出现404的问题。
对于该类情况,就需要进行ReadinessProbe 探测来检验容器是否满足需求

【2】删除pod时请求处理

当 Pod 在 kube-proxy 或 Ingress 控制器删除之前终止,我们可能会遇到停机时间。此时,Kubernetes 仍将流量路由到 IP 地址,但 Pod 已经不存在了。Ingress 控制器、kube-proxy、CoreDNS 等也没有足够的时间从其内部状态中删除 IP地址。
理想情况下,在删除 Pod 之前,Kubernetes 应该等待集群中的所有组件更新了 endpoint 列表,但是 Kubernetes 不是那样工作的。

Kubernetes 提供了原语来分发 endpoint(即 Endpoint 对象和更高级的抽象,例如 Endpoint Slices),所以 Kubernetes 不会验证订阅 endpoint 更改的组件是否是最新的集群状态信息。

那么,如何避免这种竞争情况并确保在 endpoint 广播之后删除 Pod?我们需要等待,当 Pod 即将被删除时,它会收到 SIGTERM 信号。我们的应用程序可以捕获该信号并开始关闭。由于 endpoint 不会立即从 Kubernetes 的所有组件中删除。
处理过程:
1:等待一定的时间,然后退出
2:即便有 SIGTERM 信号,但仍然可以处理传入流量。
3:最后,关闭现有的长期连接。关闭该进程。

那么我们应该等多久?默认情况下,Kubernetes 将发送 SIGTERM 信号并等待 30 秒,然后强制终止该进程。因此,我们可以使用前 15 秒继续操作。该间隔应足以将 endpoint 删除信息传播到 kube-proxy、Ingress 控制器、CoreDNS 等,然后,到达 Pod 的流量会越来越少,直到停止。15 秒后,我们就可以安全地关闭与数据库的连接并终止该过程。

如果我们认为需要更多时间,那么可以在 20 或 25 秒时停止该过程。这里有一点要注意,Kubernetes 将在 30 秒后强行终止该进程(除非我们更改 Pod 定义中的 terminationGracePeriodSeconds)。如果我们无法更改代码以获得更长的等待时间要怎么办?我们可以调用脚本以获得固定的等待时间,然后退出应用程序。

在调用 SIGTERM 之前,Kubernetes 会在 Pod 中公开一个 preStop hook。我们可以将 preStop hook 设置为等待 15 秒。下面是一个例子:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
      lifecycle:
        preStop:
          exec:
            command: ["sleep", "15"]

目前的解决方式:
1:设置prestop休眠15秒
2:启动时延迟几十秒,进行tcp端口的探测,其中8888端口是容器应用的端口
3:通过本次的修改在现阶段的发布部署基本上不会有任何的升级停机切换的感知了,是非常平稳的滚动升级

lifecycle:
          preStop:
            exec:
              command: ["sleep","15"]
        readinessProbe:
          failureThreshold: 3
          initialDelaySeconds: 15
          periodSeconds: 30
          successThreshold: 1
          tcpSocket:
            port: 8888
          timeoutSeconds: 1

上面的休眠的方式只是一种简单的处理方式,如果你先想要自己的业务更友好的去处理启动和停机,对于SpringBoot应用来说可以进行一些特定的配置修改来完成应用上的优雅停机,Spring Boot 2.3.0.RELEASE引入了Graceful Shutdown的功能。其中所有四个嵌入式Web服务器(Tomcat,Undertow,Netty和Jetty)都为响应式和基于Servlet的Web应用程序提供优雅停机功能。