openkruisev0.10.0新特性workloadspread解读(代码片段)

阿里云云栖号 阿里云云栖号     2023-01-06     724

关键词:

简介: 针对需求,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 的优先级:

  1. 按定义从前往后的顺序,优先级从高到低。
  2. 优先级越高,越先扩容;优先级越低,越先缩容。

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请求时:

  1. 根据 subsetStatuses 顺序依次找 missingReplicas 大于 0 或为 -1 的 suitable subset。
  2. 找到suitable subset后,如果 missingReplicas 大于 0,则先减 1 并尝试更新 workloadspread status。
  3. 如果更新成功,则将该 subset定义的规则注入到 pod 中。
  4. 如果更新失败,则重新 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 控制即可呢?答案是不行,因为:

  1. webhook 收到的 Pod create 请求,最终不一定真的能成功(比如 Pod 不合法,或在后续 quota 等校验环节失败了)。
  2. webhook 收到的 Pod delete/eviction 请求,最终也不一定真的能成功(比如后续被 PDB、PUB 等拦截了)。
  3. K8s 里总有种种的可能性,导致 Pod 没有经过 webhook 就结束或没了(比如 phase 进入 Succeeded/Failed,或是 etcd 数据丢了等等)。
  4. 同时,这也不符合面向终态的设计理念。

因此,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 能力,逻辑上分为两种:

  1. SimulationSchedule:在 Kruise webhook 中根据 informer 里已有的 nodes/pods 数据,组装出调度账本,对 Pod 进行模拟调度。即通过 nodeSelector/affinity、tolerations、以及基本的 resources 资源,做一次简单的过滤。(对于 vk 这种节点不太适用)
  2. 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,帮助用户解决更多云原生部署方面的难题,构建一个更好的社区。

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

openkruisev0.10.0版本发布:新增应用弹性拓扑管理应用防护等能力

简介:阿里云开源的云原生应用自动化管理套件、CNCFSandbox项目--OpenKruise,今天发布v0.10.0新版本,这也会是OpenKruisev1.0之前的最后一个minor版本。本文将带你一览v0.10.0的新变化,其中新增的WorkloadSpread、PodUnavailableB... 查看详情

openkruisev0.10.0版本发布:新增应用弹性拓扑管理应用防护等能力(代码片段)

简介: 阿里云开源的云原生应用自动化管理套件、CNCFSandbox项目--OpenKruise,今天发布v0.10.0新版本,这也会是OpenKruisev1.0之前的最后一个minor版本。本文将带你一览v0.10.0的新变化,其中新增的WorkloadSpread、PodUnavailabl... 查看详情

11g新特性与12c新特性

1.11g新特性概图 管理新特性> 开发新特性>     2.12c新特性概图  查看详情

新特性springcloudfinchley新特性

Finchley正式版的发布貌似经历了相当长的时间,这次的重大发布主要带来了以下4项重大更新。重大更新1、新增SpringCloudGateway组件SpringCloudGateway是一个基于SpringWebflux和响应式Netty的下一代API网关,用来替换SpringCloudNetflixZuul。它提... 查看详情

新特性

新特性:新特性是 相对于jdk1.4 而言的 总共有7个新特性:   三大  四小        三大: 泛型 注解  枚举      四小 查看详情

es6新特性系列

《ES6新特性1:let和const命令》《ES6新特性2:箭头函数》《ES6新特性3:变量的解构赋值》《ES6新特性4:class类》《ES6新特性5:Module模块化》《ES6新特性6:对象的扩展》 查看详情

图说jdk1.8新特性---编译器新特性

/***Returnsthenameoftheparameter.Iftheparameter'snameis*{@linkplain#isNamePresent()present},thenthismethodreturns*thenameprovidedbytheclassfile.Otherwise,thismethod*synthesizesanameoftheformargN,w 查看详情

版本新特性沙盒

model方式不建议采取,新特性控制器不会销毁  查看详情

ios系统版本特性(7版本--14版本)

文章目录iOS14新特性:iOS13新特性:iOS12新特性:iOS11新特性:iOS10新特性:iOS9新特性:iOS8新特性:iOS7新特性:iOS14新特性:1.Widgets(小组件)小组件这个功能其实原来就有的,只不过原来是在负一页面(首页左滑)中显示,在iOS14中,... 查看详情

23.flink-高级特性-新特性-streamingfliesink介绍代码演示flink-高级特性-新特性-flinksql整合hive添加依赖和jar包和配置(代码片段)

23.Flink-高级特性-新特性-StreamingFlieSink23.1.介绍23.2.代码演示24.Flink-高级特性-新特性-FlinkSQL整合Hive24.1.介绍24.2.版本24.3.添加依赖和jar包和配置24.4.FlinkSQL整合Hive-CLI命令行整合24.5.FlinkSQL整合Hive-代码整合23.Flink-高级特性-新特性-Stream... 查看详情

html5新特性

Html5新特性Html5新规则新特性应该基于HTML、CSS、DOM以及JavaScript。减少对外部插件的需求(比如Flash)更优秀的错误处理更多取代脚本的标记HTML5应该独立于设备开发进程应对公众透明新特性用于绘画的canvas元素用于媒介回放的video... 查看详情

3.2c++11新特性

LinuxC/C++服务器C++11新特性介绍C++11关于线程的新特性,并实现一个线程池 查看详情

java8新特性

一、java8新特性简介  查看详情

java8新特性-语言新特性

一、Lambda表达式和函数式接口    它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式... 查看详情

java8新特性-官方库新特性

一、Optional    Java应用中最常见的bug就是空值异常。在Java8之前,GoogleGuava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java8也将Optional加入了官方库。&... 查看详情

jdk1.8新特性——主要内容

目录一、jdk1.8新特性的主要内容二、jdk1.8新特性的优点一、jdk1.8新特性的主要内容Lambda表达式函数式接口方法引用与构造器引用StreamAPI接口中的默认方法与静态方法新时间日期API其他新特性二、jdk1.8新特性的优点速度更快代码更... 查看详情

拥抱变化,面向java17,java8-18全系列特性详解(代码片段)

文章目录:Java8新特性Java9新特性Java10新特性Java11新特性Java12新特性Java13新特性Java14新特性Java15新特性Java16新特性Java17新特性Java18新特性💡文章较长,建议点赞、收藏、评论后慢慢看,合理利用“只看目录功能”... 查看详情

拥抱变化,面向java17,java8-18全系列特性详解(代码片段)

文章目录:Java8新特性Java9新特性Java10新特性Java11新特性Java12新特性Java13新特性Java14新特性Java15新特性Java16新特性Java17新特性Java18新特性💡文章较长,建议点赞、收藏、评论后慢慢看,合理利用“只看目录功能”... 查看详情