关于云原生:OpenKruise-v0100-新特性-WorkloadSpread-解读

42次阅读

共计 7677 个字符,预计需要花费 20 分钟才能阅读完成。

简介:针对需要,OpenKruise 在 v0.10.0 版本中新增了 WorkloadSpread 个性。目前它反对配合 Deployment、ReplicaSet、CloneSet 这些 workload,来治理它们上司 Pod 的分区部署与弹性伸缩。下文会深刻介绍 WorkloadSpread 的利用场景和实现原理,帮忙用户更好的理解该个性。

背景

Workload 散布在不同 zone,不同的硬件类型,甚至是不同的集群和云厂商曾经是一个十分广泛的需要。过来个别只能将一个利用拆分为多个 workload(比方 Deployment)来部署,由 SRE 团队手工治理或者对 PaaS 层深度定制,来反对对一个利用多个 workload 的精细化治理。

进一步来说,在利用部署的场景下有着多种多样的拓扑打散以及弹性的诉求。其中最常见就是按某种或多种拓扑维度打散,比方:

  • 利用部署须要按 node 维度打散,防止重叠(进步容灾能力)。
  • 利用部署须要按 AZ(available zone)维度打散(进步容灾能力)。
  • 按 zone 打散时,须要指定在不同 zone 中部署的比例数。

随着云原生在国内外的迅速遍及落地,利用对于弹性的需要也越来越多。各私有云厂商陆续推出了 Serverless 容器服务来撑持弹性部署场景,如阿里云的弹性容器服务 ECI,AWS 的 Fragate 容器服务等。以 ECI 为例,ECI 能够通过 Virtual Kubelet 对接 Kubernetes 零碎,给予 Pod 肯定的配置就能够调度到 virtual-node 背地的 ECI 集群。总结一些常见的弹性诉求,比方:

  • 利用优先部署到自有集群,资源有余时再部署到弹性集群。缩容时,优先从弹性节点缩容以节省成本。
  • 用户本人布局根底节点池和弹性节点池。利用部署时须要固定数量或比例的 Pod 部署在根底节点池,其余的都扩到弹性节点池。

针对这些需要,OpenKruise 在 v0.10.0 版本中新增了 WorkloadSpread 个性。目前它反对配合 Deployment、ReplicaSet、CloneSet 这些 workload,来治理它们上司 Pod 的分区部署与弹性伸缩。下文会深刻介绍 WorkloadSpread 的利用场景和实现原理,帮忙用户更好的理解该个性。

WorkloadSpread 介绍

官网文档(见文末相干链接一)

简而言之,WorkloadSpread 可能将 workload 所属的 Pod 按肯定规定散布到不同类型的 Node 节点上,可能同时满足上述的打散与弹性场景。

现有计划比照

简略比照一些社区已有的计划。

Pod Topology Spread Constrains(见文末相干链接二)

Pod Topology Spread Constrains 是 Kubernetes 社区提供的计划,能够定义按 topology key 的程度打散。用户在定义完后,调度器会根据配置抉择合乎散布条件的 node。

因为 PodTopologySpread 更多的是平均打散,无奈反对自定义的分区数量以及比例配置,且缩容时会毁坏散布。WorkloadSpread 能够自定义各个分区的数量,并且治理着缩容的程序。因而在一些场景下能够防止 PodTopologySpread 的有余。

UnitedDeployment(见文末相干链接三)

UnitedDeployment 是 Kruise 社区提供的计划,通过创立和治理多个 workload 治理多个区域下的 Pod。

UnitedDeployment 十分好的反对了打散与弹性的需要,不过它是一个全新的 workload,用户的应用和迁徙老本会比拟高。而 WorkloadSpread 是一种轻量化的计划,只须要简略的配置并关联到 workload 即可。

利用场景

上面我会列举一些 WorkloadSpread 的利用场景,给出对应的配置,帮忙大家疾速理解 WorkloadSpread 的能力。

1. 根底节点池至少部署 100 个正本,残余的部署到弹性节点池

