返回顶部
首页 > 资讯 > 精选 >揭秘:如何为 Kubernetes 实现原地升级
  • 123
分享到

揭秘:如何为 Kubernetes 实现原地升级

2023-06-04 10:06:01 123人浏览 安东尼
摘要

作者 | 王思宇(酒祝) 阿里云技术专家参与阿里巴巴云原生文末留言互动,即有机会获得赠书福利及作者答疑!概念介绍原地升级一词中,“升级”不难理解,是将应用实例的版本由旧版替换为新版。那么如何结合 kubernetes 环境来理解“原地”呢

揭秘:如何为 Kubernetes 实现原地升级

作者 | 王思宇(酒祝) 阿里云技术专家

参与阿里巴巴云原生文末留言互动,即有机会获得赠书福利及作者答疑!

概念介绍

原地升级一词中,“升级”不难理解,是将应用实例的版本由旧版替换为新版。那么如何结合 kubernetes 环境来理解“原地”呢?

我们先来看看 k8s 原生 workload 的发布方式。这里假设我们需要部署一个应用,包括 foo、bar 两个容器在 Pod 中。其中,foo 容器第一次部署时用的镜像版本是 v1,我们需要将其升级为 v2 版本镜像,该怎么做呢?

  • 如果这个应用使用 Deployment 部署,那么升级过程中 Deployment 会触发新版本 ReplicaSet 创建 Pod,并删除旧版本 Pod。如下图所示:

揭秘:如何为 Kubernetes 实现原地升级

在本次升级过程中,原 Pod 对象被删除,一个新 Pod 对象被创建。新 Pod 被调度到另一个 node 上,分配到一个新的 IP,并把 foo、bar 两个容器在这个 Node 上重新拉取镜像、启动容器。

  • 如果这个应该使用 StatefulSet 部署,那么升级过程中 StatefulSet 会先删除旧 Pod 对象,等删除完成后用同样的名字在创建一个新的 Pod 对象。如下图所示:

揭秘:如何为 Kubernetes 实现原地升级

值得注意的是,尽管新旧两个 Pod 名字都叫 pod-0,但其实是两个完全不同的 Pod 对象(uid也变了)。StatefulSet 等到原先的 pod-0 对象完全从 Kubernetes 集群中被删除后,才会提交创建一个新的 pod-0 对象。而这个新的 Pod 也会被重新调度、分配IP、拉镜像、启动容器。

  • 而所谓原地升级模式,就是在应用升级过程中避免将整个 Pod 对象删除、新建,而是基于原有的 Pod 对象升级其中某一个或多个容器的镜像版本:

揭秘:如何为 Kubernetes 实现原地升级

在原地升级的过程中,我们仅仅更新了原 Pod 对象中 foo 容器的 image 字段来触发 foo 容器升级到新版本。而不管是 Pod 对象,还是 Node、IP 都没有发生变化,甚至 foo 容器升级的过程中 bar 容器还一直处于运行状态。

总结:这种只更新 Pod 中某一个或多个容器版本、而不影响整个 Pod 对象、其余容器的升级方式,被我们称为 Kubernetes 中的原地升级。

收益分析

那么,我们为什么要在 Kubernetes 中引入这种原地升级的理念和设计呢?

首先,这种原地升级的模式极大地提升了应用发布的效率,根据非完全统计数据,在阿里环境下原地升级至少比完全重建升级提升了 80% 以上的发布速度。这其实很容易理解,原地升级为发布效率带来了以下优化点:

  1. 节省了调度的耗时,Pod 的位置、资源都不发生变化;
  2. 节省了分配网络的耗时,Pod 还使用原有的 IP;
  3. 节省了分配、挂载远程盘的耗时,Pod 还使用原有的 PV(且都是已经在 Node 上挂载好的);
  4. 节省了大部分拉取镜像的耗时,因为 Node 上已经存在了应用的旧镜像,当拉取新版本镜像时只需要下载很少的几层 layer。

其次,当我们升级 Pod 中一些 sidecar 容器(如采集日志监控等)时,其实并不希望干扰到业务容器的运行。但面对这种场景,Deployment 或 StatefulSet 的升级都会将整个 Pod 重建,势必会对业务造成一定的影响。而容器级别的原地升级变动的范围非常可控,只会将需要升级的容器做重建,其余容器包括网络、挂载盘都不会受到影响。

最后,原地升级也为我们带来了集群的稳定性和确定性。当一个 Kubernetes 集群中大量应用触发重建 Pod 升级时,可能造成大规模的 Pod 飘移,以及对 Node 上一些低优先级的任务 Pod 造成反复的抢占迁移。这些大规模的 Pod 重建,本身会对 apiserver、scheduler、网络/磁盘分配等中心组件造成较大的压力,而这些组件的延迟也会给 Pod 重建带来恶性循环。而采用原地升级后,整个升级过程只会涉及到 controller 对 Pod 对象的更新操作和 kubelet 重建对应的容器。

技术背景

在阿里巴巴内部,绝大部分电商应用在云原生环境都统一用原地升级的方式做发布,而这套支持原地升级的控制器就位于 OpenKruise 开源项目中。

