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に任せる作業と人が実行する作業

コードを書く側

Claude Codeに任せる

  • Spec/Statusフィールドの設計とGo実装(型定義とマーカーの追加)
  • Reconcile()ロジックの実装(差分レビュー必須)
  • +kubebuilder:rbac マーカーの追加(make manifests の生成結果で確かめる)
  • Webhookのバリデーション実装(弾く条件は業務要件次第。差分レビュー必須)
  • kubebuilder init / create api の実行(プロジェクト構造を作るだけなので条件付きで可)
クラスタに触れる側

人が実行する

  • make install / make deploy: CRDやコントローラーをクラスタへ反映する
  • make run: kubeconfigの現在のコンテキストが指すクラスタに対して動く
  • CRD・リソースの削除コマンド: 取り消しにくい

前提条件

公式の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が生成される仕組みなので、マーカーを直接編集しただけではクラスタ上の権限は変わりません。

公式のCronJobチュートリアルが実装するReconcile()は、依頼文に書く観点の手がかりになります。チュートリアルのコードは次の構造です。

  • 冒頭で r.Get してオブジェクトを読み、NotFoundなら削除済みとして ctrl.Result{}, nil で終える
  • 子リソース(チュートリアルではJob)に ctrl.SetControllerReference で所有者を設定し、SetupWithManager で Owns(&kbatch.Job{}) を宣言する。これで子の変更や削除時に親のReconcileが呼ばれる
  • 状態は r.Status().Update で書き、開始期限に遅れた実行は meta.SetStatusCondition で MissedSchedule という理由のDegraded条件にする
  • 次回の実行時刻は ctrl.Result{RequeueAfter: ...} で返す

Claude Codeに頼むときも「NotFoundはnilで返す」「子にはOwnerReferenceを付ける」のように、観点を文で指定する書き方になります。この工程を任せるときは、CLAUDE.mdに次のようなルールを書いておくと、生成漏れを防ぎやすくなります。

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

変更後の検証ループ

  1. 1

    マニフェストを再生成する

    make manifests で、CRDとRBACのYAMLを最新の型とマーカーに揃えます。

  2. 2

    CRDをクラスタへ入れる

    make install の対象は、kubeconfigの現在のコンテキストが指すクラスタです。kubectl cluster-info で向き先を確かめてから実行します。

  3. 3

    コントローラーをローカルで動かす

    make run は、クラスタへの接続に使っている認証情報のまま動きます。Webhookを足したプロジェクトでの動かし方は、後述の「Webhookを試す2つの経路」にあります。

  4. 4

    ログを読ませる

    出力をそのままClaude Codeに渡し、エラーが出ていれば直させます。

手順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の動作確認は、Reconcileの確認と同じ経路では済みません。

くらべる

Webhookを試す2つの経路

make run

ローカルで動かす

ENABLE_WEBHOOKS=false make run でWebhookを切り、Reconcileだけを確かめます。Webhookまでローカルで動かすなら、/tmp/k8s-webhook-server/serving-certs/ に tls.crt と tls.key を置く必要があります。リモートクラスタを使う場合は、そこからローカルのWebhookサーバーへ通信を届ける手立ても要ります。

make deploy

クラスタに載せる

cert-managerで証明書を用意し、イメージをビルドして make deploy IMG=<registry>/<name>:tag で反映します。kindなら kind load docker-image でローカルのイメージを直接ロードできます。

反映後は config/samples/ のサンプルリソースを kubectl create -f で作成し、意図した入力が弾かれるかを確かめます。

よくあるつまずき

  • RBACマーカーを変更したのに権限エラーが直らない: +kubebuilder:rbacを編集しただけではクラスタの権限は変わりません。make manifestsを実行してconfig/rbac/role.yamlを再生成し、make installまたはmake deployで反映してから確認します。
  • make manifestsがエラーで止まる: エラー文に実行すべきコマンドが示されます。そのコマンドを実行してから、make manifestsをやり直します。
  • 新しいKindを追加したのにコントローラーが認識しない: Kindの型は、api/v1/ のGoファイルのinit()で SchemeBuilder.Register により登録されます。新しいKindを手で足したときは、この登録行があるかを見ます。
  • +optionalと+kubebuilder:validation:Optionalの使い分けで迷う: +kubebuilder:validation:Optional をパッケージに付けると、そのパッケージの全フィールドが任意になります。マーカーのリファレンスはこの使い方より、フィールド単位の +optional を勧めています。
  • Kind(ローカルクラスタ)でイメージが反映されない: リモートレジストリへのdocker-pushを経由せず、kind load docker-image <image>:tag --name <cluster>でローカルのDockerイメージを直接クラスタへロードできます。開発中はこちらのほうが高速です。
  • Webhookのパスを変えても反映されない: controller-runtimeがv0.21未満では、Webhookのパスをカスタムできず、自動生成の形式になります。パスを変えたのに挙動が合わないときは、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(kubectl get *)"
    ],
    "ask": [
      "Bash(make install)",
      "Bash(make run)",
      "Bash(make deploy *)",
      "Bash(make undeploy)",
      "Bash(kubectl delete *)"
    ]
  }
}

allow に置いたのはコード生成とマニフェスト再生成などクラスタを変えない操作で、ask はクラスタに触れる操作です。make run もコントローラーがクラスタを読み書きするため、ask 側です。

ルールはdeny、ask、allowの順に評価され、最初に一致した結果が採られます。make manifests && make install && make run のような複合コマンドは && で分解され、各コマンドが別々に照合されます。この例では make install と make run で確認が出ます。ENABLE_WEBHOOKS=false make run のように変数代入を前に付けても、askルールは代入を越えて一致します。境界は運用に合わせて調整してください。

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

まとめ

型とReconcilerの実装はClaude Codeに任せ、make run で動かす段階からは、毎回人が向き先のクラスタを確かめてから実行を許します。Webhookはローカルで動かすより、make deploy とcert-managerの経路で試すほうが手間が少なく済みます。

参照した一次ソース

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

  1. book.kubebuilder.io/quick-start.html
  2. book.kubebuilder.io/cronjob-tutorial/controller-overview.html
  3. book.kubebuilder.io/cronjob-tutorial/webhook-implementation.html
  4. code.claude.com/docs/en/permissions
  5. book.kubebuilder.io/cronjob-tutorial/running.html
  6. book.kubebuilder.io/cronjob-tutorial/controller-implementation.html
  7. book.kubebuilder.io/cronjob-tutorial/cert-manager.html
  8. book.kubebuilder.io/reference/markers/crd-validation.html
この記事を共有:XはてブLinkedIn