Claude CodeでPodDisruptionBudgetを足しドレインを確かめる
DeploymentにPodDisruptionBudgetとHPAを足したとき、ノードドレインで止まるかをClaude Codeに調べさせる手順です。minAvailableの決め方とkindでの確認までを扱います。
PodDisruptionBudget(PDB)は、ノードのメンテナンスなど自発的な中断のとき、同時に落としてよいPod数を制限するリソースです。Claude Codeにマニフェストへ足させるのは簡単ですが、本当に効くのは足した後の確認です。値の置き方によっては、PDBが原因でkubectl drainが終わらなくなります。この記事では、PDBとHPA(HorizontalPodAutoscaler)をDeploymentに足し、ドレインで止まらないかをClaude Codeに確かめさせる流れを順に追います。
PDBが守るのは自発的な中断だけ
PDBが制限するのは、ノードのドレインやクラスタのアップグレードのような「自発的な中断」です。ハードウェア故障やカーネルパニックのような非自発的な中断は防げません。ドキュメントも、PDBは指定したPod数が常に稼働することを保証するものではないと書いています。
自発的な中断でも、すべてがPDBの対象ではありません。DeploymentやPodを直接削除する操作は、PDBを素通りします。PDBが効くのはEviction API経由の退避で、kubectl drainはこの経路でPodを退避させます。
つまり、Claude Codeに任せる確認の主役はkubectl drainです。kubectl delete podで試してもPDBの検証にはなりません。
minAvailableとmaxUnavailableの決め方
PDBのspecにはselectorが必須で、minAvailableとmaxUnavailableはどちらか一方だけを指定します。迷ったときの指針は、守りたい対象の性質で決まります。
| 対象 | ドキュメントが挙げる考え方 |
|---|---|
| ステートレスなフロントエンド | ドキュメントが挙げる考え方処理能力を10%以上減らさない。minAvailable: 90%など |
| 単一インスタンスのステートフル | ドキュメントが挙げる考え方PDBを使わず停止を許容する。またはmaxUnavailable: 0にして運用側と合意する |
| ZooKeeperやetcdのような複数インスタンス | ドキュメントが挙げる考え方maxUnavailable: 1か、クォーラム数のminAvailable |
| 再実行できるバッチJob | ドキュメントが挙げる考え方PDBを作らない。Jobコントローラーが代わりのPodを作る |
minAvailableとmaxUnavailableの違いは、レプリカ数が変わったときの追従にあります。ドキュメントは、maxUnavailableのほうがコントローラーのレプリカ数の変化に自動で追従するとして推奨しています。次節で扱うHPAと組み合わせるなら、なおさらこちらが向きます。
パーセンテージ指定には丸めのクセがあります。7 PodでminAvailable: "50%"なら、切り上げて4 Podが必要です。maxUnavailableを割合で書いたときも切り上げなので、中断できる数が指定した割合を超えることがあります。レプリカが1つだけの場合、maxUnavailable: 30%でもその1つは中断を許され、実効の停止率は100%になります。
DeploymentにPDBとHPAを足す
ここから、既存のDeploymentに足す形で書きます。Deploymentそのものの書き方は、Claude CodeでKubernetesマニフェストを書く手順にまとめています。
Claude Codeへの指示は、数字を自分で決めて渡すのがコツです。「PDBを足して」とだけ頼むと、minAvailableの値をClaude Codeが選ぶことになります。
「nginx-deploymentにPDBを足して。replicasは3、HPAのminReplicasは3、maxReplicasは6。PDBはmaxUnavailable: 1、selectorはDeploymentのmatchLabelsと同じにして。ドレインで止まらないか、後で確かめるから適用はまだしないで」
返ってくるYAMLの形は、ドキュメントの例に沿った次のような構成になります(例示であり、Claude Codeの実際の出力ではありません)。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: nginx
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deployment
minReplicas: 3
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60PDBのselector.matchLabelsは、Deploymentのselectorと同じ値にします。空のselector({})は、名前空間内のすべてのPodに一致します。
HPAには前提がいくつかあります。CPU使用率で伸縮させるなら、Podテンプレートにresources.requestsが必要です。HPAはこの値を基準に使用率を計算します。リソースメトリクスはmetrics.k8s.io APIから取得し、これは通常metrics-serverが提供するため、クラスタ側に入っていなければなりません。さらにHPAを置いたDeploymentでは、マニフェストにspec.replicasを書き残さないほうが安全です。HPAがその値を管理するためです。
足した後に確認する3つの数字
適用したら、PDBの状態を見ます。確認にはkubectl get poddisruptionbudgetsを使い、出てくる列のうちALLOWED DISRUPTIONSが肝です。この値が1以上なら、コントローラーがPodを数えて、中断してよい数を計算済みです。
kubectl get poddisruptionbudgets
kubectl get poddisruptionbudgets nginx-pdb -o yaml-o yamlで見るstatusには、currentHealthy・desiredHealthy・disruptionsAllowed・expectedPodsが並びます。PDBがReadyのPodだけを健全と数える点は、見落としやすいところです。ReadyでないPodがあるとcurrentHealthyが減り、disruptionsAllowedも縮みます。
ここで確認する数字は次の3つです。
PDB適用後に読む順序
- 1
ALLOWED DISRUPTIONSが0でないか
0なら、いま退避できるPodが1つもありません。ドレインは進みません。
- 2
expectedPodsがreplicasと合っているか
PDBのselectorが想定のPodを捕まえているかの確認です。0や少なすぎる値は、selectorのずれを疑います。
- 3
currentHealthyがdesiredHealthyを上回っているか
差が
disruptionsAllowedになります。差が0なら、その瞬間は余裕がありません。
ドレインが止まる典型パターン
ドキュメントには、ドレインを止めるパターンが明記されています。maxUnavailable: 0かminAvailable: 100%、またはレプリカ数と同じminAvailableを指定すると、自発的な退避はゼロ件になります。このPodが載るノードにkubectl drainをかけても、ドレインは完了しません。ドキュメントはこれを、PDBの意味論どおりの動作として扱っています。
HPAと組み合わせたときの落とし穴は、次の1行に集約できます。
落とし穴
HPAのminReplicasとPDBのminAvailableが同じ値だと、縮小時に退避できない
HPAが縮んでminReplicasに張り付き、PDBがminAvailableにその値を指定していると、ALLOWED DISRUPTIONSは0になります。HPAは負荷が下がると最小まで縮むため、負荷の低い時間帯ほどドレインが詰まりやすくなります。maxUnavailable: 1にしておけば、レプリカ数が何であれ1つは退避できます。
もう1つ、PDBが原因でドレインが詰まる型があります。CrashLoopBackOffのPodや、Readyにならないままのpodです。既定のIfHealthyBudgetでは、健全でない実行中のPodは、アプリが中断状態でない場合にしか退避できません。そのため、壊れたアプリがドレインを塞ぎます。ドキュメントは、ドレイン時にこうしたアプリを退避できるよう、PDBにunhealthyPodEvictionPolicy: AlwaysAllowを設定することを勧めています。この機能はKubernetes 1.31以降で標準です。
spec:
maxUnavailable: 1
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app: nginxkindで複数ノードを作ってドレインを試す
実機で試すなら、kindが手軽です。ドレインはPodを別のノードへ移す操作なので、ワーカーが2台以上のクラスタを作ります。kindの設定ファイルは、role: workerを並べるだけです。
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: workerkind create cluster --config kind-pdb-test.yaml
kubectl apply -f deployment.yaml -f pdb-hpa.yaml
kubectl get pods -o wideドレインの前に、--dry-runを試したくなります。kubectl drainの--dry-runはnone・server・clientの3値を取り、serverは「リソースを保存せずにサーバー側へリクエストを送る」と説明されています。ただしドキュメントには、このserverのドライランがPDBによる退避拒否まで再現するという記述がありません。したがって、PDBで詰まるかどうかの判断はALLOWED DISRUPTIONSで行い、--dry-runは対象Podの一覧を見る用途にとどめます。
実際のドレインは、タイムアウトを付けて実行します。
kubectl drain kind-worker --ignore-daemonsets --timeout=60s
kubectl get pods -o wide
kubectl uncordon kind-worker--ignore-daemonsetsは、DaemonSet管理のPodを無視して進めるためのフラグです。付けないと、DaemonSetのPodがあるノードではドレインが進みません。--timeoutを付けるのは、PDBに塞がれるとドレインが再試行を続けるからです。ドキュメントによれば、退避リクエストは一時的に拒否されることがあり、kubectlは対象Podがすべて終了するか設定したタイムアウトに達するまで、失敗したリクエストを定期的に再試行します。--timeoutは0を指定すると無限に待つ設定なので、Claude Codeに任せるコマンドでは必ず秒数を入れます。
終わったらkubectl uncordonでノードを戻します。ドレインしたノードはスケジュール対象から外れたままだからです。
確認をClaude Codeに回させる
ここまでの確認は、1回のプロンプトにまとめて回せます。Claude Codeは各コマンドの出力を読みながら進められるので、ドレインが詰まったときに原因の候補まで一緒に追わせられます。
「kind-pdb-testクラスタで、PDBのALLOWED DISRUPTIONSを確認してから、ワーカー1台をタイムアウト60秒でドレインして。詰まったら、kubectl describeでPDBとPodの状態を見て原因を説明して。終わったらuncordonして、その結果をまとめて」
非対話で回したい場合は、claude -pにプロンプトを渡し、--allowedToolsで許可するコマンドを絞ります。
claude -p "PDBのALLOWED DISRUPTIONSを確認し、結果を要約して" \
--allowedTools "Bash(kubectl get *)"書き込みを伴うコマンドの扱いは、権限ルールで線を引きます。.claude/settings.jsonには、読み取りだけを許可リストに入れ、drainとapplyは確認を残します。
{
"permissions": {
"allow": [
"Bash(kubectl get *)",
"Bash(kubectl describe *)"
],
"ask": [
"Bash(kubectl drain *)",
"Bash(kubectl apply *)"
]
}
}askはallowより優先されます。マッチするaskルールがあれば、より狭いallowルールがあっても確認が出ます。kubectl drainは退避と配置換えを伴い、検証用のkindなら構いませんが、実クラスタでは意図しない対象に当たりかねません。本番の権限設計については、Kubernetes MCPサーバーの権限設計に読み取り専用で繋ぐ考え方がまとまっています。
前提となるクラスタ名と対象の名前空間は、CLAUDE.mdに書いておきます。
## Kubernetes検証ルール
- 検証クラスタは kind-pdb-test のみ。他のコンテキストでは drain / apply しない
- PDB は maxUnavailable を使う。minAvailable は HPA の minReplicas と同値にしない
- drain は必ず --ignore-daemonsets --timeout=60s を付ける
- drain の前に kubectl get poddisruptionbudgets を実行して結果を出すこうしておけば、セッションが変わってもコマンドの付け方がぶれません。GitOpsでマニフェストを配る構成なら、Claude CodeでArgoCD Applicationマニフェストを書く手順のように、適用の経路ごと整えることもできます。クラスタ自体を会話で操作する方法は、Kubernetes MCPサーバーでClaudeからクラスタを操作するで扱っています。
詰まったときの切り分け
ドレインが終わらないとき、見る順序は決まっています。
| 症状 | 疑う場所 | 確認コマンド |
|---|---|---|
| ドレインがいつまでも終わらない | 疑う場所ALLOWED DISRUPTIONSが0 | 確認コマンドkubectl get poddisruptionbudgets |
ALLOWED DISRUPTIONSが0のまま | 疑う場所HPAがminReplicasまで縮んでいる、またはminAvailableがレプリカ数と同じ | 確認コマンドkubectl get hpa、PDBのspec |
expectedPodsが0 | 疑う場所PDBのselectorがPodのlabelと合わない | 確認コマンドkubectl get pods --show-labels |
| 特定のPodだけ退避されない | 疑う場所CrashLoopBackOffやNotReadyの状態でbudgetを使い切っている | 確認コマンドkubectl describe pod、unhealthyPodEvictionPolicy |
| DaemonSetのPodで止まる | 疑う場所--ignore-daemonsetsが無い | 確認コマンドドレインのエラーメッセージ |
--disable-evictionを付ければPDBを無視して進められますが、PDBのチェックを飛ばす危険なフラグです。止まる原因を直すより先に使うものではありません。
まとめ
PDBは書くだけでは完成せず、ALLOWED DISRUPTIONSを読み、タイムアウト付きでドレインして初めて確かめられます。HPAを足すならmaxUnavailable: 1を基本にし、minAvailableはクォーラムのように数が決まっている対象に限ると、縮小時の詰まりを避けやすくなります。Claude Codeには、数字を決めて渡し、読み取りは許可リスト、drainとapplyは確認付きという分担で回させます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- kubernetes.io/docs/tasks/run-application/configure-pdb
- kubernetes.io/docs/tasks/administer-cluster/safely-drain-node
- kubernetes.io/docs/concepts/workloads/pods/disruptions
- kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autosca…
- kubernetes.io/docs/reference/kubectl/generated/kubectl_drain
- kind.sigs.k8s.io/docs/user/quick-start
- code.claude.com/docs/en/permissions
- code.claude.com/docs/en/headless