也就是说,阿里内部的云原生应用都是统一使用 OpenKruise 中的扩展 workload 做部署管理的,而并没有采用原生 Deployment/StatefulSet 等。


那么 OpenKruise 是如何实现原地升级能力的呢?在介绍原地升级实现原理之前,我们先来看一些原地升级功能所依赖的原生 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: v1kind: Podspec:  containers:  - name: Nginx    image: nginx:lateststatus:  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 nginxnginx            latest              2622e6cca7eb        2 days aGo          132MBnginx            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: v1kind: Podspec:  readinessGates:  - conditionType: MyDemostatus:  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。

在原地升级时:

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

原地升级结束后,再将 InPlaceUpdateReady condition 设为 “True”,使 Pod 重新回到 Ready 状态。

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

4. 组合发布策略

原地升级和 Pod 重建升级一样,可以配合各种发布策略来执行:

  • partition:如果配置 partition 做灰度,那么只会将 replicas-partition 数量的 Pod 做原地升级;
  • maxUnavailable:如果配置 maxUnavailable,那么只会将满足 unavailable 数量的 Pod 做原地升级;
  • maxSurge:如果配置 maxSurge 做弹性,那么当先扩出来 maxSurge 数量的 Pod 之后,存量的 Pod 仍然使用原地升级;
  • priority/scatter:如果配置了发布优先级/打散策略,会按照策略顺序对 Pod 做原地升级。

总结

如上文所述,OpenKruise 结合 Kubernetes 原生提供的 kubelet 容器版本管理、readinessGates 等功能,实现了针对 Pod 的原地升级能力。

而原地升级也为应用发布带来大幅的效率、稳定性提升。值得关注的是,随着集群、应用规模的增大,这种提升的收益越加明显。正是这种原地升级能力,在近两年帮助了阿里巴巴超大规模的应用容器平稳迁移到了基于 Kubernetes 的云原生环境,而原生 Deployment/StatefulSet 是完全无法在这种体量的环境下铺开使用的。(欢迎加入钉钉交流群:23330762)

- 赠书福利 -

揭秘:如何为 Kubernetes 实现原地升级

6 月 19 日 12:00 前在【阿里巴巴云原生公众号】留言区提出你的疑问,精选留言点赞第 1 名将免费获得此书,届时我们还会请本文作者针对留言点赞前 5 名的问题进行答疑!

课程推荐

为了更多开发者能够享受到 serverless 带来的红利,这一次,我们集结了 10+ 位阿里巴巴 Serverless 领域技术专家,打造出最适合开发者入门的 Serverless 公开课,让你即学即用,轻松拥抱云计算的新范式——Serverless。

点击即可免费观看课程:https://developer.aliyun.com/learning/roadmap/serverless

“阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

--结束END--

本文标题: 揭秘:如何为 Kubernetes 实现原地升级

