問題:メンテナンスのデッドロック
ノードをドレイン(drain)しようとして、ターミナルがいつまでも停止したままになったことはありませんか?OSのパッチ適用やインスタンスのサイズ変更など、標準的なノードのメンテナンスを行っている最中かもしれませんが、処理が一向に進まないことがあります。kubectl drain <node-name>を実行しても、ノードが空になる代わりに、数秒おきに次のようなエラーがループして表示されます。
error when evicting pods/"api-gateway-v2-7c4d98bf9-xyz1" (assigned to "worker-node-01"):
Cannot evict pod as it would violate the pod's disruption budget.
evicting pod default/api-gateway-v2-7c4d98bf9-xyz1
error when evicting pods/"api-gateway-v2-7c4d98bf9-xyz1": Cannot evict pod...
Kubernetesがこのような挙動を示すのは、PodDisruptionBudget (PDB) が「アプリケーションをオンラインに保つ」という唯一の任務を遂行しているからです。しかし、設定が厳格すぎると、安全策であるはずのPDBが、クラスター自体のハードウェア管理を妨げる障壁(バリケード)になってしまうことがあります。
ステップ1:ブロックしているPDBを特定する
まずは、ドレインを止めている特定のPDBを見つけます。影響を受けている名前空間で次のコマンドを実行してください。
kubectl get pdb -n <your-namespace>
出力結果の列を確認することが重要です。特にALLOWED DISRUPTIONSに注目してください。この値が0の場合、Eviction APIはその予算によって管理されているポッドには一切手を出しません。例外はありません。
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
api-gateway-pdb 1 N/A 0 12d
ステップ2:なぜ停止(Disruption)がブロックされているのか?
許可された停止(Allowed disruptions)がゼロになる原因は、主に次の3つのシナリオに集約されます。
1. シングルレプリカの罠
Deploymentのreplicas: 1で、PDBがminAvailable: 1に設定されている場合、デッドロックが発生します。ポッドを移動させるには、Kubernetesは現在のポッドを削除する必要があります。しかし、削除するとカウントが0になり、「最小1」というルールに違反してしまいます。システムは、一時的なダウンタイムを引き起こしてまで新しいポッドのためのスペースを作るために既存のポッドを削除することはありません。
2. ヘルス容量の不足
3つのレプリカを持つDeploymentがあり、PDBでminAvailable: 3が必要だとします。もし、すでに1つのポッドがCrashLoopBackOffやImagePullBackOffなどで失敗している場合、正常なポッドは2つしかありません。すでに必要最小限を下回っているため、Kubernetesはさらなる劣化を防ぐために残りのポッドをロックします。
3. 「100%」設定のミス
minAvailable: 100%を設定すると、メンテナンスのためにポッドを1つもオフラインにしたくないという指示になります。分散システムにおいてこれが実際に求められるケースは稀であり、手動での介入なしにはノードのアップグレードができなくなってしまいます。
ステップ3:ブロックを解消する
現在のリスク許容度に合わせて解決策を選択してください。
解決策A:一時的なスケールアップ(最もクリーンな修正)
ドレインのブロックを解除する最も安全な方法は、予算に余裕を持たせることです。PDBで1つの利用可能なポッドが必要で、現在1つしかない場合は、Deploymentを2にスケールします。2つ目のポッドが別のノードでReady状態になれば、ALLOWED DISRUPTIONSが1に変わり、ドレインが自動的に進行します。
kubectl scale deployment api-gateway --replicas=2 -n <namespace>
解決策B:maxUnavailableへの変更
小規模なワークロードの場合、maxUnavailableの方が直感的なことが多いです。PDBを編集して1つのポッドの停止を許可するように変更する方が、スケールダウン時に特定の数のポッドの維持を強制するよりも安全です。
# 編集のためにPDBを開く
kubectl edit pdb api-gateway-pdb -n <namespace>
# minAvailableを以下に置き換える:
maxUnavailable: 1
解決策C:「最終手段」オプション(最も早い修正)
メンテナンス時間中で、30秒程度の瞬断を許容できる場合は、単にPDBを削除してください。kubectl drainコマンドは即座にポッドを退去(evict)させます。ノードが復旧した後にPDBを再作成できます。
kubectl delete pdb api-gateway-pdb -n <namespace>
ステップ4:検証
最後にもう一度PDBの状態を確認します。ALLOWED DISRUPTIONSが1以上になっていることを確認してください。
kubectl get pdb -n <namespace>
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
api-gateway-pdb N/A 1 1 12d
これでkubectl drainのプロセスが再開され、ポッドが他のノードに移動し、対象のノードがシャットダウン前にSchedulingDisabled状態に移行できるようになります。
次回に向けた教訓
- minAvailable: 100% を避ける: クラスターが停止する原因になります。50%や80%などのパーセンテージを使用しましょう。
- 単一ポッドにPDBは不要: 2つ以上のレプリカにスケールできないアプリの場合、
minAvailable: 1のPDBを設定するとアップグレード時に手間が増えるだけです。 - maxUnavailable: 1 を使用する: 小規模なクラスターでは、
minAvailableの値を計算するよりも一般的に回復力が高くなります。 - 許可された停止(Allowed Disruptions)を監視する: Prometheusのアラートなどを使用して、メンテナンスを開始する前に許可された停止が0になっているPDBを見つけられるようにしましょう。

