---
title: "Kubebuilderとは？kubectlとの違い、コントローラーとOperatorの違いと開発手順【v4.16】"
url: "https://www.issoh.co.jp/tech/details/6972/"
published: 2025-05-27
updated: 2026-09-27
categories: ["Kubernetes・コンテナ"]
publisher: "株式会社一創"
---

# Kubebuilderとは？kubectlとの違い、コントローラーとOperatorの違いと開発手順【v4.16】

Kubebuilderは、CustomResourceDefinition（CRD）とコントローラーを組み合わせて、Kubernetesを独自のAPIで拡張するためのGo製SDKです。kubectlがクラスタに「命令を送る道具」であるのに対し、Kubebuilderはクラスタの中で動き続ける「Operatorを作る道具」で、役割は重なりません。この記事では、2026年9月10日公開のKubebuilder v4.16.0とkubectl v1.37.0で、プロジェクト作成からReconcileの実装、envtestでのテスト、kubectlによるカスタムリソースの操作までを実行した出力をもとに、コントローラーとOperatorの違い、Operator SDKなど他の選択肢との選び分けを整理します。

## まとめ：kubectlとKubebuilderの違いとOperator開発の要点

- **役割の違い**：kubectlはkube-apiserverへリクエストを送るCLIクライアント。KubebuilderはCRDとコントローラーのコード・マニフェスト・テスト環境を生成する開発用SDKで、完成したOperatorのCRDを登録したり動作を確かめたりする場面でkubectlを使います。
- **コントローラーとOperator**：コントローラーは望ましい状態へ実際の状態を近づける制御ループ全般。Operatorはそのうち、カスタムリソースを使ってアプリケーションの運用知識をコード化したものです。Operatorはすべてコントローラーですが、逆は成り立ちません。
- **開発の流れ**：`kubebuilder init`→`kubebuilder create api`→型定義とReconcileの実装→`make manifests`→`make test`（envtest）→`make install`と`make run`でクラスタに当てる、の順です。
- **2026年9月時点の版**：Kubebuilder v4.16.0、controller-runtime v0.25.1、Operator SDK v1.42.3。v4.16.0の生成物はgo.modで`go 1.26.0`を宣言します。
- **選び方**：GoでOperatorを書くならKubebuilderが基準。OLMでの配布やAnsible・Helmベースが要るならOperator SDK。CRDの検証だけが目的ならコントローラーを書かずにCRDのスキーマやValidatingAdmissionPolicyで足ります。

## kubectlとKubebuilderの役割の違い

両者は名前が似ていますが、使う時点も動く場所も違います。kubectlは運用者や開発者の端末で実行し、kube-apiserverへ作成・取得・更新・削除のリクエストを送るクライアントです。Kubernetesのバージョンスキューポリシーでは、kubectlは通信先のkube-apiserverに対して前後1マイナーバージョンの範囲でサポートされます。Kubebuilderは開発時にだけ使うコード生成ツールで、生成したOperatorはコンテナとしてクラスタ内で常駐します。

| 観点        | kubectl      | Kubebuilder             |
| --------- | ------------ | ----------------------- |
| 種類        | CLIクライアント    | Operator開発用SDK          |
| 使う時点      | 日々の運用・デバッグ   | Operatorの開発時            |
| 動く場所      | 手元の端末        | 生成物がクラスタ内で常駐            |
| 主な出力      | リソースの作成・取得結果 | Goコード・CRD・RBAC・Makefile |
| CRDとの関係   | CRDの登録と操作    | CRDの定義と生成               |
| 2026年9月の版 | v1.37.0      | v4.16.0                 |

生成されたMakefileの`install`と`deploy`ターゲットは、内部でkustomizeの出力を`kubectl apply`に渡します。KubebuilderはkubectlをCRDの登録手段として前提にしており、どちらか一方を選ぶ関係ではありません。Kubernetes自体の構成要素から押さえたい場合は[Kubernetes（クバネティス）の仕組みとDockerとの違い](/tech/details/2928/)を先に読むと、以降の用語がつながります。

## コントローラーとOperatorの違い

