PodDisruptionBudget の仕組み

v1.37 時点(2026年10月26日 確認)

Kubernetes 基本仕様第36回 / 全45回 ・ 第7部 スケーリングと可用性
目次

この記事ではノードの保守のような計画した作業で同時に止まる Pod の数を抑える PodDisruptionBudget(以下、PDB)の仕組みを、図で説明します。PDB が抑えられる中断と抑えられない中断の違いと、退避(eviction)を頼まれたときに PDB に照らして許すか断るかを誰が決めるのかがわかります。ノードから Pod を退かす kubectl drain と、ノードに新しい Pod を置かせない kubectl cordon の動きもわかります。後半では kind のマルチノードのクラスタで、drain が PDB に断られて待ちながら Pod を 1 つずつ退避させる様子を観察する手順を示します。

Deployment が ReplicaSet を通して Pod の数を保つ仕組みを扱った第12回の内容を前提にしています。PDB で守る対象は Deployment のように Pod の数を保つワークロードです。まだの方は先にこちらをどうぞ。

基本仕様 ・ 第12回 Deployment とローリングアップデートの仕組みReplicaSet を持ち、Pod を宣言的に更新する Deployment の仕組みを図で説明します。ひな形を変えたときに新しい ReplicaSet へ Pod を少しずつ移すローリングアップデート、pod-template-hash の役割、リビジョンと切り戻しを押さえ、kind のクラスタでイメージを更新して rollout undo で戻す手順を紹介します。

以下の図は、kubectl drain が Pod の退避を頼んだときに、どのコンポーネントが何をするかを表しています。後半の手順で作る Deployment web の Pod と、PDB web-pdb を例にしています。

退避の頼みを PDB に照らして許すか断るかを決める kube-apiserver と、PDB の status を書く disruption-controller

drain は Pod を直接消すのではなく、Eviction API で退避を頼みます。頼まれた kube-apiserver は、その Pod を選ぶ PDB に照らして、許すなら Pod に削除の印を付け(200 OK)、許さないなら断ります(429 Too Many Requests)。PDB の status には今いくつまで止めてよいかが書かれていて、それを主に書くのは kube-controller-manager の中の disruption-controller です(例外は「status と disruption-controller」で説明します)。Pod を実際に止めるのは、これまでの回と同じくノードの kubelet です。図では、kube-controller-manager が kube-apiserver を通して Pod を見る線と、kube-apiserver が status に書き込むと読める線を省き、Pod へ向かう線は Pod 1 つずつではなくノードの枠に向けて引いています。

参考:API-initiated Eviction | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes、PodDisruptionBudget | Kubernetes API Reference、kube-controller-manager | Kubernetes

PodDisruptionBudget とは

PodDisruptionBudget は、複数の Pod で動くアプリケーションについて、自発的な中断(voluntary disruption)で同時に止まる Pod の数を抑えるリソース です。中断(disruption)は、ここでは Pod が止められたり消えたりすることを指します。自発的な中断は、ノードの drain のように、人やツールがわざと起こす中断です(次の節で詳しく見ます)。PDB は v1.21 から安定版(Stable)で、API のグループとバージョンは policy/v1 です。

参考:Disruptions | Kubernetes、PodDisruptionBudget | Kubernetes API Reference

公式ドキュメントは PDB を、アプリケーションの持ち主がアプリケーションごとに作れるものとして説明しています。たとえば、多数決(quorum)で動くアプリケーションなら、動いている Pod の数が多数決に要る数を下回らないようにしたい、Web のフロントエンドなら、要求を受ける Pod の数が全体の一定の割合を下回らないようにしたい、という使い方です。一方、クラスタの管理者やホスティングの事業者は、Pod や Deployment を直接消すのではなく、Eviction API を呼んで PDB を尊重するツールを使うべき(should)だとされています。kubectl drain はそうしたツールの 1 つです。

参考:Disruptions | Kubernetes

drain は保守の前にノードから Pod を退かす作業です。公式ドキュメントではカーネルの更新やハードウェアの保守の前に、ノードのすべての Pod を安全に退避させるのに kubectl drain を使える、と説明しています。

参考:Safely Drain a Node | Kubernetes

なぜ PodDisruptionBudget が必要なのか

第12回で見たとおり、Deployment は spec.replicas に書いた数の Pod を保ちます。Pod が消えれば ReplicaSet が代わりを作りますが、代わりの Pod が起動して Ready(第34回)になるまでには時間がかかります。drain はノードのすべての Pod を退避させようとするので、同じ Deployment の Pod が 1 つのノードに集まっていると、それらが一度に止まり、代わりが動き出すまで要求を受けられる Pod が足りなくなることが考えられます。第33回の Topology Spread で Pod を散らしておいても、複数のノードを続けて drain すれば、同じことが起こり得ると考えられます。

PDB を作っておくと、Eviction API はその PDB が許す数までしか同時に退避を許しません。公式ドキュメントの例では、.spec.replicas: 5 の Deployment に、使える Pod が 4 つ以上残ることを求める PDB(4 つあればよいとする PDB)があると、5 − 4 で、Eviction API は 1 つずつ(2 つ同時ではなく)自発的な中断を許します。drain は断られた退避を頼み直しながら進みます(「kubectl cordon と kubectl drain」で説明します)。

参考:Disruptions | Kubernetes

ただし、どのクラスタでも PDB が要るわけではありません。公式ドキュメントによると、基本的な Kubernetes のクラスタには、自動で起きる自発的な中断はありません(利用者が起こすものだけです)。管理者やホスティングの事業者が、ノードのソフトウェアの更新やノードの自動スケーリングのように、自発的な中断を起こすサービスを動かしていることがあります(may)。自分のクラスタでそうした中断が起きるかを管理者などに確かめ、何も有効でなければ PDB を作らなくてもよい、と書かれています。

参考:Disruptions | Kubernetes、Disruptions | Kubernetes

PodDisruptionBudget の仕組み

自発的な中断と非自発的な中断

公式ドキュメントによると、Pod は、誰か(人かコントローラー)が消すか、避けられないハードウェアやシステムのソフトウェアのエラーが起きない限り、消えません。避けられない場合を非自発的な中断(involuntary disruption)、それ以外を自発的な中断と呼びます。公式ドキュメントが挙げる例を次の表にまとめます。

種類公式ドキュメントの例
非自発的な中断ノードの物理マシンの故障、管理者による VM の誤った削除、クラウドプロバイダーやハイパーバイザーの障害、カーネルパニック、ネットワークの分断でノードがクラスタから消えること、ノードの資源不足による Pod の退避
自発的な中断(アプリケーションの持ち主)Pod を管理する Deployment などの削除、Pod のひな形の更新による入れ替え、Pod の直接の削除(誤って消した場合など)
自発的な中断(クラスタの管理者)修理や更新のためのノードの drain、クラスタを縮めるためのノードの drain、別の Pod を置くためにノードから Pod を取り除くこと

