K8s:通过 Pod 干扰预算(PDB)提高节点故障、维护期间 Pod 频繁调度时工作负载的可用性

2023-08-21 14:17:59 浏览数 (2)

1写在前面


  • 分享一些 Pod Disruption Budgets(PDB) 的笔记
  • 博文内容涉及:
    • 为什么需要 PDB,什么是 PDB
    • PDB 可以做什么?配置创建 PDB Demo
    • PDB 原理简单说明
  • 理解不足小伙伴帮忙指正

不念过去,不畏将来,不负余生

2为什么需要 PDB

对于对高可用要求很高的一些容器化应用,例如一些有状态的工作负载,比如数据库,分布式协调服务等, K8s 集群中 Pod 频繁的调度是不能容忍的一件事。尤其涉及到应用集群数据同步,共识,心跳等诸多因素. 容易造成可用性降低,数据延迟甚至潜在的数据丢失。

集群中的 Pod 正常情况下不会频繁的调度,即使存在大量的超售超用,也可以通过 Qos 等手段在准入的时候控制。当然,除非有人操作,或者节点故障等一些因素的干扰。

在 k8s 中,我们把这些干扰分为两类,自愿干扰和非自愿干扰:

非自愿干扰(Involuntary Disruptions) 的情况常见下面一些场景:

  • 节点下层物理机的硬件故障
  • 集群管理员错误地删除虚拟机(实例)
  • 云提供商或虚拟机管理程序中的故障导致的虚拟机消失
  • 内核错误
  • 节点由于集群网络隔离从集群中消失
  • 由于节点资源不足导致 pod 被驱逐。

自愿干扰(Voluntary Disruptions)的情况常见下面一些场景:

  • 排空(drain)节点进行修复或升级。
  • 从集群中排空节点以缩小集群。
  • 从节点中移除一个 Pod,以允许其他 Pod 使用该节点。
  • 修改了工作负载调度规则(策略)

对于非自愿干扰,常常是不可避免的,能做的只能是减轻, k8s 官方提供的一些非自愿干扰的方法:

  • 确保 Pod 在请求中给出所需资源。
  • 如果需要更高的可用性,考虑增加合适的副本
  • 为了在运行复制应用时获得更高的可用性,请跨机架(使用 反亲和性) 或跨区域(如果使用多区域集群)扩展应用,考虑拓扑分布。

对于 自愿干扰 来讲,可能由集群管理员直接执行,也可能由集群管理员所使用的自动化工具执行,或者由集群托管云厂商 自动执行。k8s 提供了Pod干扰预算(PDB)来 解决这些问题,支持运行高度可用的应用。

3PDB 是什么?

用最简单的话描述,Pod Disruption Budgets(PDB)是 K8s 中的一项功能,可以确保在进行维护、升级或扩展集群等自愿操作时,不会影响应用程序的稳定性,从而提高可用性。

PDB 是确保 K8s 环境中高可用性的强大功能,强烈建议在生产环境中使用。

简单介绍一下 PDB 的发展历程

  • Pod Disruption Budgets 首次引入在2016年 Kubernetes v1.4 版本中,开始作为Alpha版本提供
  • 2017 年 Kubernetes v1.6 版本中被标记为Beta版本,使其更易于使用
  • 到了 Kubernetes v1.8,PDB 增加了更多的功能,包括针对故障域的限制和管理多个 Pod 组合的能力。
  • 在 Kubernetes v1.14 中,引入了 TopologySpreadConstraints API(拓扑分布约束),以更精确地控制 Pod 的分布,并进一步完善了 PDB 的功能。
  • 经过长时间的测试和验证后,在 Kubernetes v1.21 版本中,PDB 被标记为stable版本。这意味着其 API 已经稳定,并且与未来版本兼容

4PDB 可以做什么?

可以为每个工作负载(deployment或者statefulSet)创建一个 PodDisruptionBudget(PDB)

PDB 将限制在同一时间因自愿干扰导致的多副本应用中发生宕机的 Pod 数量

例如:

  • 基于选举投票机制的应用集群希望确保运行中的副本数永远不会低于票选所需的数量。
  • Web 前端可能希望确保提供负载的副本数量永远不会低于总数的某个百分比。

PDB 可以指定工作负载可以容忍的副本数量(相当于应该有多少副本)。

例如,具有 .spec.replicas: 5 的 Deployment 在任何时间都应该有 5 个 Pod。如果 PDB 允许其在某一时刻有 4 个副本,那么驱逐 API 将允许同一时刻仅有一个(而不是两个)Pod 自愿干扰。这里和 滚动升级机制 的优先级需要考虑一下。