subsets:
- name: subset-normal
  maxReplicas: 100
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - normal
- name: subset-elastic #正本数量不限
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - elastic


当 workload 少于 100 正本时,全副部署到 normal 节点池,超过 100 个部署到 elastic 节点池。缩容时会优先删除 elastic 节点上的 Pod。

因为 WorkloadSpread 不侵入 workload,只是限制住了 workload 的散布,咱们还能够通过联合 HPA 依据资源负载动静调整正本数,这样当业务顶峰时会主动调度到 elastic 节点下来,业务低峰时会优先开释 elastic 节点池上的资源。

2. 优先部署到根底节点池,资源有余再部署到弹性资源池

scheduleStrategy:
  type: Adaptive
  adaptive:
    rescheduleCriticalSeconds: 30
    disableSimulationSchedule: false
subsets:
- name: subset-normal #正本数量不限
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - normal
- name: subset-elastic #正本数量不限
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - elastic

两个 subset 都没有正本数量限度,且启用 Adptive 调度策略的模仿调度和 Reschedule 能力。部署成果是优先部署到 normal 节点池,normal 资源有余时,webhook 会通过模仿调度抉择 elastic 节点。当 normal 节点池中的 Pod 处于 pending 状态超过 30s 阈值, WorkloadSpread controller 会删除该 Pod 以触发重建,新的 Pod 会被调度到 elastic 节点池。缩容时还是优先缩容 elastic 节点上的 Pod,为用户节省成本。

3. 打散到 3 个 zone,比例别离为 1:1:3

subsets:
- name: subset-a
  maxReplicas: 20%
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: topology.kubernetes.io/zone
      operator: In
      values:
      - zone-a
- name: subset-b
  maxReplicas: 20%
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: topology.kubernetes.io/zone
      operator: In
      values:
      - zone-b
- name: subset-c
  maxReplicas: 60%
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: topology.kubernetes.io/zone
      operator: In
      values:
      - zone-c

依照不同 zone 的理论状况,将 workload 依照 1:1:3 的比例打散。WorkloadSpread 会确保 workload 扩缩容时依照定义的比例散布。

4. workload 在不同 CPU Arch 上配置不同的资源配额

workload 散布的 Node 可能有不同的硬件配置,CPU 架构等,这就可能须要为不同的 subset 别离制订 Pod 配置。这些配置能够是 label 和 annotation 等元数据也能够是 Pod 外部容器的资源配额,环境变量等。

subsets:
- name: subset-x86-arch
  # maxReplicas...
  # requiredNodeSelectorTerm...
  patch:
    metadata:
      labels:
        resource.cpu/arch: x86
    spec: 
      containers:
      - name: main
        resources:
          limits:
            cpu: "500m"
            memory: "800Mi"
- name: subset-arm-arch
  # maxReplicas...
  # requiredNodeSelectorTerm...
  patch:
    metadata:
      labels:
        resource.cpu/arch: arm
    spec: 
      containers:
      - name: main
        resources:
          limits:
            cpu: "300m"
            memory: "600Mi"

从下面的样例中咱们为两个 subset 的 Pod 别离 patch 了不同的 label, container resources,不便咱们对 Pod 做更精细化的治理。当 workload 的 Pod 散布在不同的 CPU 架构的节点上,配置不同的资源配额以更好的利用硬件资源。

实现原理

WorkloadSpread 是一个纯旁路的弹性 / 拓扑管控计划。用户只须要针对本人的 Deployment/CloneSet/Job 对象创立对应的 WorkloadSpread 即可,无需对 workload 做改变,也不会对用户应用 workload 造成额定老本。

1. subset 优先级与正本数量管制

WorkloadSpread 中定义了多个 subset,每个 subset 代表一个逻辑域。用户能够自在的依据节点配置,硬件类型,zone 等来划分 subset。特地的,咱们规定了 subset 的优先级:

  • 按定义从前往后的程序,优先级从高到低。
  • 优先级越高,越先扩容;优先级越低,越先缩容。

