Claude Media
Claude CodeでKubebuilder Operatorを作る手順

Claude CodeでKubebuilder Operatorを作る手順

KubebuilderのCRD・Reconciler・Webhookそれぞれの工程で、Claude Codeにどこまで実装を任せられるかを手順ごとに示します。

Kubebuilderとは何か

KubebuilderはKubernetes公式のsigs.k8s.ioプロジェクトで、独自のCustom Resource Definition(CRD)とそれを制御するコントローラー(Operator)をGoで組み立てるためのスキャフォールディングツールです。+kubebuilder から始まるマーカーコメントをcontroller-genが読み取り、CRDマニフェストやRBACロール、DeepCopy実装を自動生成します。

Kubernetes API群にはGroup・Version・Kind(GVK)という単位があり、1つのGVKがGoの1つのルート型に対応します。Kubebuilderのcreate apiコマンドはこのGVKに対応するGoファイル一式を生成し、開発者はSpec(望ましい状態)とStatus(観測した状態)のフィールドを埋めていく形で実装を進めます。

Claude Codeはこのスキャフォールディング後の「空欄を埋める」作業、つまりSpec/Statusのフィールド設計、Reconcileロジックの実装、Webhookのバリデーション処理に向いています。一方でクラスタへの適用やCRDの削除を伴うコマンドは、人間が最終確認したうえで実行するのが安全です。

Claude Codeに任せてよい作業の早見表

Kubebuilderのワークフローは「コマンドでコードを生成する工程」と「生成されたコードを埋める工程」に分かれます。前者は副作用が大きく、後者はレビューしやすいという性質の違いがあるため、作業ごとに向き不向きを分けて考えます。

作業Claude Codeに任せる目安理由
kubebuilder init / create api の実行Claude Codeに任せる目安条件付きで可理由プロジェクト構造を作るだけで既存コードへの影響は小さい
Spec/Statusフィールドの設計とGo実装Claude Codeに任せる目安可理由型定義とマーカーコメントの追加はレビューしやすい
Reconcile()ロジックの実装Claude Codeに任せる目安可(差分レビュー必須)理由ロジックの正しさは人間の確認が要る
+kubebuilder:rbac マーカーの追加Claude Codeに任せる目安可理由生成結果をmake manifestsで確認できる
Webhookのバリデーション実装Claude Codeに任せる目安可(差分レビュー必須)理由不正な入力を弾く条件は業務要件次第
make deploy / クラスタへの適用Claude Codeに任せる目安人間が実行理由本番相当のクラスタ状態を変更する
CRD・リソースの削除コマンドClaude Codeに任せる目安人間が実行理由取り消しにくい操作

前提条件

公式のQuick Startガイドが挙げる前提条件は次のとおりです。

  • go version v1.24.6+
  • docker version 17.03+
  • kubectl version v1.11.3+
  • Kubernetes v1.11.3+ クラスタへのアクセス

Kubebuilder本体は次のコマンドでインストールします。

curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder && sudo mv kubebuilder /usr/local/bin/

Claude Code側は、このリポジトリをカレントディレクトリにして起動するだけで準備は完了します。Goのビルドやテストコマンドは通常のBash権限の範囲で動くため、追加のMCPサーバーは不要です。

手順1. プロジェクトとCRDの雛形を生成する

まずプロジェクトを初期化し、続けてAPI(CRD)を作成します。

mkdir -p ~/projects/guestbook && cd ~/projects/guestbook
kubebuilder init --domain my.domain --repo my.domain/guestbook
kubebuilder create api --group webapp --version v1 --kind Guestbook

create apiの対話プロンプトで「Create Resource」「Create Controller」の両方にyと答えると、api/v1/guestbook_types.go(API定義)とinternal/controller/guestbook_controller.go(Reconcilerの雛形)が生成されます。ここまではコマンド実行そのものなので、Claude Codeに投げてもレビューする対象がほぼありません。

続くSpec/Statusのフィールド設計はClaude Codeの得意分野です。公式のQuick Startが示す例(例示であり実際のClaude出力ではありません)は次のような形で、+kubebuilder:validation マーカー付きのフィールドをGoの型定義に落とし込んでいます。Claude Codeに任せる場合も、フィールドごとの制約(最小値・最大値・許可する文字列など)を自然言語で伝えれば、同じ形のマーカー付きフィールドを書かせられます。

