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からクラスタを操作するで扱っている話です。本記事はファイルとしてマニフェストを書く作業に絞ります。
ステップ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の値を決め直す手戻りが減ります。
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種類のプローブは失敗時の挙動が異なります。livenessProbeが失敗を検知すると、kubeletはコンテナを終了して再起動します。readinessProbeが失敗を返している間、そのPodはServiceからのトラフィックを受け取りません。両者は互いに依存せず、起動直後だけ待たせたい場合はinitialDelaySecondsかstartupProbeを使います。起動に時間がかかるコンテナにはstartupProbeを足し、一度成功すればそこからlivenessProbeに切り替わります。startupProbeがfailureThreshold × periodSecondsの間に一度も成功しないと、コンテナは終了しrestartPolicyに従います。
| プローブ | 失敗時の挙動 | 使う場面 |
|---|---|---|
| livenessProbe | 失敗時の挙動kubeletがコンテナを終了・再起動する | 使う場面デッドロックやハングを検知したい |
| readinessProbe | 失敗時の挙動ServiceからのトラフィックをそのPodへ送らない | 使う場面起動直後や一時的な過負荷でリクエストを受けたくない |
| startupProbe | 失敗時の挙動failureThreshold×periodSeconds超で終了し、restartPolicyに従う | 使う場面起動が遅いコンテナでliveness誤検知を防ぎたい |
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へ外部からどこまで到達させたいかで選びます。
| type | 説明 | 到達範囲 |
|---|---|---|
| ClusterIP | 説明クラスタ内部専用のIPを割り当てる既定のtype | 到達範囲クラスタ内のみ |
| NodePort | 説明各NodeのIPで静的ポートを開く。内部的にClusterIPも作られる | 到達範囲クラスタ外(Node経由) |
| LoadBalancer | 説明外部ロードバランサーを使う。ロードバランサー自体はクラウド連携かクラスタ外の実装が必要 | 到達範囲クラスタ外(LB経由) |
| ExternalName | 説明selectorを持たず、DNSのCNAMEとして外部ホスト名へマッピングするだけ | 到達範囲プロキシなし(DNSのみ) |
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 diffと--dry-run=clientで確認します。どちらも実際のクラスタ状態を変更しません。
kubectl apply --dry-run=client -f deployment.yaml -o yaml
kubectl diff -f deployment.yamlkubectl applyは、設定ファイルとクラスタ上の実際の状態、そしてkubectl.kubernetes.io/last-applied-configurationアノテーションの3つを比べてパッチを計算します。このアノテーションに残っていてファイルから消えたフィールドは削除対象になり、ファイル内の値がクラスタ上の値と異なるフィールドは更新対象になる仕組みです。最初からkubectl createで作ったオブジェクトにはこのアノテーションが付いていないため、そのままapplyをかけてもファイルから消したフィールドが削除対象として検出されません。kubectl replace --save-config -f <filename>でアノテーションを付け直せば、以降はapplyの差分検出が正しく働くようになります。ディレクトリ単位で作成・更新したいときはkubectl apply -f <directory>、対象から外したオブジェクトも削除したいときはkubectl apply -f <directory> --pruneを使います。削除だけを明示したい場合は、kubectl delete -f <filename>が公式の推奨手順です。
適用後はkubectl rollout status deployment/<name>でロールアウトの完了を確認します。成功時はsuccessfully rolled outと表示され、終了コードは0です。ロールアウトが進まないときの典型的な原因は、リソースクォータ不足・readinessProbeの失敗・イメージのpullエラー・権限不足・LimitRangeの制約・アプリケーション側の起動設定ミスです。spec.progressDeadlineSecondsを設定しておけば、この停滞を一定時間後にステータスとして検知できます。
本番クラスタへ反映する権限をどこまでClaude Codeに渡すかで迷う場合は、Kubernetes MCPサーバーの権限設計で扱っている読み取り専用接続の考え方が参考になります。
CLAUDE.mdにクラスタの前提を書いておく
クラスタ名・namespace・命名規則といった前提は、都度チャットで伝えるのでなくプロジェクトのCLAUDE.md(./CLAUDE.mdまたは./.claude/CLAUDE.md)に書いておくと、セッションをまたいで引き継がれます。たとえば「本番はnamespace: prod、検証はnamespace: staging」のように書いておけば、生成されるマニフェストのmetadata.namespaceが毎回ぶれなくなります。
検証コマンドの実行確認を毎回省きたい場合は、.claude/settings.jsonのpermissions.allowにBash(kubectl diff *)やBash(kubectl apply --dry-run=client *)のようなルールを追加します。構文はBash(npm run lint)のような文字列パターンで、コマンドの先頭部分を指定する形です。どちらもクラスタの状態を変更しない読み取り専用の実行なので許可リストに向きますが、状態を変えるkubectl apply本体は許可リストに入れず、実行前の確認を毎回残します。
書いたマニフェストをGitで管理する場合は、Claude CodeでPRを作成する手順に沿ってコミットからPR作成までを任せる流れに乗せられます。
よくあるつまずき
- selectorを変更しようとして失敗する:
spec.selector.matchLabelsは作成後に変更できません。ラベル設計を変えたい場合はDeploymentを作り直します。 - selectorが他のDeploymentと重複する: 同じlabelを別のDeploymentやStatefulSetのselectorに使うと、両方が同じPodを管理しようとして挙動が不安定になります。
- maxSurgeとmaxUnavailableを両方0にしてしまう: どちらも0にするとPodを1つも入れ替えられず、ロールアウトが進みません。
- createで作ったオブジェクトから消したフィールドが、applyで削除されない:
last-applied-configurationアノテーションが付いていないオブジェクトにapplyをかけると、ファイルから消したフィールドが削除対象として検出されません。kubectl replace --save-configでアノテーションを付け直すか、最初からkubectl applyで作成しておきます。 - LoadBalancerにしても外部から届かない: LoadBalancerタイプは自前でロードバランサーを用意するかクラウド連携が必要で、単体では外部到達が成立しません。
- readinessProbeを省略してlivenessProbeだけ書く: 起動直後の初期化中にトラフィックが届いてリクエストが失敗します。両方を書き分けます。
- メモリlimitを低く設定しすぎる: OOMでコンテナが繰り返し再起動します。実際の使用量を計測してから
limits.memoryを決めます。
クラスタの選定自体(開発用途か本番運用か)で迷う場合は、Docker/Podman/Kubernetes MCPの選び方が判断材料になります。
まとめ
Claude CodeでKubernetesマニフェストを書く作業は、YAMLの生成とkubectl diffによる検証をセットで進めるとエラーの手戻りが減ります。selectorのimmutableな性質とServiceのtypeによる到達範囲の違いを踏まえておけば、DeploymentとServiceの組み合わせはこの手順で足ります。resources.requests/limitsと3種類のプローブを書き加えれば、リソース超過や起動失敗のときの挙動まで制御できます。適用後はkubectl rollout statusで完了を確認し、進まないときはprogressDeadlineSecondsで検知する構成にしておくと運用が安定します。