Contents
共通前提条件とツール一覧
| ツール | 推奨バージョン (2026‑04) | 主な機能 |
|---|---|---|
| Go | 1.22.5 | Kubebuilder v4 が要求するコンパイラ |
| Docker Desktop / Docker Engine | 24.0.x 系 | k3d が内部で使用するコンテナランタイム |
| kubectl | v1.30.x 系 | Kubernetes クラスタ操作 CLI |
| k3d | v5.6.0 | Docker 上に軽量な K8s クラスタを構築 |
注記:上記バージョンは「2026‑04」時点の最新です。公式サイトや
brew,apt,chocolateyなどで提供されている最新版が利用できる場合は、そちらを優先してください。
macOS のインストール手順
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# Homebrew が未インストールの場合は先に導入 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # 1. Go (バージョン管理ツールを使う場合は asdf 等でインストール可) brew install go@1.22 # 2. Docker Desktop(GUI アプリなので公式サイトからダウンロード推奨) open https://desktop.docker.com/mac/stable/Docker.dmg # ダウンロード → インストール # 3. kubectl curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl" chmod +x kubectl && sudo mv kubectl /usr/local/bin/ # 4. k3d brew install k3d |
ポイント:Homebrew 経由でインストールすると、
brew upgradeにより自動的に最新パッチが取得できます。
Ubuntu のインストール手順
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sudo apt update && sudo apt upgrade -y # 1. Go (apt が提供する 1.22 系を使用) sudo apt install -y golang-1.22 # 2. Docker Engine(Docker Desktop は GUI が無いため公式リポジトリからインストール) curl -fsSL https://get.docker.com | sudo sh sudo usermod -aG docker $USER # ログアウト・再ログインで反映 # 3. kubectl curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl && sudo mv kubectl /usr/local/bin/ # 4. k3d curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash |
ポイント:
dockerグループにユーザーを追加した後は、ターミナルを再起動してください。
Windows のインストール手順(PowerShell 推奨)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 1. Chocolatey が未インストールなら導入 Set-ExecutionPolicy Bypass -Scope Process -Force; ` [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; ` iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) # 2. Go choco install golang --version=1.22.5 -y # 3. Docker Desktop(公式サイトから MSI を取得しインストール) Start-Process "https://desktop.docker.com/win/stable/Docker%20Desktop%20Installer.exe" # 4. kubectl curl -LO "https://dl.k8s.io/release/$(Invoke-RestMethod -Uri https://dl.k8s.io/release/stable.txt)/bin/windows/amd64/kubectl.exe" Move-Item .\kubectl.exe $Env:ProgramFiles\Kubernetes\kubectl.exe # 5. k3d(Scoop が便利) choco install scoop -y scoop bucket add main https://github.com/ScoopInstaller/Main scoop install k3d |
注意:Docker Desktop のインストール後は Windows を再起動し、PowerShell を管理者権限で実行してください。
その他 Linux ディストリビューション向けインストール例
| ディストリビューション | Go インストール方法 | Docker Engine インストール | k3d インストール |
|---|---|---|---|
| Fedora | sudo dnf install golang(1.22 系) |
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo → sudo dnf install docker-ce docker-ce-cli containerd.io |
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash |
| Arch Linux | pacman -S go(最新版) |
pacman -S docker → systemctl start docker && systemctl enable docker |
yay -S k3d(AUR) |
| openSUSE Leap | zypper install golang |
zypper addrepo https://download.docker.com/linux/opensuse/docker-ce.repo → zypper install docker-ce |
同上のシェルスクリプト |
それぞれのパッケージマネージャが提供するバージョンは リポジトリ更新タイミング に依存します。必要に応じて公式バイナリを直接ダウンロードしてインストールしてください。
バージョン管理ツールで複数バージョンを安全に切り替える方法
開発環境や CI ランナーでは、プロジェクトごとに異なる Go のバージョンが必要になるケースがあります。代表的なマルチバージョン管理ツールは以下です。
| ツール | 主な特徴 |
|---|---|
asdf (プラグイン asdf-go) |
.tool-versions にプロジェクト単位でバージョンを書ける。Linux/macOS 両方に対応。 |
| SDKMAN! | 主に JVM 系だが、Go 用プラグインも提供。Unix 系のみ。 |
| brew (macOS) | brew install go@1.21 のように複数バージョンを同時に保持可能。brew link --overwrite go@1.22 で切替え。 |
asdf を使った例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# インストール(Homebrew 推奨) brew install asdf # Go プラグイン追加 asdf plugin-add golang https://github.com/kennyp/asdf-golang.git # 必要なバージョンをインストール asdf install golang 1.22.5 asdf install golang 1.21.13 # 古いプロジェクト用 # プロジェクトルートで使用したいバージョンを指定 cd sample-operator asdf local golang 1.22.5 # .tool-versions が自動生成される |
メリット:CI のセットアップスクリプトでも同様に
asdf installとasdf global/localを呼び出すだけで、環境差異を最小化できます。
環境変数と動作確認
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# Go バージョン確認 go version # → go version go1.22.5 darwin/amd64(例) # Docker デーモンが起動しているか docker run --rm hello-world # kubectl のバージョンと接続先確認 kubectl version --client && kubectl cluster-info || echo "k8s クラスタ未起動" # k3d クラスタ作成テスト(macOS/Ubuntu/Windows 共通) k3d cluster create test-cluster --agents 2 kubectl get nodes # Ready が表示されれば OK # 環境変数例(必要に応じて .bashrc/.zshrc に追記) export KUBECONFIG=$(pwd)/kubeconfig.yaml export PATH=$HOME/go/bin:$PATH |
ポイント:
go.modのgo 1.22指定は必須です。Docker Desktop の「Kubernetes を有効化」設定は不要で、k3d が独立したコンテナ上にクラスターを提供します。
トラブルシューティング / FAQ
| 質問 | 回答 |
|---|---|
k3d が failed to create network と出る |
Docker のネットワーク数が上限に達している可能性があります。docker network prune -f で不要なネットワークを削除してください。 |
kubectl が Unable to connect to the server: dial tcp ... |
k3d クラスタが正しく作成されていないか、KUBECONFIG 環境変数が古い可能性があります。k3d kubeconfig get test-cluster > ~/.kube/config で再取得してください。 |
Go のビルドが cannot find package "sigs.k8s.io/controller-runtime" |
モジュールキャッシュが壊れていることがあります。go clean -modcache && go mod tidy を実行し、依存関係を再解決します。 |
| Docker Desktop が起動できない(Mac で Apple Silicon) | M1/M2 チップ向けの Docker Desktop のバージョンが必要です。公式サイトから「Apple chip」版をダウンロードしてください。 |
Windows で k3d コマンドが認識されない |
PowerShell が管理者権限で実行されていない、または $Env:Path にインストール先が追加されていません。一度ターミナルを再起動し、scoop reset k3d でパスを更新してください。 |
Kubebuilder プロジェクトの雛形生成
|
1 2 3 4 5 6 7 8 9 |
# 作業ディレクトリ作成 & 移動 mkdir sample-operator && cd sample-operator # Kubebuilder 初期化(module 名は GitHub リポジトリに合わせる) kubebuilder init --domain example.com --repo github.com/yourname/sample-operator # API と Kind の生成 kubebuilder create api --group apps --version v1 --kind AppDeployment |
生成される主要ディレクトリ構造(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sample-operator/ ├─ api/ │ └─ v1/ │ ├─ appdeployment_types.go # CRD の Go 構造体定義 │ └─ zz_generated.deepcopy.go ├─ controllers/ │ └─ appdeployment_controller.go # Reconcile 実装の雛形 ├─ config/ │ ├─ default/ │ │ ├─ kustomization.yaml │ │ └─ manager_auth_proxy_patch.yaml │ └─ crd/ │ └─ bases/ │ └─ apps.example.com_appdeployments.yaml └─ Makefile # make test, make run 等が定義済み |
図解:
api/v1/→ CR のスキーマ、controllers/→ ビジネスロジック、config/→ デプロイ用マニフェスト。
CRD 設計例と Go 型へのマッピング
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// api/v1/appdeployment_types.go package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // AppDeploymentSpec はユーザーが意図する状態を定義します。 type AppDeploymentSpec struct { // +kubebuilder:validation:Minimum=1 // +kubebuilder:default=1 Replicas int32 `json:"replicas,omitempty"` // +kubebuilder:validation:Pattern=`^[a-z0-9]+([._-][a-z0-9]+)*$` Image string `json:"image,omitempty"` } // AppDeploymentStatus はコントローラが報告する観測状態です。 type AppDeploymentStatus struct { // 現在稼働中の Pod 数 AvailableReplicas int32 `json:"availableReplicas,omitempty"` // 最終更新時刻(ISO8601 形式) LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` } |
アノテーションのベストプラクティス
| アノテーション | 効果 |
|---|---|
+kubebuilder:validation:Minimum=1 |
replicas が 0 未満にならないことを保証 |
+kubebuilder:default=1 |
フィールド未指定時に自動で 1 を設定 |
+kubebuilder:validation:Pattern=… |
イメージ名のフォーマットチェック(正規表現) |
推奨:必須フィールドだけを
requiredにし、拡張性を保つために残りはomitemptyとしておく。
Reconcile ロジックのサンプル実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
// controllers/appdeployment_controller.go package controllers import ( "context" "time" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" v1 "github.com/yourname/sample-operator/api/v1" ) func (r *AppDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx).WithValues("appdeployment", req.NamespacedName) logger.Info("開始: Reconcile") // 1. CR の取得 var appDep v1.AppDeployment if err := r.Get(ctx, req.NamespacedName, &appDep); err != nil { logger.Error(err, "CR が見つからない") return ctrl.Result{}, client.IgnoreNotFound(err) } // 2. Deployment の作成/更新 dep := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: appDep.Name + "-dep", Namespace: appDep.Namespace, }, } _, err := controllerutil.CreateOrUpdate(ctx, r.Client, dep, func() error { // OwnerReference 設定で CR が削除されたら Deployment も GC if err := ctrl.SetControllerReference(&appDep, dep, r.Scheme); err != nil { return err } replicas := int32(appDep.Spec.Replicas) dep.Spec = appsv1.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"app": appDep.Name}, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"app": appDep.Name}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ Name: "main", Image: appDep.Spec.Image, }}, }, }, } return nil }) if err != nil { logger.Error(err, "Deployment の作成/更新に失敗") return ctrl.Result{}, err } // 3. Status 更新(Pod 数取得) var podList corev1.PodList if err := r.List(ctx, &podList, client.InNamespace(appDep.Namespace), client.MatchingLabels{"app": appDep.Name}); err != nil { logger.Error(err, "Pod のリスト取得に失敗") return ctrl.Result{}, err } available := int32(len(podList.Items)) appDep.Status.AvailableReplicas = available appDep.Status.LastUpdateTime = metav1.Now() if err := r.Status().Update(ctx, &appDep); err != nil { logger.Error(err, "Status 更新に失敗") return ctrl.Result{}, err } logger.Info("Reconcile 正常終了", "availableReplicas", available) // 変更があったら少し待機して再調整(例: 1 分後に再実行) return ctrl.Result{RequeueAfter: time.Minute}, nil } // SetupWithManager registers the controller. func (r *AppDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&v1.AppDeployment{}). Owns(&appsv1.Deployment{}). Complete(r) } |
実装上のポイント
- 冪等性:
controllerutil.CreateOrUpdateが差分のみ適用し、再実行時にリソースが破壊されません。 - 構造化ロギング:
log.FromContextによりk8s.io/component-base/logs互換の JSON ログが出力され、Grafana Loki 等で容易に検索可能です。 - エラーハンドリング:
client.IgnoreNotFoundを使うことで削除イベント時の不要な再試行を防止します。
テスト・CI/CD フロー全体像
1. 単体テスト (envtest)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Makefile の一部抜粋 ENVTEST_K8S_VERSION = 1.30.0 test: envtest ## unit test with envtest go test ./... -coverprofile=coverage.out envtest: @set -e; \ if ! command -v setup-envtest >/dev/null; then \ go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest; \ fi; \ export KUBEBUILDER_ASSETS=$$(setup-envtest use $(ENVTEST_K8S_VERSION) -p path); \ echo "envtest assets: $$KUBEBUILDER_ASSETS" |
テストコード例(controllers/appdeployment_controller_test.go)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
package controllers import ( "context" "path/filepath" "testing" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/log/zap" v1 "github.com/yourname/sample-operator/api/v1" ) func TestReconcile(t *testing.T) { ctx := context.Background() scheme := ctrl.NewScheme() _ = v1.AddToScheme(scheme) _ = appsv1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) testEnv := &envtest.Environment{ CRDDirectoryPaths: []string{filepath.Join("config", "crd", "bases")}, } cfg, err := testEnv.Start() if err != nil { t.Fatalf("envtest 起動失敗: %v", err) } defer testEnv.Stop() k8sClient, err := client.New(cfg, client.Options{Scheme: scheme}) if err != nil { t.Fatalf("client 作成失敗: %v", err) } reconciler := &AppDeploymentReconciler{ Client: k8sClient, Scheme: scheme, Log: zap.New(zap.UseDevMode(true)), } // テスト対象 CR の作成 app := &v1.AppDeployment{ ObjectMeta: metav1.ObjectMeta{Name: "demo", Namespace: "default"}, Spec: v1.AppDeploymentSpec{ Replicas: 2, Image: "nginx:alpine", }, } if err = k8sClient.Create(ctx, app); err != nil { t.Fatalf("CR 作成失敗: %v", err) } // Reconcile 実行 _, err = reconciler.Reconcile(ctx, ctrl.Request{ NamespacedName: client.ObjectKeyFromObject(app), }) if err != nil { t.Fatalf("Reconcile エラー: %v", err) } // Deployment が生成されたか検証 dep := &appsv1.Deployment{} key := client.ObjectKey{Name: "demo-dep", Namespace: "default"} if err = k8sClient.Get(ctx, key, dep); err != nil { t.Fatalf("Deployment が見つからない: %v", err) } } |
2. E2E テスト (k3d)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# CI 用スクリプト例(ci_e2e.sh) #!/usr/bin/env bash set -euxo pipefail # 1. クラスタ作成 k3d cluster create ci-cluster --agents 1 --api-port 6443 # 2. Operator のビルド & イメージ化 make docker-build IMG=ghcr.io/${GITHUB_REPOSITORY}:${GITHUB_SHA} # 3. k3d にイメージをロード k3d image import ghcr.io/${GITHUB_REPOSITORY}:${GITHUB_SHA} -c ci-cluster # 4. マニフェスト適用 kubectl config use-context k3d-ci-cluster make install # CRD と RBAC をインストール make deploy IMG=ghcr.io/${GITHUB_REPOSITORY}:${GITHUB_SHA} # 5. サンプルリソース投入 & 検証 cat <<'EOF' > sample.yaml apiVersion: apps.example.com/v1 kind: AppDeployment metadata: name: demo spec: replicas: 2 image: nginx:alpine EOF kubectl apply -f sample.yaml sleep 15 # Deployment が生成されるまで待機 ready=$(kubectl get deployment demo-dep -o jsonpath='{.status.readyReplicas}') if [[ "$ready" != "2" ]]; then echo "E2E FAILED: expected 2 ready replicas, got $ready" exit 1 fi echo "E2E SUCCESS" |
3. Dockerfile(マルチステージ)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# ---------- Build stage ---------- FROM golang:1.22-alpine AS builder WORKDIR /workspace COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go # ---------- Runtime stage ---------- FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . USER nonroot:nonroot ENTRYPOINT ["/manager"] |
4. GitHub Actions ワークフロー例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
name: CI on: push: branches: [ main ] pull_request: jobs: unit-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.22' - name: Cache Go modules uses: actions/cache@v3 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Run unit tests (envtest) run: make test e2e-test: needs: unit-test runs-on: ubuntu-latest services: docker: image: moby/buildkit:latest options: --privileged steps: - uses: actions/checkout@v4 - name: Install k3d run: | curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash - name: Build Docker image env: IMG: ghcr.io/${{ github.repository }}:${{ github.sha }} run: make docker-build IMG=$IMG - name: Create k3d cluster run: k3d cluster create ci --agents 1 --api-port 6443 - name: Load image into k3d run: k3d image import ${{ env.IMG }} -c ci - name: Deploy operator to the test cluster env: IMG: ${{ env.IMG }} run: | make install make deploy - name: Run E2E script run: ./ci_e2e.sh |
まとめと次のステップ
| 項目 | キーポイント |
|---|---|
| 環境構築 | Go 1.22、Docker Desktop/Engine、k3d、kubectl をインストールすればローカルで本番に近い K8s クラスタが作れる。Windows、macOS、Ubuntu だけでなく Fedora・Arch 等でも同様の手順が存在することを覚えておく。 |
| バージョン管理 | asdf / brew / chocolatey 等で複数バージョンを安全に切り替え、CI でも同一ツールチェーンを利用すると環境差異が減少する。 |
| Kubebuilder 雛形 | init と create api が生成する標準ディレクトリ構造は保守性と拡張性の土台になる。 |
| CRD 設計 | Spec と Status を明確に分離し、+kubebuilder:validation アノテーションで API の安全性を担保。 |
| Reconcile 実装 | CreateOrUpdate + OwnerReference による冪等性、構造化ロギングでデバッグ容易性を確保。 |
| テスト・CI/CD | envtest → k3d の二段階テストに加え、GitHub Actions で自動ビルド/デプロイを実装すれば、プッシュごとに信頼できるリリースパイプラインが完成する。 |
次のステップ
1. 本記事の手順でローカル環境を構築し、make test && make runが問題なく動くことを確認してください。
2. サンプル CR (AppDeployment) を実際に作成し、期待通り Deployment と Pod が生成されるか検証します。
3. CI の設定が完了したら、ブランチを切って機能追加やバグ修正を行い、プッシュ → GitHub Actions の結果で自動的にテスト・デプロイが走るフローを体感してください。
このハンズオンを足掛かりに、社内の業務アプリケーション向けカスタムコントローラやオペレーターを次々と作成していけば、Kubernetes 上で安全かつスピーディなサービス運用が実現できます。 Happy coding!