非自発的な中断の例の多くは、ノードのマシンや、ノードを動かす仮想マシン(VM)とその基盤(ハイパーバイザー)、OS の中心部(カーネル)の故障や停止です。

参考:Disruptions | Kubernetes

非自発的な中断の「ノードの資源不足による退避」は、第29回で見た kubelet の node-pressure eviction です。第29回で見たとおり、node-pressure eviction では kubelet は PDB を守りません。「ネットワークの分断でノードが消える」は、第4回で見た、ノードと連絡が取れなくなる場面にあたると考えられます。公式ドキュメントによると、PDB は非自発的な中断を防げません。ただし、非自発的な中断で止まった Pod も、PDB の数に入ります(原文は count against the budget)。止まった Pod は健全な Pod に数えられないので、そのあいだは disruptionsAllowed が減り、同じときに drain すると退避が断られやすくなると考えられます。

参考:Disruptions | Kubernetes、Node-pressure Eviction | Kubernetes

自発的な中断でも、すべてが PDB に抑えられるわけではありません。公式ドキュメントは注意として、Deployment や Pod を削除すると PDB は迂回される(bypass) と書いています。PDB を尊重するのは、次の節の Eviction API を通した退避です。kubectl delete のような直接の削除は PDB を迂回します。

参考:Disruptions | Kubernetes、API-initiated Eviction | Kubernetes

ひな形の更新による入れ替え(ローリングアップデート)の扱いも決まっています。ローリングアップデートで消えたり使えなくなったりした Pod は PDB の数に入りますが(そのあいだ disruptionsAllowed が減ると考えられます)、Deployment や StatefulSet のローリングアップデートそのものは PDB で制限されません。更新中の失敗の扱いはそれぞれのワークロードの spec で決める、とされています。Deployment で、更新中に同時に使えなくなる Pod の数を決めるのは、第13回で見た maxUnavailable と maxSurge です。

参考:Disruptions | Kubernetes、Deployments | Kubernetes

Eviction API で退避を頼む

API で頼む退避(API-initiated eviction)は、Eviction API で Eviction オブジェクトを作り、Pod の穏やかな終了(graceful termination)を起こすこと です。Eviction API を直接呼ぶか、kubectl drain のような kube-apiserver のクライアントから頼みます。公式ドキュメントは Pod の Eviction を作ることを、ポリシーで制御された Pod の DELETE のようなもの、と説明しています。このときは PDB と、Pod の terminationGracePeriodSeconds(第8回で見た終了までの猶予の時間。既定では 30 秒)が守られます。

参考:API-initiated Eviction | Kubernetes、Pod Lifecycle | Kubernetes

以下は公式ドキュメントが示す、Pod quux の退避を頼む Eviction の例です。そのまま引用しています。policy/v1 の Eviction は v1.22 から使えます。

json
{
  "apiVersion": "policy/v1",
  "kind": "Eviction",
  "metadata": {
    "name": "quux",
    "namespace": "default"
  }
}

参考:API-initiated Eviction | Kubernetes

公式ドキュメントによると、これを Pod の eviction というサブリソース(第35回で見た scale と同じく、オブジェクトに付いた別のパス)に POST すると、kube-apiserver はアドミッションの検査をして、次のどれかで応答します。

応答意味
200 OK退避が許された。Eviction が作られ、Pod の URL に DELETE を送ったときと同じように Pod が削除される
429 Too Many Requests設定された PDB のため今は退避が許されず、あとで頼み直せることがある。API の流量の制限(rate limiting)でこの応答になることもある
500 Internal Server Error設定の誤りで退避が許されない。たとえば、複数の PDB が同じ Pod を選んでいる場合

退避したい Pod が PDB を持つワークロードに属していなければ、kube-apiserver は必ず 200 OK を返して退避を許します。

参考:API-initiated Eviction | Kubernetes

退避が許されたあとの流れは、第8回で見た、Pod を kubectl delete で消したときの流れとよく似ています。kube-apiserver が Pod に削除の時刻(deletion timestamp)と猶予の時間を記録し、Pod のノードの kubelet がそれに気付いて Pod を穏やかに止め始めます。そのあいだに、コントロールプレーンは Pod を EndpointSlice(第21回)から外します。猶予の時間が過ぎると kubelet が Pod を強制的に終了させ、最後に kube-apiserver が Pod を削除します。

参考:API-initiated Eviction | Kubernetes

PodDisruptionBudget のフィールド

PDB にはどの Pod を守るかを決める spec.selector と、spec.minAvailable か spec.maxUnavailable のどちらか一方を書きます。以下は公式ドキュメントの、app: zookeeper のラベルの Pod を守る PDB の例です。そのまま引用しています。ZooKeeper は、公式ドキュメントが etcd と並べて挙げる、複数の Pod の多数決(quorum)で動く分散アプリケーションで、例の zk はその略だと考えられます。

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper

参考:Specifying a Disruption Budget for your Application | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes

フィールドの意味を、公式ドキュメントと API リファレンスに沿って次の表にまとめます。

フィールド意味
selector退避を PDB で管理する Pod を選ぶラベルセレクタ(第11回)で、タスクのページは必須としている。空({})だと、Namespace のすべての Pod を選ぶ
minAvailable退避させても、選んだ Pod のうち残りの使える Pod がこの数以上なら、退避を許す。数か割合で書く
maxUnavailable退避したあと、選んだ Pod のうち使えない Pod がこの数までなら、退避を許す。数か割合で書く
unhealthyPodEvictionPolicyReady でない Pod を、どんなときに退避の対象にするか(「Ready でない Pod の扱い」で説明)

参考:Specifying a Disruption Budget for your Application | Kubernetes、PodDisruptionBudget | Kubernetes API Reference

minAvailable と maxUnavailable は 1 つの PDB にどちらか一方しか書けません。公式ドキュメントは組み込みのコントローラー(Deployment、ReplicationController、ReplicaSet、StatefulSet)の Pod を守るときは、コントローラーの .spec.selector と同じセレクタを PDB の .spec.selector に書くよう説明しています。PDB の例が 3 つの Pod の StatefulSet を選ぶなら、minAvailable: 2 と maxUnavailable: 1 はまったく同じ意味で、公式ドキュメントはコントローラーの数の変化に自動で合わせられる maxUnavailable を勧めています。なお、PDB の maxUnavailable は、第13回で見た Deployment の maxUnavailable(更新のときの数)とは名前が同じだけの別のフィールドです。

参考:Specifying a Disruption Budget for your Application | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes、Deployments | Kubernetes

公式ドキュメントによると、割合("50%" のような文字列)で書くと、Pod の数は切り上げで求めます。たとえば Pod が 7 つで minAvailable が "50%" なら、使える状態で残すべき数は 4 です。maxUnavailable では止めてよい数を切り上げるので、止まる数が書いた割合を超えることがあります。

