Claude CodeでKubernetesマニフェストを書く手順
Claude CodeでDeploymentとServiceのYAMLを書き、kubectl diffとdry-runで検証してから適用する手順をまとめます。
Claude CodeにDeploymentとServiceのマニフェストを書かせると、YAMLの構文だけでなくselectorとmatchLabelsの対応関係まで一度に整えられます。書いたその場でkubectl diffや--dry-runにかけられるのが、エディタで書く場合との違いです。この記事ではDeploymentとServiceの最小構成から、適用前の検証手順までを追います。
Claude CodeでKubernetesマニフェストを書く前に
Kubernetesマニフェストとは、クラスタに作りたい状態をYAMLで宣言的に記述したファイルです。Claude CodeはこのYAMLをプロジェクトのファイルとして直接編集し、保存した直後にkubectlで検証まで実行できます。手で書く作業と違うのは、フィールド名やインデントの間違いをターミナルの実行結果を見ながらすぐ直せる点です。
前提として、ローカルにkubectlをインストールし、検証用のクラスタ(minikubeやkind、あるいは実クラスタ)にアクセスできる状態にしておきます。クラスタへの接続情報そのものをClaude Codeに渡してMCP経由で操作する方法は、Kubernetes MCPサーバーでClaudeからクラスタを操作するで扱っている話です。本記事はファイルとしてマニフェストを書く作業に絞ります。書いたマニフェストに実行ユーザーや書き込み制限を足す手順は、securityContextを足してkube-linterで確かめる記事にあります。
ステップ1: Deploymentマニフェストを書く
DeploymentはapiVersion: apps/v1、kind: Deployment、spec.replicas、spec.selector.matchLabels、spec.templateの5つを埋めれば最小構成として動きます。Claude Codeへは次のように指示すれば、この構成を守ったYAMLが返ってきます。
「nginxのDeploymentをapps/v1で書いて。replicasは3、labelはapp: nginxで統一して」
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80spec.selector.matchLabelsはPodテンプレートのlabelsと一致させるフィールドで、ReplicaSetがどのPodを管理するかをここで決めます。公式ドキュメントは、このフィールドを作成後に変更できないimmutableな値として扱い、他のDeploymentやStatefulSetのselectorと重複させないよう注意しています。
ロールアウトの方式はspec.strategy.typeで指定します。値はRollingUpdateとRecreateの2択で、指定しなければRollingUpdateが既定です。RollingUpdateのmaxUnavailableとmaxSurgeはどちらも既定25%で、maxSurgeを0にする場合はmaxUnavailableを0にできません。ロールアウトが進まないと感じたら、まずこの2つの値を確認します。
フィールドの意味に自信が無いときは、Claude Codeにkubectl explain deployment.spec.strategyのようなコマンドを実行させます。クラスタが実際に認識しているAPIスキーマの説明がその場で返るので、フィールド名から役割を推測するより確実です。
ステップ2: Podのヘルスチェックとリソース制限を追加する
DeploymentのPodテンプレートにresources.requests/limitsとプローブを足しておくと、リソース超過や起動失敗のときの挙動を制御できます。Claude Codeに追加を頼む前に、アプリが実際に使うCPUとメモリの量を把握しておくと、requestsとlimitsの値を決め直す手戻りが減ります。実測から値を決める手順はClaude CodeでKubernetesのrequests・limitsを実測から見直すにあります。
resources.requests.cpuとrequests.memoryはスケジューリング時に使われる値で、CPUリクエストは複数コンテナがCPUを奪い合うときの重み付けとして働きます。limitsを超えたときの挙動はCPUとメモリで異なります。CPU limitを超えると、カーネルがCPUへのアクセスを制限するスロットリングが働くだけでコンテナは終了しません。メモリlimitを超えると、カーネルがOOM(Out of Memory)としてコンテナを終了させることがあります。
resources:
requests:
cpu: "250m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "128Mi"プローブは3種類あり、失敗したときに起きることがそれぞれ違います。
3種類のプローブ
livenessProbe
失敗を検知するとkubeletがコンテナを終了して再起動します。デッドロックのようにプロセスは生きていても応答しない状態を拾う用途です。
readinessProbe
失敗を返している間、そのPodはServiceからのトラフィックを受け取りません。コンテナは再起動されないので、起動直後や一時的な過負荷の保護に向きます。
startupProbe
起動に時間がかかるアプリ向けで、一度成功するとlivenessProbeに引き継ぎます。成功しないままだと、公式の例では300秒でコンテナが終了し、restartPolicyに従います。
livenessProbeとreadinessProbeは互いに依存しません。起動直後だけ待たせたいならinitialDelaySecondsを使います。startupProbeの待ち時間はfailureThreshold × periodSecondsで決まるので、最悪の起動時間を覆う値にします。
readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 15
periodSeconds: 10ステップ3: Serviceマニフェストを書く
ServiceのtypeはClusterIP・NodePort・LoadBalancer・ExternalNameの4種類で、何も指定しなければClusterIPが既定です。Podへ外部からどこまで到達させたいかで選びます。
Serviceのtype 4種
ClusterIP(既定)
クラスタ内部専用のIPを割り当てます。外からは届きません。
NodePort
各NodeのIPで静的ポートを開き、クラスタ外からNode経由で届きます。内部的にClusterIPも作られます。
LoadBalancer
外部ロードバランサーを使います。ロードバランサー自体はクラウド連携かクラスタ外の実装が必要で、Serviceだけでは外部到達が成立しません。
ExternalName
selectorを持たず、DNSのCNAMEとして外部ホスト名へ対応づけるだけです。プロキシは挟みません。
spec.clusterIPを"None"にすると、IPを割り当てないheadless Serviceになります。Pod単位で直接名前解決したい構成で使う値です。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80port・targetPort・protocolの3点はServiceの通信経路を決めるフィールドです。既定のprotocolはTCPで、targetPortを省略するとportと同じ値が使われます。Podのコンテナポートに名前を付けておけば、targetPortにその名前を指定できます。
ステップ4: kubectl diffとdry-runで検証する
書いたマニフェストは、クラスタへ反映する前にkubectl apply --dry-runとkubectl diffにかけます。Claude Codeには、この順序で実行して結果を読ませます。
適用前から適用後までの順序
- 1
dry-runでYAMLを読ませる
kubectl apply --dry-run=client -f deployment.yaml -o yaml手元でYAMLを読んで表示するだけの段階です。構文とスキーマの誤りの確認に使います。
- 2
diffでクラスタとの差を見る
kubectl diff -f deployment.yaml現在のクラスタの状態と、適用した場合の状態の差が出ます。
- 3
applyして完了を待つ
kubectl apply -f deployment.yaml kubectl rollout status deployment/nginx-deployment成功すると
successfully rolled outと表示され、終了コードは0です。
dry-runとdiffは何を確かめてくれるか
--dry-runはnone・server・clientの3値を取ります。kubectl apply --helpの説明では、clientは「送るはずのオブジェクトを表示するだけ」、serverは「リソースを保存せずにサーバー側へリクエストを送る」です。
この違いがラベルの不一致に効きます。公式ドキュメントは「.spec.selectorが.spec.template.metadata.labelsと一致しなければAPIに拒否される」と書いています。拒否するのはAPIサーバーなので、クラスタに送らないclientでは出てきません。--dry-run=serverかkubectl diffまで進めて初めて確かめられます。
kubectl diffはkubectl diff --helpで、出力が常にYAMLであること、終了コードが次の意味であることが示されます。
- 0: 差分なし
- 1: 差分あり
- 2以上: kubectlまたはdiffがエラーで失敗
差分があると終了コード1になるため、Claude Codeにdiffを実行させた結果が「1」で返っても、エラーとは限りません。出力にYAMLの差分が付いていれば、成功した差分検出です。
もう1点、diffは読み取りだけの操作ではありません。公式ドキュメントによれば、diffはサーバー側ドライランを使うため、kube-apiserverでの有効化と、PATCH・CREATE・UPDATEの権限が必要です。実際には保存されなくても、権限は書き込み系で要ります。
kubectl applyは、設定ファイルとクラスタ上の実際の状態、そしてkubectl.kubernetes.io/last-applied-configurationアノテーションの3つを比べてパッチを計算します。このアノテーションに残っていてファイルから消えたフィールドは削除対象になり、ファイル内の値がクラスタ上の値と異なるフィールドは更新対象になる仕組みです。kubectl createで作ったオブジェクトをapplyで管理するなら、先にkubectl replace --save-config -f <filename>でこのアノテーションを付けます。ディレクトリ単位で作成・更新したいときはkubectl apply -f <directory>を使います。対象から外したオブジェクトも削除したいときはkubectl apply -f <directory> --pruneです。--pruneは削除を伴うので、-lでラベルを絞って慎重に実行します。削除だけを明示したい場合は、kubectl delete -f <filename>が公式の推奨手順です。
ロールアウトが止まったまま気づかれない事態は、spec.progressDeadlineSecondsで防げます。この秒数を超えて進まなければ、停滞がDeploymentのステータスに現れます。原因の切り分けは、後ろの症状表にまとめています。
本番クラスタの権限をどこまで渡すかは別の設計課題です。Kubernetes MCPサーバーの権限設計で、MCP経由の読み取り専用接続の考え方を扱っています。
CLAUDE.mdにクラスタの前提を書いておく
クラスタ名・namespace・命名規則といった前提は、都度チャットで伝えるのでなくプロジェクトのCLAUDE.md(./CLAUDE.mdまたは./.claude/CLAUDE.md)に書いておくと、セッションをまたいで引き継がれます。たとえば「本番はnamespace: prod、検証はnamespace: staging」のように書いておけば、生成されるマニフェストのmetadata.namespaceが毎回ぶれなくなります。
検証コマンドの実行確認を毎回省きたい場合は、.claude/settings.jsonのpermissions.allowにルールを足します。構文はBash(npm run lint)のような文字列パターンで、末尾に半角スペースと*を付けるとコマンドの先頭一致になります。
{
"permissions": {
"allow": [
"Bash(kubectl diff *)",
"Bash(kubectl apply --dry-run=client *)"
]
}
}どちらもリソースを保存しないので許可リストに向きますが、diffは権限の確認が別に要ります。状態を変えるkubectl apply本体は許可リストに入れず、実行前の確認を毎回残します。
権限ルールの照合には、公式の説明にある癖が3つあります。
- 末尾の
*の前にスペースがあると、引数なしのkubectl diffにも一致します &&や;でつないだコマンドは、サブコマンドごとに別々に照合されます。kubectl diff -f a.yaml && kubectl apply -f a.yamlなら、diffは通ってもapplyの確認は残ります- askルールはallowより優先されます。
askにBash(kubectl apply *)を入れると、allowに書いたdry-run付きのapplyも確認が出ます
一度きりの許可なら、起動時に--allowedToolsで渡す方法もあります。claude --help(Claude Code v2.1.289)には、例として"Bash(git *) Edit"の形が出ます。
書いたマニフェストをGitで管理する場合は、Claude CodeでPRを作成する手順に沿ってコミットからPR作成までを任せる流れに乗せられます。インフラをTerraformで管理しているなら、Claude CodeでTerraformのコードを書く手順でも同じ検証ループを使えます。古いapiVersionの洗い出しはClaude Codeとplutoの移行手順に分けています。
症状からどこを見るか
適用後に動かないときは、症状ごとに見る場所が決まります。Claude Codeには、症状とkubectl describeの出力を一緒に渡して原因を絞らせます。調査用の読み取り専用の権限はCrashLoopBackOffの切り分けにまとめています。
| 症状 | 疑う場所 | 見るもの |
|---|---|---|
| ロールアウトが進まない | 疑う場所クォータ・readinessProbe・イメージのpull・権限・LimitRange・起動設定 | 見るものkubectl rollout status、progressDeadlineSeconds |
| Podが繰り返し再起動する | 疑う場所livenessProbeの失敗、またはメモリlimitのOOM | 見るものkubectl describe podの再起動理由 |
| Serviceに届かない | 疑う場所readinessProbeの失敗、selectorとlabelの不一致 | 見るものkubectl describe service、Podのlabel |
| selectorを変えたいのに更新できない | 疑う場所selectorはimmutable | 見るものDeploymentの作り直し |
| 消したフィールドがapplyで消えない | 疑う場所last-applied-configurationアノテーションが無い | 見るものkubectl replace --save-config |
selectorを変える場合、公式ドキュメントは削除して作り直すよう案内しています。既定ではDeploymentの削除でPodも消えてダウンタイムが出ます。Podを残したいなら--cascade=orphanを使います。
クラスタの選定自体(開発用途か本番運用か)で迷う場合は、Docker/Podman/Kubernetes MCPの選び方が判断材料になります。ノードのドレインで止まらないようにするPodDisruptionBudgetの足し方は、Claude CodeでPodDisruptionBudgetを足しドレインを確かめるに続きます。
まとめ
Claude Codeに書かせたマニフェストは、clientのdry-runで構文を見たあと、serverのdry-runかdiffでAPIの判定まで通してから適用します。許可リストに入れる検証コマンドは、保存はしなくても書き込み系の権限が要るかどうかで選びます。DeploymentとServiceを1つのカスタムAPIにまとめたい場合は、Claude CodeでCrossplaneのCompositionを書く手順でXRDとの役割分担を扱っています。環境別の差分は、Claude Codeでkustomizeのoverlayを組む手順のようにbase/overlay構成で管理できます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- kubernetes.io/docs/concepts/workloads/controllers/deployment
- kubernetes.io/docs/concepts/services-networking/service
- kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config
- kubernetes.io/docs/concepts/configuration/manage-resources-containers
- kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-read…
- code.claude.com/docs/en/memory
- code.claude.com/docs/en/settings
- code.claude.com/docs/en/permissions