Kubernetes原地升级实现原理

kubernetes原地升级实现原理

在介绍原地升级实现原理之前,我们先来看一些原地升级功能所依赖的原生 Kubernetes 功能:

技术背景

背景 1:Kubelet 针对 Pod 容器的版本管理

每个 Node 上的 Kubelet,会针对本机上所有 Pod.spec.containers 中的每个 container 计算一个 hash 值,并记录到实际创建的容器中。

如果我们修改了 Pod 中某个 container 的 image 字段,kubelet 会发现 container 的 hash 发生了变化、与机器上过去创建的容器 hash 不一致,而后 kubelet 就会把旧容器停掉,然后根据最新 Pod spec 中的 container 来创建新的容器。

这个功能,其实就是针对单个 Pod 的原地升级的核心原理。

背景 2:Pod 更新限制

在原生 kube-apiserver 中,对 Pod 对象的更新请求有严格的 validation 校验逻辑:

// validate updateable fields:
// 1.  spec.containers[*].image
// 2.  spec.initContainers[*].image
// 3.  spec.activeDeadlineSeconds

简单来说,对于一个已经创建出来的 Pod,在 Pod Spec 中只允许修改containers/initContainers 中的 image 字段,以及 activeDeadlineSeconds 字段。对 Pod Spec 中所有其他字段的更新,都会被 kube-apiserver 拒绝。

背景 3:containerStatuses 上报

kubelet 会在 pod.status 中上报 containerStatuses,对应 Pod 中所有容器的实际运行状态:

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
status:
  containerStatuses:
  - name: nginx
    image: nginx:mainline
    imageID: docker-pullable://nginx@sha256:2f68b99bc0d6d25d0c56876b924ec20418544ff28e1fb89a4c27679a40da811b

绝大多数情况下,spec.containers[x].image 与 status.containerStatuses[x].image 两个镜像是一致的。
但是也有上述这种情况,kubelet 上报的与 spec 中的 image 不一致(spec 中是 nginx:latest,但 status 中上报的是 nginx:mainline)。
这是因为,kubelet 所上报的 image 其实是从 CRI 接口中拿到的容器对应的镜像名。而如果 Node 机器上存在多个镜像对应了一个 imageID,那么上报的可能是其中任意一个:

$ docker images | grep nginx
nginx            latest              2622e6cca7eb        2 days ago          132MB
nginx            mainline            2622e6cca7eb        2 days ago

因此,一个 Pod 中 spec 和 status 的 image 字段不一致,并不意味着宿主机上这个容器运行的镜像版本和期望的不一致。

背景 4:ReadinessGate 控制 Pod 是否 Ready

在 Kubernetes 1.12 版本之前,一个 Pod 是否处于 Ready 状态只是由 kubelet 根据容器状态来判定:如果 Pod 中容器全部 ready,那么 Pod 就处于 Ready 状态。
但事实上,很多时候上层 operator 或用户都需要能控制 Pod 是否 Ready 的能力。因此,Kubernetes 1.12 版本之后提供了一个 readinessGates 功能来满足这个场景。如下:

apiVersion: v1
kind: Pod
spec:
  readinessGates:
  - conditionType: MyDemo
status:
  conditions:
  - type: MyDemo
    status: "True"
  - type: ContainersReady
    status: "True"
  - type: Ready
    status: "True"

目前 kubelet 判定一个 Pod 是否 Ready 的两个前提条件:
1.Pod 中容器全部 Ready(其实对应了 ContainersReady condition 为 True);
2.如果 pod.spec.readinessGates 中定义了一个或多个 conditionType,那么需要这些 conditionType 在 pod.status.conditions 中都有对应的 status: “true” 的状态。
只有满足上述两个前提,kubelet 才会上报 Ready condition 为 True。

实现原理

了解了上面的四个背景之后,接下来分析一下 OpenKruise 是如何在 Kubernetes 中实现原地升级的原理。

1、单个 Pod 如何原地升级?

