Claude Media
Claude CodeでPodDisruptionBudgetを足しドレインを確かめる

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: 60

PDBの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. 1

    ALLOWED DISRUPTIONSが0でないか

    0なら、いま退避できるPodが1つもありません。ドレインは進みません。

  2. 2

    expectedPodsがreplicasと合っているか

    PDBのselectorが想定のPodを捕まえているかの確認です。0や少なすぎる値は、selectorのずれを疑います。

  3. 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: nginx

kindで複数ノードを作ってドレインを試す

実機で試すなら、kindが手軽です。ドレインはPodを別のノードへ移す操作なので、ワーカーが2台以上のクラスタを作ります。kindの設定ファイルは、role: workerを並べるだけです。

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
kind 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は確認付きという分担で回させます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. kubernetes.io/docs/tasks/run-application/configure-pdb
  2. kubernetes.io/docs/tasks/administer-cluster/safely-drain-node
  3. kubernetes.io/docs/concepts/workloads/pods/disruptions
  4. kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autosca…
  5. kubernetes.io/docs/reference/kubectl/generated/kubectl_drain
  6. kind.sigs.k8s.io/docs/user/quick-start
  7. code.claude.com/docs/en/permissions
  8. code.claude.com/docs/en/headless
この記事を共有:XはてブLinkedIn