2. 如何管制缩容优先级

实践上,WorkloadSpread 这种旁路计划是无奈干预到 workload 控制器里的缩容程序逻辑的。

不过,这个问题在近期得以解决—— 通过一代代用户的不懈努力(反馈),K8s 从 1.21 版本开始为 ReplicaSet(Deployment)反对了通过设置 controller.kubernetes.io/pod-deletion-cost 这个 annotation 来指定 Pod 的“删除代价”:deletion-cost 越高的 Pod,删除的优先级越低。

而 Kruise 从 v0.9.0 版本开始,就在 CloneSet 中反对了 deletion-cost 个性。

因而,WorkloadSpread controller 通过调整各个 subset 上司 Pod 的 deletion-cost,来管制 workload 的缩容程序。

举个例子:对于以下 WorkloadSpread,以及它关联的 CloneSet 有 10 个正本:

  subsets:
  - name: subset-a
    maxReplicas: 8
  - name: subset-b # 正本数量不限

则 deletion-cost 数值以及删除程序为:

  • 2 个在 subset- b 上的 Pod,deletion-cost 为 100(优先缩容)
  • 8 个在 subset- a 上的 Pod,deletion-cost 为 200(最初缩容)

而后,如果用户批改了 WorkloadSpread 为:

  subsets:
  - name: subset-a
    maxReplicas: 5 # 8-3, 
  - name: subset-b

则 workloadspread controller 会将其中 3 个在 susbet-a 上 Pod 的 deletion-cost 值由 200 改为 -100

  • 3 个在 subset-a 上的 Pod,deletion-cost 为 -100(优先缩容)
  • 2 个在 subset-b 上的 Pod,deletion-cost 为 100(其次缩容)
  • 5 个在 subset-a 上的 Pod,deletion-cost 为 200(最初缩容)

这样就可能优先缩容那些超过 subset 正本限度的 Pod 了,当然总体还是依照 subset 定义的程序从后向前缩容。

3. 数量管制

如何确保 webhook 严格依照 subset 优先级程序、maxReplicas 数量来注入 Pod 规定是 WorkloadSpread 实现层面的重点难题。

3.1 解决并发一致性问题

在 workloadspread 的 status 中有对应每个 subset 的 status,其中 missingReplicas 字段示意了这个 subset 须要的 Pod 数量,-1 示意没有数量限度(subset 没有配置 maxReplicas)。

spec:
  subsets:
  - name: subset-a
    maxReplicas: 1
  - name: subset-b
  # ...
status:
  subsetStatuses:
  - name: subset-a
    missingReplicas: 1
  - name: subset-b
    missingReplicas: -1
  # ...

当 webhook 收到 Pod create 申请时:

  • 依据 subsetStatuses 程序顺次找 missingReplicas 大于 0 或为 -1 的 suitable subset。
  • 找到 suitable subset 后,如果 missingReplicas 大于 0,则先减 1 并尝试更新 workloadspread status。
  • 如果更新胜利,则将该 subset 定义的规定注入到 pod 中。
  • 如果更新失败,则从新 get 这个 workloadspread 以获取最新的 status,并回到步骤 1(有肯定重试次数限度)。

同样,当 webhook 收到 Pod delete/eviction 申请时,则将 missingReplicas 加 1 并更新。

毫无疑问,咱们在应用乐观锁来解决更新抵触。然而仅应用乐观锁是不适合的,因为 workload 在创立 Pod 时会并行创立大量的 Pod,apiserver 会在一瞬间发送很多 Pod create 申请到 webhook,并行处理会产生十分多的抵触。大家都晓得,抵触太多就不适宜应用乐观锁了,因为它解决抵触的重试老本十分高。为此咱们还退出了 workloadspread 级别的互斥锁,将并行处理限度为串行解决。退出互斥锁还有新的问题,即以后 groutine 获取锁后,极有可能从 infromer 中拿的 workloadspread 不是最新的,还是会抵触。所以 groutine 在更新完 workloadspread 之后,先将最新的 workloadspread 对象缓存起来再开释锁,这样新的 groutine 获取锁后就能够间接从缓存中拿到最新的 workloadspread。当然,多个 webhook 的状况下还是须要联合乐观锁机制来解决抵触。