由“背景 1”可知,其实我们对一个存量 Pod 的 spec.containers[x] 中字段做修改,kubelet 会感知到这个 container 的 hash 发生了变化,随即就会停掉对应的旧容器,并用新的 container 来拉镜像、创建和启动新容器。

由“背景 2”可知,当前我们对一个存量 Pod 的 spec.containers[x] 中的修改,仅限于 image 字段。

因此,得出第一个实现原理:**对于一个现有的 Pod 对象,我们能且只能修改其中的 spec.containers[x].image 字段,来触发 Pod 中对应容器升级到一个新的 image。

2、如何判断 Pod 原地升级成功?

接下来的问题是,当我们修改了 Pod 中的 spec.containers[x].image 字段后,如何判断 kubelet 已经将容器重建成功了呢?

由“背景 3”可知,比较 spec 和 status 中的 image 字段是不靠谱的,因为很有可能 status 中上报的是 Node 上存在的另一个镜像名(相同 imageID)。

因此,得出第二个实现原理:判断 Pod 原地升级是否成功,相对来说比较靠谱的办法,是在原地升级前先将status.containerStatuses[x].imageID 记录下来。在更新了 spec 镜像之后,如果观察到 Pod 的 status.containerStatuses[x].imageID 变化了,我们就认为原地升级已经重建了容器。

但这样一来,我们对原地升级的 image 也有了一个要求:不能用 image 名字(tag)不同、但实际对应同一个 imageID 的镜像来做原地升级,否则可能一直都被判断为没有升级成功(因为 status 中 imageID 不会变化)。

当然,后续我们还可以继续优化。OpenKruise 即将开源镜像预热的能力,会通过 DaemonSet 在每个 Node 上部署一个 NodeImage Pod。通过 NodeImage 上报我们可以得知 pod spec 中的 image 所对应的 imageID,然后和 pod status 中的 imageID 比较即可准确判断原地升级是否成功。

3、如何确保原地升级过程中流量无损?

在 Kubernetes 中,一个 Pod 是否 Ready 就代表了它是否可以提供服务。因此,像 Service 这类的流量入口都会通过判断 Pod Ready 来选择是否能将这个 Pod 加入 endpoints 端点中。

由“背景 4”可知,从 Kubernetes 1.12+ 之后,operator/controller 这些组件也可以通过设置 readinessGates 和更新pod.status.conditions 中的自定义 type 状态,来控制 Pod 是否可用。
因此,得出第三个实现原理:可以在 pod.spec.readinessGates 中定义一个叫 InPlaceUpdateReady 的 conditionType。

在原地升级时:先将 pod.status.conditions 中的 InPlaceUpdateReady condition 设为 “False”,这样就会触发 kubelet 将 Pod 上报为 NotReady,从而使流量组件(如 endpoint controller)将这个 Pod 从服务端点摘除;

再更新 pod spec 中的 image 触发原地升级。原地升级结束后,再将 InPlaceUpdateReady condition 设为 “True”,使 Pod 重新回到 Ready 状态。

另外在原地升级的两个步骤中,第一步将 Pod 改为 NotReady 后,流量组件异步 watch 到变化并摘除端点可能是需要一定时间的。因此我们也提供优雅原地升级的能力,即通过 gracePeriodSeconds 配置在修改 NotReady 状态和真正更新 image 触发原地升级两个步骤之间的静默期时间。

原文链接:https://developer.aliyun.com/article/765919

实验

知道了原理,那就来实际验证一下吧。

实验步骤

  1. 启动一个 0.0.4版本的镜像,然后设置一个共享内存
  2. 修改pod编排,将镜像版本修改为0.0.5
  3. 等待kubelet更新后,查看共享内存是否还在

第一步:启动程序,查看设置的共享内存
在这里插入图片描述
第二步:更新pod镜像版本号。更新成功之后,会增加一次重启次数!
在这里插入图片描述
第三步:进入容器,检查共享内存是否还存在
在这里插入图片描述

总结

验证成功,确实可以通过这种方式去更新,而且共享内存也还存在。
但是这是一种粗暴的验证方法,更新期间没有去处理流量的问题!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值