参考:Specifying a Disruption Budget for your Application | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes

maxUnavailable を 0 か 0% にするか、minAvailable を 100% か Pod の数と同じにすると、自発的な退避を 1 つも許さないことになります。公式ドキュメントによると、そうした Pod が動いているノードは drain に成功できず、drain は終わりません。これは PDB の決まりのとおりの動きだとされています。公式ドキュメントは 1 つだけで動く状態のあるアプリケーションの守り方の候補として、maxUnavailable を 0 にしておき、止めてよいときに PDB を消して、あとで作り直すやり方を挙げています。

参考:Specifying a Disruption Budget for your Application | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes

Deployment などの組み込みのワークロード以外が管理する Pod や、コントローラーの無い Pod にも PDB は使えます。ただし、その場合は minAvailable の整数しか使えず、maxUnavailable や割合は使えません。対応する持ち主のリソースが無いと、Kubernetes は Pod の全体の数を求められないからです。maxUnavailable は、選んだ Pod がすべて同じコントローラーに管理されているときだけ使えます。また、セレクタが重なって 1 つの Pod を複数の PDB が選ぶと、その Pod の退避は Eviction API が許さないので、公式ドキュメントは重なりを避けたほうがよいとしています。

参考:Specifying a Disruption Budget for your Application | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes

status と disruption-controller

PDB の status には、選んだ Pod を数えた結果が入ります。公式ドキュメントの例では、zk-pdb の status は次のとおりです(ほかの部分は省略しています)。

yaml
status:
  currentHealthy: 3
  desiredHealthy: 2
  disruptionsAllowed: 1
  expectedPods: 3
  observedGeneration: 1

参考:Specifying a Disruption Budget for your Application | Kubernetes

各フィールドの意味を、API リファレンスに沿って次の表にまとめます。

フィールド意味
currentHealthy今の健全な(healthy)Pod の数
desiredHealthy望ましい、健全な Pod の最小の数
disruptionsAllowed今許されている Pod の中断の数
expectedPodsこの PDB が数えた Pod の全体の数
observedGenerationstatus を更新したときに見た PDB の世代。PDB の metadata.generation と同じときだけ、disruptionsAllowed などが正しい
conditionsDisruptionAllowed の状態。理由(reason)は SufficientPods、InsufficientPods、SyncFailed など

参考:PodDisruptionBudget | Kubernetes API Reference

v1.37 時点の公式ドキュメントでは、今の実装での健全な Pod は .status.conditions に type="Ready" で status="True" の項目を持つ Pod、つまり Ready の Pod です。望ましい数(公式ドキュメントの「intended」の数)は、Pod を管理するワークロードのリソースの .spec.replicas から求めます。コントロールプレーンは Pod の .metadata.ownerReferences(第6回の持ち主の記録)をたどって、持ち主のリソースを見つけます。

参考:Specifying a Disruption Budget for your Application | Kubernetes、Disruptions | Kubernetes

status を主に書くのは、kube-controller-manager の中の disruption-controller です(既定で有効)。disruptionsAllowed が 0 でないことは、disruption-controller が Pod を数えて status を更新したことを表す、と説明されています。API リファレンスの PodDisruptionBudget のページには disruption controller と PodDisruptionBudget controller の 2 つの呼び名が出てきますが、どちらもこのコントローラーを指すと考えられます。

参考:Specifying a Disruption Budget for your Application | Kubernetes、kube-controller-manager | Kubernetes、PodDisruptionBudget | Kubernetes API Reference

退避を許すか断るかを決めるのは、kube-apiserver です。API リファレンスによると、status の disruptedPods には、kube-apiserver の eviction のサブリソースが退避の頼みを処理した Pod が入ります。入っているのは、kube-apiserver が処理した時点から、PDB のコントローラーが削除の印を確かめるまで(またはタイムアウトまで)です。kube-apiserver が処理した時点から入るとあるので、この記録を書くのは kube-apiserver だと読めます。コマ送り図の(1/4)で disruptionsAllowed がすぐ 0 になるのは、kube-apiserver が退避を処理したときにこの記録を残し、コントローラーが数え直す前に次の頼みが来ても、許す退避の数を多く見積もらないようにするためだと考えられます。

参考:PodDisruptionBudget | Kubernetes API Reference

Ready でない Pod の扱い(unhealthyPodEvictionPolicy)

PDB は健全な Pod の退避を断ることで、currentHealthy が desiredHealthy を下回らないようにします。では、動いている(Running)が Ready でない Pod はどうでしょうか。それを決めるのが unhealthyPodEvictionPolicy で、v1.31 から安定版です(機能ゲートはすでに外され、切り替えられません)。2 つの値を、公式ドキュメントに沿って次の表にまとめます。

値Running で Ready でない Pod の扱い公式ドキュメントが挙げる影響
IfHealthyBudget(v1.37 時点で、書かないときの既定)守っているアプリケーションが中断していない(currentHealthy が desiredHealthy 以上の)ときだけ退避できるすでに中断しているアプリケーションの Pod に、健全になる機会を最大限に与える。その代わり、CrashLoopBackOff の Pod などがあると drain が止まることがある
AlwaysAllowPDB の条件を満たすかどうかにかかわらず、中断しているとみなして退避できる健全になりかけた Pod が、その機会を得られないことがある。管理者は、うまく動かないアプリケーションを退避させやすくなる

参考:Specifying a Disruption Budget for your Application | Kubernetes、PodDisruptionBudget | Kubernetes API Reference

どちらの値でも、健全な Pod の退避は PDB に従います。Pending、Succeeded、Failed のフェーズの Pod はいつでも退避の対象になります。公式ドキュメントの Disruptions のページと Safely Drain a Node のページは、drain のときにうまく動かないアプリケーションを退避させられるよう、PDB に AlwaysAllow を設定することを勧めています(recommended)。既定の動きでは、drain はアプリケーションの Pod が健全になるのを待ちます。

参考:Specifying a Disruption Budget for your Application | Kubernetes、Disruptions | Kubernetes、Safely Drain a Node | Kubernetes

kubectl cordon と kubectl drain

kubectl cordon は、ノードをスケジュールできない状態(unschedulable)にするコマンドです。ノードの spec.unschedulable は新しい Pod をそのノードに置けるかを決めるフィールドで、既定ではスケジュールできます。公式ドキュメントによると、スケジュールできない印を付けると、スケジューラーはそのノードに新しい Pod を置かなくなりますが、すでに動いている Pod には影響しません。ノードの再起動などの保守の準備に役立つ、と書かれています。元に戻すのは kubectl uncordon です。

参考:Nodes | Kubernetes、Node | Kubernetes API Reference、kubectl cordon | Kubernetes、kubectl uncordon | Kubernetes

