OpenKruise v1.2:新增 PersistentPodState 实现有状态 Pod 拓扑固定与 IP 复用

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,182元/月
可观测可视化 Grafana 版,10个用户账号 1个月
可观测监控 Prometheus 版,每月50GB免费额度
简介: 在 v1.2 版本中,OpenKruise 提供了一个名为 PersistentPodState 的新 CRD 和控制器,在 CloneSet status 和 lifecycle hook 中新增字段, 并对 PodUnavailableBudget 做了多重优化。

作者:王思宇(酒祝)



云原生应用自动化管理套件、CNCF Sandbox 项目 -- OpenKruise,近期发布了 v1.2 版本。


OpenKruise[1]是针对 Kubernetes 的增强能力套件,聚焦于云原生应用的部署、升级、运维、稳定性防护等领域。所有的功能都通过 CRD 等标准方式扩展,可以适用于 1.16 以上版本的任意 Kubernetes 集群。单条 helm 命令即可完成 Kruise 的一键部署,无需更多配置。


版本解析


在 v1.2 版本中,OpenKruise 提供了一个名为 PersistentPodState 的新 CRD 和控制器,在 CloneSet status 和 lifecycle hook 中新增字段, 并对 PodUnavailableBudget 做了多重优化。


1. 新增 CRD 和 Controller-PersistentPodState


随着云原生的发展,越来越多的公司开始将有状态服务(如:Etcd、MQ)进行 Kubernetes 部署。K8s StatefulSet 是管理有状态服务的工作负载,它在很多方面考虑了有状态服务的部署特征。然而,StatefulSet 只能保持有限的 Pod 状态,如:Pod Name 有序且不变,PVC 持久化,并不能满足其它 Pod 状态的保持需求,例如:固定 IP 调度,优先调度到之前部署的 Node 等。典型案例有:


  • 服务发现中间件服务对部署之后的 Pod IP 异常敏感,要求 IP 不能随意改变
  • 数据库服务将数据持久化到宿主机磁盘,所属 Node 改变将导致数据丢失

针对上述描述,Kruise 通过自定义 PersistentPodState CRD,能够保持 Pod 其它相关状态,例如:“固定 IP 调度”。


一个 PersistentPodState 资源对象 YAML 如下:


apiVersion: apps.kruise.io/v1alpha1
kind: PersistentPodState
metadata:
  name: echoserver
  namespace: echoserver
spec:
  targetRef:
    # 原生k8s 或 kruise StatefulSet
    # 只支持 StatefulSet 类型
    apiVersion: apps.kruise.io/v1beta1
    kind: StatefulSet
    name: echoserver
  # required node affinity,如下:Pod重建后将强制部署到同Zone
  requiredPersistentTopology:
    nodeTopologyKeys:
      failure-domain.beta.kubernetes.io/zone[,other node labels]
  # preferred node affinity,如下:Pod重建后将尽量部署到同Node
  preferredPersistentTopology:
    - preference:
        nodeTopologyKeys:
          kubernetes.io/hostname[,other node labels]
      # int, [1 - 100]
      weight: 100


“固定 IP 调度”应该是比较常见的有状态服务的 K8s 部署要求,它的含义不是“指定 Pod IP 部署”,而是要求 Pod 在第一次部署之后,业务发布或机器驱逐等常规性运维操作都不会导致 Pod IP 发生变化。达到上述效果,首先就需要 K8s 网络组件能够支持 Pod IP 保留以及尽量保持 IP 不变的能力,本文将 flannel 网络组件中的 Host-local 插件做了一些代码改造, 使之能够达到同 Node 下保持 Pod IP 不变的效果,相关原理就不在此陈述,代码请参考:host-local[2]


“固定 IP 调度”好像有网络组件支持就好了,这跟 PersistentPodState 有什么关系呢?因为,网络组件实现"Pod IP 保持不变"都有一定的限制, 例如:flannel 只能支持同 Node 保持 Pod IP 不变。但是,K8s 调度的最大特性就是“不确定性”,所以“如何保证 Pod 重建后调度到同 Node 上”就是 PersistentPodState 解决的问题。


另外,你可以通过在 StatefulSet 或 Advanced StatefulSet 上面新增如下的 annotations,来让 Kruise 自动为你的 StatefulSet 创建 PersistentPodState 对象,从而避免了手动创建所有 PersistentPodState 的负担。


apiVersion: apps.kruise.io/v1alpha1
kind: StatefulSet
metadata:
  annotations:
    # 自动生成PersistentPodState对象
    kruise.io/auto-generate-persistent-pod-state: "true"
    # preferred node affinity,如下:Pod重建后将尽量部署到同Node
    kruise.io/preferred-persistent-topology: kubernetes.io/hostname[,other node labels]
    # required node affinity,如下:Pod重建后将强制部署到同Zone
    kruise.io/required-persistent-topology: failure-domain.beta.kubernetes.io/zone[,other node labels]


