当前位置: 首页 > news >正文

OpenKruise

因语雀与csdn markdown 格式有区别,请查看原文:
https://www.yuque.com/dycloud/pss8ys

一、OpenKruise 介绍

1.1 OpenKruise 是什么

OpenKruise 是一个基于 Kubernetes 的扩展套件,主要聚焦于云原生应用的自动化,比如部署、发布、运维以及可用性防护。OpenKruise 提供的绝大部分能力都是基于 CRD 扩展来定义的,它们不存在于任何外部依赖,可以运行在任意纯净的 Kubernetes 集群中。

当集群规模达到数千节点、数万 Pod 时,原生工作负载暴露出明显短板:

  1. 升级效率低下:滚动更新需逐个替换 Pod,千节点集群升级需小时级
  2. 状态应用支持弱:StatefulSet 无法处理复杂拓扑约束(如跨 AZ 平衡)
  3. 运维风险高:缺乏细粒度分批发布、原地升级等安全机制
  4. 资源浪费严重:无法按需管理 Sidecar 容器生命周期

OpenKruise 提供了以下的一些核心能力:

  • 增强版本的 Workloads:OpenKruise 包含了一系列增强版本的工作负载,比如 <font style="color:rgb(28, 30, 33);">CloneSet</font><font style="color:rgb(28, 30, 33);">Advanced StatefulSet</font><font style="color:rgb(28, 30, 33);">Advanced DaemonSet</font><font style="color:rgb(28, 30, 33);">BroadcastJob</font> 等。它们不仅支持类似于 Kubernetes 原生 Workloads 的基础功能,还提供了如原地升级、可配置的扩缩容/发布策略、并发操作等。其中,原地升级是一种升级应用容器镜像甚至环境变量的全新方式,它只会用新的镜像重建 Pod 中的特定容器,整个 Pod 以及其中的其他容器都不会被影响。因此它带来了更快的发布速度,以及避免了对其他 Scheduler、CNI、CSI 等组件的负面影响。
  • 应用的旁路管理:OpenKruise 提供了多种通过旁路管理应用 sidecar 容器、多区域部署的方式,“旁路” 意味着你可以不需要修改应用的 Workloads 来实现它们。比如,SidecarSet 能帮助你在所有匹配的 Pod 创建的时候都注入特定的 **<font style="color:rgb(28, 30, 33);">sidecar</font>** 容器,甚至可以原地升级已经注入的 sidecar 容器镜像、并且对 Pod 中其他容器不造成影响。而 WorkloadSpread 可以约束无状态 Workload 扩容出来 Pod 的区域分布,赋予单一 workload 的多区域和弹性部署的能力。
  • 高可用性防护:OpenKruise 可以保护你的 Kubernetes 资源不受级联删除机制的干扰,包括 CRD、Namespace、以及几乎全部的 Workloads 类型资源。相比于 Kubernetes 原生的 PDB 只提供针对 Pod Eviction 的防护,PodUnavailableBudget 能够防护 Pod Deletion、Eviction、Update 等许多种 voluntary disruption 场景。
  • 高级的应用运维能力:OpenKruise 也提供了很多高级的运维能力来帮助你更好地管理应用,比如可以通过 ImagePullJob 来在任意范围的节点上预先拉取某些镜像,或者指定某个 Pod 中的一个或多个容器被原地重启。

1.2 核心组件架构

OpenKruise 的整体架构如下:

1.2.1 API

所有 OpenKruise 的功能都是通过 Kubernetes API 来提供, 比如:

$ kubectl get crd | grep kruise.io
advancedcronjobs.apps.kruise.io            2021-09-16T06:02:36Z
broadcastjobs.apps.kruise.io               2021-09-16T06:02:36Z
clonesets.apps.kruise.io                   2021-09-16T06:02:36Z
containerrecreaterequests.apps.kruise.io   2021-09-16T06:02:36Z
daemonsets.apps.kruise.io                  2021-09-16T06:02:36Z
imagepulljobs.apps.kruise.io               2021-09-16T06:02:36Z
nodeimages.apps.kruise.io                  2021-09-16T06:02:36Z
podunavailablebudgets.policy.kruise.io     2021-09-16T06:02:36Z
resourcedistributions.apps.kruise.io       2021-09-16T06:02:36Z
sidecarsets.apps.kruise.io                 2021-09-16T06:02:36Z
statefulsets.apps.kruise.io                2021-09-16T06:02:36Z
uniteddeployments.apps.kruise.io           2021-09-16T06:02:37Z
workloadspreads.apps.kruise.io             2021-09-16T06:02:37Z
# ...

  • 资源对象中的特定标识(labels, annotations, envs 等),比如
apiVersion: v1
kind: Namespace
metadata:labels:# 保护这个 namespace 下的 Pod 不被整个 ns 级联删除policy.kruise.io/delete-protection: Cascading

1.2.2 Manager

Kruise-manager 是一个运行 controller 和 webhook 中心组件,它通过 Deployment 部署在 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">kruise-system</font> 命名空间中。

$ kubectl get deploy -n kruise-system
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
kruise-controller-manager   2/2     2            2           4h6m$ kubectl get pod -n kruise-system -l control-plane=controller-manager
NAME                                         READY   STATUS    RESTARTS   AGE
kruise-controller-manager-68dc6d87cc-k9vg8   1/1     Running   0          4h6m
kruise-controller-manager-68dc6d87cc-w7x82   1/1     Running   0          4h6m

逻辑上来说,如 <font style="color:#DF2A3F;">cloneset-controller</font>/<font style="color:#DF2A3F;">sidecarset-controller</font> 这些的 controller 都是独立运行的。不过为了减少复杂度,它们都被打包在一个独立的二进制文件、并运行在 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">kruise-controller-manager-xxx</font> 这个 Pod 中。

除了 controller 之外,<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">kruise-controller-manager-xxx</font> 中还包含了针对 Kruise CRD 以及 Pod 资源的 <font style="color:#DF2A3F;">admission webhook</font><font style="color:#DF2A3F;">Kruise-manager</font> 会创建一些 webhook configurations 来配置哪些资源需要感知处理、以及提供一个 Service 来给 kube-apiserver 调用。

$ kubectl get svc -n kruise-system
NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kruise-webhook-service   ClusterIP   172.24.9.234   <none>        443/TCP   4h9m

上述的 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">kruise-webhook-service</font> 非常重要,是提供给 kube-apiserver 调用的。

1.2.3 Daemon

这是从 Kruise v0.8.0 版本开始提供的一个新的 daemon 组件。

它通过 DaemonSet 部署到每个 Node 节点上,提供镜像预热、容器重启等功能。

$ kubectl get pod -n kruise-system -l control-plane=daemon
NAME                  READY   STATUS    RESTARTS   AGE
kruise-daemon-6hw6d   1/1     Running   0          4h7m
kruise-daemon-d7xr4   1/1     Running   0          4h7m
kruise-daemon-dqp8z   1/1     Running   0          4h7m
kruise-daemon-dv96r   1/1     Running   0          4h7m
kruise-daemon-q7594   1/1     Running   0          4h7m
kruise-daemon-vnsbw   1/1     Running   0          4h7m

1.3 核心工作负载

1.3.1 CloneSet(无状态应用增强)

解决问题:Deployment 无法满足高效、灵活的无状态应用管理
核心特性

  • 原地升级:仅重启容器不重建 Pod(IP 不变)
  • 按拓扑打散:保证 Pod 均匀分布在节点/AZ/区域
  • 灰度发布:支持 MaxUnavailable/MaxSurge 精细控制
  • 指定删除:优先删除特定 Pod(如故障节点上的 Pod)
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:name: web-server
spec:replicas: 1000updateStrategy:type: InPlaceIfPossible  # 优先原地升级partition: 100          # 保留100个旧版本PodmaxUnavailable: 10%     # 最大不可用比例selector:matchLabels:app: web-servertemplate:metadata: {...}spec:containers: {...}

1.3.2 Advanced StatefulSet (有状态应用增强)

解决问题:原生 StatefulSet 升级策略单一,缺乏运维控制
核心增强

  • 并行升级:支持多 Pod 同时升级(原生仅串行)
  • 暂停/继续:随时中断或继续升级过程
  • 版本回滚:一键回滚到历史版本
  • 序号跳过:排除特定序号 Pod 不升级

典型场景:数据库集群升级

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
metadata:name: mysql-cluster
spec:updateStrategy:type: RollingUpdaterollingUpdate:podUpdatePolicy: Parallel  # 并行升级maxUnavailable: 2          # 允许同时升级2个Podpaused: false              # 可随时设为true暂停# 其他配置同原生StatefulSet...

1.3.3 SidecarSet (Sidecar 容器管理)

解决问题:统一管理所有 Pod 的 Sidecar 容器(如日志采集器)
核心能力

  • 独立升级:单独升级 Sidecar 不影响主容器
  • 注入控制:按命名空间/节点标签选择性注入
  • 资源复用:多个 SidecarSet 可注入同一 Pod
  • 热升级:通过共享卷实现 Sidecar 无缝替换

典型场景:日志采集器热更新

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:name: log-collector
spec:selector:matchLabels:sidecar.kruise.io/inject: "true"containers:- name: filebeatimage: filebeat:7.14.0volumeMounts:- name: logsmountPath: /var/log/appvolumeTemplates:          # 自动创建共享卷- name: logsemptyDir: {}injectionStrategy:revisionHistoryLimit: 5 # 保留历史版本updateStrategy:type: RollingUpdatemaxUnavailable: 20%

1.3.4 UnitedDeployment(多区域部署)

解决问题:跨可用区/地域的全局应用部署
核心设计

  • 多子集管理:每个区域对应一个子工作负载(如 CloneSet)
  • 统一副本数:自动分配各区域 Pod 数量
  • 区域感知:根据节点拓扑自动分组

典型场景:跨 AZ 高可用服务

apiVersion: apps.kruise.io/v1alpha1
kind: UnitedDeployment
metadata:name: global-service
spec:replicas: 12topology:subsets:- name: az-eastnodeSelector:topology.kubernetes.io/zone: eastreplicas: 4          # 东部区域部署4副本- name: az-westnodeSelector:topology.kubernetes.io/zone: westreplicas: 4          # 西部区域部署4副本- name: az-centralnodeSelector:topology.kubernetes.io/zone: centralreplicas: 4          # 中部区域部署4副本template:                # 定义子集模板cloneSetTemplate:spec: {...}

