K8s:通过 Pod 干扰预算(PDB)提高节点故障、维护期间 Pod 频繁调度时工作负载的可用性
写在前面
-
分享一些 Pod Disruption Budgets(PDB)
的笔记 -
博文内容涉及: -
为什么需要 PDB
,什么是PDB
? -
PDB
可以做什么?配置创建PDB
Demo -
PDB
原理简单说明
-
-
理解不足小伙伴帮忙指正
不念过去,不畏将来,不负余生
为什么需要 PDB
对于对高可用
要求很高的一些容器化应用,例如一些有状态
的工作负载,比如数据库,分布式协调服务等, K8s 集群中 Pod 频繁的调度是不能容忍的一件事。 尤其涉及到应用集群数据同步,共识,心跳
等诸多因素. 容易造成可用性降低,数据延迟甚至潜在的数据丢失。
集群中的 Pod 正常情况下不会频繁的调度,即使存在大量的超售超用,也可以通过 Qos 等手段在准入的时候控制。当然,除非有人操作,或者节点故障等一些因素的干扰。
在 k8s 中,我们把这些干扰分为两类,自愿干扰和非自愿干扰
:
非自愿干扰(Involuntary Disruptions)
的情况常见下面一些场景:
-
节点下层物理机的硬件故障 -
集群管理员错误地删除虚拟机(实例) -
云提供商或虚拟机管理程序中的故障导致的虚拟机消失 -
内核错误 -
节点由于集群网络隔离从集群中消失 -
由于节点资源不足导致 pod 被驱逐。
自愿干扰(Voluntary Disruptions)
的情况常见下面一些场景:
-
排空(drain)节点进行修复或升级。 -
从集群中排空节点以缩小集群。 -
从节点中移除一个 Pod,以允许其他 Pod 使用该节点。 -
修改了工作负载调度规则(策略)
对于非自愿干扰
,常常是不可避免的,能做的只能是减轻, k8s 官方提供的一些非自愿干扰的方法:
-
确保 Pod 在请求中给出所需资源。 -
如果需要更高的可用性,考虑增加合适的副本 -
为了在运行复制应用时获得更高的可用性,请 跨机架
(使用 反亲和性) 或跨区域
(如果使用多区域集群)扩展应用,考虑拓扑分布。
对于 自愿干扰
来讲,可能由集群管理员直接执行,也可能由集群管理员所使用的自动化工具执行,或者由集群托管云厂商 自动执行。 k8s 提供了Pod干扰预算(PDB)
来 解决这些问题,支持运行高度可用的应用。
PDB 是什么?
用最简单的话描述,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 已经稳定,并且与未来版本兼容
PDB 可以做什么?
可以为每个工作负载(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%。
单实例有状态应用
:
-
关注:不要在不通知的情况下终止该应用。解决方案 -
不使用 PDB,并忍受偶尔的停机。 -
设置 maxUnavailable=0
的 PDB。 意为(Kubernetes 范畴之外的)集群操作人员需要在终止应用前与用户协商, 协商后准备停机,然后删除 PDB 表示准备接受干扰,后续再重新创建。
-
多实例有状态应用
,如 Consul、ZooKeeper 或 etcd:
-
关注:不要将实例数量减少至低于 仲裁规模
,否则将出现写入失败。解决方案-
设置 maxUnavailable
值为 1 (适用于不同规模的应用)。 -
设置 minAvailable
值为仲裁规模(例如规模为 5 时设置为 3)。 (允许同时出现更多的干扰)。
-
可重新启动的批处理任务:
-
关注:自发干扰的情况下,需要确保任务完成。解决方案:不创建 PDB。 任务控制器会创建一个替换 Pod。
配置创建 PDB
用 PodDisruptionBudget 来保护应用的一般步骤:
-
确定想要使用 PodDisruptionBudget (PDB) 来保护的应用。 -
考虑应用对干扰的反应。 -
以 YAML 文件形式定义 PDB。 -
通过 YAML 文件创建 PDB 对象
想要保护通过内置的 Kubernetes 控制器指定的应用,这是最常见的使用场景,支持下面一些控制器
-
Deployment -
ReplicationController -
ReplicaSet -
StatefulSet
minAvailable
或 maxUnavailable
的值可以表示为整数或百分比。需要考虑指定百分比时的舍入逻辑:
-
指定整数值时,它表示 Pod 个数。 -
设置为百分比的字符串表示形式(例如 "50%")来指定百分比时,它表示占总 Pod 数的百分比。
如果将值指定为百分比,则可能无法映射到确切数量的 Pod。 Kubernetes 采用向上取整
到最接近的整数的办法。
一个 PodDisruptionBudget 有 3 个字段:
-
.spec.selector
用于指定其所作用的 Pod 集合,该字段为必需字段。 -
.spec.minAvailable
表示驱逐后仍须保证可用的 Pod 数量。即使因此影响到 Pod 驱逐 (即该条件在和 Pod 驱逐发生冲突时优先保证)。 -
.spec.maxUnavailable
(Kubernetes 1.7 及更高的版本中可用)表示驱逐后允许不可用的 Pod 的最大数量。其值可以是绝对值或是百分比。
注意事项:
-
用户在同一个 PodDisruptionBudget
中只能够指定maxUnavailable
和minAvailable
中的一个。 -
maxUnavailable
只能够用于控制存在相应控制器
的 Pod 的驱逐(即不受控制器控制的 Pod 不在maxUnavailable
控制范围内)。 -
如果将 maxUnavailable
的值设置为 0%(或 0)或设置 minAvailable 值为 100%(或等于副本数) 则会阻止所有的自愿驱逐。将无法成功地腾空(drain )运行其中一个 Pod 的节点.
在下面的示例中, “所需副本” 指的是相应控制器的 scale
,控制器对 PodDisruptionBudget 所选择的 Pod 进行管理。
示例 1:设置 minAvailable 值为 5 的情况下,驱逐时需保证 PodDisruptionBudget 的 selector 选中的 Pod 中 5 个或 5 个以上处于健康状态。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 5
selector:
matchLabels:
app: zookeeper
示例 2:设置 maxUnavailable 值为 30% 的情况下,只要不健康的副本数量不超过所需副本总数的 30% (取整到最接近的整数),就允许驱逐。如果所需副本的总数仅为一个
,则仍允许该单个副本中断, 从而导致不可用性实际达到 100%。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
maxUnavailable: 30%
selector:
matchLabels:
app: zookeeper
一个 Demo
┌──[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
┌──[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
┌──[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>
┌──[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 对象是否已被更新。如果观察到的世代与实际世代不同,则会标记为过时状态。
原理
关于 Pod 干扰预算(PDB) 和小伙伴们分享到这里,这里只是简单介绍了 PDB,对于原理没有太多介绍。
k8s 官网有一个有趣的例子,篇幅问题没有整理,感兴趣可以去看看,大概意思说, 通过 PDB 限制,k8s 可能会在某些时间进入阻塞状态,延迟对一些 API 调用(命令)的响应,等到符合 PDB 限制,在做成响应,优雅的处理自愿干扰对 Pod 副本的影响,重而改变干扰发生的速率,Pod 调度的影响。
关于 K8s 如何改变干扰发生的速率,除了 PDB ,还要考虑下面一些因素:
-
应用需要多少个副本 -
优雅关闭应用实例需要多长时间 -
启动应用新实例需要多长时间 -
控制器的类型 -
集群的资源能力
值得一说的是 ,干扰预算并不能真正保证指定数量/百分比的 Pod 一直处于运行状态,
预算只能够针对自发的驱逐提供保护
,而不能针对所有 Pod不可用的诱因
。
例如:当 Pod 集合的规模处于预算指定的最小值时,承载集合中某个 Pod 的节点发生了故障,这样就导致集合中可用 Pod 的数量低于预算指定值。
生活加油哈 ^_^
网易云看到一句话,蛮喜欢... 我曾以为总有一天,我能够改变一些什么,可当我看见她渐渐淹没在人海的时候,我才明白,我这一生除了相遇大概注定一事无成了。
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知,这是一个开源项目,如果你认可它,不要吝啬星星哦 :)
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
© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
- 点赞
- 收藏
- 关注作者
评论(0)