2. CloneSet 针对百分比形式 partition 计算逻辑变化,新增 status 字段


过去,CloneSet 通过 “向上取整” 的方式来计算它的 partition 数值(当它是百分比形式的数值时),这意味着即使你将 partition 设置为一个小于 100% 的百分比,CloneSet 也有可能不会升级任何一个 Pod 到新版本。比如,对于一个 replicas=8 和 partition=90% 的 CloneSet 对象,它所计算出的实际 partition 数值是 8(来自 8 * 90% 向上取整), 因此它暂时不会执行升级动作。这有时候会为用户来带困惑,尤其是对于使用了一些 rollout 滚动升级组件的场景,比如 Kruise Rollout 或 Argo。


因此,从 v1.2 版本开始,CloneSet 会保证在 partition 是小于 100% 的百分比数值时,至少有 1 个 Pod 会被升级,除非 CloneSet 处于 replicas <= 1 的情况。


不过,这样会导致用户难以理解其中的计算逻辑,同时又需要在 partition 升级的时候知道期望升级的 Pod 数量,来判断该批次升级是否完成。


所以我们另外又在 CloneSet status 中新增了 expectedUpdatedReplicas 字段,它可以很直接地展示基于当前的 partition 数值,期望有多少 Pod 会被升级。对于用户来说:


只需要比对 status.updatedReplicas>= status.expectedUpdatedReplicas 以及另外的 updatedReadyReplicas 来判断当前发布阶段是否达到完成状态。


apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
  replicas: 8
  updateStrategy:
    partition: 90%
status:
  replicas: 8
  expectedUpdatedReplicas: 1
  updatedReplicas: 1
  updatedReadyReplicas: 1


3. 在 lifecycle hook 阶段设置 Pod not-ready


Kruise 在早先的版本中提供了 lifecycle hook 功能,其中 CloneSet 和 Advanced StatefulSet 都支持了 PreDelete、InPlaceUpdate 两种 hook, Advanced DaemonSet 目前只支持 PreDelete hook。


过去,这些 hook 只会将当前的操作卡住,并允许用户在 Pod 删除之前或者原地升级的前后来做一些自定义的事情(比如将 Pod 从服务端点中摘除)。但是,Pod 在这些阶段中很可能还处于 Ready 状态,此时将它从一些自定义的 service 实现中摘除, 其实一定程度上有点违背 Kubernetes 的常理,一般来说它只会将处于 NotReady 状态的 Pod 从服务端点中摘除。


因此,这个版本我们在 lifecycle hook 中新增了 markPodNotReady 字段,它控制了 Pod 在处于 hook 阶段的时候是否会被强制设为 NotReady 状态。


type LifecycleStateType string
// Lifecycle contains the hooks for Pod lifecycle.
type Lifecycle struct 
    // PreDelete is the hook before Pod to be deleted. 
    PreDelete *LifecycleHook `json:"preDelete,omitempty"` 
    // InPlaceUpdate is the hook before Pod to update and after Pod has been updated. 
    InPlaceUpdate *LifecycleHook `json:"inPlaceUpdate,omitempty"`
}
type LifecycleHook struct {
    LabelsHandler     map[string]string `json:"labelsHandler,omitempty"`
    FinalizersHandler []string          `json:"finalizersHandler,omitempty"`
    /**********************  FEATURE STATE: 1.2.0 ************************/
    // MarkPodNotReady = true means:
    // - Pod will be set to 'NotReady' at preparingDelete/preparingUpdate state.
    // - Pod will be restored to 'Ready' at Updated state if it was set to 'NotReady' at preparingUpdate state.
    // Default to false.
    MarkPodNotReady bool `json:"markPodNotReady,omitempty"`
    /*********************************************************************/ 
}


对于配置了 markPodNotReady: true 的 PreDelete hook,它会在 PreparingDelete 阶段的时候将 Pod 设置为 NotReady, 并且这种 Pod 在我们重新调大 replicas 数值的时候无法重新回到 normal 状态。


对于配置了 markPodNotReady: true 的 InPlaceUpdate hook,它会在 PreparingUpdate 阶段将 Pod 设置为 NotReady, 并在 Updated 阶段将强制 NotReady 的状态去掉。


4. PodUnavailableBudget 支持自定义 workload 与性能优化


Kubernetes 自身提供了 PodDisruptionBudget 来帮助用户保护高可用的应用,但它只能防护 eviction 驱逐一种场景。对于多种多样的不可用操作,PodUnavailableBudget 能够更加全面地防护应用的高可用和 SLA,它不仅能够防护 Pod 驱逐,还支持其他如删除、原地升级等会导致 Pod 不可用的操作。


过去,PodUnavailableBudget 仅仅支持一些特定的 workload,比如 CloneSet、Deployment 等,但它不能够识别用户自己定义的一些未知工作负载。


