首页 > 文章列表 > K8s 增强版工作负载 OpenKruise 之运维增强功能

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

容器
299 2023-06-09

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

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

SidecarSet

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

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

注入热升级容器热升级流程热升级流程主要分为三个步骤:Upgrade: 将 empty 容器升级为当前最新的 sidecar 容器,例如:envoy-2.Image = envoy:1.17.0Migration​: lifecycle.postStart 完成热升级流程中的状态迁移,当迁移完成后退出Reset: 状态迁移完成后,热升级流程将设置 envoy-1 容器为 empty 镜像,例如:envoy-1.Image = empty:1.0上述三个步骤完成了热升级中的全部流程,当对 Pod 执行多次热升级时,将重复性的执行上述三个步骤。热升级流程这里我们以 OpenKruise 的官方示例来进行说明,首先创建上面的 hotupgrade-sidecarset 这个 SidecarSet。然后创建一个如下所示的 CloneSet 对象:Container RestartContainerRecreateRequest​ 控制器可以帮助用户重启/重建存量 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 已经变大,则认为容器已经被重建成功了(比如可能发生了一次原地升级)。

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

容器重启请求

一般情况下,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 对象:

比如创建如下所示的 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 信息。

容器启动顺序Container Launch Priority 提供了控制一个 Pod 中容器启动顺序的方法。通常来说 Pod 容器的启动和退出顺序是由 Kubelet 管理的,Kubernetes 曾经有一个 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 这个环境变量:

资源分发在对 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 字段必须是一个完整、正确的资源描述,如下所示:

用户可以先在本地某个命名空间中创建相应资源并进行测试,确认资源配置正确后再拷贝过来。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 了解更多信息。