Taints and Tolerations のページは、ノードがスケジュールできないときに node controller が自動で付ける組み込みの Taint として、node.kubernetes.io/unschedulable を挙げています(第32回)。Safely Drain a Node のページは、新しい Pod がこの Taint を許容していると、drain したノードに置かれることがある(might)と注意し、DaemonSet 以外でこの Taint を許容するのは避けるよう書いています(Avoid)。第15回で見たとおり、daemonset-controller は DaemonSet の Pod にこの Taint の Toleration を自動で足すので、DaemonSet の Pod はスケジュールできないノードでも動きます。

同じページによると、自分やほかの API の利用者が Pod の nodeName(第31回で見た、ノードの名前を直接指定するフィールド)を直接書くと、スケジューラーを迂回するので、その Pod は drain してスケジュールできない印を付けたノードにも割り当てられて動きます。

参考:Taints and Tolerations | Kubernetes、Taints and Tolerations | Kubernetes、Safely Drain a Node | Kubernetes

kubectl drain は、保守の準備としてノードを空けるコマンドです。v1.37 時点のリファレンスに沿って、drain がすることを次の表にまとめます。

対象drain の扱い
ノードまずスケジュールできない印を付けて、新しい Pod が来ないようにする(cordon と同じ)
ふつうの Podkube-apiserver が対応していれば Eviction API で退避させ、対応していなければふつうの DELETE で消す
mirror Pod(第4回)kube-apiserver から消せないので、対象にしない
DaemonSet の Pod--ignore-daemonsets が無いと drain は進まない。付けても消さない
コントローラーの無い PodReplicationController、ReplicaSet、DaemonSet、StatefulSet、Job のどれにも管理されていない Pod があると、--force が無ければどの Pod も消さない
emptyDir(第26回)を使う Pod--delete-emptydir-data を付けると、それでも続ける(ノードの emptyDir のデータは消える)

参考:kubectl drain | Kubernetes

DaemonSet の Pod を消さないのは、消しても daemonset-controller がすぐに同じノードへ作り直すからです。Safely Drain a Node のページによると、DaemonSet の Pod の多くは、ネットワークやストレージ、ログの収集のように、ノードが動いているあいだはそのノードに残る必要がある、ノードごとのサービスです。--ignore-daemonsets はそれらを飛ばして drain を終わらせるための指定で、DaemonSet の Pod は保守のあいだも動き続け、ノードをクラスタから外したときに消えます。

参考:Safely Drain a Node | Kubernetes、kubectl drain | Kubernetes

drain は Pod の穏やかな終了を待ちます。リファレンスは、コマンドが終わるまでマシンを操作しないよう(should not)書いています。kubectl drain が正常に終われば、対象にしない Pod を除くすべての Pod が、猶予の時間と PDB を守って退避し終えています。そのあとは物理マシンの電源を切るか、クラウドなら仮想マシンを消してかまいません。保守のあいだノードをクラスタに残しておいた場合は、作業のあとに kubectl uncordon を実行して、新しい Pod を置けるように戻す必要があります。

参考:kubectl drain | Kubernetes、Safely Drain a Node | Kubernetes

PDB に断られた退避について、公式ドキュメントは次のように説明しています。kubectl が代わりに送る退避の頼みは一時的に断られることがあるので、drain は、対象のノードのすべての Pod が終わるか、設定できるタイムアウト(--timeout。0 は無限)に達するまで、失敗した頼みを定期的にすべて頼み直します。

参考:Disruptions | Kubernetes、kubectl drain | Kubernetes

drain と PDB が組み合わさる流れ

公式ドキュメントの Disruptions のページには、3 つのノードと、少なくとも 3 つのうち 2 つが使える状態であることを求める PDB を持つ Deployment の例があります。1 つ目のノードを drain すると、その上の Pod の退避はすぐに成功します。代わりの Pod が起動し終わる前に、待ちきれない管理者が 2 つ目のノードを drain しようとすると、使える Pod が 2 つしかないので drain は止まります(block)。代わりの Pod が使えるようになると、2 つ目のノードの drain を進められます。代わりの Pod を置く資源がクラスタに無ければ、代わりの Pod は Pending のままで、drain は再び止まります。その場合は、ノードをクラスタに戻さないと作業を進められません。

参考:Disruptions | Kubernetes

公式ドキュメントはこの例から、中断が起きる速さは、アプリケーションに要る Pod の数、穏やかな終了にかかる時間、新しい Pod の起動にかかる時間、コントローラーの種類、クラスタの資源の量で変わる、とまとめています。

参考:Disruptions | Kubernetes

以下の図は、後半の手順と同じく、3 つの Pod がすべて 1 つ目のワーカーにある Deployment web と、minAvailable: 2 の PDB web-pdb があるときに、1 つ目のワーカーを drain する流れをコマ送りで表しています。図の disruptionsAllowed の値は公式ドキュメントの説明から考えたもので、手元で確かめた値ではありません。Pod の名前は見分けるために 1〜6 の番号にしています(実際は web- に続けてハッシュと文字列が付きます)。kube-apiserver と ReplicaSet は省き、退避の頼みは利用者からノードの枠への線に、断りは PDB から利用者への線にまとめています。

(初期)drain の前。web-1〜web-3 が 1 つ目のワーカーで動いていて Ready。disruptionsAllowed は 1
(1/4)drain が 1 つ目のワーカーを cordon し、3 つの Pod の退避を頼む。許されるのは 1 つだけで、web-1 が終了中になり、disruptionsAllowed は 0 になる。ReplicaSet が代わりの web-4 を作り、web-4 は cordon されていない 2 つ目のワーカーに置かれる。残りの 2 つは 429 で断られ、drain は待って頼み直す
(2/4)web-1 が消える。web-4 が Ready になるまで disruptionsAllowed は 0 のままで、退避は断られ続ける
(3/4)web-4 が Ready になると disruptionsAllowed が 1 に戻り、web-2 の退避が許されて、disruptionsAllowed はまた 0 になる。代わりの web-5 が 2 つ目のワーカーに作られ、web-3 の退避は断られ続ける
(4/4)web-5 が Ready になると web-3 の退避が許され、web-3 が消えると drain は終わる。代わりの web-6 は少し遅れて 2 つ目のワーカーで Ready になる。1 つ目のワーカーは cordon されたまま

(1/4)〜(3/4)では、同時に止まる web の Pod は 1 つまでに抑えられています。drain はノードの 3 つの Pod をどれも退避させようとしますが、PDB が 2 つ同時の退避を許さないからです。代わりの Pod の置き場所があるので、待つ時間は代わりの Pod が Ready になるまでの時間だと考えられます。

参考:Disruptions | Kubernetes、Disruptions | Kubernetes

手元で試す