本文链接: https://lsjlt.com/news/237604.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 揭秘:如何为 Kubernetes 实现原地升级
    作者 | 王思宇(酒祝) 阿里云技术专家参与阿里巴巴云原生文末留言互动,即有机会获得赠书福利及作者答疑!概念介绍原地升级一词中,“升级”不难理解,是将应用实例的版本由旧版替换为新版。那么如何结合 Kubernetes 环境来理解“原地”呢...
    99+
    2023-06-04
  • 函数重写原理揭秘:子类如何掌控父类行为
    问题:函数重写原理是什么?答案:函数重写允许子类通过声明一个同名方法并使用 override 关键字,重新定义从父类继承的方法,从而控制父类行为。步骤:在子类的构造函数中声明虚拟方法,使...
    99+
    2024-05-04
    函数重写 子类继承
  • Android如何模拟实现华为系统升级进度条
    这篇文章主要为大家展示了“Android如何模拟实现华为系统升级进度条”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Android如何模拟实现华为系统升级进度条”这篇文章吧。下面开始讲解虚线进度...
    99+
    2023-06-28
  • phpStudy中的MySQL如何实现升级
    本篇文章给大家主要讲的是关于phpStudy中的MySQL如何实现升级的内容,感兴趣的话就一起来看看这篇文章吧,相信看完phpStudy中的MySQL如何实现升级对大家多少有点参考价值吧。1.下载MySQL...
    99+
    2024-04-02
  • 服务器操作系统安全升级大揭秘:零成本实现网络安全
    随着网络技术的飞速发展,服务器操作系统安全升级变得越来越重要。服务器操作系统是服务器的核心软件,负责管理服务器的资源和运行应用程序。如果服务器操作系统存在安全漏洞,可能会被黑客利用,从而导致数据泄露、系统崩溃等严重后果。因此,及时对服务器...
    99+
    2024-02-07
    服务器操作系统 安全升级 网络安全 零成本
  • Python底层技术揭秘:如何实现IO操作
    Python底层技术揭秘:如何实现IO操作引言Python作为一种流行且易学的编程语言,被广泛应用于各种领域。在Python中,IO操作是最为常见和重要的功能之一。本文将重点探讨Python中IO操作的底层实现,并通过具体的代码示例帮助读者...
    99+
    2023-11-08
    实现方法 底层技术 Python IO
  • Python底层技术揭秘:如何实现哈希表
    Python底层技术揭秘:如何实现哈希表哈希表是在计算机领域中十分常见且重要的数据结构,它可以高效地存储和查找大量的键值对。在Python中,我们可以使用字典来使用哈希表,但是很少有人深入了解它的实现细节。本文将揭秘Python中哈希表的底...
    99+
    2023-11-08
    哈希算法 数据结构 键值对
  • Python底层技术揭秘:如何实现图算法
    随着计算机技术的不断发展,图论(graph theory)及其相关算法已经成为了计算机领域中非常重要的一部分。而对于Python程序员来说,掌握这些底层技术不仅可以提高代码的效率和质量,还有助于优化程序的性能和开发效率。本文将介绍Pytho...
    99+
    2023-11-08
    Python 图算法 底层技术
  • 如何实现react+redux的升级版todoList
    这篇文章主要为大家展示了“如何实现react+redux的升级版todoList”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何实现react+redux的升...
    99+
    2024-04-02
  • 如何实现Ubuntu 15.04升级到Ubuntu 15.10
    这篇文章主要讲解了“如何实现Ubuntu 15.04升级到Ubuntu 15.10”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何实现Ubuntu 15.04升级到Ubuntu 15.10...
    99+
    2023-06-13
  • 如何实现C# Web Services升级程序
    这篇文章将为大家详细讲解有关如何实现C# Web Services升级程序,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。介绍一种用C# Web Services升级程序。通过C# Web Services...
    99+
    2023-06-18
  • python中如何实现pip安装、升级以及升级固定的包
    这篇文章主要介绍了python中如何实现pip安装、升级以及升级固定的包,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1、pip下载安装1.1 pip下载进入https://...
    99+
    2023-06-29
  • Python底层技术揭秘:如何实现情感分析
    Python底层技术揭秘:如何实现情感分析,需要具体代码示例引言:随着社交媒体的普及和大数据时代的到来,情感分析成为了一个被广泛关注和应用的领域。情感分析可以帮助我们理解和分析用户的情感和意见,从而对产品、服务或市场做出更合理的决策。Pyt...
    99+
    2023-11-08
    Python 情感分析 底层技术
  • Python底层技术揭秘:如何实现图像处理
    Python底层技术揭秘:图像处理的实现及代码示例导语:图像处理是计算机科学中十分重要的一个领域。通过使用Python以及相关的底层技术,我们能够实现各种各样的图像处理操作。在本文中,我们将揭示Python图像处理的底层技术,并提供一些实用...
    99+
    2023-11-08
    Python 技术 图像处理
  • Python底层技术揭秘:如何实现哈希算法
    Python底层技术揭秘:如何实现哈希算法,需要具体代码示例摘要:哈希算法是计算机领域中常用的技术之一,用于快速确定数据的唯一标识。Python作为一门高级语言,提供了许多内建的哈希函数,如hash()函数以及各种散列算法的实现。本文将揭示...
    99+
    2023-11-08
    Python 技术 哈希
  • 揭秘 JavaScript 原型链:它是如何工作的以及为什么它很重要?
    JavaScript 原型链是如何工作的? 在 JavaScript 中,每个对象都有一个内部属性,称为原型对象。原型对象是一个普通对象,它包含了一组属性和方法,这些属性和方法可以被对象继承。当您创建一个新的对象时,它会自动继承其构造函...
    99+
    2024-02-06
    JavaScript 原型链 继承 代码复用 面向对象编程
  • Python底层技术揭秘:如何实现TCP/IP协议栈
    Python底层技术揭秘:如何实现TCP/IP协议栈,需要具体代码示例引言:随着互联网的快速发展,TCP/IP协议成为了现代互联网中最重要的协议之一。对于想要深入了解网络通信底层原理的开发者来说,了解TCP/IP协议栈的实现原理将是一个非常...
    99+
    2023-11-08
    实现 TCP/IP 底层技术
  • Python底层技术揭秘:如何实现字节码编译器
    Python底层技术揭秘:如何实现字节码编译器Python作为一门高级语言,其强大的特性和灵活性吸引着众多开发者。然而,要真正深入了解Python,我们需要深入其底层技术,探索其内部的工作机制。本文将带你揭秘Python底层的字节码编译器,...
    99+
    2023-11-08
    Python 编译器 字节码
  • 如何实现Ubuntu用户升级到Kernel 4.2.3内核
    这篇文章主要介绍“如何实现Ubuntu用户升级到Kernel 4.2.3内核”,在日常操作中,相信很多人在如何实现Ubuntu用户升级到Kernel 4.2.3内核问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
    99+
    2023-06-13
  • 如何在Couchbase中实现数据迁移和升级
    在Couchbase中实现数据迁移和升级可以通过以下方式实现: 使用Couchbase中的数据导出和导入工具:Couchbase...
    99+
    2024-04-09
    Couchbase
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作