由于应用的滚动升级而被删除或不可用的 Pod 确实会计入干扰预算, 但是工作负载资源(如 Deployment 和 StatefulSet) 在进行滚动升级时不受 PDB 的限制。应用更新期间的故障处理方式是在对应的工作负载资源的 spec 中配置的。

一些自愿干扰场景中使用PDB分析

确定在自发干扰时,多少实例可以在短时间内同时关闭。其中 minAvailable 表示最小活跃 pod。maxUnavailable 表示最大不活跃 Pod ,可以表示为整数或百分比

无状态的前端

  • 关注:不能降低服务能力 10% 以上。解决方案:例如,使用 PDB,指定其 minAvailable 值为 90%。

单实例有状态应用

  • 关注:不要在不通知的情况下终止该应用。解决方案
    1. 不使用 PDB,并忍受偶尔的停机。
    2. 设置 maxUnavailable=0 的 PDB。意为(Kubernetes 范畴之外的)集群操作人员需要在终止应用前与用户协商, 协商后准备停机,然后删除 PDB 表示准备接受干扰,后续再重新创建。

多实例有状态应用,如 Consul、ZooKeeper 或 etcd:

  • 关注:不要将实例数量减少至低于仲裁规模,否则将出现写入失败。解决方案
    1. 设置 maxUnavailable 值为 1 (适用于不同规模的应用)。
    2. 设置 minAvailable 值为仲裁规模(例如规模为 5 时设置为 3)。(允许同时出现更多的干扰)。

可重新启动的批处理任务:

  • 关注:自发干扰的情况下,需要确保任务完成。解决方案:不创建 PDB。任务控制器会创建一个替换 Pod。

5配置创建 PDB

用 PodDisruptionBudget 来保护应用的一般步骤:

  1. 确定想要使用 PodDisruptionBudget (PDB) 来保护的应用。
  2. 考虑应用对干扰的反应。
  3. 以 YAML 文件形式定义 PDB。
  4. 通过 YAML 文件创建 PDB 对象

想要保护通过内置的 Kubernetes 控制器指定的应用,这是最常见的使用场景,支持下面一些控制器

  • Deployment
  • ReplicationController
  • ReplicaSet
  • StatefulSet

minAvailablemaxUnavailable 的值可以表示为整数或百分比。需要考虑指定百分比时的舍入逻辑:

  • 指定整数值时,它表示 Pod 个数。
  • 设置为百分比的字符串表示形式(例如 "50%")来指定百分比时,它表示占总 Pod 数的百分比。

如果将值指定为百分比,则可能无法映射到确切数量的 Pod。Kubernetes 采用向上取整到最接近的整数的办法。

一个 PodDisruptionBudget 有 3 个字段:

  • .spec.selector 用于指定其所作用的 Pod 集合,该字段为必需字段。
  • .spec.minAvailable 表示驱逐后仍须保证可用的 Pod 数量。即使因此影响到 Pod 驱逐 (即该条件在和 Pod 驱逐发生冲突时优先保证)。
  • .spec.maxUnavailable (Kubernetes 1.7 及更高的版本中可用)表示驱逐后允许不可用的 Pod 的最大数量。其值可以是绝对值或是百分比。

注意事项:

  1. 用户在同一个 PodDisruptionBudget 中只能够指定 maxUnavailableminAvailable 中的一个。
  2. maxUnavailable 只能够用于控制存在相应控制器的 Pod 的驱逐(即不受控制器控制的 Pod 不在 maxUnavailable 控制范围内)。
  3. 如果将 maxUnavailable 的值设置为 0%(或 0)或设置 minAvailable 值为 100%(或等于副本数) 则会阻止所有的自愿驱逐。将无法成功地腾空(drain )运行其中一个 Pod 的节点.

在下面的示例中, “所需副本” 指的是相应控制器的 scale,控制器对 PodDisruptionBudget 所选择的 Pod 进行管理。

示例 1:设置 minAvailable 值为 5 的情况下,驱逐时需保证 PodDisruptionBudget 的 selector 选中的 Pod 中 5 个或 5 个以上处于健康状态。

代码语言:javascript复制
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 5
  selector:
    matchLabels:
      app: zookeeper

示例 2:设置 maxUnavailable 值为 30% 的情况下,只要不健康的副本数量不超过所需副本总数的 30% (取整到最接近的整数),就允许驱逐。如果所需副本的总数仅为一个,则仍允许该单个副本中断, 从而导致不可用性实际达到 100%。

代码语言:javascript复制
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  maxUnavailable: 30%
  selector:
    matchLabels:
      app: zookeeper

一个 Demo