ここまでの内容を、手元の kind のクラスタで試す手順を示します。公式のタスク(Specifying a Disruption Budget for your Application と Safely Drain a Node)に沿って、Deployment web と PDB web-pdb を作り、ワーカーを drain します。まず、drain が PDB に断られながら web の Pod を 1 つずつ退避させる様子を見ます。次に、代わりの Pod の置き場所が無いと drain が待ち続ける様子を見ます。

各段で何が表示されるかは、どれも公式ドキュメントから予想した結果で、手元で実行して確かめたものではありません。以降の手順では、この予想を「はずです」と書きます。公式ドキュメントで確かめられなかった点(ノードの名前や、kubectl が出す行の文面など)は、その都度「考えられます」と断ります。引用する出力の例は公式ドキュメントに載っている、ほかのクラスタで取ったもので、名前や数などの値は手元とは違います。各段の最後に、成功かどうかを見分ける目安を書きます。

コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。

項目内容
kindv0.33.0
Kubernetesv1.37.0(kind v0.33.0 の既定のノードイメージ)
ノード構成マルチノード(クラスタ名 multi-node):コントロールプレーン 1、ワーカー 2
必要なものDocker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)3 つ

参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes

kind でクラスタを作る手順は、以下の記事で解説しています。この回は第31〜33回と同じマルチノードのクラスタを使います。1 つ目のワーカーを drain したときに、代わりの Pod を置ける 2 つ目のワーカーが要るからです。

基本仕様 ・ 第2回 kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。

この回では端末を 3 つ使います。第31〜33回と同じく 1 つ目を監視に、2 つ目を操作に使い、さらに 3 つ目を足して PDB を監視します。drain は終わるまで端末を占めるので、PDB の数の変化は別の端末で追います。役割を次の表にまとめます。

端末役割開くタイミングと注意
1 つ目default の Namespace の Pod の監視「監視を始める」の節で開く。監視だけなので cd は要らない
2 つ目操作(マニフェストを渡す、cordon、drain など)最初に開き、マニフェストを保存するディレクトリに cd する。drain のあいだは占有される
3 つ目PDB の監視「監視を始める」の節で開く。監視だけなので cd は要らない

マルチノードのクラスタを用意する

第2回・第31回と同じ次の設定ファイルを使います。公式ドキュメントの例をそのまま引用しています。kind-multi-node.yaml という名前で、コマンドを実行するディレクトリに保存します。前の回までに保存したファイルが残っていれば、それを使えます。

yaml
# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker

参考:Quick Start | kind

2 つ目の端末を開き、cd でマニフェストを保存するディレクトリに移動してから、前の回のクラスタが残っていないかを確かめます。

console
$ kind get clusters

multi-node が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の kind が表示されても、この回では使いません。手元のマシンの資源(メモリなど)に余裕がなければ、kind delete cluster --name kind で消してから進めてください。

console
$ kind create cluster --name multi-node --config kind-multi-node.yaml
$ kind get clusters

kind get clusters に multi-node が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-multi-node を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。

console
$ kubectl get nodes --context kind-multi-node

ノードが 3 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。

参考:Quick Start | kind、kubectl get | Kubernetes

次に、第31回と同じく、node-role.kubernetes.io/control-plane のラベルを持たないノード、つまりワーカーのノードだけを一覧します。

console
$ kubectl get nodes -l '!node-role.kubernetes.io/control-plane' --context kind-multi-node

参考:Labels and Selectors | Kubernetes、Well-Known Labels, Annotations and Taints | Kubernetes

ノードが 2 つ表示されるはずです。名前は multi-node-worker や multi-node-worker2 のような形になると考えられます(公式ドキュメントでは確かめられなかった推測です)。ここからは表示された順に「1 つ目のワーカー」「2 つ目のワーカー」と呼びます。2 つの名前をメモしておいてください。このあとのコマンドの <1 つ目のワーカーの名前> と <2 つ目のワーカーの名前> は、< と > も含めて、メモした名前に置き換えます。

各ノードがスケジュールできるかと、Taint のキーを表示します。第32回で使った kubectl のクイックリファレンスのコマンドを、spec.unschedulable の列を足して短くした形です。[*] をシェルに解釈させないよう、指定全体を一重引用符で囲みます。

console
$ kubectl get nodes -o 'custom-columns=NAME:.metadata.name,UNSCHEDULABLE:.spec.unschedulable,TAINTS:.spec.taints[*].key' --context kind-multi-node

参考:kubectl Quick Reference | Kubernetes、Node | Kubernetes API Reference

UNSCHEDULABLE 列は、どのノードも値が無いことを表す表示になるはずです。spec.unschedulable は書かれていなければスケジュールできる、という意味だからです。TAINTS 列は、コントロールプレーンのノードが node-role.kubernetes.io/control-plane で、ワーカーの 2 つは値が無いことを表す表示になるはずです(第32回)。どちらかのワーカーの UNSCHEDULABLE 列が true なら、前にこの回を試した名残なので、kubectl uncordon <そのワーカーの名前> --context kind-multi-node で戻してから進めます。

前の回のクラスタを使い続けている場合は、default の Namespace に Pod と PDB が残っていないことも確かめます。

console
$ kubectl get pods,pdb --context kind-multi-node

参考:Command line tool (kubectl) | Kubernetes

pdb は poddisruptionbudgets の短い名前(shortname)です。見つからないことを知らせる 1 行が出れば、そのまま進めます。残っていたら、自分で作ったものかを確かめてから消します。

監視を始める

ここで 1 つ目と 3 つ目の端末を開きます。1 つ目の端末で、Pod が置かれたノードの名前(NODE 列)も表示しながら Pod を監視します。この監視は片付けの前まで止めずに続けます。

console
$ kubectl get pods --watch -o wide --context kind-multi-node

3 つ目の端末で、PDB を監視します。PDB はまだ無いので、名前を指定せずに種類全体を監視します。この監視も片付けの前まで続けます。

console
$ kubectl get pdb --watch --context kind-multi-node

参考:kubectl get | Kubernetes

どちらの端末も、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。

Pod を 1 つ目のワーカーに集める

drain で Pod が 1 つずつ退避する様子をはっきり見るため、web の 3 つの Pod を、最初は 1 つ目のワーカーに集めておきます。Pod をどのノードに置くかは kube-scheduler が決めるので(第31回)、そのままでは 2 つのワーカーに散らばるかもしれません。そこで 2 つ目のワーカーを cordon してから Deployment を作り、作り終えたら uncordon します。

2 つ目の端末で、2 つ目のワーカーを cordon します。

console
$ kubectl cordon <2 つ目のワーカーの名前> --context kind-multi-node

参考:kubectl cordon | Kubernetes

ノードに印を付けたことを知らせる 1 行が出るはずです。公式のチュートリアルの drain の例では、ノードに印を付けたときの行は node "kubernetes-node-pb41" cordoned の形でした。もう一度、さきほどの custom-columns のコマンドを実行します。

