您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

K8s 增强版工作负载 OpenKruise 之运维增强功能

时间:2023-04-06 12:22:55  来源:微信公众号  作者:k8s技术圈

 

前面我们和大家已经学习了 OpenKruise 的基本概念以及常用的几个增强控制器,接下来我们来继续了解其他高级功能。

SidecarSet

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 的 Pod
      app: nginx
  updateStrategy:
    type: RollingUpdate
    maxUnavAIlable: 1
  containers:
    - name: sidecar1
      image: busybox
      command: ["sleep", "999d"]
      volumeMounts:
        - name: log-volume
          mountPath: /var/log
  volumes: # 该属性会被合并到 pod.spec.volumes 去
    - name: log-volume
      emptyDir: {}

 

直接创建这个资源对象即可:

 

➜ kubectl get sidecarset
NAME       MATCHED   UPDATED   READY   AGE
scs-demo   0         0         0       6s

 

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

 

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

 

直接创建上面的资源对象:

 

➜ kubectl get pod test-pod
NAME       READY   STATUS    RESTARTS   AGE
test-pod   2/2     Running   0          22s

 

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

 

➜ kubectl get pod test-pod -o yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: test-pod
  namespace: default
spec:
  containers:
  - command:
    - sleep
    - 999d
    env:
    - name: IS_INJECTED
      value: "true"
    image: busybox
    imagePullPolicy: Always
    name: sidecar1
    resources: {}
    volumeMounts:
    - mountPath: /var/log
      name: log-volume
  - image: nginx:1.7.9
    imagePullPolicy: IfNotPresent
    name: app
# ......
  volumes:
  - emptyDir: {}
    name: log-volume
# ......

 

现在我们去更新 SidecarSet 中的 sidecar 容器镜像替换成 busybox:1.35.0:

 

➜ kubectl patch sidecarset scs-demo --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value": "busybox:1.35.0"}]'
sidecarset.apps.kruise.io/scs-demo patched

 

更新后再去查看 Pod 中的 sidecar 容器:

 

➜ kubectl get pod test-pod
NAME       READY   STATUS    RESTARTS      AGE
test-pod   2/2     Running   1 (67s ago)   28m
➜ kubectl describe pod test-pod
......
Events:
  Type    Reason     Age                From               Message
  ----    ------     ----               ----               -------
  # ......
  Normal  Created    10m                kubelet            Created container app
  Normal  Started    10m                kubelet            Started container app
  Normal  Killing    114s               kubelet            Container sidecar1 definition changed, will be restarted
  Normal  Pulling    84s                kubelet            Pulling image "busybox:1.35.0"
  Normal  Created    77s (x2 over 11m)  kubelet            Created container sidecar1
  Normal  Started    77s (x2 over 11m)  kubelet            Started container sidecar1
  Normal  Pulled     77s                kubelet            Successfully pulled image "busybox:1.35.0" in 6.901558972s (6.901575894s including waiting)
➜ kubectl get pod test-pod -o yaml |grep busybox
    kruise.io/sidecarset-inplace-update-state: '{"scs-demo":{"revision":"f78z4854d9855xd6478fzx9c84645z2548v24z26455db46bdfzw44v49v98f2cw","updateTimestamp":"2023-04-04T08:05:18Z","lastContainerStatuses":{"sidecar1":{"imageID":"Docker.io/library/busybox@sha256:b5d6fe0712636ceb7430189de28819e195e8966372edfc2d9409d79402a0dc16"}}}}'
    image: busybox:1.35.0
    image: docker.io/library/busybox:1.35.0
    imageID: docker.io/library/busybox@sha256:223ae047b1065bd069aac01ae3ac8088b3ca4a527827e283b85112f29385fb1b

 

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

基本特性

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

 

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
  name: sidecarset
