Disruption

このガイドは、高可用性アプリケーションを構築したいと考えており、そのために、Podに対してどのような種類のDisruptionが発生する可能性があるか理解する必要がある、アプリケーション所有者を対象としたものです。

また、クラスターのアップグレードやオートスケーリングなどのクラスターの操作を自動化したいクラスター管理者も対象にしています。

自発的なDisruptionと非自発的なDisruption

Podは誰か(人やコントローラー)が破壊するか、避けることができないハードウェアまたはシステムソフトウェアエラーが発生するまで、消えることはありません。

これらの不可避なケースをアプリケーションに対する非自発的なDisruptionと呼びます。 例えば:

  • ノードのバックエンドの物理マシンのハードウェア障害
  • クラスター管理者が誤ってVM(インスタンス)を削除した
  • クラウドプロバイダーまたはハイパーバイザーの障害によってVMが消えた
  • カーネルパニック
  • クラスターネットワークパーティションが原因でクラスターからノードが消えた
  • ノードのリソース不足によるPodの退避

リソース不足を除いて、これら条件は全て、大半のユーザーにとって馴染みのあるものでしょう。 これらはKubernetesに固有のものではありません。

それ以外のケースのことを自発的なDisruptionと呼びます。 これらはアプリケーションの所有者によって起こされたアクションと、クラスター管理者によって起こされたアクションの両方を含みます。 典型的なアプリケーションの所有者によるアクションには次のものがあります:

  • Deploymentやその他のPodを管理するコントローラーの削除
  • 再起動を伴うDeployment内のPodのテンプレートの更新
  • Podの直接削除(例:アクシデントによって)

クラスター管理者のアクションには、次のようなものが含まれます:

  • 修復やアップグレードのためのノードのドレイン
  • クラスターのスケールダウンのためにクラスターからノードをドレインする(クラスター自動スケーリングについて学ぶ)。
  • そのノードに別のものを割り当てることができるように、ノードからPodを削除する。

これらのアクションはクラスター管理者によって直接実行されるか、クラスター管理者やクラスターをホスティングしているプロバイダーによって自動的に実行される可能性があります。

クラスターに対して自発的なDisruptionの要因となるものが有効になっているかどうかについては、クラスター管理者に聞くか、クラウドプロバイダーに相談または配布文書を参照してください。 有効になっているものが何もなければ、Pod Disruption Budgetの作成はスキップすることができます。

Disruptionへの対応

非自発的なDisruptionを軽減する方法をいくつか紹介します:

  • Podは必要なリソースを要求するようにする。
  • 高可用性が必要な場合はアプリケーションをレプリケートする。(レプリケートされたステートレスおよびステートフルアプリケーションの実行について学ぶ。)
  • レプリケートされたアプリケーションを実行する際にさらに高い可用性を得るには、(アンチアフィニティを使って)ラックを横断して、または(マルチゾーンクラスターを使用している場合には)ゾーンを横断してアプリケーションを分散させる。

自発的なDisruptionの頻度は様々です。 基本的なKubernetesクラスターでは、自動で発生する自発的なDisruptionはありません(ユーザーによってトリガーされたものだけです)。 しかし、クラスター管理者やホスティングプロバイダーが何か追加のサービスを実行して自発的なDisruptionが発生する可能性があります。 例えば、ノード上のソフトウェアアップデートのロールアウトは自発的なDisruptionの原因となります。 また、クラスター(ノード)自動スケーリングの実装の中には、ノードのデフラグとコンパクト化のために自発的なDisruptionを伴うものがあります。 クラスタ管理者やホスティングプロバイダーは、自発的なDisruptionがある場合、どの程度のDisruptionが予想されるかを文書化しているはずです。 Podのspecの中でPriorityClassesを使用している場合など、特定の設定オプションによっても自発的(および非自発的)なDisruptionを引き起こす可能性があります。

Pod Disruption Budget

FEATURE STATE: Kubernetes v1.21 [stable]

Kubernetesは、自発的なDisruptionが頻繁に発生する場合でも、可用性の高いアプリケーションの運用を支援する機能を提供しています。

アプリケーションの所有者として、各アプリケーションに対してPodDisruptionBudget (PDB)を作成することができます。 PDBは、レプリカを持っているアプリケーションのうち、自発的なDisruptionによって同時にダウンするPodの数を制限します。 例えば、クォーラムベースのアプリケーションでは、実行中のレプリカの数がクォーラムに必要な数を下回らないようにする必要があります。 Webフロントエンドは、負荷に対応するレプリカの数が、全体に対して一定の割合を下回らないようにしたいかもしれません。