console
$ kubectl get nodes -o 'custom-columns=NAME:.metadata.name,UNSCHEDULABLE:.spec.unschedulable,TAINTS:.spec.taints[*].key' --context kind-multi-node

参考:Running ZooKeeper, A Distributed System Coordinator | Kubernetes、Node | Kubernetes API Reference

2 つ目のワーカーの行の UNSCHEDULABLE 列が true になるはずです。「kubectl cordon と kubectl drain」で見たとおり、スケジュールできないノードには node controller が node.kubernetes.io/unschedulable の Taint を付けるので、TAINTS 列にもこのキーが現れるはずです。

以下は nginx の Pod を 3 つ動かす Deployment のマニフェストです。第33回と同じく、イメージは nginx で、ラベルは app: web にしています。web.yaml という名前で保存します。

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent

参考:Deployments | Kubernetes

2 つ目の端末で、マニフェストを渡します。

console
$ kubectl apply -f web.yaml --context kind-multi-node

deployment.apps/web が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には web の Pod の行が 3 つ書き足されていき、STATUS 列は Pending、ContainerCreating を経て Running になるはずです。NODE 列は 3 つとも 1 つ目のワーカーの名前になるはずです。2 つ目のワーカーはスケジュールできず、コントロールプレーンのノードは NoSchedule の Taint で外れる(第32回)ので、置けるノードは 1 つ目のワーカーだけだからです。クラスタを作ったばかりなら、イメージの取得に時間がかかるので、ContainerCreating のまましばらく止まって見えることがあります(第9回)。

参考:Nodes | Kubernetes、Taints and Tolerations | Kubernetes

3 つとも Running になったら、2 つ目のワーカーを uncordon します。

console
$ kubectl uncordon <2 つ目のワーカーの名前> --context kind-multi-node

公式のチュートリアルの例では、出力は次のとおりです。手元ではノードの名前が 2 つ目のワーカーの名前になり、書式も例と違うことがあると考えられます。

node "kubernetes-node-pb41" uncordoned

参考:Running ZooKeeper, A Distributed System Coordinator | Kubernetes、kubectl uncordon | Kubernetes

uncordon しても、すでに動いている Pod は動かないはずです。スケジュールできるかどうかは新しい Pod を置くときに見るもので、動いている Pod には影響しないからです。

この段では、次の 2 点が合っていれば成功です。

  • 1 つ目の端末で、web の 3 つの Pod が READY 列 1/1、STATUS 列 Running で、NODE 列がすべて 1 つ目のワーカーの名前になっている
  • kubectl uncordon が印を外したことを知らせる 1 行を出して終わる

PodDisruptionBudget を作る

以下は web の Pod のうち少なくとも 2 つが使える状態で残るように退避を抑える PDB のマニフェストです。公式ドキュメントの zk-pdb の例をもとに、名前とセレクタを web に合わせ、「Ready でない Pod の扱い」で見た推奨の unhealthyPodEvictionPolicy: AlwaysAllow を足しています。web-pdb.yaml という名前で保存します。

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web
  unhealthyPodEvictionPolicy: AlwaysAllow

参考:Specifying a Disruption Budget for your Application | Kubernetes、PodDisruptionBudget | Kubernetes API Reference

selector は Deployment web の .spec.selector と同じ app: web です。「PodDisruptionBudget のフィールド」で見た StatefulSet の例と同じく、3 つの Pod に対する minAvailable: 2 は maxUnavailable: 1 と同じ意味で、同時に止めてよいのは 1 つまでのはずです。minAvailable にしたのは、引用する公式ドキュメントの出力の例と見比べやすくするためです。どちらの drain でも、退避の対象になるノードの web の Pod は drain を始めた時点でどれも Ready なので、AlwaysAllow は結果を変えないと考えられます。

2 つ目の端末で、マニフェストを渡します。

console
$ kubectl apply -f web-pdb.yaml --context kind-multi-node

poddisruptionbudget.policy/web-pdb が作られたことを知らせる 1 行が出るはずです。3 つ目の端末には web-pdb の行が現れるはずです。公式ドキュメントの例では、合う Pod が 3 つあるときの kubectl get poddisruptionbudgets の表示は次のとおりです。

NAME     MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
zk-pdb   2               N/A               1                     7s

参考:Specifying a Disruption Budget for your Application | Kubernetes

手元では名前が web-pdb になり、AGE 列の値も手元のものになるはずです。--watch で追っているので、変化のたびに行が書き足されます。作った直後は、disruption-controller がまだ Pod を数えていないため、ALLOWED DISRUPTIONS 列が 0 の行が先に出ることもあると考えられます。公式ドキュメントにも、合う Pod が無いときは 0 になる例があります。

続けて、2 つ目の端末で status を表示します。

console
$ kubectl get pdb web-pdb -o yaml --context kind-multi-node

末尾の status: に、「status と disruption-controller」で引用した例と同じ currentHealthy: 3、desiredHealthy: 2、disruptionsAllowed: 1、expectedPods: 3 が並ぶはずです。手元では conditions も表示されるはずです。

この段では、次の 2 点が合っていれば成功です。

  • 3 つ目の端末の web-pdb の行で、MIN AVAILABLE 列が 2、ALLOWED DISRUPTIONS 列が 1 になる
  • status の currentHealthy が 3、disruptionsAllowed が 1 になっている

1 つ目のワーカーを drain する

Safely Drain a Node のページの手順で、1 つ目のワーカーを drain します。kind のノードでは、第4回で見た kube-proxy と kindnet の DaemonSet の Pod が動いているので、--ignore-daemonsets を付けます。付けないと、drain は進みません。2 つ目の端末で、次のコマンドを実行します。

console
$ kubectl drain --ignore-daemonsets <1 つ目のワーカーの名前> --context kind-multi-node

参考:Safely Drain a Node | Kubernetes、kubectl drain | Kubernetes

drain のあいだは、1 つ目の端末の READY 列に注目します。第34回で見たとおり、READY 列の 1/1 は Pod のコンテナが Ready になったことを表します。この手順の Pod はコンテナが 1 つなので、1/1 なら Pod も Ready です。PDB が健全な Pod として数えるのは、この Ready の Pod です。drain のあいだに起こるはずのことを、端末ごとに次の表にまとめます。表の変化はコマ送り図の(1/4)〜(4/4)にあたります。

端末表示の変化
1 つ目(Pod)web の Pod の 1 つが Terminating になり、新しい web の Pod が 2 つ目のワーカーに現れて、READY 列が 0/1 から 1/1 になる。これが 3 回繰り返される
2 つ目(drain)ノードに印を付けたことを知らせる行のあと、Pod の退避を知らせる行が並び、最後に drain が終わったことを知らせる行が出る
3 つ目(PDB)ALLOWED DISRUPTIONS 列が 0 と 1 を行き来する