spec:
  selector:
    matchLabels:
      app: sample
  containers:
    # 默认的 K8s 容器字段
    - name: nginx
      image: nginx:alpine
      volumeMounts:
        - mountPath: /nginx/conf
          name: nginxconf
      # 扩展的 sidecar 容器字段
      podInjectPolicy: BeforeAppContainer
      shareVolumePolicy: # 数据卷共享
        type: disabled | enabled
      transferEnv: # 环境变量共享
        - sourceContainerName: main # 会把main容器中的PROXY_IP环境变量注入到当前定义的sidecar容器中
          envName: PROXY_IP
  volumes:
    - Name: nginxconf
      hostPath: /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,partitinotallow=40,maxUnavailable=10,控制器会发布 100-40=60 个 Pod 到新版本,但是同一时间只会发布 10 个 Pod,每发布好一个 Pod 才会再找一个发布,直到 60 个发布完成。
  • 当 {matched pod}=100,partitinotallow=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: RollingUpdate
    maxUnavailable: 20%
    partition: 10
    paused: true

 

金丝雀发布

对于有金丝雀发布需求的业务,可以通过 selector​ 来实现,对于需要率先金丝雀灰度的 pod 打上固定的 [canary.release] = true​ 的标签,再通过 selector.matchLabels 来选中该 pod 即可。

比如现在我们有一个 3 副本的 Pod,也具有 app=nginx 的标签,如下所示:

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 3
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: ngx
          image: nginx:1.7.9
          ports:
            - containerPort: 80

 

创建后现在就具有 4 个 app=nginx​ 标签的 Pod 了,由于都匹配上面创建的 SidecarSet 对象,所以都会被注入一个 sidecar1​ 的容器,镜像为 busybox:

 

➜ 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
 

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

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

 

然后同样需要给 test-pod 添加上 canary.release=true 这个标签:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
    canary.release: "true"
  name: test-pod
spec:
  containers:
    - name: app
      image: 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 restarted
  Normal  Created    7m23s (x2 over 8m17s)  kubelet            Created container sidecar1
  Normal  Started    7m23s (x2 over 8m17s)  kubelet            Started container sidecar1
  Normal  Pulling    7m23s                  kubelet            Pulling image "busybox"
  Normal  Pulled     7m23s                  kubelet            Successfully pulled image "busybox" in 603.928658ms

热升级

SidecarSet 原地升级会先停止旧版本的容器,然后创建新版本的容器,这种方式适合不影响 Pod 服务可用性的 sidecar 容器,比如说日志收集的 Agent。

但是对于很多代理或运行时的 sidecar 容器,例如 Istio Envoy,这种升级方法就有问题了,Envoy 作为 Pod 中的一个代理容器,代理了所有的流量,如果直接重启,Pod 服务的可用性会受到影响,如果需要单独升级 envoy sidecar,就需要复杂的优雅终止和协调机制,所以我们为这种 sidecar 容器的升级提供了一种新的解决方案。

# hotupgrade-sidecarset.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
  name: hotupgrade-sidecarset
spec:
  selector:
    matchLabels:
      app: hotupgrade
  containers:
    - name: sidecar
      image: openkruise/hotupgrade-sample:sidecarv1
      imagePullPolicy: Always
      lifecycle:
        postStart:
          exec:
            command:
              - /bin/sh
              - /migrate.sh
      upgradeStrategy:
        upgradeType: HotUpgrade
        hotUpgradeEmptyImage: openkruise/hotupgrade-sample:empty
 
  • upgradeType​: HotUpgrade 代表该 sidecar 容器的类型是热升级方案。
  • hotUpgradeEmptyImage​: 当热升级 sidecar 容器时,业务必须要提供一个 empty 容器用于热升级过程中的容器切换,empty 容器同 sidecar 容器具有相同的配置(除了镜像地址),例如:command​、lifecycle​、probe 等,但是它不做任何工作。
  • lifecycle.postStart: 在 postStart 这个 hook 中完成热升级过程中的状态迁移,该脚本需要由业务根据自身的特点自行实现,例如:nginx 热升级需要完成 Listen FD 共享以及流量排水(reload)操作。

整体来说热升级特性总共包含以下两个过程:

  • Pod 创建时,注入热升级容器。
  • 原地升级时,完成热升级流程。

注入热升级容器