// GuestbookSpec defines the desired state of Guestbook
type GuestbookSpec struct {
	// +kubebuilder:validation:Minimum=1
	// +kubebuilder:validation:Maximum=10
	Size int32 `json:"size"`
 
	// +kubebuilder:validation:MaxLength=15
	// +kubebuilder:validation:MinLength=1
	ConfigMapName string `json:"configMapName"`
 
	// +kubebuilder:validation:Enum=Phone;Address;Name
	Type string `json:"type,omitempty"`
}

フィールドを追加・変更したら、必ずmake manifestsを実行してCRDマニフェストを再生成します。ここを飛ばすと、Goの型定義とクラスタに反映されるCRDスキーマがずれます。

手順2. Reconcilerのロジックを組み立てる

Kubebuilderが生成するReconciler構造体は、client.ClientとSchemeを持つだけの空の器です。実際の制御ロジックはReconcile()メソッドの中に書きます。

type GuestbookReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}
 
// +kubebuilder:rbac:groups=webapp.my.domain,resources=guestbooks,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.my.domain,resources=guestbooks/status,verbs=get;update;patch
 
func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = logf.FromContext(ctx)
	// your logic here
	return ctrl.Result{}, nil
}
 
func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&webappv1.Guestbook{}).
		Complete(r)
}

Reconcile()は「オブジェクト名を受け取り、もう一度呼び直すべきかどうかを返す」という単純な契約を持ちます。クラスタの実際の状態(actual state)を、そのオブジェクトのSpecが表す望ましい状態(desired state)へ近づける処理をここに書き、エラー時や周期実行が必要な場合はctrl.Resultで再試行を指示します。この「差分を見て、近づけて、また見る」というループがreconciliation loopの考え方です。

RBACマーカー(+kubebuilder:rbac)を追加・変更した場合もmake manifestsが必須です。マーカーからconfig/rbac/role.yamlが生成される仕組みなので、マーカーを直接編集しただけではクラスタ上の権限は変わりません。

Claude Codeにこの工程を任せるときは、CLAUDE.mdに次のようなルールを書いておくと、生成漏れを防ぎやすくなります。