### コントローラーはリソースの状態を合わせる制御ループ

Kubernetesの公式ドキュメントは、コントローラーを「1種類のリソースを望ましい状態として参照し、管理対象をその状態に近づける制御ループ」と説明しています。Deploymentに`replicas: 3`と書けばPodが3つに保たれるのは、kube-controller-manager内のコントローラーが差分を検知して埋め続けているためです。コントローラーの処理は「今の状態を読み、望ましい状態との差を埋める」ことに限られ、何回呼ばれても同じ結果になる（冪等である）ことが求められます。

### Operatorはカスタムリソースで運用知識をコード化したコントローラー

Operatorについて、[公式ドキュメント](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)は「カスタムリソースを使ってアプリケーションとその構成要素を管理するKubernetesのソフトウェア拡張」と定義しています。データベースのバックアップ、バージョンアップの手順、障害時のフェイルオーバーのように、人が手順書で行っていた運用をコントローラーに書き込み、`kind: PostgresCluster`のような独自リソースで宣言できるようにしたものです。Ceph向けのRookはその代表例で、[Rook CephがKubernetes上でCephを動かすオペレータの仕組み](/tech/details/17287/)で構成を追えます。

区別の基準は、カスタムリソースを使ってアプリケーション固有の運用知識を実装しているかです。カスタムリソースの提供方法にはCRDとAPI Aggregationがあり、独自のCRDを持つこと自体が必須条件ではありません。組み込みリソースだけを監視してラベルを付け直すコントローラーは、Operatorとは呼びません。

### CRDだけで足りる場合とコントローラーが必要な場合

CRDを登録しただけでは、kube-apiserverがカスタムリソースを保存して返すだけで、何も動きません。設定値の置き場としてCRDを使い、別のツールが読むだけなら、コントローラーは不要です。カスタムリソースの作成や変更をきっかけにPod・ConfigMap・外部のクラウドリソースを作る、あるいは状態を`status`に書き戻す必要があるときに、初めてコントローラーを書く意味が出ます。CRDとAPIグループの関係は[Kubernetes APIのリソース・エンドポイント構造とCRD拡張](/tech/details/9733/)で詳しく扱っています。

## Kubebuilderの構成要素と位置付け

KubebuilderはKubernetesのSIG API Machineryが管理するkubernetes-sigs配下のプロジェクトで、次の部品を束ねてひな形を作ります。

- **controller-runtime**：Manager・Client・キャッシュ・Reconcilerの実行基盤。v4.16.0の生成物はv0.25.0を依存に書き込みました（公開中の最新はv0.25.1）。
- **controller-tools（controller-gen）**：Goのコメントに書いたマーカーから、CRD・RBAC・DeepCopyメソッドを生成します。生成物はv0.22.0を取得しました。
- **setup-envtest**：テスト用のkube-apiserverとetcdのバイナリを取得します。
- **kustomize**：`config/`配下のマニフェストを組み立てます。生成物はv5.8.1を指定していました。

既定のプラグインは`go/v4`で、公式ドキュメントでは`kustomize.common.kubebuilder.io/v2`と`base.go.kubebuilder.io/v4`を合成したものと説明されています。Operator SDKもKubebuilderをCLIとプラグインのライブラリとして使っているため、Go製Operatorのディレクトリ構成はどちらで作っても同じ標準に沿います。

## Kubebuilderのインストールとプロジェクト作成手順

### Kubebuilderの動作前提とインストール