Pod 创建时,SidecarSet Webhook 将会注入两个容器:

  • {sidecarContainer.name}-1​: 如下图所示 envoy-1​,这个容器代表正在实际工作的 sidecar 容器,例如:envoy:1.16.0。
  • {sidecarContainer.name}-2​: 如下图所示 envoy-2​,这个容器是业务配置的 hotUpgradeEmptyImage​ 容器,例如:empty:1.0,用于后面的热升级机制。

图片

注入热升级容器

热升级流程

热升级流程主要分为三个步骤:

  • Upgrade: 将 empty 容器升级为当前最新的 sidecar 容器,例如:envoy-2.Image = envoy:1.17.0
  • Migration​: lifecycle.postStart 完成热升级流程中的状态迁移,当迁移完成后退出
  • Reset: 状态迁移完成后,热升级流程将设置 envoy-1 容器为 empty 镜像,例如:envoy-1.Image = empty:1.0

上述三个步骤完成了热升级中的全部流程,当对 Pod 执行多次热升级时,将重复性的执行上述三个步骤。

图片

热升级流程

这里我们以 OpenKruise 的官方示例来进行说明,首先创建上面的 hotupgrade-sidecarset 这个 SidecarSet。然后创建一个如下所示的 CloneSet 对象:

# hotupgrade-cloneset.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
  name: busybox
  labels:
    app: hotupgrade
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hotupgrade
  template:
    metadata:
      labels:
        app: hotupgrade
    spec:
      containers:
        - name: busybox
          image: openkruise/hotupgrade-sample:busybox

 

创建完成后,CloneSet 管理的 Pod 已经注入 sidecar-1​ 和 sidecar-2 两个容器:

 

➜ kubectl get sidecarset hotupgrade-sidecarset
NAME                    MATCHED   UPDATED   READY   AGE
hotupgrade-sidecarset   1         1         0       58s
➜ kubectl get pods -l app=hotupgrade
NAME            READY   STATUS    RESTARTS   AGE
busybox-nd5bp   3/3     Running   0          31s
➜ kubectl describe pod busybox-nd5bp
Name:             busybox-nd5bp
Namespace:        default
Node:             node2/10.206.16.10
# ......
Controlled By:  CloneSet/busybox
Containers:
  sidecar-1:
    Container ID:   containerd://511aa4b60d36483177e92805653c1b618495e47d8d5de331008259f78b3be89e
    Image:          openkruise/hotupgrade-sample:sidecarv1
    Image ID:       docker.io/openkruise/hotupgrade-sample@sha256:3d677ca19712b67d2c264374736d71089d21e100eff341f6b4bb0f5288ec6f34
    Environment:
      IS_INJECTED:             true
      SIDECARSET_VERSION:       (v1:metadata.annotations['version.sidecarset.kruise.io/sidecar-1'])
      SIDECARSET_VERSION_ALT:   (v1:metadata.annotations['versionalt.sidecarset.kruise.io/sidecar-1'])
    # ......
  sidecar-2:
    Container ID:   containerd://6b0678695ccb977695248e41108606b409ad0c7e3e4fe1ba9b48e839e3c235ef
    Image:          openkruise/hotupgrade-sample:empty
    Image ID:       docker.io/openkruise/hotupgrade-sample@sha256:606be602967c9f91c47e4149af4336c053e26225b717a1b5453ac8fa9a401cc5
    Environment:
      IS_INJECTED:             true
      SIDECARSET_VERSION:       (v1:metadata.annotations['version.sidecarset.kruise.io/sidecar-2'])
      SIDECARSET_VERSION_ALT:   (v1:metadata.annotations['versionalt.sidecarset.kruise.io/sidecar-2'])
    # ......
  busybox:
    Container ID:   containerd://da7eebb0161bab37f7de75635e68c5284a973a21f6d3f095bb5e8212ac8ce908
    Image:          openkruise/hotupgrade-sample:busybox
    Image ID:       docker.io/openkruise/hotupgrade-sample@sha256:08f9ede05850686e1200240e5e376fc76245dd2eb56299060120b8c3dba46dc9
    # ......