クラスター管理者やホスティングプロバイダーは、直接PodやDeploymentを削除するのではなく、Eviction APIを呼び出す、PodDisruptionBudgetsに配慮したツールを使用すべきです。

例えば、kubectl drainサブコマンドはノードを休止中とマークします。 kubectl drainを実行すると、ツールは休止中としたノード上の全てのPodを退避しようとします。 kubectlがあなたの代わりに送信する退避要求は一時的に拒否される可能性があるため、ツールは対象のノード上の全てのPodが終了するか、設定可能なタイムアウト時間に達するまで、全ての失敗した要求を定期的に再試行します。

PDBはアプリケーションの意図したレプリカ数に対して、許容できるレプリカの数を指定します。 例えば.spec.replicas: 5を持つDeploymentは常に5つのPodを持つことが想定されます。 PDBが同時に4つまでを許容する場合、Eviction APIは1度に(2つではなく)1つのPodの自発的なDisruptionを許可します。

アプリケーションを構成するPodのグループは、アプリケーションのコントローラー(Deployment、StatefulSetなど)が使用するものと同じラベルセレクターを使用して指定されます。

"意図した"Podの数は、これらのPodを管理するワークロードリソースの.spec.replicasから計算されます。 コントロールプレーンはPodの.metadata.ownerReferencesを調べることで、所有しているワークロードリソースを見つけます。

非自発的なDisruptionはPDBによって防ぐことができません; しかし、予算にはカウントされます。

アプリケーションのローリングアップデートによって削除または利用できなくなったPodはDisruptionの予算にカウントされますが、ローリングアップグレードを実行している時は(DeploymentやStatefulSetなどの)ワークロードリソースはPDBによって制限されません。 代わりに、アプリケーションのアップデート中の障害のハンドリングは、個々のワークロードリソースに対するspecで設定されます。

ノードのドレイン中に動作がおかしくなったアプリケーションの退避をサポートするために、Unhealthy Pod Eviction PolicyAlwaysAllowを設定することを推奨します。 既定の動作は、ドレインを継続する前にアプリケーションPodがhealthyな状態になるまで待機します。

Eviction APIを使用してPodを退避した場合、PodSpecで設定したterminationGracePeriodSecondsに従って正常に終了します。

PodDisruptionBudgetの例

node-1からnode-3まで3つのノードがあるクラスターを考えます。 クラスターにはいくつかのアプリケーションが動いています。 それらのうちの1つは3つのレプリカを持ち、最初はpod-apod-bそしてpod-cと名前が付いています。 もう一つ、これとは独立したPDBなしのpod-xと呼ばれるものもあります。 初期状態ではPodは次のようにレイアウトされています:

node-1 node-2 node-3
pod-a available pod-b available pod-c available
pod-x available

3つのPodはすべてDeploymentの一部で、これらはまとめて1つのPDBを持ち、3つのPodのうちの少なくとも2つが常に存在していることを要求します。

例えばクラスター管理者がカーネルのバグを修正するために、再起動して新しいカーネルバージョンにしたいとします。 クラスター管理者はまず、kubectl drainコマンドを使ってnode-1をドレインしようとします。 ツールはpod-apod-xを退避しようとします。 これはすぐに成功します。 2つのPodは同時にterminating状態になります。 これにより、クラスターは次のような状態になります:

node-1 draining node-2 node-3
pod-a terminating pod-b available pod-c available
pod-x terminating

DeploymentはPodの1つが終了中であることに気づき、pod-dという代わりのPodを作成します。 node-1はcordonされたため、別のノードに展開されます。 また、pod-xの代わりとしてpod-yも作られました。

