mcpField
K
k8s-crd-from-typed-schema

k8s-crd-from-typed-schema

github.com/mizchiクラウド・DevOps41648 回インストール1 週間前に更新
リポジトリ

k8s-crd-from-typed-schema について

型付きスキーマから Kubernetes CRD を生成する開発者向けの実践スキルです。Structural Schema への変換、CRD の組み立て、検証に加え、見落としやすい metadata、status、バージョンの制約を扱います。

主な機能

  1. 01JSON Schema を Structural Schema に適合
  2. 02非対応スキーマ機能の処理
  3. 03CRD 外枠の構築
  4. 04リソース名の複数形生成
  5. 05kubectl のサーバー側 dry-run 検証
  6. 06metadata と status の注意点

活用シーン

  1. 01zod などのスキーマから CRD を生成
  2. 02kubectl による CRD 拒否エラーの調査
  3. 03status 更新後の operator ループ防止
  4. 04クラスター適用前のスキーマとリソース検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mizchi/skills --skill k8s-crd-from-typed-schema

インストール後、Claude Code で/k8s-crd-from-typed-schemaを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

型付きスキーマから k8s CRD を生成する

型付きスキーマ(zod/TypeBox/Valibot/手書きの JSON Schema)から CRD を生成するのは簡単そうに見えます。OpenAPI v3 に変換し、openAPIV3Schema に埋め込めば完了です。しかし、そうではありません。CRD では OpenAPI v3 のサブセットと k8s 固有の拡張(「構造スキーマ」)が使われており、一般的な JSON Schema の機能の中には互換性がなく、エラーもなく無視されるものがいくつかあります。さらに、スキーマ以外にも注意点が 2 つあり(status サブリソースと metadata の禁止)、CRD を使おうとするまで表面化しません。

このスキルは、kubectl apply -f crd.yaml が成功し、オペレーターが実際に動作するようになる前に修正すべき項目のチェックリストです。

使用する場面

  • オペレーター/コントローラーを構築し、独自の CRD を作成する場合。
  • YAML を手作業で書くのではなく、型付きソースから CRD を生成する場合。
  • unknown field "$schema" / must not have "oneOf" / must not specify anything other than name and generateName / cannot have both "additionalProperties" and "properties" のような kubectl エラーが発生した場合。
  • オペレーターが一度は動作するものの、最初のステータス書き込み後に無限ループで再調整を繰り返す場合。

ワークフロー

1. 型付きスキーマを JSON Schema(OpenAPI v3 方言)に変換する

zod の場合:

import { zodToJsonSchema } from 'zod-to-json-schema';

const json = zodToJsonSchema(specSchema, {
  target: 'openApi3',
  $refStrategy: 'none',  // CRDs forbid $refs across the document
});

$refStrategy: 'none' は必須です。k8s の構造スキーマはドキュメントをまたぐ $ref を解決しません。すべてインライン化してください。

TypeBox の場合:スキーマをそのまま渡し、その後、以下のアダプターを実行してください。

2. 構造スキーマのアダプターを実行する

構造スキーマの方言では、いくつかの JSON Schema の機能が拒否されます。削除または書き換えてください。

JSON Schema の機能対応方法理由
$schema メタキーワード削除するkubectl は不明なルートキーを拒否します
oneOf / anyOf / allOfノードを { x-kubernetes-preserve-unknown-fields: true } に置き換えるx-kubernetes-validations 以外では、構造スキーマはスキーマ形式のユニオンを禁止します
タプル形式の items(スキーマの配列)単一のスキーマにまとめる(items[0] を使用)k8s がサポートするのは単一の項目スキーマのみです
uri / date-time 以外の formatformat キーを削除するapiserver が認識するのは少数の許可リストのみです。不明な形式は検証に失敗します
properties と併記された additionalProperties: falseadditionalProperties キーを削除する構造スキーマでは両方の併記が禁止されています。宣言済みの properties により、すでに閉じたスキーマになります
空のリーフ {}(例:z.unknown() によるもの){ x-kubernetes-preserve-unknown-fields: true } に置き換える構造スキーマでは、すべてのノードに type を宣言するか、回避策を使う必要があります

参考用のアダプター(TypeScript、約 40 行)は、このスキルの examples/adapter.ts にあります(または k1c/src/cli/export-crds.ts からコピーしてください)。

3. spec スキーマを CRD の外枠で包む
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: <plural>.<group>     # e.g. r2buckets.cloudflare.k1c.io
spec:
  group: <group>
  scope: Namespaced
  names:
    kind: <Kind>
    singular: <kind>
    plural: <plural>
    listKind: <Kind>List
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            apiVersion: { type: string }
            kind:       { type: string }
            spec:       <your adapted schema here>
            status:
              type: object
              x-kubernetes-preserve-unknown-fields: true
          required: [spec]
      subresources:
        status: {}            # <-- mandatory if your operator writes status
4. 複数形を正しく計算する