# ......
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  50s   default-scheduler  Successfully assigned default/busybox-nd5bp to node2
  Normal  Pulling    49s   kubelet            Pulling image "openkruise/hotupgrade-sample:sidecarv1"
  Normal  Pulled     41s   kubelet            Successfully pulled image "openkruise/hotupgrade-sample:sidecarv1" in 7.929984849s (7.929998445s including waiting)
  Normal  Created    41s   kubelet            Created container sidecar-1
  Normal  Started    41s   kubelet            Started container sidecar-1
  Normal  Pulling    36s   kubelet            Pulling image "openkruise/hotupgrade-sample:empty"
  Normal  Pulled     29s   kubelet            Successfully pulled image "openkruise/hotupgrade-sample:empty" in 7.434180553s (7.434189239s including waiting)
  Normal  Created    29s   kubelet            Created container sidecar-2
  Normal  Started    29s   kubelet            Started container sidecar-2
  Normal  Pulling    29s   kubelet            Pulling image "openkruise/hotupgrade-sample:busybox"
  Normal  Pulled     22s   kubelet            Successfully pulled image "openkruise/hotupgrade-sample:busybox" in 6.583450981s (6.583456512s including waiting)
  Normal  Created    22s   kubelet            Created container busybox
  Normal  Started    22s   kubelet            Started container busybox
......

 

busybox 主容器每 100 毫秒会请求一次 sidecar(versinotallow=v1)服务:

 

➜ kubectl logs -f busybox-nd5bp -c busybox
I0404 09:12:26.513128       1 main.go:39] request sidecar server success, and response(body=This is version(v1) sidecar)
I0404 09:12:26.623496       1 main.go:39] request sidecar server success, and response(body=This is version(v1) sidecar)
I0404 09:12:26.733958       1 main.go:39] request sidecar server success, and response(body=This is version(v1) sidecar)
......

 

现在我们去升级 sidecar 容器,将镜像修改为 openkruise/hotupgrade-sample:sidecarv2:

➜ kubectl patch sidecarset hotupgrade-sidecarset --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value": "openkruise/hotupgrade-sample:sidecarv2"}]'

更新后再去观察 pod 的状态,可以看到 sidecar-2 镜像正常更新了:

➜ kubectl get pods -l app=hotupgrade
NAME            READY   STATUS    RESTARTS      AGE
busybox-nd5bp   3/3     Running   2 (45s ago)   23m
➜ kubectl describe pods busybox-nd5bp
......
Events:
  ......
  Normal  Pulling                33s                kubelet                Pulling image "openkruise/hotupgrade-sample:sidecarv2"
  Normal  Killing                33s                kubelet                Container sidecar-2 definition changed, will be restarted
  Normal  Started                25s (x2 over 22m)  kubelet                Started container sidecar-2
  Normal  Created                25s (x2 over 22m)  kubelet                Created container sidecar-2
  Normal  Pulled                 25s                kubelet                Successfully pulled image "openkruise/hotupgrade-sample:sidecarv2" in 8.169453753s (8.16946743s including waiting)
  Normal  Killing                14s                kubelet                Container sidecar-1 definition changed, will be restarted
  Normal  ResetContainerSucceed  14s                sidecarset-controller  reset sidecar container image empty successfully
  Normal  Pulling                14s                kubelet                Pulling image "openkruise/hotupgrade-sample:empty"
  Normal  Created                12s (x2 over 22m)  kubelet                Created container sidecar-1
  Normal  Started                12s (x2 over 22m)  kubelet                Started container sidecar-1
  Normal  Pulled                 12s                kubelet                Successfully pulled image "openkruise/hotupgrade-sample:empty" in 1.766097364s (1.766109087s including waiting)

 

并且在更新过程中观察 busybox 容器仍然会不断请求 sidecar 服务,但是并没有失败的请求出现:

➜ kubectl logs -f busybox-nd5bp -c busybox
I0306 11:08:47.587727       1 main.go:39] request sidecar server success, and response(body=This is version(v1) sidecar)
I0404 09:14:28.588973       1 main.go:39] request sidecar server success, and response(body=This is version(v2) sidecar)
# ......