从 v1.2 版本开始,PodUnavailableBudget 支持了保护任意自定义工作负载的 Pod,只要这些工作负载声明了 scale subresource 子资源。


在 CRD 中,scale 子资源的声明方式如下:


    subresources:
      scale:
        labelSelectorPath: .status.labelSelector
        specReplicasPath: .spec.replicas
        statusReplicasPath: .status.replicas


不过,如果你的项目是通过 kubebuilder 或 operator-sdk 生成的,那么只需要在你的 workload 定义结构上加一行注解并重新 make manifests 即可:


// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.labelSelector


另外,PodUnavailableBudget 还通过关闭 client list 时候的默认 DeepCopy 操作,来提升了在大规模集群中的运行时性能。


5. 其他改动


你可以通过 Github release[3]页面,来查看更多的改动以及它们的作者与提交记录。


社区参与


非常欢迎你通过 Github/Slack/钉钉/微信 等方式加入我们来参与 OpenKruise 开源社区。你是否已经有一些希望与我们社区交流的内容呢?可以在我们的社区双周会

https://shimo.im/docs/gXqmeQOYBehZ4vqo)上分享你的声音,或通过以下渠道参与讨论:



参考链接:

[1] OpenKruise:

https://openkruise.io/


[2] host-local:

https://github.com/openkruise/samples


[3] Github release :

https://github.com/openkruise/kruise/releases


此处,查看 OpenKruise 项目 github 主页!!

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
Kubernetes 容器
kubeadm 部署的 k8s 增加 ip 并重新生成证书
kubeadm 部署的 k8s 增加 ip 并重新生成证书
1367 0
|
运维 资源调度 Kubernetes
阿里巴巴超大规模Kubernetes基础设施运维体系揭秘
在过去近三年时间,Kubernetes架构在阿里巴巴集团经历了飞速发展:既支持了集团统一调度超大规模场景,又支持了大批云上云产品用户,还在支持OXS区容器化。经过这些年的发展,我们也逐渐锤炼出了K8S Serverless全托管稳定性运维体系。这些运维和稳定性能力不是K8S原生就具备的,而是在大量实践和失败过程中的沉淀出来的系统稳定性加固能力。
阿里巴巴超大规模Kubernetes基础设施运维体系揭秘
|
JSON 算法 Go
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
163 0
|
存储 JSON Go
在go语言中通过Post的方法提交json的数据
1.把URL及info的对像这两个参数发给login函数把结构体对象转换成json, 2.用POST方法提交JSON的数据到服务器上 3.通过调用Client.Do方法得到服务器的响应response的JSON 4.把服务器响应回来的JSON解析成结构体对象来存储相应的信息 5.调用解析JSON的结构体对象的各属性得到相应的信息
1065 0
|
弹性计算 监控 网络协议
记一个诡异的TCP挥手乱序问题
tcp四次挥手是超经典的网络知识,但是网络中的异常状况千奇百怪,说不定会“偷袭”到标准流程的盲区。最近笔者遇到了一个罕见的挥手乱序问题,经过对内核代码的分析和试验,最后终于找到了原因,角度可谓刁钻。本文从技术视角,将排查过程记录下来,既是对整个过程的小小总结,将事情彻底完结掉,也是对tcp实现的一些细节的学习记录。
137974 11
|
存储 运维 Kubernetes
云原生游戏最佳实践系列| 学习笔记
快速学习云原生游戏最佳实践系列
云原生游戏最佳实践系列| 学习笔记
|
架构师 Java 测试技术
软件设计实践:如何使用UML完成一个设计文档?
软件设计实践:如何使用UML完成一个设计文档?UML 建模可以很复杂,也可以很简单,简单掌握类图、时序图、组件图、部署图、用例 图、状态图、活动图这 7 种模型图,根据场景的不同,灵活在需求分析、概要设计和详细设计阶段绘制对应的模型图,可以实实在在地做好软件建模,搞好系统设计,做一个掌控局面、引领技术团队的架构师。
460 1
软件设计实践:如何使用UML完成一个设计文档?
|
弹性计算 运维 Kubernetes
OpenKruiseGame 的设计理念详解| 学习笔记
快速学习 OpenKruiseGame 的设计理念详解
OpenKruiseGame 的设计理念详解| 学习笔记
|
存储 缓存 Kubernetes
与 Kubernetes 共存:集群升级的4种方式
与 Kubernetes 共存:集群升级的4种方式
与 Kubernetes 共存:集群升级的4种方式
|
Kubernetes 负载均衡 监控
kube-proxy源码分析:深入浅出理解k8s的services工作原理
在进行k8s实践中, services 是经常碰到的资源对象,services 充当了 k8s 集群 pod 服务抽象的功能,为后端pod 提供了负载均衡和服务发现,那他到底是如何工作的呢,这里从 services 的具体实现 kube-proxy 出发解读 services 的工作机制。
2617 1
kube-proxy源码分析:深入浅出理解k8s的services工作原理