# 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快速入門指南
- 邊緣計算
- 人工智能