## Kubebuilder運用ルール
- api/v1/*_types.go のSpec/Statusを変更したら、必ず `make manifests` を実行してから差分をコミットする
- +kubebuilder:rbac マーカーを追加・削除したら `make manifests` で config/rbac/role.yaml の再生成を確認する
- Reconcile() の変更後は `make run` でフォアグラウンド実行し、ログにエラーが出ないことを確認してから次の指示を出す

実装をさせたあとは、make manifests && make install && make runを実行してコマンドの出力をそのままClaude Codeに読ませ、エラーが出ていれば修正させる、という検証ループを1往復させると差分の質が上がります。

手順3. Defaulting/Validating Webhookを追加する

CRDの値を作成・更新時にAPIサーバー側で検証したい場合は、Admission Webhookを追加します。Webhookの雛形もKubebuilderのコマンドで生成します。

kubebuilder create webhook --group webapp --version v1 --kind Guestbook \
  --defaulting --programmatic-validation

このコマンドは、admission.Defaulterインターフェースを実装するDefaulterと、admission.Validatorインターフェースを実装するValidatorの雛形をinternal/webhook/v1/以下に生成します。Defaulter側はDefault()メソッドで未設定フィールドに既定値を入れ、Validator側はValidateCreate / ValidateUpdate / ValidateDeleteの3メソッドに分かれています。作成時だけ許可したいフィールドや、削除時だけ特別扱いしたい条件を、この3メソッドの使い分けで表現します。

func (v *GuestbookValidator) ValidateCreate(_ context.Context, obj *webappv1.Guestbook) (admission.Warnings, error) {
	// 作成時のみのバリデーション
	return nil, nil
}

Webhookのパスを--defaulting-pathや--validation-pathでカスタマイズする場合は、controller-runtime v0.21以降が前提条件です。それより古いバージョンでは、パスはグループ・バージョン・種別から/mutate-batch-v1-cronjobのように自動生成される形式に固定され、変更できません。

ValidateCreate/ValidateUpdate/ValidateDelete以降の具体的なバリデーションロジック(たとえば文字列フォーマットの妥当性チェックのような正規表現に頼りたくない検証)は、業務要件を自然言語でClaude Codeに伝えて実装させるのに向いた工程です。ただしWebhookはmake runだけでは動作しません。APIサーバーからWebhookサーバーへの到達性とサービング証明書が必要なため、kindクラスタにcert-managerを導入したうえでイメージをビルドしてkind load docker-imageでロードし、make deployでクラスタへ反映します。そのうえでkubectl create -f config/samples/配下のサンプルリソースを使い、意図した入力が弾かれるかを確認します。

よくあるつまずき

  • RBACマーカーを変更したのに権限エラーが直らない: +kubebuilder:rbacを編集しただけではクラスタの権限は変わりません。make manifestsを実行してconfig/rbac/role.yamlを再生成し、make installまたはmake deployで反映してから確認します。
  • 新しいKindを追加したのにコントローラーが認識しない: SchemeBuilder.Register(&Guestbook{}, &GuestbookList{})の登録漏れが典型的な原因です。新しいKindを追加したら、対応するGoファイルのinit()にこの登録行があるかを確認します。
  • +optionalと+kubebuilder:validation:Optionalの使い分けで迷う: 両方ともcontroller-genには効きますが、+kubebuilder:validation:Optionalはパッケージ全体に一括適用できるのに対し、+optionalはフィールド単位の慣用表記です。自前クライアントを書く開発者向けの互換性を考えるなら、+optionalも併記しておくのが無難です。
  • Kind(ローカルクラスタ)でイメージが反映されない: リモートレジストリへのdocker-pushを経由せず、kind load docker-image <image>:tag --name <cluster>でローカルのDockerイメージを直接クラスタへロードできます。開発中はこちらのほうが高速です。
  • Webhookのパスをカスタムしたのに404になる: controller-runtimeのバージョンがv0.21未満だと、カスタムパスの指定自体が無視され自動生成パスにフォールバックします。go.modのcontroller-runtimeバージョンを先に確認します。

Claude Codeの権限設定

Kubebuilderのワークフローには、コードを書くだけのコマンドと、クラスタの状態を変えるコマンドが混在します。Claude Codeのpermissions設定で、生成・確認系コマンドは許可し、クラスタへ適用・削除する系のコマンドは都度確認する構成にしておくと、誤ってクラスタへ意図しない変更を適用するリスクを減らせます。確認を挟みたい操作はallowではなくaskに置きます。公式ドキュメントが示すBash(サブコマンド *)という書式に沿うと、次のような設定例になります。

{
  "permissions": {
    "allow": [
      "Bash(kubebuilder create *)",
      "Bash(make manifests)",
      "Bash(make generate)",
      "Bash(make run)",
      "Bash(kubectl get *)"
    ],
    "ask": [
      "Bash(make install)",
      "Bash(make deploy *)",
      "Bash(make undeploy)",
      "Bash(kubectl delete *)"
    ]
  }
}

allowに置いたコマンド群はコード生成・ローカルビルドなどクラスタの状態を変えない操作、askに置いたコマンド群はCRDの適用やリソース削除などクラスタの状態を書き換える操作で、実行のたびに確認を求めます。make installはCRDをクラスタへ適用するコマンドなので、早見表の「クラスタへの適用は人間が実行」と揃える形でallowからaskへ移しています。実際のチームの運用に合わせて境界は調整してください。

Kubernetesマニフェスト自体の書き方はClaude CodeでKubernetesマニフェストを書く手順で扱っています。OperatorをビルドしたあとClaude Codeから直接クラスタを操作したい場合は、Kubernetes MCPサーバーでClaudeからクラスタを操作するが導入手順を、本番クラスタに接続する際の権限設計はKubernetes MCPサーバーの権限設計がそれぞれ参考になります。

まとめ

Kubebuilderでのoperator開発は「コマンドでコードを生成する」「生成された型・Reconciler・Webhookを埋める」の2工程に分かれ、Claude Codeが力を発揮するのは後者です。Spec/Statusのフィールド設計、Reconcile()の実装、Webhookのバリデーションロジックは自然言語での指示と相性がよく、一方でmake deployやリソース削除のような取り消しにくい操作は人間が実行する運用が安全です。CLAUDE.mdにmake manifestsの実行漏れを防ぐルールを書き、permissions設定で生成系と適用系のコマンドを分けておくことで、Kubebuilderの開発サイクルにClaude Codeを組み込みやすくなります。

この記事を共有:XはてブLinkedIn