整个热升级示例代码可以参考仓库的实现:https://Github.com/openkruise/samples/tree/master/hotupgrade。

Container Restart

ContainerRecreateRequest​ 控制器可以帮助用户重启/重建存量 Pod 中一个或多个容器。和 Kruise 提供的原地升级类似,当一个容器重建的时候,Pod 中的其他容器还保持正常运行,重建完成后,Pod 中除了该容器的 restartCount 增加以外不会有什么其他变化。

不过需要注意之前临时写到旧容器 rootfs​ 中的文件会丢失,但是 volume mount 挂载卷中的数据都还存在。这个功能依赖于 kruise-daemon 组件来停止 Pod 容器。

为要重建容器的 Pod 提交一个 ContainerRecreateRequest 自定义资源(缩写 CRR):

# crr-demo.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: ContainerRecreateRequest
metadata:
  name: crr-dmo
spec:
  podName: pod-name
  containers: # 要重建的容器名字列表,至少要有 1 个
    - name: app
    - name: sidecar
  strategy:
    failurePolicy: Fail # 'Fail' 或 'Ignore',表示一旦有某个容器停止或重建失败, CRR 立即结束
    orderedRecreate: false # 'true' 表示要等前一个容器重建完成了,再开始重建下一个
    terminationGracePeriodSeconds: 30 # 等待容器优雅退出的时间,不填默认用 Pod 中定义的
    unreadyGracePeriodSeconds: 3 # 在重建之前先把 Pod 设为 not ready,并等待这段时间后再开始执行重建
    minStartedSeconds: 10 # 重建后新容器至少保持运行这段时间,才认为该容器重建成功
  activeDeadlineSeconds: 300 # 如果 CRR 执行超过这个时间,则直接标记为结束(未结束的容器标记为失败)
  ttlSecondsAfterFinished: 1800 # CRR 结束后,过了这段时间自动被删除掉

一般来说,列表中的容器会一个个被停止,但可能同时在被重建和启动,除非 orderedRecreate​ 被设置为 true。unreadyGracePeriodSeconds​ 功能依赖于 KruisePodReadinessGate​ 这个 feature-gate,后者会在每个 Pod 创建的时候注入一个 readinessGate​,否则,默认只会给 Kruise 工作负载创建的 Pod 注入 readinessGate,也就是说只有这些 Pod 才能在 CRR 重建时使用 unreadyGracePeriodSeconds。

当用户创建了一个 CRR,Kruise webhook 会把当时容器的 containerID/restartCount 记录到 spec.containers[x].statusContext​ 之中。 在 kruise-daemon 执行的过程中,如果它发现实际容器当前的 containerID​ 与 statusContext​ 不一致或 restartCount 已经变大,则认为容器已经被重建成功了(比如可能发生了一次原地升级)。

图片

容器重启请求

一般情况下,kruise-daemon 会执行 preStop hook 后把容器停掉,然后 kubelet 感知到容器退出,则会新建一个容器并启动。最后 kruise-daemon 看到新容器已经启动成功超过 minStartedSeconds 时间后,会上报这个容器的 phase 状态为 Succeeded。

如果容器重建和原地升级操作同时触发了:

  • 如果 kubelet 根据原地升级要求已经停止或重建了容器,kruise-daemon 会判断容器重建已经完成。
  • 如果 kruise-daemon 先停了容器,Kubelet 会继续执行原地升级,即创建一个新版本容器并启动。
  • 如果针对一个 Pod 提交了多个 ContainerRecreateRequest 资源,会按时间先后一个个执行。

ImagePullJob

NodeImage​ 和 ImagePullJob 是从 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: 0
  failed: 0
  pulling: 0
  succeeded: 0

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

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

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

➜ kubectl describe nodeimage node1
Name:         node1
Namespace:
# ......
Spec:
  Images:
    Ubuntu:
      Tags:
        Created At:  2023-04-04T09:29:18Z
        Pull Policy:
          Active Deadline Seconds:     1200
          Backoff Limit:               3
          Timeout Seconds:             600
          Ttl Seconds After Finished:  300
        Tag:                           latest