(備考: StatefulSetの場合、pod-apod-0のように呼ばれ、代わりのPodが作成される前に完全に終了する必要があります。 この代わりのPodは、UIDは異なりますが、同じpod-0という名前になります。 それを除けば、本例は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

この時点で、せっかちなクラスター管理者がnode-2node-3をドレインしようとすると、Deploymentの利用可能なPodは2つしかなく、また、PDBによって最低2つのPodが要求されているため、drainコマンドはブロックされます。 しばらくすると、pod-dが使用可能になります。

クラスターの状態はこのようになります:

node-1 drained node-2 node-3
pod-b available pod-c available
pod-d available pod-y

ここでクラスター管理者がnode-2をドレインしようとします。 drainコマンドは2つのPodをなんらかの順番で退避しようとします。 例えば最初にpod-b、次にpod-dとします。 pod-bについては退避に成功します。 しかしpod-dを退避しようとすると、Deploymentに対して利用可能なPodは1つしか残らないため、退避は拒否されます。

Deploymentはpod-bの代わりとしてpod-eを作成します。 クラスターにはpod-eをスケジューリングする十分なリソースがないため、ドレインは再びブロックされます。 クラスターは次のような状態になります:

node-1 drained node-2 node-3 no node
pod-b terminating pod-c available pod-e pending
pod-d available pod-y

この時点で、クラスター管理者はアップグレードを継続するためにクラスターにノードを追加する必要があります。

KubernetesがどのようにDisruptionの発生率を変化させているかについては、次のようなものから知ることができます:

  • いくつのレプリカをアプリケーションが必要としているか
  • インスタンスのグレースフルシャットダウンにどれくらいの時間がかかるか
  • 新しいインスタンスのスタートアップにどれくらいの時間がかかるか
  • コントローラーの種類
  • クラスターリソースのキャパシティ

Pod Disruption Condition

FEATURE STATE: Kubernetes v1.26 [beta]

有効にすると、専用のPod DisruptionTarget Conditionが追加されます。 これはPodがDisruptionによって削除されようとしていることを示すものです。 Conditionのreasonフィールドにて、追加で以下のいずれかをPodの終了の理由として示します:

PreemptionByScheduler
Podはより高い優先度を持つ新しいPodを収容するために、スケジューラーによってプリエンプトされる予定です。 詳細についてはPodの優先度とプリエンプションを参照してください。
DeletionByTaintManager
Podが許容しないNoExecute taintによって、Podは(kube-controller-managerの中のノードライフサイクルコントローラーである)Taintマネージャーによって削除される予定です。 taintベースの退避を参照してください。
EvictionByEvictionAPI
PodはKubernetes APIを使用して退避するようにマークされました。
DeletionByPodGC
すでに存在しないノードに紐づいているPodのため、Podのガベージコレクションによって削除される予定です。
TerminationByKubelet
node-pressureによる退避またはGraceful Node Shutdownのため、Podはkubeletによって終了させられました。

フィーチャーゲートPodDisruptionConditionsを有効にすると、Podのクリーンアップと共に、Podガベージコレクタ(PodGC)が非終了フェーズにあるPodを失敗とマークします。 (Podガベージコレクションも参照してください)。

Job(またはCronJob)を使用している場合、JobのPod失敗ポリシーの一部としてこれらのPod Disruption Conditionを使用したいと思うかもしれません。

クラスターオーナーとアプリケーションオーナーロールの分離

多くの場合、クラスター管理者とアプリケーションオーナーは、互いの情報を一部しか持たない別の役割であると考えるのが便利です。 このような責任の分離は、次のようなシナリオで意味を持つことがあります:

  • 多くのアプリケーションチームでKubernetesクラスターを共有していて、役割の専門化が自然に行われている場合
  • クラスター管理を自動化するためにサードパーティのツールやサービスを使用している場合

Pod Disruption Budgetはロール間のインターフェースを提供することによって、この役割の分離をサポートします。

もしあなたの組織でこのような責任の分担がなされていない場合は、Pod Disruption Budgetを使用する必要はないかもしれません。

クラスターで破壊的なアクションを実行する方法

あなたがクラスターの管理者で、ノードやシステムソフトウェアのアップグレードなど、クラスター内のすべてのノードに対して破壊的なアクションを実行する必要がある場合、次のような選択肢があります:

  • アップグレードの間のダウンタイムを許容する。
  • もう一つの完全なレプリカクラスターにフェールオーバーする。
    • ダウンタイムはありませんが、重複するノードと、切り替えを調整する人的労力の両方のコストがかかる可能性があります。
  • Disruptionに耐性のあるアプリケーションを書き、PDBを使用する。
    • ダウンタイムはありません。
    • リソースの重複は最小限です。
    • クラスター管理をより自動化できます。
    • Disruptionに耐えうるアプリケーションを書くことは大変ですが、自発的なDisruptionに耐えうるようにするための作業は、非自発的なDisruptionに耐えうるために必要な作業とほぼ重複しています。

次の項目