1.3.5 BroadcastJob(节点级任务)

场景:在所有节点执行运维任务(如安全补丁)
特性

  • 支持并行/串行执行
  • 任务完成后自动清理 Pod
  • 失败任务自动重试
apiVersion: apps.kruise.io/v1alpha1
kind: BroadcastJob
metadata:name: security-patch
spec:parallelism: 10         # 同时运行10个节点completionPolicy:type: Always          # 所有节点必须完成template:spec:containers: {...}

1.3.6 Advanced DaemonSet(DaemonSet 增强)

增强点

  • 滚动更新支持 MaxUnavailable
  • 版本回滚能力
  • 节点打标控制安装

1.4 安装部署

https://openkruise.io/zh/docs/installation

版本和 k8s 兼容性

Kruise Version1.181.201.221.241.261.281.301.32
1.4.x++-????
1.5.x+++-???
1.6.x++++???
1.7.x+++++??
1.8.x++++++?
HEAD???++++

标记:

  • <font style="color:rgb(28, 30, 33);">✓</font>: Kruise 与对应 Kubernetes 版本中的 API 对象/字段完全一致。
  • <font style="color:rgb(28, 30, 33);">+</font>: Kruise 中包含的一些 API 对象/字段在当前 Kubernetes 集群中可能不存在,但两者共有的功能是兼容的。
  • <font style="color:rgb(28, 30, 33);">-</font>: Kubernetes 集群中存在一些 Kruise 无法使用的功能(例如额外的 API 对象、字段等)。
  • <font style="color:rgb(28, 30, 33);">?</font>: Kruise 尚未在该版本的 Kubernetes 集群中进行测试。

我的 k8s 版本是 1.22.5,所以我选择安装 1.4.x 版本的 OpenKruise

helm repo add openkruise https://openkruise.github.io/charts/
helm repo update
helm fetch openkruise/kruise --version 1.4.2 # 修改镜像地址
[root@VM_24_119_tlinux /usr/local/src/kruise]# vim values.yaml 
manager:# settings for log printlog:# log level for kruise-managerlevel: "4"replicas: 2image:repository:  harbor.test.com/cicd/kruise-managertag: v1.4.2[root@VM_24_119_tlinux /usr/local/src/kruise]# helm upgrade --install kruise . 
Release "kruise" has been upgraded. Happy Helming!
NAME: kruise
LAST DEPLOYED: Wed Jul 16 15:26:59 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None[root@VM_24_119_tlinux /usr/local/src/kruise]# kubectl get pods -n kruise-system 
NAME                                         READY   STATUS    RESTARTS   AGE
kruise-controller-manager-7c596b847b-4b8dv   1/1     Running   0          2m38s
kruise-controller-manager-7c596b847b-5lbdh   1/1     Running   0          2m38s
kruise-daemon-5slnf                          1/1     Running   0          2m38s
kruise-daemon-crqq4                          1/1     Running   0          2m38s
kruise-daemon-df8vk                          1/1     Running   0          2m38s
kruise-daemon-gvlfj                          1/1     Running   0          2m38s
kruise-daemon-htsj7                          1/1     Running   0          2m38s
kruise-daemon-mdprc                          1/1     Running   0          2m38s
kruise-daemon-pchzj                          1/1     Running   0          2m38s
kruise-daemon-x6d7k                          1/1     Running   0          2m38s

二、 CloneSet

CloneSet 控制器是 OpenKruise 提供的对原生 Deployment 的增强控制器,在使用方式上和 Deployment 几乎一致,如下所示是我们声明的一个 CloneSet 资源对象:

# cloneset-demo.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:name: cs-demo
spec:replicas: 3selector:matchLabels:app: cstemplate:metadata:labels:app: csspec:containers:- name: nginximage: nginx:alpineimagePullPolicy: IfNotPresentports:- containerPort: 80

直接创建上面的这个 CloneSet 对象:

[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl apply -f cloneset-demo.yaml
[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get cloneset cloneset-test 
NAME            DESIRED   UPDATED   UPDATED_READY   READY   TOTAL   AGE
cloneset-test   3         3         3               3       3       21m
➜ kubectl describe cloneset cs-demo
Name:         cloneset-test
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  apps.kruise.io/v1alpha1
Kind:         CloneSet
# ......
Events:Type    Reason                       Age   From                 Message----    ------                       ----  ----                 -------Normal  SuccessfulCreate             21m   cloneset-controller  succeed to create pod cloneset-test-lx8w8Normal  SuccessfulCreate             21m   cloneset-controller  succeed to create pod cloneset-test-vnlbkNormal  SuccessfulCreate             21m   cloneset-controller  succeed to create pod cloneset-test-f8m84

该对象创建完成后我们可以通过 kubectl describe 命令查看对应的 Events 信息,可以发现 cloneset-controller 是直接创建的 Pod,这个和原生的 Deployment 就有一些区别了,Deployment 是通过 ReplicaSet 去创建的 Pod,所以从这里也可以看出来 CloneSet 是直接管理 Pod 的,3 个副本的 Pod 此时也创建成功了:

[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get pods
NAME                  READY   STATUS    RESTARTS      AGE
cloneset-test-lph9q   1/1     Running   1 (12m ago)   16m
cloneset-test-mbqml   1/1     Running   1 (11m ago)   16m
cloneset-test-tg5qc   1/1     Running   1 (11m ago)   16m

2.1 扩缩容功能

2.1.1 支持 PVC 模板

CloneSet 允许用户配置 PVC 模板 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">volumeClaimTemplates</font>,用来给每个 Pod 生成独享的 PVC,这是 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">Deployment</font> 所不支持的。 如果用户没有指定这个模板,CloneSet 会创建不带 PVC 的 Pod。

一些注意点:

  • 每个被自动创建的 PVC 会有一个 <font style="color:#DF2A3F;">ownerReference</font> 指向 CloneSet,因此 CloneSet 被删除时,它创建的所有 Pod 和 PVC 都会被删除。
  • 每个被 CloneSet 创建的 Pod 和 PVC,都会带一个 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">apps.kruise.io/cloneset-instance-id: xxx</font> 的 label。关联的 Pod 和 PVC 会有相同的 instance-id,且它们的名字后缀都是这个 instance-id
  • 如果一个 Pod 被 CloneSet controller 缩容删除时,这个 Pod 关联的 PVC 都会被一起删掉
  • 如果一个 Pod 被外部直接调用删除或驱逐时,这个 Pod 关联的 PVC 还都存在;并且 CloneSet controller 发现数量不足重新扩容时,新扩出来的 Pod 会复用原 Pod 的 instance-id 并关联原来的 PVC
  • 当 Pod 被重建升级时,关联的 PVC 会跟随 Pod 一起被删除、新建。
  • 当 Pod 被原地升级时,关联的 PVC 会持续使用。
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:labels:app: samplename: sample-data
spec:replicas: 5selector:matchLabels:app: sampletemplate:metadata:labels:app: samplespec:containers:- name: nginximage: harbor.test.com/cicd/nginx:1.28.0volumeMounts:- name: data-volmountPath: /usr/share/nginx/htmlvolumeClaimTemplates:- metadata:name: data-volspec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 20Gi[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get pods 
NAME                READY   STATUS    RESTARTS   AGE
sample-data-9mj6m   1/1     Running   0          97s
sample-data-cgqrq   1/1     Running   0          97s
sample-data-fnhmp   1/1     Running   0          97s
sample-data-ftjm2   1/1     Running   0          97s
sample-data-gtc6z   1/1     Running   0          97s
[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get pvc 
NAME                         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-vol-sample-data-9mj6m   Bound    pvc-73d05a13-40ac-489a-be2d-1ecc401f26d2   20Gi       RWO            cbs            100s
data-vol-sample-data-cgqrq   Bound    pvc-967e07c4-f2ed-4fb0-9990-774e032f2608   20Gi       RWO            cbs            100s
data-vol-sample-data-fnhmp   Bound    pvc-e1c0a753-fb84-40dd-80b5-f6500d33cb8d   20Gi       RWO            cbs            100s
data-vol-sample-data-ftjm2   Bound    pvc-1c853f37-7a39-43fc-a2d2-3d3acb057ac3   20Gi       RWO            cbs            100s
data-vol-sample-data-gtc6z   Bound    pvc-337e9fe1-1d03-4ad5-9a0e-d70cfd5fe5ed   20Gi       RWO            cbs            100s

EATURE STATE: Kruise v1.4.0

如果一个 Pod 被外部直接调用删除或驱逐时,这个 Pod 关联的 PVCs 还都存在;并且 CloneSet controller 发现数量不足重新扩容时,新扩出来的 Pod 会复用原 Pod 的 instance-id 并关联原来的 PVCs。

然而,如果 Pod 所在的 Node 出现异常,复用可能会导致新 Pod 启动失败,详情参考 issue 1099。为了解决这个问题,可以设置字段 **<font style="color:rgb(28, 30, 33);">DisablePVCReuse=true</font>**。在这种情况下,与 Pod 相关的 PVCs 将被自动删除,不再被复用。

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:...replicas: 4scaleStrategy:disablePVCReuse: true

当 volumeClaimTemplates 改变时,将会重建升级 Pod 和关联的 volume

FEATURE STATE: Kruise v1.7.0

默认情况下,如果 <font style="color:#DF2A3F;">image</font><font style="color:#DF2A3F;">volumeClaimTemplates</font> 同时改变,CloneSet 将会原地升级 Pod并且不会重建 volume,导致 <font style="color:#DF2A3F;">volumeClaimTemplates</font> 配置不生效。

  1. 初始情况下,<font style="color:rgb(28, 30, 33);">image</font>=harbor.test.com/cicd/nginx:1.28.0, <font style="color:rgb(28, 30, 33);">volumeClaimTemplates</font>=20G
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:template:spec:containers:- name: nginximage: harbor.test.com/cicd/nginx:1.28.0volumeMounts:- name: data-volmountPath: /usr/share/nginx/htmlvolumeClaimTemplates:- metadata:name: data-volspec:storageClassName: cbsaccessModes: [ "ReadWriteOnce" ]resources:requests:storage: 20Gi
  1. 将 image变更为<font style="color:rgb(28, 30, 33);">harbor.test.com/icd/nginx:latest</font><font style="color:rgb(28, 30, 33);">volumeClaimTemplates</font>=40G
  2. CloneSet 会原地升级Pod,并不会重建 volume,因此新 Pod 对应的 volume 大小还是 20G,并没有生效最新的。

针对上述场景,可以打开 feature-gate RecreatePodWhenChangeVCTInCloneSetGate=true。CloneSet 将会重建 Pod 和 volumes 升级, 此时,新 Pod 的 volume 将为 40G。

注意:如果你只变更 volumeClaimTemplates 字段,依旧不会触发 Pod 升级,需要同步着修改 Labels、Annotations、Image、Env 等字段才可以。

2.1.2 指定 Pod 缩容

当一个 CloneSet 被缩容时,有时候用户需要指定一些 Pod 来删除。这对于 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">StatefulSet</font> 或者 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">Deployment</font> 来说是无法实现的,因为 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">StatefulSet</font> 要根据序号来删除 Pod,而 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">Deployment</font>/<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">ReplicaSet</font> 目前只能根据控制器里定义的排序来删除。

CloneSet 允许用户在缩小 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">replicas</font> 数量的同时,指定想要删除的 Pod 名字。参考下面这个例子:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:# ...replicas: 4scaleStrategy:podsToDelete:- sample-9m4hp

当控制器收到上面这个 CloneSet 更新之后,会确保 replicas 数量为 4。如果 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">podsToDelete</font> 列表里写了一些 Pod 名字,控制器会优先删除这些 Pod。 对于已经被删除的 Pod,控制器会自动从 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">podsToDelete</font> 列表中清理掉。

如果你只把 Pod 名字加到 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">podsToDelete</font>,但没有修改 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">replicas</font> 数量,那么控制器会先把指定的 Pod 删掉,然后再扩一个新的 Pod。 另一种直接删除 Pod 的方式是在要删除的 Pod 上打 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">apps.kruise.io/specified-delete: true</font> 标签。

相比于手动直接删除 Pod,使用 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">podsToDelete</font><font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">apps.kruise.io/specified-delete: true</font> 方式会有 CloneSet 的 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">maxUnavailable</font>/<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">maxSurge</font> 来保护删除, 并且会触发 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">PreparingDelete</font> 生命周期 hook 。

2.1.3 流式扩容

CloneSet 扩容时可以指定 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">ScaleStrategy.MaxUnavailable</font> 来限制扩容的步长,以达到服务应用影响最小化的目的。 它可以设置为一个绝对值或者百分比,如果不填,则 Kruise 会设置为默认值为 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">nil</font>,即表示不设限制。

该字段可以配合 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">Spec.MinReadySeconds</font> 字段使用, 例如:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:# ...minReadySeconds: 60scaleStrategy:maxUnavailable: 1

上述配置能达到的效果是:在扩容时,只有当上一个扩容出的 Pod 已经 Ready 超过一分钟后,CloneSet 才会执行创建下一个 Pod 的操作。

2.2 升级功能

2.2.1 升级类型

CloneSet 提供了 3 种升级方式,默认为 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">ReCreate</font>

  • <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">ReCreate</font>: 控制器会删除旧 Pod 和它的 PVC,然后用新版本重新创建出来。
  • <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlaceIfPossible</font>: 控制器会优先尝试原地升级 Pod,如果不行再采用重建升级。当前, 仅支持容器镜像等字段的原地升级。
  • <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlaceOnly</font>: 控制器只允许采用原地升级。因此,用户只能修改容器镜像等字段,如果尝试修改其他字段会被 Kruise 拒绝。

2.2.2 原地升级

2.2.2.1 什么是原地升级

这里有一个重要概念:原地升级,这也是 OpenKruise 提供的核心功能之一,当我们要升级一个 Pod 中镜像的时候,下图展示了重建升级原地升级的区别:

重建升级时我们要删除旧 Pod、创建新 Pod:

  • Pod 名字和 uid 发生变化,因为它们是完全不同的两个 Pod 对象(比如 Deployment 升级)
  • Pod 名字可能不变、但 uid 变化,因为它们是不同的 Pod 对象,只是复用了同一个名字(比如 StatefulSet 升级)
  • Pod 所在 Node 名字发生变化,因为新 Pod 很大可能性是不会调度到之前所在的 Node 节点的
  • Pod IP 发生变化,因为新 Pod 很大可能性是不会被分配到之前的 IP 地址的

但是对于原地升级,我们仍然复用同一个 Pod 对象,只是修改它里面的字段。因此:

  • 可以避免如 调度分配 IP__挂载盘 等额外的操作和代价
  • 更快的镜像拉取,因为开源复用已有旧镜像的大部分 layer 层,只需要拉取新镜像变化的一些 layer
  • 当一个容器在原地升级时,Pod 中的其他容器不会受到影响,仍然维持运行
2.2.2.2 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlaceIfPossible</font>介绍

这种 Kruise workload 的升级类型名为 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlaceIfPossible</font>,它意味着 Kruise 会尽量对 Pod 采取原地升级,如果不能则退化到重建升级。

以下的改动会被允许执行原地升级:

  1. 更新 workload 中的 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">spec.template.metadata.*</font>,比如 labels/annotations,Kruise 只会将 metadata 中的改动更新到存量 Pod 上。
  2. 更新 workload 中的 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">spec.template.spec.containers[x].image</font>,Kruise 会原地升级 Pod 中这些容器的镜像,而不会重建整个 Pod。
  3. 从 Kruise v1.0 版本开始(包括 v1.0 alpha/beta),更新 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">spec.template.metadata.labels/annotations</font> 并且 container 中有配置 env from 这些改动的 labels/anntations,Kruise 会原地升级这些容器来生效新的 env 值。
  4. 从 Kruise v1.8 版本开始,若 Kubernetes 集群已启用<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlacePodVerticalScaling</font>特性,且 Kruise 中也启用了<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlaceWorkloadVerticalScaling</font>特性,当更新<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">spec.template.spec.containers[x].resources</font>时,Kruise 将直接对 Pod 中相应容器的资源进行原地升级,而无需重建整个 Pod。

否则,其他字段的改动,比如 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">spec.template.spec.containers[x].env</font><font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">spec.template.spec.containers[x].resources</font>(未启用 Kruise <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">InPlaceWorkloadVerticalScaling</font> 特性),都是会回退为重建升级。

例如对下述 CloneSet YAML:

  1. 修改 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">app-image:v1</font> 镜像,会触发原地升级。
  2. 修改 annotations 中 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">app-config</font> 的 value 内容,会触发原地升级。
  3. 同时修改上述两个字段,会在原地升级中同时更新镜像和环境变量。
  4. 直接修改 env 中 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">APP_NAME</font> 的 value 内容或者新增 env 等其他操作,会触发 Pod 重建升级。
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:...
spec:replicas: 1template:metadata:annotations:app-config: "... the real env value ..."spec:containers:- name: appimage: app-image:v1env:- name: APP_CONFIGvalueFrom:fieldRef:fieldPath: metadata.annotations['app-config']- name: APP_NAMEvalue: xxxupdateStrategy:type: InPlaceIfPossible
2.2.2.3 工作流程总览

如果你在安装或升级 Kruise 的时候启用了 <font style="color:#DF2A3F;">PreDownloadImageForInPlaceUpdate</font> 这个 feature-gate,CloneSet 控制器会自动在所有旧版本 pod 所在节点上预热你正在灰度发布的新版本镜像,这对于应用发布加速很有帮助。

默认情况下 CloneSet 每个新镜像预热时的并发度都是 1,也就是一个个节点拉镜像,如果需要调整,你可以在 CloneSet 通过 <font style="color:#DF2A3F;">apps.kruise.io/image-predownload-parallelism</font> 这个 annotation 来设置并发度。

另外从 Kruise v1.1.0 开始,还可以使用 <font style="color:#DF2A3F;">apps.kruise.io/image-predownload-min-updated-ready-pods</font> 来控制在少量新版本 Pod 已经升级成功之后再执行镜像预热。它的值可能是绝对值数字或是百分比。

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:annotations:apps.kruise.io/image-predownload-parallelism: "5"apps.kruise.io/image-predownload-min-updated-ready-pods: "2"

注意,为了避免大部分不必要的镜像拉取,目前只针对 replicas > 3 的 CloneSet 做自动预热。

2.2.2.4 原地升级——多容器升级顺序控制

FEATURE STATE: Kruise v1.1.0

当你同时原地升级多个具有不同启动顺序的容器时,Kruise 会按照相同的权重顺序来逐个升级这些容器。

  • 对于不存在容器启动顺序的 Pod,在多容器原地升级时没有顺序保证。
  • 对于存在容器启动顺序的 Pod:
    • 如果本次原地升级的多个容器具有不同的启动顺序,会按启动顺序来控制原地升级的先后顺序。
    • 如果本地原地升级的多个容器的启动顺序相同,则原地升级时没有顺序保证。

例如,一个包含两个不同启动顺序容器的 CloneSet 如下:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:...
spec:replicas: 1template:metadata:annotations:app-config: "... config v1 ..."spec:containers:- name: sidecarenv:- name: KRUISE_CONTAINER_PRIORITYvalue: "10"- name: APP_CONFIGvalueFrom:fieldRef:fieldPath: metadata.annotations['app-config']- name: mainimage: main-image:v1updateStrategy:type: InPlaceIfPossible

当我们更新 CloneSet,将其中 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">app-config</font> annotation 和 main 容器的镜像修改后,意味着 sidecar 与 main 容器都需要被更新,Kruise 会先原地升级 Pod 来将其中 sidecar 容器重建来生效新的 env from annotation。

此时,我们可以在已升级的 Pod 中看到 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">apps.kruise.io/inplace-update-state</font> annotation 和它的值:

{"revision": "{CLONESET_NAME}-{HASH}",         // 本次原地升级的目标 revision 名字"updateTimestamp": "2022-03-22T09:06:55Z",    // 整个原地升级的初次开始时间"nextContainerImages": {"main": "main-image:v2"},                // 后续批次中还需要升级的容器镜像// "nextContainerRefMetadata": {...},                            // 后续批次中还需要升级的容器 env from labels/annotations"preCheckBeforeNext": {"containersRequiredReady": ["sidecar"]},  // pre-check 检查项,符合要求后才能原地升级后续批次的容器"containerBatchesRecord":[{"timestamp":"2022-03-22T09:06:55Z","containers":["sidecar"]}  // 已更新的首个批次容器(它仅仅表明容器的 spec 已经被更新,例如 pod.spec.containers 中的 image 或是 labels/annotations,但并不代表 node 上真实的容器已经升级完成了)]
}

当 sidecar 容器升级成功之后,Kruise 会接着再升级 main 容器。最终你会在 Pod 中看到如下的 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">apps.kruise.io/inplace-update-state</font> annotation:

{"revision": "{CLONESET_NAME}-{HASH}","updateTimestamp": "2022-03-22T09:06:55Z","lastContainerStatuses":{"main":{"imageID":"THE IMAGE ID OF OLD MAIN CONTAINER"}},"containerBatchesRecord":[{"timestamp":"2022-03-22T09:06:55Z","containers":["sidecar"]},{"timestamp":"2022-03-22T09:07:20Z","containers":["main"]}]
}

通常来说,用户只需要关注其中 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">containerBatchesRecord</font> 来确保容器是被分为多批升级的。如果这个 Pod 在原地升级的过程中卡住了,你可以检查 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">nextContainerImages/nextContainerRefMetadata</font> 字段,以及 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">preCheckBeforeNext</font> 中前一次升级的容器是否已经升级成功并 ready 了。

在原地升级中提供了 **<font style="color:rgb(28, 30, 33);">graceful period</font>**<font style="color:rgb(28, 30, 33);"> </font>选项,作为优雅原地升级的策略。用户如果配置了 <font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">gracePeriodSeconds</font> 这个字段,控制器在原地升级的过程中会先把 Pod status 改为 not-ready,然后等一段时间(<font style="color:#DF2A3F;background-color:rgb(246, 247, 248);">gracePeriodSeconds</font>),最后再去修改 Pod spec 中的镜像版本。 这样,就为 endpoints-controller 这些控制器留出了充足的时间来将 Pod 从 endpoints 端点列表中去除。

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:# ...updateStrategy:type: InPlaceIfPossibleinPlaceUpdateStrategy:gracePeriodSeconds: 10
2.2.2.5 灰度发布

此外 CloneSet 还支持分批进行灰度,在 updateStrategy 属性中可以配置 partition 参数,该参数可以用来保留旧版本 Pod 的数量或百分比,默认为 0:

  • 如果是数字,控制器会将 (replicas - partition) 数量的 Pod 更新到最新版本
  • 如果是百分比,控制器会将 (replicas * (100% - partition)) 数量的 Pod 更新到最新版本

比如,我们将上面示例中的的 image 更新为 nginx:latest 并且设置 partition=2,更新后,过一会查看可以发现只升级了 2 个 Pod:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:# ...generation: 2
spec:replicas: 5template:metadata:labels:app: samplespec:containers:- image: nginx:mainlineimagePullPolicy: Alwaysname: nginxupdateStrategy:partition: 3# ...
status:observedGeneration: 2readyReplicas: 5replicas: 5currentRevision: sample-d4d4fb5bdupdateRevision: sample-56dfb978d4updatedReadyReplicas: 2updatedReplicas: 2

注意 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">status.updateRevision</font> 已经更新为 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">sample-56dfb978d4</font> 新的值。 由于我们设置了 <font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">partition=3</font>,控制器只升级了 2 个 Pod。

$ kubectl get pod -L controller-revision-hash
NAME           READY   STATUS    RESTARTS   AGE     CONTROLLER-REVISION-HASH
sample-chvnr   1/1     Running   0          6m46s   sample-d4d4fb5bd
sample-j6c4s   1/1     Running   0          6m46s   sample-d4d4fb5bd
sample-ns85c   1/1     Running   0          6m46s   sample-d4d4fb5bd
sample-jnjdp   1/1     Running   0          10s     sample-56dfb978d4
sample-qqglp   1/1     Running   0          18s     sample-56dfb978d4

2.3 生命周期钩子

每个 CloneSet 管理的 Pod 会有明确所处的状态,在 Pod label 中的 <font style="color:#DF2A3F;">lifecycle.apps.kruise.io/state</font> 标记:

  • <font style="color:#DF2A3F;">Normal</font>:正常状态
  • <font style="color:#DF2A3F;">PreparingUpdate</font>:准备原地升级
  • <font style="color:#DF2A3F;">Updating</font>:原地升级中
  • <font style="color:#DF2A3F;">Updated</font>:原地升级完成
  • <font style="color:#DF2A3F;">PreparingDelete</font>:准备删除

而生命周期钩子,则是通过在上述状态流转中卡点,来实现原地升级前后删除前的自定义操作(比如开关流量、告警等)。CloneSet 的 <font style="color:#DF2A3F;">lifecycle</font> 下面主要支持 <font style="color:#DF2A3F;">preDelete</font><font style="color:#DF2A3F;">inPlaceUpdate</font> 两个属性。

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:# 通过 finalizer 定义 hooklifecycle:preDelete:  # PreDelete 是 Pod 被删除之前的 hookfinalizersHandler:- example.io/unready-blockerinPlaceUpdate: # InPlaceUpdate 是 Pod 更新之前和更新后的 hookfinalizersHandler:- example.io/unready-blocker# 或者也可以通过 label 定义lifecycle:inPlaceUpdate:labelsHandler:example.io/block-unready: "true"

2.3.1 升级/删除 Pod 前将其置为 NotReady

lifecycle:preDelete:markPodNotReady: truefinalizersHandler:- example.io/unready-blockerinPlaceUpdate:markPodNotReady: truefinalizersHandler:- example.io/unready-blocker
  • 如果设置 <font style="color:#DF2A3F;">preDelete.markPodNotReady=true</font>:
    • Kruise 将会在 Pod 进入 <font style="color:#DF2A3F;">PreparingDelete</font> 状态时,将 <font style="color:#DF2A3F;">KruisePodReady</font> 这个 Pod Condition 设置为 False, Pod 将变为 NotReady。
  • 如果设置 <font style="color:#DF2A3F;">inPlaceUpdate.markPodNotReady=true</font>:
    • Kruise 将会在 Pod 进入 <font style="color:#DF2A3F;">PreparingUpdate</font> 状态时,将 KruisePodReady 这个 Pod Condition 设置为 False, Pod 将变为 NotReady。
    • Kruise 将会尝试将 KruisePodReady 这个 Pod Condition 设置回 True。

我们可以利用这一特性,在容器真正被停止之前将 Pod 上的流量先行排除,防止流量损失。

2.3.2 流转示意图

  • 当 CloneSet 删除一个 Pod(包括正常缩容和重建升级)时:
    • 如果没有定义 lifecycle hook 或者 Pod 不符合 preDelete 条件,则直接删除
    • 否则,先只将 Pod 状态改为 <font style="color:#DF2A3F;">PreparingDelete</font>。等用户 controller 完成任务去掉 label/finalizer、Pod 不符合 preDelete 条件后,kruise 才执行 Pod 删除
    • 需要注意的是 <font style="color:#DF2A3F;">PreparingDelete</font> 状态的 Pod 处于删除阶段,不会被升级
  • 当 CloneSet 原地升级一个 Pod 时:
    • 升级之前,如果定义了 lifecycle hook 且 Pod 符合 <font style="color:#DF2A3F;">inPlaceUpdate</font> 条件,则将 Pod 状态改为 <font style="color:#DF2A3F;">PreparingUpdate</font>
    • 等用户 controller 完成任务去掉 label/finalizer、Pod 不符合 <font style="color:#DF2A3F;">inPlaceUpdate</font> 条件后,kruise 将 Pod 状态改为 <font style="color:rgb(28, 30, 33);">Updating</font> 并开始升级
    • 升级完成后,如果定义了 lifecycle hook 且 Pod 不符合 <font style="color:#DF2A3F;">inPlaceUpdate</font> 条件,将 Pod 状态改为 Updated
    • 等用户 controller 完成任务加上 label/finalizer、Pod 符合 <font style="color:#DF2A3F;">inPlaceUpdate</font> 条件后,kruise 将 Pod 状态改为 Normal 并判断为升级成功

关于从 PreparingDelete 回到 Normal 状态,从设计上是支持的(通过撤销指定删除),但我们一般不建议这种用法。由于 PreparingDelete 状态的 Pod 不会被升级,当回到 Normal 状态后可能立即再进入发布阶段,对于用户处理 hook 是一个难题。

2.3.3 用户 controller 逻辑示例

按上述例子,可以定义:

  • <font style="color:rgb(28, 30, 33);">example.io/unready-blocker finalizer</font> 作为 hook
  • <font style="color:rgb(28, 30, 33);">example.io/initialing</font> annotation 作为初始化标记

在 CloneSet template 模板里带上这个字段:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:template:metadata:annotations:example.io/initialing: "true"finalizers:- example.io/unready-blocker# ...lifecycle:preDelete:finalizersHandler:- example.io/unready-blockerinPlaceUpdate:finalizersHandler:- example.io/unready-blocker

而后用户 controller 的逻辑如下:

  • 对于 Normal 状态的 Pod,如果 annotation 中有 <font style="color:rgb(28, 30, 33);">example.io/initialing: true</font> 并且 Pod status 中的 ready condition 为 True,则接入流量、去除这个 annotation
  • 对于 <font style="color:rgb(28, 30, 33);">PreparingDelete</font><font style="color:rgb(28, 30, 33);">PreparingUpdate</font> 状态的 Pod,切走流量,并去除 <font style="color:rgb(28, 30, 33);">example.io/unready-blocker</font> finalizer
  • 对于 Updated 状态的 Pod,接入流量,并打上 <font style="color:rgb(28, 30, 33);">example.io/unready-blocker finalizer</font>

2.3.3 使用场景

因为各种各样的历史原因和客观因素,有些用户可能无法将自己公司的整套体系架构 Kubernetes 化,比如有些用户暂时无法使用 Kubernetes 本身提供的 Service 服务发现机制,而是使用了独立于 Kubernetes 之外的另外一套服务注册和发现体系。在这种架构下,如果用户对服务进行 Kubernetes 化改造,可能会遇到诸多问题。例如,每当 Kubernetes 成功创建出一个 Pod,都需要自行将该 Pod 注册到服务发现中心,以便能够对内对外提供服务;相应的,想要下线一个 Pod,也通常先要将其在服务发现中心删除,才能将 Pod 优雅下线,否则就可能导致流量损失。但是在原生的 Kubernetes 体系中, Pod 的生命周期由 Workload 管理(例如 Deployment),当这些 Workload 的 Replicas 字段发生变化后,相应的 Controller 会立即添加或删除掉 Pod,用户很难定制化地去管理 Pod 的生命周期。

面对这类问题,一般来说有两种解决思路:一是约束 Kubernetes 的弹性能力,例如规定只能由特定的链路对 Workload 进行扩缩容,以保证在删除 Pod 前先把 Pod IP 在服务注册中心摘除,但这样一来会制约 Kubernetes 本身的弹性能力, 并且也增加了链路管控的难度和风险。 二是在根本上改造现有的服务发现体系,显然这是一个更加漫长和高风险的事情。

OpenKruise CloneSet 就提供了这样一组高度可定制化的扩展能力来专门应对此类场景,让用户能够对 Pod 生命周期做更精细化、定制化的管理。CloneSet 在 Pod 生命周期中几个重要的时间节点预留了 Hook,使得用户可以在这些时间节点插入一些定制化的扩展动作。比如,在 Pod 升级前,将 Pod IP 在服务发现中心删除,升级完成后再将 Pod IP 注册到服务发现中心,或者做一些特殊的嗅探和监控动作。

我们假设现在有这样一个场景:

  • 用户不使用 Kubernetes Service 作为服务发现机制,服务发现体系完全独立于 Kubernetes;
  • 使用 CloneSet 作为 Kubernetes 工作负载。

并且对具体的需求做如下合理假设:

  • 当 Kubernetes Pod 被创建时:
    • 在创建成功,且 Pod Ready 之后,将 Pod IP 注册到服务发现中心;
  • 当 Kubernetes Pod 原地升级时:
    • 在升级之前,需要将 Pod IP 从服务发现中心删除(或主动 FailOver);
    • 在升级完成,且 Pod Ready 之后,将 Pod IP 再次注册到服务发现中心;
  • 当 Kubernetes Pod 被删除时:
    • 在删除之前,需要先将 Pod IP 从服务发现中心删除;

基于以上假设,其实我们就可以利用 CloneSet LifeCycle 来编写一个简单的 Operator 实现用户定义的 Pod 生命周期管理机制。

CloneSet LifeCycle 将 Pod 的生命周期定义为了 5 种状态,5 种状态之间的转换逻辑由一个状态机所控制。我们可以只选择自己所关心的一种或多种,编写一个独立的 Operator 来实现这些状态的转换,控制 Pod 的生命周期,并在所关心的时间节点插入自己的定制化逻辑。

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:namespace: demoname: cloneset-lifecycle-demo
spec:replicas: 2############################################################################ 生命周期配置lifecycle:inPlaceUpdate:labelsHandler:## 定义标签:##    1. 为 cloneset 控制器阻止原地更新 Pod 操作##    2. 通知 operator 执行 inPlace update 钩子example.com/unready-blocker-inplace: "true"preDelete:labelsHandler:## 定义标签:##    1. 为 cloneset 控制器阻止删除 pod 操作##    2. 通知 operator 执行 preDelete 钩子example.com/unready-blocker-delete: "true"##########################################################################selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginx## 这个标签可以用来判断此 Pod 是否是新创建的example.com/newly-create: "true"## 对应于 spec.lifecycle.inPlaceUpdate.labelsHandler.example.com/unready-blocker-inplaceexample.com/unready-blocker-inplace: "true"## 对应 spec.lifecycle.preDelete.labelsHandler.example.com/unready-blocker-inplaceexample.com/unready-blocker-delete: "true"containers:- name: mainimage: nginx:latestimagePullPolicy: AlwaysupdateStrategy:maxUnavailable: 20%type: InPlaceIfPossible
const (deleteHookLabel  = "example.com/unready-blocker-delete"inPlaceHookLabel = "example.com/unready-blocker-inplace"newlyCreateLabel = "example.com/newly-create"
)func (r *SampleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {... ...switchLabel := func(pod *v1.Pod, key, value string) error {body := fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, key, value)if err := r.Patch(context.TODO(), pod, client.RawPatch(types.StrategicMergePatchType, []byte(body))); err != nil {return err}return nil}/*Pod LifeCycle Hook 逻辑*/switch {// 处理新创建的 Podcase IsNewlyCreateHooked(pod):// 将此 Pod 注册到你的服务发现中心if err := postRegistry(pod); err != nil {return reconcile.Result{}, err}if err := switchLabel(pod, newlyCreateLabel, "false"); err != nil {return reconcile.Result{}, err}// 处理准备进行原地升级的 Podcase IsPreUpdateHooked(pod):// 让服务发现中心将此 Pod fail overif err := postFailOver(pod); err != nil {return reconcile.Result{}, err}if err := switchLabel(pod, inPlaceHookLabel, "false"); err != nil {return reconcile.Result{}, err}// 处理更新完成后的 Podcase IsUpdatedHooked(pod):// 让服务发现中心重新注册 Podif err := postRegistry(pod); err != nil {return reconcile.Result{}, err}if err := switchLabel(pod, inPlaceHookLabel, "true"); err != nil {return reconcile.Result{}, err}// 处理准备删除的 Podcase IsPreDeleteHooked(pod):// 从你的服务发现中心取消该Pod的注册if err := postUnregister(pod); err != nil {return reconcile.Result{}, err}if err := switchLabel(pod, deleteHookLabel, "false"); err != nil {return reconcile.Result{}, err}}return ctrl.Result{}, nil
}func IsNewlyCreateHooked(pod *v1.Pod) bool {return kruiseappspub.LifecycleStateType(pod.Labels[kruiseappspub.LifecycleStateKey]) == kruiseappspub.LifecycleStateNormal && pod.Labels[newlyCreateLabel] == "true" && IsPodReady(pod)
}func IsPreUpdateHooked(pod *v1.Pod) bool {return kruiseappspub.LifecycleStateType(pod.Labels[kruiseappspub.LifecycleStateKey]) == kruiseappspub.LifecycleStatePreparingUpdate && pod.Labels[inPlaceHookLabel] == "true"
}func IsUpdatedHooked(pod *v1.Pod) bool {return kruiseappspub.LifecycleStateType(pod.Labels[kruiseappspub.LifecycleStateKey]) == kruiseappspub.LifecycleStateUpdated && pod.Labels[inPlaceHookLabel] == "false" && IsPodReady(pod)
}func IsPreDeleteHooked(pod *v1.Pod) bool {return kruiseappspub.LifecycleStateType(pod.Labels[kruiseappspub.LifecycleStateKey]) == kruiseappspub.LifecycleStatePreparingDelete && pod.Labels[DeleteHookLabel] == "true"
}

上述代码中四个分支分别从上到下对应 Pod 的创建后、升级前、升级后、删除前等四个重要声明周期节点,我们可以根据自己的实际需求来完善相应的 Hook,我们这里上述几个 Hook 的行为具体为:

  • postRegistry(pod *v1.Pod): 发送请求通知服务发现中心注册该 Pod 服务;
  • postFailOver(pod *v1.Pod): 发送请求通知服务发现中心 Fail Over 该 Pod 服务;
  • postUnregiste(pod *v1.Pod): 发送请求通知服务发现中心将该 Pod 服务注销。

这就是 CloneSet Lifecycle 的强大之处,我们完全可以根据需求在 Pod 生命周期管理中插入定制化逻辑。

三、Advanced StatefulSet

该控制器在原生的 StatefulSet 基础上增强了发布能力,比如 <font style="color:#DF2A3F;">maxUnavailable</font> 并行发布、原地升级等,该对象的名称也是 <font style="color:#DF2A3F;">StatefulSet</font>,但是 apiVersion 是 <font style="color:#DF2A3F;">apps.kruise.io/v1beta1</font>,这个 CRD 的所有默认字段、默认行为与原生 StatefulSet 完全一致,除此之外还提供了一些可选字段来扩展增强的策略。因此,用户从原生 StatefulSet 迁移到 <font style="color:#DF2A3F;">Advanced StatefulSet</font>,只需要把 <font style="color:#DF2A3F;">apiVersion</font> 修改后提交即可:

-  apiVersion: apps/v1
+  apiVersion: apps.kruise.io/v1beta1kind: StatefulSetmetadata:name: samplespec:#...

3.1 最大不可用

Advanced StatefulSet 在滚动更新策略中新增了 <font style="color:#DF2A3F;">maxUnavailable</font> 来支持并行 Pod 发布,它会保证发布过程中最多有多少个 Pod 处于不可用状态。注意,<font style="color:#DF2A3F;">maxUnavailable</font> 只能配合 <font style="color:#DF2A3F;">podManagementPolicy</font><font style="color:#DF2A3F;">Parallel</font> 来使用。

这个策略的效果和 Deployment 中的类似,但是可能会导致发布过程中的 order 顺序不能严格保证,如果不配置 <font style="color:#DF2A3F;">maxUnavailable</font>,它的默认值为 1,也就是和原生 StatefulSet 一样只能串行发布 Pod,即使把 <font style="color:#DF2A3F;">podManagementPolicy</font> 配置为 <font style="color:#DF2A3F;">Parallel</font> 也是这样。

比如现在我们创建一个如下所示的 Advanced StatefulSet:

# advanced-sts-demo.yaml
apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
metadata:name: webnamespace: default
spec:serviceName: "nginx-headless"podManagementPolicy: Parallelreplicas: 5updateStrategy:type: RollingUpdaterollingUpdate:maxUnavailable: 3 # 40% of 5 = 2# partition: 4selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginx spec:containers:- name: nginximage: nginxports:- name: webcontainerPort: 80

直接创建该对象,由于对象名称也是 StatefulSet,所以不能直接用 <font style="color:#DF2A3F;">get sts</font> 来获取了,要通过 <font style="color:#DF2A3F;">get asts</font> 获取

[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get asts
NAME   DESIRED   CURRENT   UPDATED   READY   AGE
web    5         5         5         5       38s
[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          49s
web-1   1/1     Running   0          49s
web-2   1/1     Running   0          49s
web-3   1/1     Running   0          49s
web-4   1/1     Running   0          49s

该应用下有五个 Pod,并且应用能容忍 3 个副本不可用,当我们把 StatefulSet 里的 Pod 升级版本的时候,可以通过以下步骤来做:

  1. 设置 <font style="color:#DF2A3F;">maxUnavailable=3</font>
  2. (可选) 如果需要灰度升级,设置 <font style="color:#DF2A3F;">partition=4</font>,Partition 默认的意思是 order 大于等于这个数值的 Pod 才会更新,在这里就只会更新 P4,即使我们设置了 <font style="color:#DF2A3F;">maxUnavailable=3</font>
  3. 在 P4 升级完成后,把 partition 调整为 0,此时,控制器会同时升级 P1、P2、P3 三个 Pod。注意,如果是原生 StatefulSet,只能串行升级 P3、P2、P1。
  4. 一旦这三个 Pod 中有一个升级完成了,控制器会立即开始升级 P0。

比如这里我们把上面应用的镜像版本进行修改,更新后查看 Pod 状态,可以看到有 3 个 Pod 并行升级的:

[root@VM_24_119_tlinux ~]# kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          12s
web-1   1/1     Running   0          12s
web-2   1/1     Running   0          15s
web-3   1/1     Running   0          15s
web-4   1/1     Running   0          15s

3.2 原地升级

此外 Advanced StatefulSet 也增加了 <font style="color:#DF2A3F;">podUpdatePolicy</font> 来允许用户指定重建升级还是原地升级。此外还在原地升级中提供了 graceful period 选项,作为优雅原地升级的策略。用户如果配置了 <font style="color:#DF2A3F;">gracePeriodSeconds</font> 这个字段,控制器在原地升级的过程中会先把 Pod status 改为 <font style="color:#DF2A3F;">not-ready</font>,然后等一段时间(gracePeriodSeconds),最后再去修改 Pod spec 中的镜像版本。这样,就为 endpoints-controller 这些控制器留出了充足的时间来将 Pod 从 endpoints 端点列表中去除。

如果使用 <font style="color:#DF2A3F;">InPlaceIfPossible</font><font style="color:#DF2A3F;">InPlaceOnly</font> 策略,必须要增加一个 <font style="color:#DF2A3F;">InPlaceUpdateReady readinessGate</font>,用来在原地升级的时候控制器将 Pod 设置为 NotReady,比如设置上面的应用为原地升级的方式:

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
metadata:name: webnamespace: default
spec:serviceName: "nginx-headless"podManagementPolicy: Parallelreplicas: 5updateStrategy:type: RollingUpdaterollingUpdate:podUpdatePolicy: InPlaceIfPossible # 尽可能执行原地升级maxUnavailable: 3 # 允许并行更新,最大不可以实例数为3inPlaceUpdateStrategy:gracePeriodSeconds: 10 # 原地升级的优雅时间selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:readinessGates:- conditionType: InPlaceUpdateReady # 确保 Pod 在发生原地更新时保持在 NotReady 状态containers:- name: nginximage: nginxports:- name: webcontainerPort: 80

这里我们设置 <font style="color:#DF2A3F;">updateStrategy.rollingUpdate.podUpdatePolicy</font><font style="color:#DF2A3F;">InPlaceIfPossible</font> 模式,表示尽可能使用原地升级的方式进行更新,此外在 Pod 模板中我们还添加了一个 <font style="color:#DF2A3F;">readinessGates</font> 属性,可以用来确保 Pod 在发生原地更新时保持在 NotReady 状态。比如我们现在使用上面资源清单更新应用,然后重新修改镜像的版本更新,则会进行原地升级:

➜ kubectl describe asts web
Events:Type    Reason                      Age                  From                    Message----    ------                      ----                 ----                    -------Normal  SuccessfulUpdatePodInPlace  3m30s                statefulset-controller  successfully update pod web-4 in-place(revision web-84644dfc7d)Normal  SuccessfulUpdatePodInPlace  3m30s                statefulset-controller  successfully update pod web-3 in-place(revision web-84644dfc7d)Normal  SuccessfulUpdatePodInPlace  3m30s                statefulset-controller  successfully update pod web-2 in-place(revision web-84644dfc7d)

同样的 Advanced StatefulSet 也支持原地升级自动预热。

也可以通过设置 paused 为 true 来暂停发布,不过控制器还是会做 replicas 数量管理:

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
spec:# ...updateStrategy:rollingUpdate:paused: true

另外 Advanced StatefulSet 还支持序号保留功能,通过在 <font style="color:#DF2A3F;">reserveOrdinals</font> 字段中写入需要保留的序号,Advanced StatefulSet 会自动跳过创建这些序号的 Pod,如果 Pod 已经存在,则会被删除。

注意,spec.replicas 是期望运行的 Pod 数量,spec.reserveOrdinals 是要跳过的序号。

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
spec:# ...replicas: 4reserveOrdinals:- 1

比如上面的描述 replicas=4, reserveOrdinals=[1] 的 Advanced StatefulSet,表示实际运行的 Pod 序号为 [0,2,3,4]。

  • 如果要把 Pod-3 做迁移并保留序号,则把 3 追加到 reserveOrdinals 列表中,控制器会把 Pod-3 删除并创建 Pod-5(此时运行中 Pod 为 [0,2,4,5])。
  • 如果只想删除 Pod-3,则把 3 追加到 reserveOrdinals 列表并同时把 replicas 减一修改为 3。控制器会把 Pod-3 删除(此时运行中 Pod 为 [0,2,4])。

为了避免在一个新 Advanced StatefulSet 创建后有大量失败的 pod 被创建出来,从 Kruise v0.10.0 版本开始引入了在 scale strategy 中的 maxUnavailable 策略,这和 CloneSet 的流式扩容是一样的方式。

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
spec:# ...replicas: 100scaleStrategy:maxUnavailable: 10% # percentage or absolute number

当这个字段被设置之后,Advanced StatefulSet 会保证创建 pod 之后不可用 pod 数量不超过这个限制值。比如说,上面这个 StatefulSet 一开始只会一次性创建 100*10%=10 个 pod,在此之后,每当一个 pod 变为 running、ready 状态后,才会再创建一个新 pod 出来。

注意,这个功能只允许在 podManagementPolicy 是 Parallel 的 StatefulSet 中使用。

同样 Advanced StatefulSet 和 CloneSet 提供的生命周期钩子也是一样的,可以通过 spec.lifecycle 字段来设置:

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
spec:# ...lifecycle:preDelete:exec:command:- /bin/sh- -c- echo "preDelete"

四、Advanced Daemonset

同样这个控制器基于原生 DaemonSet 上增强了发布能力,比如灰度分批、按 Node label 选择、暂停、热升级等。同样的该对象的 Kind 名字也是 DaemonSet,只是 apiVersion 是 apps.kruise.io/v1alpha1,这个 CRD 的所有默认字段、默认行为与原生 DaemonSet 完全一致,除此之外还提供了一些可选字段来扩展增强的策略。

因此,用户从原生 DaemonSet 迁移到 Advanced DaemonSet,只需要把 apiVersion 修改后提交即可:

-  apiVersion: apps/v1
+  apiVersion: apps.kruise.io/v1alpha1kind: DaemonSetmetadata:name: samplespec:#...

4.1 升级

Advanced DaemonSet 在 <font style="color:#DF2A3F;">spec.updateStrategy.rollingUpdate</font> 中有一个 <font style="color:#DF2A3F;">rollingUpdateType</font> 字段,标识了如何进行滚动升级:

  • <font style="color:#DF2A3F;">Standard</font>: 对于每个节点,控制器会先删除旧的 daemon Pod,再创建一个新 Pod,和原生 DaemonSet 行为一致,同样也可以通过 <font style="color:#DF2A3F;">maxUnavailable</font><font style="color:#DF2A3F;">maxSurge</font> 来控制重建新旧 Pod 的顺序。
  • <font style="color:#DF2A3F;">Surging</font>: 对于每个 node,控制器会先创建一个新 Pod,等它 ready 之后再删除老 Pod。
  • <font style="color:#DF2A3F;">InPlaceIfPossible</font>: 控制器会尽量采用原地升级的方式,如果不行则重建升级,注意,在这个类型下,只能使用 <font style="color:#DF2A3F;">maxUnavailable</font> 而不能用 <font style="color:#DF2A3F;">maxSurge</font>

创建如下所示的资源对象:

apiVersion: apps.kruise.io/v1alpha1
kind: DaemonSet
metadata:name: nginxnamespace: default
spec:updateStrategy:type: RollingUpdaterollingUpdate:rollingUpdateType: Standardselector:matchLabels:k8s-app: nginxtemplate:metadata:labels:k8s-app: nginxspec:containers:- image: harbor.test.com/cicd/nginx:1.28.0name: nginxports:- name: httpcontainerPort: 80

创建后需要通过 get daemon 来获取该对象:

[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get daemon
NAME    DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   AGE
nginx   5         5         5       5            5           35s
[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]# kubectl get pods -l k8s-app=nginx
NAME          READY   STATUS    RESTARTS   AGE
nginx-7npf6   1/1     Running   0          45s
nginx-dwjpq   1/1     Running   0          45s
nginx-nkhwx   1/1     Running   0          45s
nginx-s5lnm   1/1     Running   0          45s
nginx-xhzlm   1/1     Running   0          45s

我们这里有 五个 Work 节点,所以一共运行了 5 个 Pod,每个节点上一个,和默认的 DaemonSet 行为基本一致。此外这个策略还支持用户通过配置 node 标签的 selector,来指定灰度升级某些特定类型 node 上的 Pod,比如现在我们只升级 node1 节点的应用,则可以使用 selector 标签来标识:

apiVersion: apps.kruise.io/v1alpha1
kind: DaemonSet
spec:# ...updateStrategy:type: RollingUpdaterollingUpdate:rollingUpdateType: Standardselector:matchLabels:kubernetes.io/hostname: node1
# ...

更新应用后可以看到只会更新 node1 节点上的 Pod:

Events:Type    Reason            Age   From                  Message----    ------            ----  ----                  -------Normal  SuccessfulCreate  6m7s  daemonset-controller  Created pod: nginx-xhzlmNormal  SuccessfulCreate  6m7s  daemonset-controller  Created pod: nginx-s5lnmNormal  SuccessfulCreate  6m7s  daemonset-controller  Created pod: nginx-nkhwxNormal  SuccessfulCreate  6m7s  daemonset-controller  Created pod: nginx-7npf6Normal  SuccessfulCreate  6m7s  daemonset-controller  Created pod: nginx-dwjpqNormal  SuccessfulDelete  1s    daemonset-controller  Deleted pod: nginx-7npf6Normal  SuccessfulCreate  0s    daemonset-controller  Created pod: nginx-8htvc

和前面两个控制器一样,Advanced DaemonSet 也支持分批灰度升级,使用 Partition 进行配置,Partition 的语义是保留旧版本 Pod 的数量,默认为 0,如果在发布过程中设置了 partition,则控制器只会将 (status.DesiredNumberScheduled - partition) 数量的 Pod 更新到最新版本。

apiVersion: apps.kruise.io/v1alpha1
kind: DaemonSet
spec:# ...updateStrategy:type: RollingUpdaterollingUpdate:partition: 10paused: true # 暂停发布

同样 Advanced DaemonSet 也是支持原地升级的,只需要设置 rollingUpdateType 为支持原地升级的类型即可,比如这里我们将上面的应用升级方式设置为 InPlaceIfPossible 即可:

apiVersion: apps.kruise.io/v1alpha1
kind: DaemonSet
spec:# ...updateStrategy:type: RollingUpdaterollingUpdate:rollingUpdateType: InPlaceIfPossible

更新后可以通过查看控制器的事件来验证是否是通过原地升级方式更新应用:

➜ kubectl describe daemon nginx
......
Events:Type    Reason                      Age   From                  Message----    ------                      ----  ----                  -------Normal  SuccessfulDelete            76s   daemonset-controller  Deleted pod: nginx-xvrg6Normal  SuccessfulCreate            75s   daemonset-controller  Created pod: nginx-kqqmrNormal  SuccessfulDelete            31s   daemonset-controller  Deleted pod: nginx-vjwxfNormal  SuccessfulCreate            31s   daemonset-controller  Created pod: nginx-5jmwcNormal  SuccessfulUpdatePodInPlace  28s   daemonset-controller  successfully update pod nginx-kqqmr in-place

五、SidecarSet

5.1 使用案例

SidecarSet 支持通过 admission webhook 来自动为集群中创建的符合条件的 Pod 注入 sidecar 容器,除了在 Pod 创建时候注入外,SidecarSet 还提供了为 Pod 原地升级其中已经注入的 sidecar 容器镜像的能力。SidecarSet 将 sidecar 容器的定义和生命周期与业务容器解耦,它主要用于管理无状态的 sidecar 容器,比如监控、日志等 agent。

比如我们定义一个如下所示的 SidecarSet 资源对象:

# sidecarset-demo.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:name: scs-demo
spec:selector:matchLabels: # 非常重要的属性,会去匹配具有 app=nginx 的 Podapp: nginxupdateStrategy:type: RollingUpdatemaxUnavailable: 1containers:- name: sidecar1image: busyboxcommand: ["sleep", "999d"]volumeMounts:- name: log-volumemountPath: /var/logvolumes: # 该属性会被合并到 pod.spec.volumes 去- name: log-volumeemptyDir: {}

[root@VM_24_119_tlinux /usr/local/src/yaml/kruise]#  kubectl get sidecarset
NAME       MATCHED   UPDATED   READY   AGE
scs-demo   0         0         0       51s

需要注意上面我们在定义 SidecarSet 对象的时候里面有一个非常重要的属性就是 label selector,会去匹配具有 <font style="color:#DF2A3F;">app=nginx</font> 的 Pod,然后向其中注入下面定义的 sidecar1 这个容器,比如定义如下所示的一个 Pod,该 Pod 中包含 app=nginx 的标签,这样可以和上面的 SidecarSet 对象匹配:

apiVersion: v1
kind: Pod
metadata:labels:app: nginx # 匹配 SidecarSet 里面指定的标签name: test-pod
spec:containers:- name: appimage: nginx:1.7.9

可以看到该 Pod 中有 2 个容器,被自动注入了上面定义的 sidecar1 容器:

spec:containers:- command:- sleep- 999denv:- name: IS_INJECTEDvalue: "true"image: harbor.test.com/apisix/busybox:1.28imagePullPolicy: IfNotPresentname: sidecar1resources: {}terminationMessagePath: /dev/termination-logterminationMessagePolicy: FilevolumeMounts:- mountPath: /var/logname: log-volume- mountPath: /var/run/secrets/kubernetes.io/serviceaccountname: kube-api-access-bkkvzreadOnly: true- image: harbor.test.com/cicd/nginx:1.28.0imagePullPolicy: IfNotPresentname: appresources: {}terminationMessagePath: /dev/termination-logterminationMessagePolicy: FilevolumeMounts:- mountPath: /var/run/secrets/kubernetes.io/serviceaccountname: kube-api-access-bkkvzreadOnly: true

现在我们去更新 SidecarSet 中的 sidecar 容器镜像替换成 harbor.test.com/cicd/busybox:1.35.0

Events:Type    Reason     Age                From               Message----    ------     ----               ----               -------Normal  Scheduled  3m4s               default-scheduler  Successfully assigned default/test-pod to 10.122.24.53Normal  Pulling    3m3s               kubelet            Pulling image "harbor.test.com/apisix/busybox:1.28"Normal  Pulled     3m3s               kubelet            Successfully pulled image "harbor.test.com/apisix/busybox:1.28" in 193.133947msNormal  Pulled     3m3s               kubelet            Container image "harbor.test.com/cicd/nginx:1.28.0" already present on machineNormal  Created    3m3s               kubelet            Created container appNormal  Started    3m3s               kubelet            Started container appNormal  Killing    34s                kubelet            Container sidecar1 definition changed, will be restartedNormal  Pulling    4s                 kubelet            Pulling image "harbor.test.com/cicd/busybox:1.35.0"Normal  Started    3s (x2 over 3m3s)  kubelet            Started container sidecar1Normal  Created    3s (x2 over 3m3s)  kubelet            Created container sidecar1Normal  Pulled     3s                 kubelet            Successfully pulled image "harbor.test.com/cicd/busybox:1.35.0" in 260.89335ms

可以看到 Pod 中的 sidecar 容器镜像被原地升级成 busybox:1.35.0 了, 对主容器没有产生任何影响。

5.2 基本特征

需要注意的是 sidecar 的注入只会发生在 Pod 创建阶段,并且只有 Pod spec 会被更新,不会影响 Pod 所属的工作负载 template 模板。 spec.containers 除了默认的 k8s container 字段,还扩展了如下一些字段,来方便注入:

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:name: sidecarset
spec:selector:matchLabels:app: samplecontainers:# 默认的 K8s 容器字段- name: nginximage: nginx:alpinevolumeMounts:- mountPath: /nginx/confname: nginxconf# 扩展的 sidecar 容器字段podInjectPolicy: BeforeAppContainershareVolumePolicy: # 数据卷共享type: disabled | enabledtransferEnv: # 环境变量共享- sourceContainerName: main # 会把main容器中的PROXY_IP环境变量注入到当前定义的sidecar容器中envName: PROXY_IPvolumes:- Name: nginxconfhostPath: /data/nginx/conf
  • podInjectPolicy 定义了容器 注入到 pod.spec.containers 中的位置
    • BeforeAppContainer:表示注入到 pod 原 containers 的前面(默认)
    • AfterAppContainer: 表示注入到 pod 原 containers 的后面
  • 数据卷共享
    • 共享指定卷:通过 spec.volumes 来定义 sidecar 自身需要的 volume
    • 共享所有卷:通过 spec.containers[i].shareVolumePolicy.type = enabled | disabled 来控制是否挂载 pod 应用容器的卷,常用于日志收集等 sidecar,配置为 enabled 后会把应用容器中所有挂载点注入 sidecar 同一路经下(sidecar 中本身就有声明的数据卷和挂载点除外)
  • 环境变量共享:可以通过 spec.containers[i].transferEnv 来从别的容器获取环境变量,会把名为 sourceContainerName 容器中名为 envName 的环境变量拷贝到本容器

SidecarSet 不仅支持 sidecar 容器的原地升级,而且提供了非常丰富的升级、灰度策略。同样在 SidecarSet 对象中 updateStrategy 属性下面也可以配置 partition 来定义保留旧版本 Pod 的数量或百分比,默认为 0;同样还可以配置的有 maxUnavailable 属性,表示在发布过程中的最大不可用数量。

  • {matched pod}=100,partition=40,maxUnavailable=10,控制器会发布 100-40=60 个 Pod 到新版本,但是同一时间只会发布 10 个 Pod,每发布好一个 Pod 才会再找一个发布,直到 60 个发布完成。
  • {matched pod}=100,partition=80,maxUnavailable=30,控制器会发布 20 个 Pod 到新版本,因为满足 maxUnavailable 数量,所以这 20 个 Pod 会同时发布。

同样也可以设置 paused: true 来暂停发布,此时对于新创建的、扩容的 pod 依旧会实现注入能力,已经更新的 pod 会保持更新后的版本不动,还没有更新的 pod 会暂停更新。

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:name: sidecarset
spec:# ...updateStrategy:type: RollingUpdatemaxUnavailable: 20%partition: 10paused: true

5.3 金丝雀发布

对于有金丝雀发布需求的业务,可以通过 selector 来实现,对于需要率先金丝雀灰度的 pod 打上固定的 [canary.release] = true 的标签,再通过 selector.matchLabels 来选中该 pod 即可。比如现在我们有一个 3 副本的 Pod,也具有 app=nginx 的标签,如下所示

apiVersion: apps/v1
kind: Deployment
metadata:name: nginxnamespace: default
spec:replicas: 3revisionHistoryLimit: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: ngximage: nginx:1.7.9ports:- containerPort: 80

创建后现在就具有 4 个 <font style="color:rgb(38, 38, 38);">app=nginx</font> 标签的 Pod 了,由于都匹配上面创建的 SidecarSet 对象,所以都会被注入一个 <font style="color:rgb(38, 38, 38);">sidecar1</font> 的容器,镜像为 <font style="color:rgb(38, 38, 38);">busybox</font>

➜ kubectl get pods -l app=nginx
NAME                    READY   STATUS    RESTARTS       AGE
nginx-6457955f7-7hnjw   2/2     Running   0              51s
nginx-6457955f7-prkgz   2/2     Running   0              51s
nginx-6457955f7-tbtxk   2/2     Running   0              51s
test-pod                2/2     Running   0              4m2s

现在如果我们想为 <font style="color:rgb(38, 38, 38);">test-pod</font> 这个应用来执行灰度策略,将 sidecar 容器镜像更新成 <font style="color:rgb(38, 38, 38);">busybox:1.35.0</font>,则可以在 <font style="color:rgb(38, 38, 38);">updateStrategy</font> 下面添加 <font style="color:rgb(38, 38, 38);">selector.matchLabels</font> 属性 <font style="color:rgb(38, 38, 38);">canary.release: "true"</font>

piVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:name: test-sidecarset
spec:updateStrategy:type: RollingUpdateselector:matchLabels:canary.release: "true"containers:- name: sidecar1image: busybox:1.35.0# ......

然后同样需要给 test-pod 添加上 <font style="color:rgb(38, 38, 38);">canary.release=true</font> 这个标签:

apiVersion: v1
kind: Pod
metadata:labels:app: nginxcanary.release: "true"name: test-pod
spec:containers:- name: appimage: nginx:1.7.9
更新后可以发现 test-pod 的 sidecar 镜像更新了,其他 Pod 没有变化,这样就实现了 sidecar 的灰度功能:
➜ kubectl describe pod test-pod
Events:Type    Reason     Age                    From               Message----    ------     ----                   ----               -------Normal  Killing    7m53s                  kubelet            Container sidecar1 definition changed, will be restartedNormal  Created    7m23s (x2 over 8m17s)  kubelet            Created container sidecar1Normal  Started    7m23s (x2 over 8m17s)  kubelet            Started container sidecar1Normal  Pulling    7m23s                  kubelet            Pulling image "busybox"Normal  Pulled     7m23s                  kubelet            Successfully pulled image "busybox" in 603.928658ms

六、镜像推送预热

NodeImageImagePullJob 是从 Kruise v0.8.0 版本开始提供的 CRD。Kruise 会自动为每个节点创建一个 NodeImage,它包含了哪些镜像需要在这个 Node 上做预热,比如我们这里 3 个节点,则会自动创建 3 个 NodeImage 对象:

➜ kubectl get nodeimage
NAME      DESIRED   PULLING   SUCCEED   FAILED   AGE
master1   0         0         0         0        5d
node1     0         0         0         0        5d
node2     0         0         0         0        5d

比如我们查看 node1 节点上的 NodeImage 对象:

➜ kubectl get nodeimage node1 -o yaml
apiVersion: apps.kruise.io/v1alpha1
kind: NodeImage
metadata:name: node1# ......
spec: {}
status:desired: 0failed: 0pulling: 0succeeded: 0

比如我们希望在这个节点上拉去一个 ubuntu:latest 镜像,则可以按照如下所示的去修改 spec:

......
spec:images:ubuntu:  # 镜像 nametags:- tag: latest  # 镜像 tagpullPolicy:ttlSecondsAfterFinished: 300  # [required] 拉取完成(成功或失败)超过 300s 后,将这个任务从 NodeImage 中清除timeoutSeconds: 600           # [optional] 每一次拉取的超时时间, 默认为 600backoffLimit: 3               # [optional] 拉取的重试次数,默认为 3activeDeadlineSeconds: 1200   # [optional] 整个任务的超时时间,无默认值

更新后我们可以从 status 中看到拉取进度以及结果,并且你会发现拉取完成 600s 后任务会被清除。

➜ kubectl describe nodeimage node1
Name:         node1
Namespace:
# ......
Spec:Images:Ubuntu:Tags:Created At:  2023-04-04T09:29:18ZPull Policy:Active Deadline Seconds:     1200Backoff Limit:               3Timeout Seconds:             600Ttl Seconds After Finished:  300Tag:                           latest
Status:Desired:  1Failed:   0Image Statuses:Ubuntu:Tags:Completion Time:  2023-04-04T09:29:28ZPhase:            SucceededProgress:         100Start Time:       2023-04-04T09:29:18ZTag:              latestPulling:                0Succeeded:              1
Events:Type    Reason            Age   From                       Message----    ------            ----  ----                       -------Normal  PullImageSucceed  11s   kruise-daemon-imagepuller  Image ubuntu:latest, ecalpsedTime 10.066193581s

我们可以在 node1 节点上查看到这个镜像已经被拉取下来了:

ubuntu@node1:~$ sudo ctr -n k8s.io i ls  |grep ubuntu
docker.io/library/ubuntu:latest                                                                                                    application/vnd.oci.image.index.v1+json                   sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21 28.2 MiB  linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x                                                                  io.cri-containerd.image=managed
docker.io/library/ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21                                   application/vnd.oci.image.index.v1+json                   sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21 28.2 MiB  linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x                                                                  io.cri-containerd.image=managed
ubuntu@node1:~$

此外用户可以创建 ImagePullJob 对象,来指定一个镜像要在哪些节点上做预热。

比如创建如下所示的 ImagePullJob 资源对象:

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:name: job-with-always
spec:image: nginx:1.9.1 # [required] 完整的镜像名 name:tagparallelism: 10 # [optional] 最大并发拉取的节点梳理, 默认为 1selector: # [optional] 指定节点的 名字列表 或 标签选择器 (只能设置其中一种)names:- node1- node2matchLabels:node-type: xxx# podSelector:         # [optional] pod label 选择器来在这些 pod 所在节点上拉取镜像, 与 selector 不能同时设置.#  pod-label: xxxcompletionPolicy:type: Always # [optional] 默认为 AlwaysactiveDeadlineSeconds: 1200 # [optional] 无默认值, 只对 Alway 类型生效ttlSecondsAfterFinished: 300 # [optional] 无默认值, 只对 Alway 类型生效pullPolicy: # [optional] 默认 backoffLimit=3, timeoutSeconds=600backoffLimit: 3timeoutSeconds: 300pullSecrets:- secret-name1- secret-name2

我们可以在 selector 字段中指定节点的名字列表或标签选择器 (只能设置其中一种),如果没有设置 selector 则会选择所有节点做预热。或者可以配置 podSelector 来在这些 pod 所在节点上拉取镜像,podSelector 与 selector 不能同时设置。

同时,ImagePullJob 有几种 completionPolicy 类型:

  • Always:表示这个 job 是一次性预热,不管成功、失败都会结束
  • activeDeadlineSeconds:整个 job 的 deadline 结束时间
  • ttlSecondsAfterFinished:结束后超过这个时间,自动清理删除 job
  • Never:表示这个 job 是长期运行、不会结束,并且会每天都会在匹配的节点上重新预热一次指定的镜像

同样如果你要预热的镜像来自私有仓库,则可以通过pullSecrets 来指定仓库的 Secret 信息。

如果这个镜像来自一个私有仓库,则可以通过 pullSecrets 来指定仓库的 Secret 信息。

# ...
spec:pullSecrets:- secret-name1- secret-name2

因为 <font style="color:rgb(38, 38, 38);">ImagePullJob</font> 是一种 namespaced-scope 资源,所以这些 Secret 必须存在 <font style="color:rgb(38, 38, 38);">ImagePullJob</font> 所在的 namespace 中。然后你只需要在 pullSecrets 字段中写上这些 secret 的名字即可。

http://www.dtcms.com/a/317248.html

相关文章:

  • Linux《进程间通信(上)》
  • Git 乱码文件处理全流程指南
  • 记一次ORACLE ORA-00600 [19004] 错误的分析与解决方法
  • HarmonyOS 5 入门系列-鸿蒙HarmonyOS示例项目讲解
  • 铁路通信信号基础知识点(2)轨旁与车载ATP关系
  • 《动手学深度学习》读书笔记—9.5机器翻译与数据集
  • 虚拟机磁盘扩容
  • centos KVM
  • Java技术栈/面试题合集(19)-架构设计篇
  • Vue2中实现数据复制到 Excel
  • 【普通地质学】地球的物质组成
  • 什么是OAuth2.0协议?有哪几种认证方式?什么是JWT令牌?和普通令牌有什么区别?
  • 【JS-7-ajax】AJAX技术:现代Web开发的异步通信核心
  • 数据赋能(381)——数据挖掘——支持异类数据库
  • Springboot 默认注入方式和@Primary
  • 高职5G移动网络运维实验(训)室解决方案
  • Wireshark协助捕获信号波形
  • 【STL源码剖析】从源码看 vector:底层扩容逻辑与内存复用机制
  • 常见类型在内存中的存储
  • 百度华为硬件笔试机试题-卷4
  • 5G毫米波射频前端测试:OTA暗室与波束成形性能验证
  • WinForm之ListView 组件
  • bat脚本实现获取非微软官方服务列表
  • Minio 高性能分布式对象存储
  • LiveQing视频RTMP推流视频点播服务功能-云端录像支持按时间段下载录像时间段下载视频mp4
  • eclipse2023创建工作集
  • 西门子PLC基础指令6:读取时钟指令、设置时钟指令、使能含义与注意
  • 比特币量化模型高级因子筛选与信号生成报告
  • 视图 vs 直接使用复杂SQL:深入比较
  • 场外期权的卖方是什么策略?