公式のチュートリアルの例では、drain の出力は次のとおりです。この例は --force と --delete-emptydir-data も付けて、別のクラスタで実行したものです。

node "kubernetes-node-pb41" cordoned

WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-pb41, kube-proxy-kubernetes-node-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz
pod "zk-0" deleted
node "kubernetes-node-pb41" drained

参考:Running ZooKeeper, A Distributed System Coordinator | Kubernetes

この例で見るべき行は、ノードに印を付けた cordoned の行、Pod を消した deleted の行、drain が終わった drained の行の 3 種類です。WARNING の行は例で付けた --force などにかかわる警告です。手元では --force を付けていないので、コントローラーの無い Pod を消すことを知らせる警告は出ないと考えられます。一方、--ignore-daemonsets で飛ばした DaemonSet の Pod(第4回で見た kube-proxy と kindnet)を知らせる警告は、手元でも出ると考えられます。この警告は失敗を表すものではないと考えられます。例の出力では kube-proxy が DaemonSet に管理されていない Pod として並んでいますが、例のクラスタは kind と構成が違い、kube-proxy が DaemonSet で動いていなかったと考えられます。kind では kube-proxy が DaemonSet で動いています(第4回)。手元では、ノードの名前は 1 つ目のワーカーの名前に、Pod の名前は web- で始まる名前になり、今の kubectl では行の文面や語が例と違うこともあると考えられます。

1 つ目の端末で、web の Pod が Terminating になるのは一度に 1 つずつのはずです。「drain と PDB が組み合わさる流れ」で見たとおり、2 つ目の退避は、1 つ目の代わりの Pod が Ready になるまで断られるからです。代わりの Pod が 2 つ目のワーカーに置かれるのは、1 つ目のワーカーが cordon されていて、コントロールプレーンのノードには Taint があるからです。クラスタを作り直した場合は、2 つ目のワーカーではまだ nginx のイメージを取得していないので、最初の代わりの Pod は ContainerCreating が長めになり、そのぶん drain も長く待つと考えられます。

参考:Disruptions | Kubernetes

drain が終わるまでの時間は、主に代わりの Pod が Ready になるまでの時間の 3 回分だと考えられ、数分かかることもあります。5 分ほどたっても終わらなければ、1 つ目の端末で Pending のまま残っている Pod が無いかを確かめます。見つかったら、2 つ目の端末で Ctrl+C を押して drain を止め、kubectl describe pod <Pending の Pod の名前> --context kind-multi-node を実行して、出力の Events: の欄で理由を見ます(第32回)。理由がわからなければ、片付けへ進みます。

参考:kubectl describe | Kubernetes

1 つ目のワーカーで、kube-system などほかの Namespace の Pod(第4回で見た CoreDNS など)が動いていれば、それらも退避させられると考えられます。drain は mirror Pod と DaemonSet の Pod を除くすべての Pod を対象にするからです。第4回でソースコードから読んだとおり、CoreDNS の Pod は既定で 2 つあり、退避されても ReplicaSet が代わりをほかのノードに作ると考えられます。1 つ目の端末は default の Namespace だけを監視しているので、それらは表示されません。drain が emptyDir を使う Pod があることを知らせて止まった場合は、そのような Pod が 1 つ目のワーカーにあると考えられます。この手順の web の Pod は emptyDir を使っていないので、その Pod が何かを確かめてから、--delete-emptydir-data を付けるかどうかを決めます。

参考:kubectl drain | Kubernetes、kubeadm のソースコード(CoreDNS)

drain は、1 つ目のワーカーの Pod が消えれば終わります。最後の代わりの Pod(コマ送り図の web-6)は、drain が終わった直後にはまだ起動中のことがあります。1 つ目の端末でその Pod の READY 列が 1/1 になるまで少し待ってから、2 つ目の端末で web の Pod の置き場所を確かめます。

console
$ kubectl get pods -l app=web -o wide --context kind-multi-node

web の Pod が 3 つ表示され、どれも READY 列が 1/1 で、NODE 列がすべて 2 つ目のワーカーの名前になっているはずです。1 つ目のワーカーは cordon されたままです。custom-columns のコマンドを実行すると、1 つ目のワーカーの UNSCHEDULABLE 列が true になっているはずです。

この段では、次の 3 点が合っていれば成功です。

  • kubectl drain がエラーを出さずに終わる(例と同じなら、最後に drained の語を含む行が出る)
  • 1 つ目の端末で、web の Pod が Terminating になったあと、その代わりの Pod が READY 列 1/1 になるまで、次の web の Pod の Terminating の行が出ない
  • web の 3 つの Pod がすべて 2 つ目のワーカーで READY 列 1/1 になる

置き場所が無いと drain が待ち続ける

次に、公式ドキュメントの例の後半と同じく、代わりの Pod の置き場所が無いときに drain が止まる様子を見ます。1 つ目のワーカーは cordon されたままなので、ここで 2 つ目のワーカーを drain すると、代わりの Pod を置けるノードがありません。2 つ目の端末で、次のコマンドを実行します。

console
$ kubectl drain --ignore-daemonsets <2 つ目のワーカーの名前> --context kind-multi-node

参考:Safely Drain a Node | Kubernetes