Status:
  Desired:  1
  Failed:   0
  Image Statuses:
    Ubuntu:
      Tags:
        Completion Time:  2023-04-04T09:29:28Z
        Phase:            Succeeded
        Progress:         100
        Start Time:       2023-04-04T09:29:18Z
        Tag:              latest
  Pulling:                0
  Succeeded:              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:tag
  parallelism: 10 # [optional] 最大并发拉取的节点梳理, 默认为 1
  selector: # [optional] 指定节点的 名字列表 或 标签选择器 (只能设置其中一种)
    names:
      - node1
      - node2
    matchLabels:
      node-type: xxx
  # podSelector:         # [optional] pod label 选择器来在这些 pod 所在节点上拉取镜像, 与 selector 不能同时设置.
  #  pod-label: xxx
  completionPolicy:
    type: Always # [optional] 默认为 Always
    activeDeadlineSeconds: 1200 # [optional] 无默认值, 只对 Alway 类型生效
    ttlSecondsAfterFinished: 300 # [optional] 无默认值, 只对 Alway 类型生效
  pullPolicy: # [optional] 默认 backoffLimit=3, timeoutSecnotallow=600
    backoffLimit: 3
    timeoutSeconds: 300
  pullSecrets:
    - 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
 

因为 ImagePullJob​ 是一种 namespaced-scope 资源,所以这些 Secret 必须存在 ImagePullJob 所在的 namespace 中。然后你只需要在 pullSecrets 字段中写上这些 secret 的名字即可。

容器启动顺序

Container Launch Priority 提供了控制一个 Pod 中容器启动顺序的方法。通常来说 Pod 容器的启动和退出顺序是由 Kubelet 管理的,Kube.NETes 曾经有一个 KEP 计划在 container 中增加一个 type 字段来标识不同类型容器的启停优先级,但是由于sig-node 考虑到对现有代码架构的改动太大,所以将该提案拒绝了。

这个功能作用在 Pod 对象上,不管它的 owner 是什么类型的,因此可以适用于 Deployment、CloneSet 以及其他的工作负载。

比如我们可以设置按照容器顺序启动,只需要在 Pod 中定义一个 apps.kruise.io/container-launch-priority 的注解即可:

apiVersion: v1
kind: Pod
  annotations:
    apps.kruise.io/container-launch-priority: Ordered
spec:
  containers:
  - name: sidecar
    # ...
  - name: main
    # ...

 

Kruise 会保证前面的容器(sidecar)会在后面容器(main)之前启动。

此外我们还可以按自定义顺序启动,但是需要在 Pod 容器中添加 KRUISE_CONTAINER_PRIORITY 这个环境变量:

apiVersion: v1
kind: Pod
spec:
  containers:
    - name: main
      # ...
    - name: sidecar
      env:
        - name: KRUISE_CONTAINER_PRIORITY
          value: "1"
      # ...

该环境变量值的范围在 [-2147483647, 2147483647],不写默认是 0,权重高的容器,会保证在权重低的容器之前启动,但是需要注意相同权重的容器不保证启动顺序。

资源分发

在对 Secret、ConfigMap 等命名空间级别资源进行跨 namespace 分发及同步的场景中,原生 Kubernetes 目前只支持用户手动分发与同步,十分地不方便。比如:

  • 当用户需要使用 SidecarSet 的 imagePullSecrets 能力时,要先重复地在相关 namespaces 中创建对应的 Secret,并且需要确保这些 Secret 配置的正确性和一致性。
  • 当用户想要采用 ConfigMap 来配置一些通用的环境变量时,往往需要在多个 namespaces 做 ConfigMap 的下发,并且后续的修改往往也要求多 namespaces 之间保持同步。
  • 在多个命名空间中的 Ingress 对象需要使用同一个 Secret 对象

面对这些需要跨命名空间进行资源分发和多次同步的场景,OpenKruise 设计了一个新的 CRD - ResourceDistribution,可以更便捷的自动化分发和同步这些资源。

ResourceDistribution​ 目前支持 Secret​ 和 ConfigMap 两类资源的分发和同步。