spec.names.plural は kubectl の URL(/apis/<group>/<version>/<plural>)に使われます。kind.toLowerCase() + 's' とするのではなく、規則動詞に応じて処理してください。

function pluralize(kind: string): string {
  const lower = kind.toLowerCase();
  if (lower.endsWith('s') || lower.endsWith('x') || lower.endsWith('z')) return `${lower}es`;
  if (lower.endsWith('y')) return `${lower.slice(0, -1)}ies`;
  return `${lower}s`;
}

これは、Cloudflare や多くの k8s API 作成者が使う活用規則に一致します。不規則な kind(Endpoints、Quota → quotas)には、手動の上書きマップが必要です。

5. kubectl のドライランモードで検証する
kubectl apply --dry-run=server -f crd.yaml
kubectl apply --dry-run=server -f sample-cr.yaml   # after CRD is registered

サーバーサイドのドライランでは、ローカルのドライランでは見逃される構造スキーマ違反を検出できます(例:additionalProperties と properties の併用が強制されるのはサーバーサイドのみです)。

注意点

注意点 1:openAPIV3Schema.properties で metadata を宣言する
# ❌ kubectl rejects this:
openAPIV3Schema:
  type: object
  properties:
    metadata: { type: object, properties: { name: { type: string } } }
    spec: ...
# error: must not specify anything other than name and generateName for metadata

修正方法:metadata は一切記述しないでください。apiserver が標準の ObjectMeta スキーマを自動的に適用します。宣言するのは apiVersion / kind / spec / status のみです。

落とし穴 2:subresources.status: {} がないと、リコンサイルループが無限に続く

これがない場合、kubectl patch --subresource=status を呼び出すたびに、次のことが起こります。

  1. .metadata.generation が増加します。
  2. ウォッチが起動します。
  3. オペレーターが再リコンサイルし、再びステータスをパッチします。
  4. 1 に戻ります。

症状:オペレーターは正常に見えますが、ログには数百ミリ秒ごとに reconcile loop の行が延々と出力されます。CPU 使用率は徐々に上昇し、Apiserver の QPS 予算が消費されます。

修正方法:提供するすべてのバージョンに、必ず subresources: { status: {} } を含めてください。

落とし穴 3:判別可能なユニオン型の oneOf

z.discriminatedUnion(...) と Type.Union(...) は oneOf に変換されます。構造スキーマでは、x-kubernetes-validations(CEL)以外での使用が拒否されます。修正方法は 2 つあります。

  1. 制約を緩和する方法:ノードを x-kubernetes-preserve-unknown-fields: true に置き換えます(検証が失われます)。
  2. 厳密な CEL:x-kubernetes-validations: [{ rule: "self.type == 'a' && has(self.payload)" }] を出力します。これをサポートするのは Apiserver 1.25 以降のみです。

手順 2 のアダプターは、デフォルトで方法 1 を選択します。検証が不可欠な場合に限り、方法 2 に切り替えてください。

落とし穴 4:構造スキーマではすべてのノードに type が必要

z.unknown() は空の {} JSON Schema に変換されます。構造スキーマでは、type(または enum、あるいは回避策)がないノードは拒否されます。アダプター処理中に、すべての空のリーフに x-kubernetes-preserve-unknown-fields: true を付与してください。

落とし穴 5:ストレージバージョンにできるスキーマは 1 つだけ

versions[N].storage: true は、提供されるすべてのバージョンを通じて、CRD ごとにちょうど 1 つのエントリーでなければなりません。storage: true が複数あると、登録時に Apiserver が CRD を拒否します。v1beta1 から v1 を追加する場合は、必ずどちらか一方だけを有効にしてください。

実際のクラスターを使わずに検証する

CI で kind を起動し、次を実行します。

kubectl apply -f crd.yaml                              # registers the CRD
kubectl apply --dry-run=server -f every-example.yaml   # validates each CR against the new schema

これは k1c が .github/workflows/k8s-validate.yml で行っていることそのものです。参考となるジョブについては、そのワークフローを参照してください。

これが重要な理由

CRD は、人間、Apiserver、オペレーター間の契約です。構造スキーマのルールは恣意的なものではありません。Apiserver がサーバー側での検証、デフォルト値の設定、不要なフィールドの削除を行えるように設けられています。しかし、これらのルールは 1 か所にまとめて文書化されていません。公式の構造スキーマのドキュメントでは仕様が説明され、サブリソースのドキュメントでは /status が説明されています。また、metadata の禁止事項は、実行時の Apiserver エラーにしか記載されていません。このスキルでは、これら 4 つを 1 つのワークフローにまとめています。

関連するスキル

  • cloudflare-deploy — Cloudflare(CF)上で操作する場合の、Cloudflare 固有のリソース形式について説明します(CRD の生成とは独立しています)。
  • apm-usage — 生成した CRD バンドルをスキルの成果物として配布する方法について説明します。

コメント