# Pod中断与PDB(Pod中断预算)
这篇文档适用于要构建高可用应用程序的所有者,因此他们需要了解 Pod 可能发生什么类型的中断。也适用于要执行自动集群操作的集群管理员,如升级和集群自动扩容。
## 自愿中断和非自愿中断
Pod 不会消失,直到有人(人类或控制器)将其销毁,或者当出现不可避免的硬件或系统软件错误。
我们把这些不可避免的情况称为应用的非自愿性中断。例如:
- 后端节点物理机的硬件故障
- 集群管理员错误地删除虚拟机(实例)
- 云提供商或管理程序故障使虚拟机消失
- 内核恐慌(kernel panic)
- 节点由于集群网络分区而从集群中消失
- 由于节点[资源不足](https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource)而将容器逐出
除资源不足的情况外,大多数用户应该都熟悉以下这些情况;它们不是特定于 Kubernetes 的。
我们称这些情况为”自愿中断“。包括由应用程序所有者发起的操作和由集群管理员发起的操作。典型的应用程序所有者操作包括:
- 删除管理该 pod 的 Deployment 或其他控制器
- 更新了 Deployment 的 pod 模板导致 pod 重启
- 直接删除 pod(意外删除)
集群管理员操作包括:
- [排空(drain)节点](https://kubernetes.io/docs//tasks/administer-cluster/safely-drain-node)进行修复或升级。
- 从集群中排空节点以缩小集群(了解[集群自动调节](https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/#cluster-autoscaler))。
- 从节点中移除一个 pod,以允许其他 pod 使用该节点。
这些操作可能由集群管理员直接执行,也可能由集群管理员或集群托管提供商自动执行。
询问您的集群管理员或咨询您的云提供商或发行文档,以确定是否为您的集群启用了任何自动中断源。如果没有启用,您可以跳过创建 Pod Disruption Budget(Pod 中断预算)。
## 处理中断
以下是一些减轻非自愿性中断的方法:
- 确保您的 pod [请求所需的资源](https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-ram-container)。
- 如果您需要更高的可用性,请复制您的应用程序。 (了解有关运行复制的[无状态](https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment)和[有状态](https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application)应用程序的信息。)
- 为了在运行复制应用程序时获得更高的可用性,请跨机架(使用[反亲和性](https://kubernetes.io/docs/user-guide/node-selection/#inter-pod-affinity-and-anti-affinity-beta-feature))或跨区域(如果使用多区域集群)分布应用程序。
自愿中断的频率各不相同。在 Kubernetes 集群上,根本没有自愿的中断。但是,您的集群管理员或托管提供商可能会运行一些导致自愿中断的附加服务。例如,节点软件更新可能导致自愿更新。另外,集群(节点)自动缩放的某些实现可能会导致碎片整理和紧缩节点的自愿中断。您的集群管理员或主机提供商应该已经记录了期望的自愿中断级别(如果有的话)。
Kubernetes 提供的功能可以满足在频繁地自动中断的同时运行高可用的应用程序。我们称之为“中断预算”。
## 中断预算的工作原理
应用程序所有者可以为每个应用程序创建一个 `PodDisruptionBudget` 对象(PDB)。 PDB 将限制在同一时间自愿中断的复制应用程序中宕机的 Pod 的数量。例如,基于定额的应用程序希望确保运行的副本数量永远不会低于仲裁所需的数量。Web 前端可能希望确保提供负载的副本的数量永远不会低于总数的某个百分比。
集群管理器和托管提供商应使用遵循 `Pod Disruption Budgets` 的工具,方法是调用[Eviction API](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/#the-eviction-api)而不是直接删除 Pod。例如 `kubectl drain` 命令和 Kubernetes-on-GCE 集群升级脚本(`cluster/gce/upgrade.sh`)。
当集群管理员想要排空节点时,可以使用 `kubectl drain` 命令。该命令会试图驱逐机器上的所有 pod。驱逐请求可能会暂时被拒绝,并且该工具会定期重试所有失败的请求,直到所有的 pod 都被终止,或者直到达到配置的超时时间。
PDB 指定应用程序可以容忍的副本的数量,相对于应该有多少副本。例如,具有 `spec.replicas:5` 的 Deployment 在任何给定的时间都应该有 5 个 Pod。如果其 PDB 允许在某一时刻有 4 个副本,那么驱逐 API 将只允许仅有一个而不是两个 Pod 自愿中断。
使用标签选择器来指定应用程序的一组 pod,这与应用程序的控制器(Deployment、StatefulSet 等)使用的相同。
Pod 控制器的 `.spec.replicas` 计算“预期的” pod 数量。使用对象的 `.metadata.ownerReferences` 值从控制器获取。
PDB 不能阻止[非自愿中断](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/#voluntary-and-involuntary-disruptions)的发生,但是它们确实会影响预算。
由于应用程序的滚动升级而被删除或不可用的 Pod 确实会计入中断预算,但控制器(如 Deployment 和 StatefulSet)在进行滚动升级时不受 PDB 的限制——在应用程序更新期间的故障处理是在控制器的规格(spec)中配置(了解[更新 Deployment](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#updating-your-application-without-a-service-outage))。
使用驱逐 API 驱逐 pod 时,pod 会被优雅地终止(请参阅 PodSpec 中的 `terminationGracePeriodSeconds`)。
## PDB 示例
假设集群有3个节点,`node-1` 到 `node-3`。集群中运行了一些应用,其中一个应用有3个副本,分别是 `pod-a`、`pod-b` 和 `pod-c`。另外,还有一个与它相关的不具有 PDB 的 pod,我们称为之为 `pod-x`。最初,所有 Pod 的分布如下:
| node-1 | node-2 | node-3 |
| :----------------: | :---------------: | :---------------: |
| pod-a *available* | pod-b *available* | pod-c *available* |
| pod-x *available* | | |
所有的3个 pod 都是 Deployment 中的一部分,并且它们共同拥有一个 PDB,要求至少有3个 pod 中的2个始终处于可用状态。
例如,假设集群管理员想要重启系统,升级内核版本来修复内核中的错误。集群管理员首先使用 `kubectl drain` 命令尝试排除 `node-1`。该工具试图驱逐 `pod-a` 和 `pod-x`。这立即成功。两个 Pod 同时进入终止状态。这时的集群处于这种状态:
| node-1 *draining* | node-2 | node-3 |
| :------------------: | :---------------: | :---------------: |
| pod-a *terminating* | pod-b *available* | pod-c *available* |
| pod-x *terminating* | | |
Deployment 注意到其中有一个 pod 处于正在终止,因此会创建了一个 `pod-d` 来替换。由于 `node-1` 被封锁(cordon),它落在另一个节点上。同时其它控制器也创建了 `pod-y` 作为 `pod-x` 的替代品。
(注意:对于 `StatefulSet`,`pod-a` 将被称为 `pod-1`,需要在替换之前完全终止,替代它的也称为 `pod-1`,但是具有不同的 UID,可以创建。否则,示例也适用于 StatefulSet。)
当前集群的状态如下:
| node-1 *draining* | node-2 | node-3 |
| :------------------: | :---------------: | :---------------: |
| pod-a *terminating* | pod-b *available* | pod-c *available* |
| pod-x *terminating* | pod-d *starting* | pod-y |
在某一时刻,pod 被终止,集群看起来像下面这样子:
| node-1 *drained* | node-2 | node-3 |
| :--------------: | :---------------: | :---------------: |
| | pod-b *available* | pod-c *available* |
| | pod-d *starting* | pod-y |
此时,如果一个急躁的集群管理员试图排空(drain)`node-2` 或 `node-3`,drain 命令将被阻塞,因为对于 Deployment 只有2个可用的 pod,并且其 PDB 至少需要2个。经过一段时间,`pod-d` 变得可用。
| node-1 *drained* | node-2 | node-3 |
| :--------------: | :---------------: | :---------------: |
| | pod-b *available* | pod-c *available* |
| | pod-d *available* | pod-y |
现在,集群管理员尝试排空 `node-2`。drain 命令将尝试按照某种顺序驱逐两个 pod,假设先是 `pod-b`,然后再 `pod-d`。它将成功驱逐 `pod-b`。但是,当它试图驱逐 `pod-d` 时,将被拒绝,因为这样对 Deployment 来说将只剩下一个可用的 pod。
Deployment 将创建一个名为 `pod-e` 的 `pod-b` 的替代品。但是,集群中没有足够的资源来安排 `pod-e`。那么,drain 命令就会被阻塞。集群最终可能是这种状态:
| node-1 *drained* | node-2 | node-3 | *no node* |
| :--------------: | :---------------: | :---------------: | :-------------: |
| | pod-b *available* | pod-c *available* | pod-e *pending* |
| | pod-d *available* | pod-y | |
此时,集群管理员需要向集群中添加回一个节点以继续升级操作。
您可以看到 Kubernetes 如何改变中断发生的速率,根据:
- 应用程序需要多少副本
- 正常关闭实例需要多长时间
- 启动新实例需要多长时间
- 控制器的类型
- 集群的资源能力
## 分离集群所有者和应用程序所有者角色
将集群管理者和应用程序所有者视为彼此知识有限的独立角色通常是很有用的。这种责任分离在这些情况下可能是有意义的:
- 当有许多应用程序团队共享一个 Kubernetes 集群,并且有自然的专业角色
- 使用第三方工具或服务来自动化集群管理
Pod Disruption Budget(Pod 中断预算) 通过在角色之间提供接口来支持这种角色分离。
如果您的组织中没有这样的职责分离,则可能不需要使用 Pod 中断预算。
## 如何在集群上执行中断操作
如果您是集群管理员,要对集群的所有节点执行中断操作,例如节点或系统软件升级,则可以使用以下选择:
- 在升级期间接受停机时间。
- 故障转移到另一个完整的副本集群。
- 没有停机时间,但是对于重复的节点和人工协调成本可能是昂贵的。
- 编写可容忍中断的应用程序和使用 PDB。
- 没有停机时间。
- 最小的资源重复。
- 允许更多的集群管理自动化。
- 编写可容忍中断的应用程序是很棘手的,但对于可容忍自愿中断,和支持自动调整以容忍非自愿中断,两者在工作上有大量的重叠。
## 参考
- [Disruptions - kubernetes.io](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/)
- 通过配置[Pod Disruption Budget(Pod 中断预算)](https://kubernetes.io/docs/tasks/run-application//configure-pdb)来执行保护应用程序的步骤。
- 了解更多关于[排空节点](https://kubernetes.io/docs/tasks/administer-cluster//safely-drain-node)的信息。
- 序言
- 云原生
- 云原生(Cloud Native)的定义
- CNCF - 云原生计算基金会简介
- CNCF章程
- 云原生的设计哲学
- Play with Kubernetes
- 快速部署一个云原生本地实验环境
- Kubernetes与云原生应用概览
- 云原生应用之路——从Kubernetes到Cloud Native
- 云原生编程语言
- 云原生编程语言Ballerina
- 云原生编程语言Pulumi
- 云原生的未来
- Kubernetes架构
- 设计理念
- Etcd解析
- 开放接口
- CRI - Container Runtime Interface(容器运行时接口)
- CNI - Container Network Interface(容器网络接口)
- CSI - Container Storage Interface(容器存储接口)
- Kubernetes中的网络
- Kubernetes中的网络解析——以flannel为例
- Kubernetes中的网络解析——以calico为例
- 具备API感知的网络和安全性管理开源软件Cilium
- Cilium架构设计与概念解析
- 资源对象与基本概念解析
- Pod状态与生命周期管理
- Pod概览
- Pod解析
- Init容器
- Pause容器
- Pod安全策略
- Pod的生命周期
- Pod Hook
- Pod Preset
- Pod中断与PDB(Pod中断预算)
- 集群资源管理
- Node
- Namespace
- Label
- Annotation
- Taint和Toleration(污点和容忍)
- 垃圾收集
- 控制器
- Deployment
- StatefulSet
- DaemonSet
- ReplicationController和ReplicaSet
- Job
- CronJob
- Horizontal Pod Autoscaling
- 自定义指标HPA
- 准入控制器(Admission Controller)
- 服务发现
- Service
- Ingress
- Traefik Ingress Controller
- 身份与权限控制
- ServiceAccount
- RBAC——基于角色的访问控制
- NetworkPolicy
- 存储
- Secret
- ConfigMap
- ConfigMap的热更新
- Volume
- Persistent Volume(持久化卷)
- Storage Class
- 本地持久化存储
- 集群扩展
- 使用自定义资源扩展API
- 使用CRD扩展Kubernetes API
- Aggregated API Server
- APIService
- Service Catalog
- 资源调度
- QoS(服务质量等级)
- 用户指南
- 资源对象配置
- 配置Pod的liveness和readiness探针
- 配置Pod的Service Account
- Secret配置
- 管理namespace中的资源配额
- 命令使用
- Docker用户过度到kubectl命令行指南
- kubectl命令概览
- kubectl命令技巧大全
- 使用etcdctl访问kubernetes数据
- 集群安全性管理
- 管理集群中的TLS
- kubelet的认证授权
- TLS bootstrap
- 创建用户认证授权的kubeconfig文件
- IP伪装代理
- 使用kubeconfig或token进行用户身份认证
- Kubernetes中的用户与身份认证授权
- Kubernetes集群安全性配置最佳实践
- 访问Kubernetes集群
- 访问集群
- 使用kubeconfig文件配置跨集群认证
- 通过端口转发访问集群中的应用程序
- 使用service访问群集中的应用程序
- 从外部访问Kubernetes中的Pod
- Cabin - Kubernetes手机客户端
- Kubernetic - Kubernetes桌面客户端
- Kubernator - 更底层的Kubernetes UI
- 在Kubernetes中开发部署应用
- 适用于kubernetes的应用开发部署流程
- 迁移传统应用到Kubernetes中——以Hadoop YARN为例
- 最佳实践概览
- 在CentOS上部署Kubernetes集群
- 创建TLS证书和秘钥
- 创建kubeconfig文件
- 创建高可用etcd集群
- 安装kubectl命令行工具
- 部署master节点
- 安装flannel网络插件
- 部署node节点
- 安装kubedns插件
- 安装dashboard插件
- 安装heapster插件
- 安装EFK插件
- 生产级的Kubernetes简化管理工具kubeadm
- 使用kubeadm在Ubuntu Server 16.04上快速构建测试集群
- 服务发现与负载均衡
- 安装Traefik ingress
- 分布式负载测试
- 网络和集群性能测试
- 边缘节点配置
- 安装Nginx ingress
- 安装配置DNS
- 安装配置Kube-dns
- 安装配置CoreDNS
- 运维管理
- Master节点高可用
- 服务滚动升级
- 应用日志收集
- 配置最佳实践
- 集群及应用监控
- 数据持久化问题
- 管理容器的计算资源
- 集群联邦
- 存储管理
- GlusterFS
- 使用GlusterFS做持久化存储
- 使用Heketi作为Kubernetes的持久存储GlusterFS的external provisioner
- 在OpenShift中使用GlusterFS做持久化存储
- GlusterD-2.0
- Ceph
- 用Helm托管安装Ceph集群并提供后端存储
- 使用Ceph做持久化存储
- 使用rbd-provisioner提供rbd持久化存储
- OpenEBS
- 使用OpenEBS做持久化存储
- Rook
- NFS
- 利用NFS动态提供Kubernetes后端存储卷
- 集群与应用监控
- Heapster
- 使用Heapster获取集群和对象的metric数据
- Prometheus
- 使用Prometheus监控kubernetes集群
- Prometheus查询语言PromQL使用说明
- 使用Vistio监控Istio服务网格中的流量
- 分布式跟踪
- OpenTracing
- 服务编排管理
- 使用Helm管理Kubernetes应用
- 构建私有Chart仓库
- 持续集成与发布
- 使用Jenkins进行持续集成与发布
- 使用Drone进行持续集成与发布
- 更新与升级
- 手动升级Kubernetes集群
- 升级dashboard
- 领域应用概览
- 微服务架构
- 微服务中的服务发现
- 使用Java构建微服务并发布到Kubernetes平台
- Spring Boot快速开始指南
- Service Mesh 服务网格
- 企业级服务网格架构
- Service Mesh基础
- Service Mesh技术对比
- 采纳和演进
- 定制和集成
- 总结
- Istio
- 安装并试用Istio service mesh
- 配置请求的路由规则
- 安装和拓展Istio service mesh
- 集成虚拟机
- Istio中sidecar的注入规范及示例
- 如何参与Istio社区及注意事项
- Istio教程
- Istio免费学习资源汇总
- 深入理解Istio Service Mesh中的Envoy Sidecar注入与流量劫持
- 深入理解Istio Service Mesh中的Envoy Sidecar代理的路由转发
- Linkerd
- Linkerd 使用指南
- Conduit
- Condiut概览
- 安装Conduit
- Envoy
- Envoy的架构与基本术语
- Envoy作为前端代理
- Envoy mesh教程
- SOFAMesh
- SOFAMesh中的Dubbo on x-protocol
- SOFAMosn
- 使用 SOFAMosn 构建 SOFAMesh
- 大数据
- Spark standalone on Kubernetes
- 运行支持Kubernetes原生调度的Spark程序
- Serverless架构
- 理解Serverless
- FaaS-函数即服务
- OpenFaaS快速入门指南
- 边缘计算
- 人工智能