ResourceDistribution​ 是全局的 CRD,其主要由 resource​ 和 targets​ 两个字段构成,其中 resource​ 字段用于描述用户所要分发的资源,targets 字段用于描述用户所要分发的目标命名空间。

 

apiVersion: apps.kruise.io/v1alpha1
kind: ResourceDistribution
metadata:
  name: sample
spec:
  resource: ... ...
  targets: ... ...

 

其中 resource 字段必须是一个完整、正确的资源描述,如下所示:

 

apiVersion: apps.kruise.io/v1alpha1
kind: ResourceDistribution
metadata:
  name: sample
spec:
  resource:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: game-demo
    data:
      game.properties: |
        enemy.types=aliens,monsters
        player.maximum-lives=5
      player_initial_lives: "3"
      ui_properties_file_name: user-interface.properties
      user-interface.properties: |
        color.good=purple
        color.bad=yellow
        allow.textmode=true
  targets: ... ...

 

用户可以先在本地某个命名空间中创建相应资源并进行测试,确认资源配置正确后再拷贝过来。

targets​ 字段目前支持四种规则来描述用户所要分发的目标命名空间,包括 allNamespaces​、includedNamespaces​、namespaceLabelSelector​ 以及 excludedNamespaces:

  • allNamespaces: bool 值,如果为 true,则分发至所有命名空间。
  • includedNamespaces: 通过 Name 来匹配目标命名空间。
  • namespaceLabelSelector:通过 LabelSelector 来匹配目标命名空间。
  • excludedNamespaces: 通过 Name 来排除某些不想分发的命名空间。

allNamespaces​、includedNamespaces​、namespaceLabelSelector​ 之间是**或(OR)**的关系,而 excludedNamespaces​ 一旦被配置,则会显式地排除掉这些命名空间。另外,targets​ 还将自动忽略 kube-system​ 和 kube-public 两个命名空间。

一个配置正确的 targets 字段如下所示:

 

apiVersion: apps.kruise.io/v1alpha1
kind: ResourceDistribution
metadata:
  name: sample
spec:
  resource: ... ...
  targets:
    includedNamespaces:
      list:
        - name: ns-1
        - name: ns-4
    namespaceLabelSelector:
      matchLabels:
        group: test
    excludedNamespaces:
      list:
        - name: ns-3

 

该配置表示该 ResourceDistribution​ 的目标命名空间一定会包含 ns-1​ 和 ns-4​,并且 Labels 满足 namespaceLabelSelector​ 的命名空间也会被包含进目标命名空间,但是,即使 ns-3​ 即使满足 namespaceLabelSelector​ 也不会被包含,因为它已经在 excludedNamespaces 中被显式地排除了。

如果同步的资源需要更新则可以去更新 resource​ 字段,更新后会自动地对所有目标命名空间中的资源进行同步更新。每一次更新资源时,ResourceDistribution​ 都会计算新版本资源的哈希值,并记录到资源的 Annotations 之中,当 ResourceDistribution 发现新版本的资源与目前资源的哈希值不同时,才会对资源进行更新。

 

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo
  annotations:
    kruise.io/resourcedistribution.resource.from: sample
    kruise.io/resourcedistribution.resource.distributed.timestamp: 2021-09-06 08:44:52.7861421 +0000 UTC m=+12896.810364601
    kruise.io/resourcedistribution.resource.hashcode: 0821a13321b2c76b5bd63341a0d97fb46bfdbb2f914e2ad6b613d10632fa4b63
... ...
 

当然非常不建议用户绕过 ResourceDistribution 直接对资源进行修改,除非用户知道自己在做什么。

  • 因为直接修改资源后,资源的哈希值不会被自动计算,因此,下次 resource 字段被修改后,ResourceDistribution 可能将用户对这些资源的直接修改覆盖掉。
  • ResourceDistribution​ 通过 kruise.io/resourcedistribution.resource.from​ 来判断资源是否由该 ResourceDistribution​ 分发,如果该 Annotation 被修改或删除,则被修改的资源会被 ResourceDistribution​ 当成冲突资源,并且无法通过 ResourceDistribution 进行同步更新。

