ark-controller-development について
`ark/` の Ark Kubernetes operator 開発者向けスキルです。CRD と Helm chart の同期、Go の実装方針、テストから E2E 確認までの検証手順を扱います。
主な機能
- 01CRD 生成と Helm chart 同期
- 02Go 実装と依存関係の指針
- 03Lint・ビルド・テスト手順
- 04Controller と webhook のテスト指針
- 05PR 前の検証チェックリスト
活用シーン
- 01CRD フィールドの追加や変更
- 02CRD 同期エラーの解消
- 03Controller や webhook の変更検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mckinsey/ agents-at-scale-ark --skill ark-controller-developmentインストール後、Claude Code で/ark-controller-developmentを入力して読み込まれたか確認します。
詳細
Ark Controller の開発
ark/ で Ark Kubernetes オペレーターを開発するためのガイダンスです。
このスキルを使用する場面
- Go 型定義(
api/v1alpha1/*_types.go)を変更する場合 - CRD/Helm チャートの同期エラーを修正する場合
- 新しい CRD フィールドやリソースを追加する場合
CRD 生成フロー
api/v1alpha1/*_types.go # Go types with markers
↓
make manifests # Generates CRDs and syncs to Helm chart
↓
config/crd/bases/*.yaml # Source CRDs (auto-generated)
dist/chart/templates/crd/ # Helm chart CRDs (auto-synced)
make manifests は、テンプレート化されたヘッダーを保持しながら、ソース CRD を Helm チャートに自動的に同期します。
「CRDs out of sync」エラーの修正
make build が CRD の検証エラーで失敗した場合:
cd ark
make manifests
make build
主なディレクトリ
| ディレクトリ | 用途 |
|---|---|
api/v1alpha1/ | Go 型定義 |
config/crd/bases/ | 自動生成されたソース CRD |
dist/chart/templates/crd/ | Helm チャートの CRD(自動同期) |
internal/controller/ | 調整ロジック |
internal/webhook/ | アドミッション Webhook |
internal/genai/ | AI/ML の実行ロジック |
一般的なタスク
型またはコメントを変更した後
Go の型コメントは CRD フィールドの説明になります:
cd ark
make manifests
make build
Go コードを変更した後
make lint-fix # Format and auto-fix what the linter can fix
make lint # Fail fast on issues CI will reject — MANDATORY
make build # Build and validate
make test # Run unit + envtest suites
PR を作成する前
稼働中のクラスターに対して chainsaw e2e スイートを実行します。unit と envtest では、apply → reconcile → status の一連の処理全体をテストできません。決定論的な mock-llm スイートを優先し、LLM を利用するテストは、変更がその経路に直接関係する場合にのみ実行してください(コストを抑え、実際の LLM プロバイダーに起因する不安定さを避けられます)。
# Deterministic suite (default)
(cd tests && chainsaw test --selector '!llm')
# LLM suite — only when your change needs it
(cd tests && chainsaw test --selector 'llm')
パターンとアンチパターンについては、chainsaw スキルを参照してください(例:シェルの grep より chainsaw の assert を優先し、ポーリングより wait を優先します)。
make lint は、CI で適用されるものと同じ golangci-lint ルールを実行します。プッシュ前にローカルで実行することは必須です。lint にかかる数分よりも、PR チェックが失敗する方がはるかに大きなコストになります。
既存のコードを活用する — 車輪の再発明は避ける
パーサー、構造体、ヘルパー、リトライループ、URL ビルダーなどのユーティリティを作成する前に、次の場所に既存のものがないか確認してください。
- Go 標準ライブラリ。
go.modの直接または間接依存関係(controller-runtime、apimachinery、実装対象の仕様に対応する SDK、golang.org/x/...)。
手作業で書いたコードは、時間とともに乖離し、テストが重複し、保守の負担になります。ライブラリに処理を委ねる短いアダプターの方が、再実装よりほとんどの場合優れています。
目安:書こうとしているものが、既存の仕様、ファイル形式、ヘッダー構文、または一般的なインフラパターンに似ているなら、すでに誰かが実装しています。まず検索してください:go doc <import-path>、ls $(go env GOMODCACHE)/<dep>。
同じフィールドを持つ構造体を別途定義するのではなく、エイリアス(type X = lib.X)を使ってライブラリの型を再エクスポートしてください。
実例
このプロジェクトの開始時には、WWW-Authenticate パーサー、RFC 9728 の構造体、RFC 8414 の構造体、HTTP フェッチャーを手作業で実装し、約 150 LOC になりました。go-sdk には、すでに oauthex にそれらが含まれていました。
// Bad — 40-line parser, partial struct, duplicated HTTP wrapper.
func parseHeader(h string) (string, bool) { /* ... */ }
type Metadata struct { /* partial mirror of RFC */ }
func fetch(ctx, url) (*Metadata, error) { /* 30 lines */ }
// Good — thin adapter, full spec support, ~10 LOC.
import "github.com/modelcontextprotocol/go-sdk/oauthex"
type Metadata = oauthex.ProtectedResourceMetadata
func Fetch(ctx context.Context, metaURL, resourceURL string, timeout time.Duration) (*Metadata, error) {
return oauthex.GetProtectedResourceMetadata(ctx, metaURL, resourceURL, &http.Client{Timeout: timeout})
}
同じ原則は、リトライ/バックオフ(k8s.io/apimachinery/pkg/util/wait)、JSON パッチ(strategicpatch)、条件の更新(meta.SetStatusCondition)、HTTP クライアント、ワークキューにも当てはまります。何に取り組む場合でも、まず既存の依存関係を確認してください。
マジックナンバーを避け、設定可能な値を再利用する
数値の時間、リトライ回数、バッファーサイズを直接埋め込まないでください。優先順位は次のとおりです。
- ユーザーが設定可能な spec フィールド — リソースにすでに
spec.timeout、spec.pollInterval、spec.retriesがある場合は、それを使用します。同じ調整処理内の関連するすべてのコードパスを、1 つのオペレーター設定項目で制御します。 - パッケージレベルの const — その数値を使う理由がわかる名前を付けます。
- テストローカルの const(例:
const testTimeout = 5 * time.Second)— テスト内にリテラルを直接埋め込まないでください。
実例
// Bad — parallel constant invented out of thin air.
const discoveryTimeout = 10 * time.Second
client := &http.Client{Timeout: discoveryTimeout}
// Good — plumb the MCPServer's spec.timeout; single source of truth.
func Fetch(ctx, url string, timeout time.Duration) ...
timeout := parseTimeout(mcpServer.Spec.Timeout)
テスト — 必須です
すべてのコード変更にはテストが必要です。ark/internal/ に変更を加える PR に _test.go がない場合は未完成です。これはコントローラーの分岐、Webhook、ヘルパーにも適用されます。公開 API だけが対象ではありません。
| 変更 | 場所 | フレームワーク |
|---|---|---|
internal/<pkg>/ のヘルパー/パーサー | <file>_test.go | 標準ライブラリの testing、テーブル駆動 |
| コントローラーの分岐 | internal/controller/<scenario>_test.go | Ginkgo + envtest |
| Webhook ルール | internal/webhook/v1/<resource>_webhook_test.go | Ginkgo |
| HTTP ロジック | 同じパッケージ内の httptest.NewServer | 標準ライブラリの testing |
コントローラーの分岐に必要なテストカバレッジ:
- 正常系 → 期待されるステータスと条件。
- 各失敗モード → それぞれ異なる安定した条件理由(文字列マッチングは使用しない)。
- 冪等性 → 再調整しても重複イベントが発生せず、
lastTransitionTimeが更新されないこと。 - リグレッションガード → 既存の分岐に影響がないこと。
完了チェックリスト
「完了」とする前に:
-
make manifestsがクリーン(型を変更した場合) -
make lintがクリーン — CI と同じルールを適用 -
make buildが成功 -
make testが成功 — 出力を貼り付けるか、参照する - Chainsaw の決定論的スイート(
chainsaw test --selector '!llm')が成功。LLM スイートは変更に必要な場合のみ実行 - 新しい関数/分岐ごとに新しい
_test.goがある - Helm チャートの CRD が再生成され、コミットされている
- CRD を変更した場合は、OpenAPI/Python SDK/TypeScript SDK が再生成されている
- 実行中の Pod を再検証済み — devspace はソースを同期しますが、新しい Go バイナリを反映するにはコンテナーの再起動が必要です。調整パスに変更を加えた後は、コントローラーのログを追跡してください