[公式Quick Start](https://book.kubebuilder.io/quick-start.html)が挙げる前提は、Go v1.24.6以上、Docker 17.03以上、kubectl v1.11.3以上、v1.11.3以上のクラスタへのアクセスです。ただしv4.16.0で生成したgo.modは`go 1.26.0`を宣言しており、Go 1.21以降のツールチェーンはこれより古い版では`GOTOOLCHAIN`の設定に従って新しい版を取得するか、ビルドを止めます。手元のGoは1.26系にそろえておくと迷いません。検証用のクラスタは[kindでローカルにk8sクラスタを構築する手順](/tech/details/17438/)で用意できます。

バイナリは公式Quick Startと同じ次のURLで取得します。このURLはリダイレクトを経てGitHub Releasesの最新版（2026年9月17日時点でv4.16.0）に着地します。

```
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/
kubebuilder version
# KubeBuilder:          v4.16.0
# Kubernetes:           1.37.0
# Go OS/Arch:           darwin/amd64
```

### kubebuilder initとcreate apiで生成されるファイル

公式のGuestbookの例に沿って、プロジェクトの初期化とAPIの追加を行います。`--domain`はAPIグループの接尾辞になり、次の例では`webapp.my.domain`というグループが作られます。

```
mkdir guestbook && cd guestbook
kubebuilder init --domain my.domain --repo my.domain/guestbook
kubebuilder create api --group webapp --version v1 --kind Guestbook --resource --controller
```

実行後の主なファイルは次のとおりです（`.github`や`config/prometheus`などは省略）。

```
api/v1/guestbook_types.go              # Spec・Statusの型定義
api/v1/zz_generated.deepcopy.go        # controller-genが生成
cmd/main.go                            # Managerの起動
internal/controller/guestbook_controller.go
internal/controller/suite_test.go      # envtestの起動
config/crd/  config/rbac/  config/manager/  config/samples/
test/e2e/                             # Kindを使うE2Eテスト
Dockerfile  Makefile  PROJECT  AGENTS.md
```

`PROJECT`ファイルには`cliVersion: 4.16.0`と`layout: go.kubebuilder.io/v4`が記録されます。`kubebuilder alpha generate`はこのファイルからプロジェクトを作り直し、`kubebuilder alpha update`は3方向マージで新しいKubebuilderの版へ追従させるコマンドです。PROJECTを手で消すと、この追従手段を失います。

### マーカーによるバリデーションとkubectl表示の指定

型定義のコメントに書く`+kubebuilder:`マーカーが、CRDのOpenAPIスキーマやkubectlの表示に変換されます。次の例は、`message`を必須かつ1文字以上にし、短縮名`gb`と表示列を追加したものです。

```
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=gb
// +kubebuilder:printcolumn:name="Message",type=string,JSONPath=".spec.message"
// +kubebuilder:printcolumn:name="ConfigMap",type=string,JSONPath=".status.configMapName"
type Guestbook struct { ... }

type GuestbookSpec struct {
	// +kubebuilder:validation:MinLength=1
	Message string `json:"message"`
}

type GuestbookStatus struct {
	// +optional
	ConfigMapName string `json:"configMapName,omitempty"`
	// 生成時からあるConditionsはそのまま残す
}
```

`make manifests`を実行すると、`config/crd/bases/webapp.my.domain_guestbooks.yaml`に`apiVersion: apiextensions.k8s.io/v1`のCRDが出力され、`message`には`minLength: 1`と`required`が付きます。この例ではmessageが必須になります。ただし、`+kubebuilder:validation:Optional`をフィールドやパッケージに指定した場合などは扱いが変わるため、生成されたスキーマのrequiredも確認してください。

表示列を1つでも定義すると、kubectlの既定表示からAGE列が消えます。作成からの経過時間を残したい場合は、`JSONPath=".metadata.creationTimestamp"`と`type=date`の列を自分で足します。

## Reconcileの実装とenvtestによるテスト

### Reconcile関数の最小実装

`internal/controller/guestbook_controller.go`のReconcileとSetupWithManagerを以下の実装に置き換えます。既存のGuestbook用RBACマーカーは残し、ConfigMap用マーカーを追加します。importブロックには、既存のimportに加えて次の3行が必要です。

```
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
```

次のコードは、Guestbookが作られたら同じ名前空間に`<名前>-message`というConfigMapを作り、`spec.message`の値を書き込むコントローラーです。`CreateOrUpdate`で冪等にし、`SetControllerReference`で所有者参照を付けるため、Guestbookを消すとConfigMapもガベージコレクションで消えます。

```
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete

func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := logf.FromContext(ctx)

	var gb webappv1.Guestbook
	if err := r.Get(ctx, req.NamespacedName, &gb); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{
		Name: gb.Name + "-message", Namespace: gb.Namespace,
	}}
	op, err := controllerutil.CreateOrUpdate(ctx, r.Client, cm, func() error {
		cm.Data = map[string]string{"message": gb.Spec.Message}
		return controllerutil.SetControllerReference(&gb, cm, r.Scheme)
	})
	if err != nil {
		return ctrl.Result{}, err
	}
	log.Info("configmap reconciled", "name", cm.Name, "operation", op)

	if gb.Status.ConfigMapName != cm.Name {
		gb.Status.ConfigMapName = cm.Name
		if err := r.Status().Update(ctx, &gb); err != nil {
			return ctrl.Result{}, err
		}
	}
	return ctrl.Result{}, nil
}

func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&webappv1.Guestbook{}).
		Owns(&corev1.ConfigMap{}).
		Named("guestbook").
		Complete(r)
}
```

設計上の要点は3つあります。Reconcileは「どのイベントで呼ばれたか」を受け取らないので、毎回その時点の状態を読み直して判断すること。`Owns`を書くと、子のConfigMapを誰かが手で消したときにも親のReconcileが呼ばれて作り直されること。そして、ConfigMapを扱うRBACマーカーを書き忘れると、envtestでは通るのにクラスタ上では権限エラーになることです。envtestのテストクライアントは管理者権限で動くため、この誤りをテストでは検出できません。

### envtestでの単体テストの実行結果

生成された`internal/controller/guestbook_controller_test.go`はGinkgoとGomegaで書かれています。上の実装に合わせて、Specに`Message: "hello"`を入れ、Reconcile後にConfigMapの中身・所有者参照・`status`を確かめるアサーションを足しました。`make test`はsetup-envtestでKubernetes 1.37のkube-apiserverとetcdを`bin/k8s/`へ取得してから`go test`を走らせます。

```
$ make test
Setting up envtest binaries for Kubernetes version 1.37...
ok  	my.domain/guestbook/internal/controller	10.578s	coverage: 76.5% of statements
```

macOS（Intel）での初回はバイナリ取得を含めて約1分30秒、2回目以降はコントローラーのテストだけで10〜15秒でした。envtestには実際のkube-apiserverが動くので、CRDのバリデーションも本番と同じく効きます。`message`を空にしたGuestbookの作成が`IsInvalid`エラーになることも同じテストで確認できました。一方、kube-controller-managerは起動しないため、所有者参照によるガベージコレクションやDeploymentからのPod作成は起きません。この挙動は、Kindを使う`test/e2e/`に、Guestbookの削除後にConfigMapが消えることを確かめるテストを追加して検証します。生成されたE2Eテストだけでは、この独自の挙動は確認できません。

## kubectlでCRDとカスタムリソースを操作するコマンド

クラスタにCRDを登録するのが`make install`、手元からコントローラーを動かすのが`make run`です。登録後はkubectlでカスタムリソースを組み込みリソースと同じように扱えます。操作には次の2つのマニフェストを使います。

```
# guestbook.yaml
apiVersion: webapp.my.domain/v1
kind: Guestbook
metadata:
  name: guestbook-sample
spec:
  message: hello
---
# empty.yaml（バリデーション違反の確認用。実際は別ファイルに保存）
apiVersion: webapp.my.domain/v1
kind: Guestbook
metadata:
  name: empty
spec:
  message: ""
```

以下は、envtestのkube-apiserver（v1.37.0）にCRDだけを登録し、コントローラーは起動していない状態で、kubectl v1.37.0から操作した出力です。

```
$ kubectl get crd
NAME                          SCOPE        VERSIONS      CREATED AT
guestbooks.webapp.my.domain   Namespaced   v1(storage)   2026-09-17T08:46:10Z

$ kubectl api-resources --api-group=webapp.my.domain
NAME         SHORTNAMES   APIVERSION            NAMESPACED   KIND
guestbooks   gb           webapp.my.domain/v1   true         Guestbook

$ kubectl explain guestbook.spec
FIELDS:
  message	<string> -required-

$ kubectl apply -f guestbook.yaml
guestbook.webapp.my.domain/guestbook-sample created

$ kubectl get gb
NAME               MESSAGE   CONFIGMAP
guestbook-sample   hello     
```

CONFIGMAP列が空なのは、`status.configMapName`を書き込むコントローラーが動いていないためです。`make run`でコントローラーを起動すると、Reconcileが`guestbook-sample-message`を作成し、この列に名前が表示されます。

`kubectl get crd`の`crd`は`customresourcedefinitions`の短縮名です。短縮名`gb`はマーカーで指定したときだけ使え、指定しないCRDで`kubectl get gb`を打つと`the server doesn't have a resource type "gb"`になります。バリデーションに反するリソースは、kube-apiserverが保存前に拒否します。

```
$ kubectl apply -f empty.yaml
The Guestbook "empty" is invalid: spec.message: Invalid value: "": spec.message in body should be at least 1 chars long
```

変更を当てる前に差分だけ見たいときは`kubectl diff -f guestbook.yaml`を使います。生成された`config/samples/webapp_v1_guestbook.yaml`は`spec:`の下がコメントだけなので、そのまま適用すると空文字の場合とは別のエラーで拒否されます。`message`を書き足してから使ってください。

```
$ kubectl apply -f config/samples/webapp_v1_guestbook.yaml
The Guestbook "guestbook-sample" is invalid: spec: Required value
```

## イメージのビルドとHelmチャートでのデプロイ

クラスタ内でOperatorを動かすには、コンテナイメージを作ってDeploymentとして配置します。公式Quick Startの手順は次の2行です。

```
make docker-build docker-push IMG=<registry>/guestbook:tag
make deploy IMG=<registry>/guestbook:tag
```

Helmで配布したい場合は、`helm/v2-alpha`プラグインがkustomizeの出力からチャートを生成します。実行すると`dist/chart/`に`Chart.yaml`と`values.yaml`、RBACやメトリクスのテンプレートが作られ、Makefileに`helm-deploy`・`helm-uninstall`・`helm-status`・`helm-history`・`helm-rollback`の5ターゲットが追加されました。

```
kubebuilder edit --plugins=helm/v2-alpha
```

名前のとおりalpha段階で、Kubebuilderは「頻繁に変更され、使う時点の間で壊れうる」段階と定義しています。社外に配るチャートの正本にするなら、生成後の差分をレビューする運用を前提にしてください。チャートの基本は[Helmのチャート・リポジトリ・リリースの基本](/tech/details/7694/)で解説しています。

## Operator SDK・Metacontroller・KUDOとの選び分け

| ツール            | 最新リリース              | 記述言語            | 向く場面             |
| -------------- | ------------------- | --------------- | ---------------- |
| Kubebuilder    | v4.16.0（2026-09-10） | Go              | GoでOperatorを書く標準 |
| Operator SDK   | v1.42.3（2026-06-26） | Go・Ansible・Helm | OLMで配布する         |
| Metacontroller | v4.17.2（2026-08-13） | 任意（Webhook）     | Go以外で軽く書く        |
| KUDO           | v0.19.0（2021-04-27） | YAML宣言          | 新規採用は避ける         |

Operator SDKはKubebuilderを内部で使っているので、Goで書く部分のコードはほぼ共通です。差はOperator Lifecycle Manager（OLM）向けのバンドル生成や、AnsibleとHelmでOperatorを作れる点にあります。OpenShiftやOperatorHubへの掲載を予定しているならOperator SDK、そうでなければ依存の少ないKubebuilderから始めるのが順当です。

Metacontrollerは、同期処理を任意の言語のWebhookとして書き、ループの管理をMetacontroller側に任せる方式です。Goを書けないチームでも作れますが、Webhookサーバーの可用性がそのままOperatorの可用性になります。

KUDOはGitHubのリポジトリこそアーカイブされていないものの、最終リリースは2021年4月のv0.19.0、最終のpushは2023年8月です。新規採用では、機能の比較に加えて、現在のKubernetesへの追従状況と保守体制を判断材料にします。これから作るOperatorの選択肢から外してください。

## Kubebuilderを採用すべきでない場面

Kubebuilderはコード量もテスト基盤も本格的なので、次の条件に当てはまるなら使わない方が保守は軽くなります。

- **入力値の検証だけが目的**：CRDのOpenAPIスキーマで表せる制約はマーカーだけで済みます。組み込みリソースへの独自ルールなら、Kubernetes v1.30でstableになったValidatingAdmissionPolicyでCELの式を書けば、Webhookサーバーを運用せずに済みます。
- **マニフェストのテンプレート化だけが目的**：環境ごとの値の差し替えや一括デプロイは、HelmやKustomize、GitOpsツールの仕事です。状態を監視して直し続ける必要がないなら、コントローラーは過剰です。
- **クラウドリソースの宣言的管理**：S3バケットやRDSをカスタムリソースで管理したいだけなら、既存の実装がある[Crossplaneのクラウドインフラ管理の仕組み](/tech/details/15576/)を先に検討します。
- **Goのコードを保守できる人がいない**：Reconcileの不具合でリソースの作成・更新が繰り返されると、APIサーバーへの負荷増加や管理対象の障害を招くおそれがあります。書いた人しか直せない状態でOperatorを本番に入れるのは避けてください。

逆に、ステートフルなミドルウェアのバックアップ・昇格・バージョンアップのように、手順書が長く、順序を間違えると壊れる運用があるなら、Kubebuilderで書く価値があります。

## よくある質問

### Kubebuilderとkubectlはどちらを覚えればよいですか？

Kubernetesの日常的な操作に使うのはkubectlなので、先に覚えるのはkubectlです。Kubebuilderは、GoでカスタムコントローラーやWebhookを開発するときに使うツールで、作ったOperatorのCRD登録や動作確認にもkubectlを使います。Kubebuilderで作ったOperatorも、CRDの登録や動作確認にはkubectlを使います。

### Kubernetesのコントローラーとオペレーターの違いは何ですか？

コントローラーは、リソースの望ましい状態へ実際の状態を近づける制御ループの総称です。Operatorは、カスタムリソースを使ってアプリケーション固有の運用を自動化するコントローラーを指します。Operatorはすべてコントローラーですが、Deploymentコントローラーのような組み込みのものはOperatorとは呼びません。

### Operator SDKとKubebuilderの違いは何ですか？

Operator SDKはKubebuilderをCLIとプラグインのライブラリとして使っており、Go製Operatorの構成は共通です。Operator SDKはOLM向けのバンドル生成と、Ansible・HelmベースのOperatorに対応している点が加わります。

### KubebuilderでHelmチャートは作れますか？

作れます。`kubebuilder edit --plugins=helm/v2-alpha`で`dist/chart/`にチャートが生成され、Makefileに`helm-deploy`などのターゲットが追加されます。ただしプラグインはalpha段階で、版によって破壊的変更が入る可能性があります。

### Kubebuilderのテストにクラスタは必要ですか？

単体テストには不要です。`make test`がsetup-envtestでkube-apiserverとetcdを取得してローカルで起動します。ただしkube-controller-managerは動かないため、ガベージコレクションなどを確かめるにはKindを使う`test/e2e/`のテストが必要です。

## 関連記事

- [Kubernetes APIとは｜仕組み・リソース・エンドポイント構造とCRD拡張を実例で解説](/tech/details/9733/)
- [Kubernetes（クバネティス）とは？仕組み・Dockerとの違い・読み方をわかりやすく解説](/tech/details/2928/)
- [k8sをローカルで動かす手順｜kindでクラスタ構築からDeployment公開・撤収まで](/tech/details/17438/)
- [Helmとは？チャート・リポジトリ・リリースの基本とhelm repo addなど主要コマンド解説](/tech/details/7694/)
- [Rook Cephとは？Kubernetes上でCephを動かすオペレータの仕組みと採用判断を実装者目線で解説](/tech/details/17287/)

---

出典: [Kubebuilderとは？kubectlとの違い、コントローラーとOperatorの違いと開発手順【v4.16】](<https://www.issoh.co.jp/tech/details/6972/>)（株式会社一創）