web の Pod の 1 つは退避を許され、Terminating になるはずです。その代わりの Pod は、1 つ目の端末で STATUS 列が Pending のまま、NODE 列にもノードの名前が入らないはずです。2 つのワーカーはどちらもスケジュールできず、コントロールプレーンのノードには Taint があるからです。代わりの Pod が Ready にならないので、残りの 2 つの退避は断られ続け、3 つ目の端末の ALLOWED DISRUPTIONS 列は 0 のままのはずです。--timeout を付けていないので、drain は頼み直しを続け、2 つ目の端末のコマンドは終わらないと考えられます。公式のチュートリアルでも、PDB に断られて止まった drain は Ctrl+C で止めています(“Use CTRL-C to terminate kubectl.")。

公式のチュートリアルの例では、PDB に断られて止まった drain の出力は次のとおりです。見るべき行は Cannot evict pod as it would violate the pod's disruption budget. を含む行で、退避が PDB に反するので断られたことを表します。最後の行は退避できない Pod を指していると読めます。WARNING の行の意味は、上の例と同じです。

node "kubernetes-node-i4c4" cordoned

WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4
There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget.
pod/zk-2

参考:Running ZooKeeper, A Distributed System Coordinator | Kubernetes

手元でも、disruption budget という語を含む、退避が PDB に反することを知らせる行が出ると考えられますが、文面と出るタイミングは例と違うことがあります。1 分ほど待って、1 つ目の端末の代わりの Pod が Pending のままで、3 つ目の端末の ALLOWED DISRUPTIONS 列が 0 のままなら、PDB が退避を止めていると判断できます。公式のチュートリアルと同じく、2 つ目の端末で Ctrl+C を押して drain を止めます。チュートリアルによると、drain できなかったノードも cordon されたまま残ります。

続けて、1 つ目のワーカーを uncordon して、代わりの Pod の置き場所を作ります。

console
$ kubectl uncordon <1 つ目のワーカーの名前> --context kind-multi-node

1 つ目の端末で、Pending だった web の Pod の NODE 列に 1 つ目のワーカーの名前が入り、やがて READY 列が 1/1 になるはずです。公式のチュートリアルでも、ノードを uncordon すると、Pending だった Pod がそのノードに置き直されています。3 つ目の端末の ALLOWED DISRUPTIONS 列は、その Pod が Ready になると 1 に戻るはずです。

参考:Running ZooKeeper, A Distributed System Coordinator | Kubernetes、Disruptions | Kubernetes

この段では、次の 3 点が合っていれば成功です。

  • drain のあいだ、web の代わりの Pod が Pending のままで、ALLOWED DISRUPTIONS 列が 0 のまま
  • Ctrl+C を押すまで、kubectl drain が終わらない
  • 1 つ目のワーカーを uncordon すると、Pending だった Pod が 1 つ目のワーカーで READY 列 1/1 になる

片付け

1 つ目の端末と 3 つ目の端末で Ctrl+C を押して、監視を止めます。

2 つ目の端末で、cordon されたままの 2 つ目のワーカーを uncordon します。前の段を飛ばした場合は、1 つ目のワーカーも uncordon します。

console
$ kubectl uncordon <2 つ目のワーカーの名前> --context kind-multi-node

続けて、作った PDB と Deployment を、マニフェストを指定して削除します。Deployment を消すと、その ReplicaSet と Pod も消えます(第6回)。

console
$ kubectl delete -f web-pdb.yaml --context kind-multi-node
$ kubectl delete -f web.yaml --context kind-multi-node

参考:kubectl uncordon | Kubernetes、kubectl delete | Kubernetes

少し待ってから、残っていないかを確かめます。マニフェストでは Deployment と PDB 自身の metadata にラベルを付けていないので、-l app=web では見つかりません。そこで PDB と Deployment は種類全体で、Pod はラベルで調べます。ノードがすべてスケジュールできる状態に戻ったかも確かめます。

console
$ kubectl get pdb,deployments --context kind-multi-node
$ kubectl get pods -l app=web --context kind-multi-node
$ kubectl get nodes -o 'custom-columns=NAME:.metadata.name,UNSCHEDULABLE:.spec.unschedulable,TAINTS:.spec.taints[*].key' --context kind-multi-node

Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。1 つ目と 2 つ目のコマンドで見つからないことを知らせる 1 行が出て、3 つ目のコマンドでどのノードの UNSCHEDULABLE 列にも true が無く、ワーカーの TAINTS 列に node.kubernetes.io/unschedulable が無ければ、片付けは終わっています。

最後に、クラスタを削除し、消えたことを確かめます。続けて次の回を試す場合は、クラスタを残しておいてもかまいません。

console
$ kind delete cluster --name multi-node
$ kind get clusters

kind get clusters に multi-node が表示されなくなったことを確かめます。kubeconfig に kind-multi-node のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-multi-node で消します。

参考:Quick Start | kind、kubectl config | Kubernetes

注意点

PDB と drain でつまずきやすい点と、バージョンで変わる点を、次の表にまとめます。

項目内容
直接の削除は PDB を迂回するDeployment や Pod を削除すると PDB は効かない。PDB が抑えるのは Eviction API を通した退避
ローリングアップデートは PDB で制限されない更新で使えなくなった Pod は PDB の数に入るが、更新の速さは Deployment の maxUnavailable などで決める(第13回)
非自発的な中断は防げないノードの故障や資源不足による退避(第29回)は PDB では防げない。ただし PDB の数には入る
退避を 1 つも許さないと drain が終わらないmaxUnavailable: 0 や、minAvailable を 100% か Pod の数にすると、その Pod のノードの drain は終わらない
unhealthyPodEvictionPolicy の既定は IfHealthyBudgetv1.37 時点の既定で、アプリケーションがすでに中断している(currentHealthy が desiredHealthy を下回る)ときは、Running で Ready でない Pod があると drain が待つことがある。公式ドキュメントは AlwaysAllow を勧めている
PDB のセレクタを重ねない複数の PDB が選ぶ Pod の退避は許されず、Eviction API は 500 を返す
drain のあとは uncordon が要る中断した drain でも、ノードはスケジュールできないまま残る。ノードをクラスタに残すなら kubectl uncordon で戻す
drain したノードにも置かれる Pod があるnode.kubernetes.io/unschedulable を許容する Pod と、nodeName を直接書いた Pod(第31回)。DaemonSet 以外でこの Taint を許容するのは避けるよう書かれている

参考:Disruptions | Kubernetes、Specifying a Disruption Budget for your Application | Kubernetes、API-initiated Eviction | Kubernetes、Safely Drain a Node | Kubernetes、Running ZooKeeper, A Distributed System Coordinator | Kubernetes

関連記事

次の回では PDB や Deployment が属する Namespace で、分けられるものと分けられないものを扱います。PDB のセレクタが空のときに選ぶのも、同じ Namespace の Pod です。

基本仕様 ・ 第37回 Namespace で分けられるもの、分けられないもの1 つのクラスタの中でリソースをグループに分ける Namespace の仕組みを図で説明します。名前の範囲になること、Namespace に属さないクラスタ全体のリソース、既定では Namespace をまたいで届く通信、Namespace を消すと中身も消える流れを押さえ、kind のクラスタで確かめる手順を紹介します。

PDB では抑えられない、ローリングアップデートで同時に止まる Pod の数を決める maxUnavailable と maxSurge については、以下の記事で解説しています。

基本仕様 ・ 第13回 maxSurge と maxUnavailable の違いDeployment のローリングアップデートの速さと安全性を決める maxSurge と maxUnavailable の仕組みを図で説明します。更新中の Pod の数の上限と下限、割合を数に直すときの丸め方、先に増やすか先に減らすかの違いを押さえ、kind のクラスタで値を変えて Pod の入れ替わり方を比べる手順を紹介します。

drain したノードに付く node.kubernetes.io/unschedulable を含む、ノードの側から Pod を遠ざける Taint と Toleration については、以下の記事で解説しています。

基本仕様 ・ 第32回 Taint と Toleration で Pod の配置を制御するノードの側から Pod を遠ざける Taint と、それを許す Pod の Toleration の仕組みを図で説明します。3 つの効果、Toleration が Taint に合う条件、組み込みの Taint と自動で足される Toleration を押さえ、kind のマルチノードのクラスタで Taint を付けたノードに Pod が置かれなくなる様子と、NoExecute で Pod が追い出される様子を確かめる手順を紹介します。

Kubernetes 基本仕様の目次へ