3.2 解决数据一致性问题

那么,missingReplicas 数值是否交由 webhook 管制即可呢?答案是不行,因为:

  • webhook 收到的 Pod create 申请,最终不肯定真的能胜利(比方 Pod 不非法,或在后续 quota 等校验环节失败了)。
  • webhook 收到的 Pod delete/eviction 申请,最终也不肯定真的能胜利(比方后续被 PDB、PUB 等拦挡了)。
  • K8s 里总有种种的可能性,导致 Pod 没有通过 webhook 就完结或没了(比方 phase 进入 Succeeded/Failed,或是 etcd 数据丢了等等)。
  • 同时,这也不合乎面向终态的设计理念。

因而,workloadspread status 是由 webhook 与 controller 合作来管制的:

  • webhook 在 Pod create/delete/eviction 申请链路拦挡,批改 missingReplicas 数值。
  • 同时 controller 的 reconcile 中也会拿到以后 workload 下的所有 Pod,依据 subset 分类,并将 missingReplicas 更新为以后理论短少的数量。
  • 从下面的剖析中,controller 从 informer 中获取 Pod 很可能存在延时,所以咱们还在 status 中减少了 creatingPods map, webook 注入的时候会记录 key 为 pod.name, value 为工夫戳的一条 entry 到 map,controller 再联合 map 保护实在的 missingReplicas。同理还有一个 deletingPods map 来记录 Pod 的 delete/eviction 事件。

4. 自适应调度能力

在 WorkloadSpread 中反对配置 scheduleStrategy。默认状况下,type 为 Fixed,即固定依照各个 subset 的前后程序、maxReplicas 限度来将 Pod 调度到对应的 subset 中。

但实在的场景下,很多时候 subset 分区或拓扑的资源,不肯定能齐全满足 maxReplicas 数量。用户须要依照理论的资源状况,来为 Pod 抉择有资源的分区扩容。这就须要用 Adaptive 这种自适应的调度调配。

WorkloadSpread 提供的 Adaptive 能力,逻辑上分为两种:

  • SimulationSchedule:在 Kruise webhook 中依据 informer 里已有的 nodes/pods 数据,组装出调度账本,对 Pod 进行模仿调度。即通过 nodeSelector/affinity、tolerations、以及根本的 resources 资源,做一次简略的过滤。(对于 vk 这种节点不太实用)
  • Reschedule:在将 Pod 调度到一个 subset 后,如果调度失败超过 rescheduleCriticalSeconds 工夫,则将该 subset 临时标记为 unschedulable,并删除 Pod 触发重建。默认状况下,unschedulable 会保留 5min,即在 5min 内的 Pod 创立会跳过这个 subset。

小结

WorkloadSpread 通过联合一些 kubernetes 现有的个性以一种旁路的模式赋予 workload 弹性部署与多域部署的能力。咱们心愿用户通过应用 WorkloadSpread 升高 workload 部署复杂度,利用其弹性伸缩能力切实降低成本。

目前阿里云外部正在踊跃的落地,落地过程中的调整会及时反馈社区。将来 WorkloadSpread 还有一些新能力打算,比方让 WorkloadSpread 反对 workload 的存量 Pod 接管,反对批量的 workload 束缚,甚至是跨过 workload 层级应用 label 来匹配 Pod。其中一些能力须要理论考量社区用户的需要场景。心愿大家多多参加到 Kruise 社区,多提 issue 和 pr,帮忙用户解决更多云原生部署方面的难题,构建一个更好的社区。

Github:https://github.com/openkruise…
Official:https://openkruise.io/
Slack: Channel in Kubernetes Slack

原文链接
本文为阿里云原创内容,未经容许不得转载。

正文完
 0