代码语言:javascript复制
┌──[root@vms100.liruilongs.github.io]-[~]
└─$cat reviews-pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: reviews-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: reviews
代码语言:javascript复制
┌──[root@vms100.liruilongs.github.io]-[~]
└─$kubectl apply  -f reviews-pdb.yaml
poddisruptionbudget.policy/reviews-pdb created
┌──[root@vms100.liruilongs.github.io]-[~]
└─$kubectl get poddisruptionbudgets.policy
NAME          MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
reviews-pdb   N/A             1                 1                     9s
zk-pdb        N/A             1                 0                     5m37s
代码语言:javascript复制
┌──[root@vms100.liruilongs.github.io]-[~]
└─$kubectl describe  poddisruptionbudgets.policy reviews-pdb
Name:             reviews-pdb
Namespace:        default
Max unavailable:  1
Selector:         app=reviews
Status:
    Allowed disruptions:  1
    Current:              3
    Desired:              2
    Total:                3
Events:                   <none>
代码语言:javascript复制
┌──[root@vms100.liruilongs.github.io]-[~]
└─$kubectl get poddisruptionbudgets.policy  reviews-pdb  -o yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"policy/v1","kind":"PodDisruptionBudget","metadata":{"annotations":{},"name":"reviews-pdb","namespace":"default"},"spec":{"maxUnavailable":1,"selector":{"matchLabels":{"app":"reviews"}}}}
  creationTimestamp: "2023-05-06T17:29:29Z"
  generation: 1
  name: reviews-pdb
  namespace: default
  resourceVersion: "12068742"
  uid: 7d0d15b4-0884-433f-af6b-44cbe9036ede
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: reviews
status:
  conditions:
  - lastTransitionTime: "2023-05-06T17:29:29Z"
    message: ""
    observedGeneration: 1
    reason: SufficientPods
    status: "True"
    type: DisruptionAllowed
  currentHealthy: 3
  desiredHealthy: 2
  disruptionsAllowed: 1
  expectedPods: 3
  observedGeneration: 1

部分字段解释:

  • maxUnavailable: 指定在维护期间可以离线的 Pod 最大数量。
  • selector: 定义受 PodDisruptionBudget 约束的 Pod 集合。
  • status: 包含当前 PodDisruptionBudget 的状态信息。
  • conditions: 描述当前是否允许进行 Pod 离线的状态(例如,在维护期间)。
  • currentHealthy: 当前正在运行的与选择器匹配的健康 Pod 数量。
  • desiredHealthy: PodDisruptionBudget 想要保持的最小健康 Pod 数量。
  • disruptionsAllowed: 在当前时间段内允许的 Pod 离线次数。
  • expectedPods: 根据选择器计算出来的应该存在的 Pod 数量。
  • observedGeneration: 用于检测 PodDisruptionBudget 对象是否已被更新。如果观察到的世代与实际世代不同,则会标记为过时状态。

6原理

关于 Pod 干扰预算(PDB) 和小伙伴们分享到这里,这里只是简单介绍了 PDB,对于原理没有太多介绍。

k8s 官网有一个有趣的例子,篇幅问题没有整理,感兴趣可以去看看,大概意思说, 通过 PDB 限制,k8s 可能会在某些时间进入阻塞状态,延迟对一些 API 调用(命令)的响应,等到符合 PDB 限制,在做成响应,优雅的处理自愿干扰对 Pod 副本的影响,重而改变干扰发生的速率,Pod 调度的影响。

关于 K8s 如何改变干扰发生的速率,除了 PDB ,还要考虑下面一些因素:

  • 应用需要多少个副本
  • 优雅关闭应用实例需要多长时间
  • 启动应用新实例需要多长时间
  • 控制器的类型
  • 集群的资源能力

值得一说的是 ,干扰预算并不能真正保证指定数量/百分比的 Pod 一直处于运行状态,预算只能够针对自发的驱逐提供保护,而不能针对所有 Pod 不可用的诱因

例如:当 Pod 集合的规模处于预算指定的最小值时,承载集合中某个 Pod 的节点发生了故障,这样就导致集合中可用 Pod 的数量低于预算指定值。

生活加油哈 ^_^ 网易云看到一句话,蛮喜欢... 我曾以为总有一天,我能够改变一些什么,可当我看见她渐渐淹没在人海的时候,我才明白,我这一生除了相遇大概注定一事无成了。

7博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知,这是一个开源项目,如果你认可它,不要吝啬星星哦 :)


https://kubernetes.io/zh-cn/docs/tasks/run-application/configure-pdb/

https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/disruptions/

https://medium.com/geekculture/kubernetes-pod-disruption-budgets-pdb-b74f3dade6c1

0 人点赞