除了这些增强控制器之外 OpenKruise 还有很多高级的特性,可以前往官网 https://openkruise.io 了解更多信息。



Tags:K8s   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
五分钟 k8s 实战-滚动更新与优雅停机
当我们在生产环境发布应用时,必须要考虑到当前系统还有用户正在使用的情况,所以尽量需要做到不停机发版。所以在发布过程中理论上之前的 v1 版本依然存在,必须得等待 v2 版本启...【详细内容】
2023-11-30  Search: K8s  点击:(239)  评论:(0)  加入收藏
五分钟 K8s 实战-应用探针
今天进入 kubernetes 的运维部分(并不是运维 kubernetes,而是运维应用),其实日常我们大部分使用 kubernetes 的功能就是以往运维的工作,现在云原生将运维和研发关系变得更紧密了...【详细内容】
2023-11-27  Search: K8s  点击:(182)  评论:(0)  加入收藏
在本地 K8s 中轻松部署自签 TLS 证书
随着互联网的飞速发展,安全性日益成为我们关注的焦点。HTTPS 已从一项奢侈的技术逐渐成为现代网络交互的标准。它不仅仅是保护信息的重要工具,更是实现信任和品质的象征...【详细内容】
2023-11-03  Search: K8s  点击:(254)  评论:(0)  加入收藏
K8s 多集群实践思考和探索
本文主要讲述了一些对于K8s多集群管理的思考,包括为什么需要多集群、多集群的优势以及现有的一些基于Kubernetes衍生出的多集群管理架构实践。一、为什么需要多集群随着K8s和...【详细内容】
2023-09-07  Search: K8s  点击:(321)  评论:(0)  加入收藏
K8S 入门到实战--部署应用到 K8S
因为 k8s 部分功能其实是偏运维的,对研发来说优先级并不太高;所以我不太会涉及一些 k8s 运维的知识点,比如安装、组件等模块;主要以我们日常开发会使用到的组件讲起。背景最近这...【详细内容】
2023-09-06  Search: K8s  点击:(287)  评论:(0)  加入收藏
Prometheus on k8s 部署与实战操作进阶篇
Prometheus和Prometheus Operator的配置和使用在实际环境中可能会因版本和具体的Kubernetes发行版而有所不同。为了获得更详细和准确的指导,请查阅官方文档和适用于您特定环...【详细内容】
2023-08-29  Search: K8s  点击:(464)  评论:(0)  加入收藏
k8s 使用 containerd 运行时配置 http 私服(harbor等)
简介Kubernetes 从 v1.20 开始弃用 Docker,并推荐用户切换到基于容器运行时接口(CRI)的容器引擎,如 containerd、cri-o 等。 目前使用的环境中使用了 Kubernetes v1.22.3,contain...【详细内容】
2023-08-18  Search: K8s  点击:(258)  评论:(0)  加入收藏
k8s 用户角色 权限的划分
在Kubernetes中,角色(Role)和角色绑定(RoleBinding)用于划分用户的权限。 Kubernetes中的角色定义了一组特定操作的权限,例如创建、删除或修改特定资源。而角色绑定则将角色与用户...【详细内容】
2023-08-18  Search: K8s  点击:(252)  评论:(0)  加入收藏
K8S 核心原理分析,你学到了什么?
Docker:作为开源的应用容器引擎,可以把应用程序和其相关依赖打包生成一个Image镜像文件,是一个标准的运行环境,提供可持续交付的能力。一、背景基于分布式的架构中,需要管理的服...【详细内容】
2023-06-06  Search: K8s  点击:(292)  评论:(0)  加入收藏
K8s 增强版工作负载 OpenKruise 之运维增强功能
前面我们和大家已经学习了 OpenKruise 的基本概念以及常用的几个增强控制器,接下来我们来继续了解其他高级功能。SidecarSetSidecarSet 支持通过 admission webhook 来自动...【详细内容】
2023-04-06  Search: K8s  点击:(363)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(54)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(86)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(88)  评论:(0)  加入收藏
站内最新
站内热门
站内头条