Claude Media
Claude CodeでCrossplaneのCompositionを書く手順

Claude CodeでCrossplaneのCompositionを書く手順

Claude CodeにXRDとCompositionを書かせ、crossplane composition renderでクラスタを使わずローカル検証する手順をまとめます。

Crossplaneのcompositionは、複数のKubernetesリソースを1つのカスタムAPIとしてまとめる仕組みです。Claude Codeに書かせると、スキーマ定義(XRD)とリソース生成のロジック(Composition)を分けて進められます。この記事では、両者の役割分担と、クラスタを使わずローカルで検証するコマンドまでを扱います。

Crossplaneのcompositionとは

Compositionとは、複数のKubernetesリソースをまとめて作るためのテンプレートです。公式ドキュメントは、仮想マシン・ストレージ・ネットワークポリシーをまとめて1つの解決策にする例を挙げています。Crossplaneではこの「まとめられる側」の新しいAPIをXR(composite resource)と呼び、XRのスキーマをXRD(CompositeResourceDefinition)で定義します。

XRDとCompositionの関係は次の1点に尽きます。XRDが「どんなフィールドを持つAPIか」を決め、Compositionが「そのAPIが作成されたときに何を作るか」を決めます。同じXRD(同じ種類のXR)に対して複数のCompositionを用意し、用途ごとに使い分けることもできます。この分離が、単一のYAMLテンプレートで完結するHelmチャートとの一番の違いです。

前提条件

このガイドはCrossplaneの公式チュートリアルに沿っています。必要なのは次の2つです。

  • 2GB以上のRAMを持つKubernetesクラスタ
  • クラスタにインストール済みのCrossplane v2

ローカルでの検証だけなら、後述のcrossplane composition renderコマンドがDocker Engineを使ってその場で結果を確認できるため、クラスタへの適用は最後の1回で済みます。クラスタへ実際に接続してリソースを操作させたい場合は、Kubernetes MCPサーバーでClaudeからクラスタを操作するで扱っているMCP経由の接続が選択肢になります。本記事はファイルとしてXRDとCompositionを書く作業に絞ります。

ステップ1: XRDで新しいAPIのスキーマを定義する

Kubernetesでは、利用者が定義したAPIリソースをカスタムリソース(custom resource)と呼びます。Crossplaneは、そのうちcompositionで作られるものを特にコンポジットリソース(composite resource、XR)と呼びます。XRDはこのXRのスキーマを定義するオブジェクトです。

次はAppという名前のXRを定義するXRDの例です。spec.imageを必須の文字列フィールドとして公開し、statusにreplicasとaddressを持たせています。

apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
  name: apps.example.crossplane.io
spec:
  scope: Namespaced
  group: example.crossplane.io
  names:
    kind: App
    plural: apps
  versions:
    - name: v1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                image:
                  type: string
              required:
                - image
            status:
              type: object
              properties:
                replicas:
                  type: integer
                address:
                  type: string

Claude Codeへは、フィールドと型を指定して依頼すると迷いなく書けます。

「example.crossplane.ioグループにAppというXRDを作って。specにimage(必須の文字列)、statusにreplicas(整数)とaddress(文字列)を持たせて」

apiVersionはapiextensions.crossplane.io/v2です。次のステップで書くCompositionはapiextensions.crossplane.io/v1を使うため、Claude Codeが既存のCompositionからXRDをコピーして書くと、このAPIバージョンの食い違いをそのまま持ち込みがちです。

kubectl apply -f xrd.yaml
kubectl get compositeresourcedefinition apps.example.crossplane.io

ESTABLISHED列がTrueになれば、KubernetesはこのXRD経由でAppへのAPIリクエストを受け付け始めています。

ステップ2: CrossplaneのComposition関数を選ぶ

Compositionは単体では何も作りません。「XRが作成・更新されたときに何をするか」を決めるのはComposition関数です。公式ドキュメントは6つの言語を挙げています。

言語向いている用途
templated YAML向いている用途Helmチャートに慣れている場合
YAML向いている用途ループや条件分岐のない小さな静的構成
YAML+CEL向いている用途YAMLで書きつつCEL式でリソース間を接続。依存関係の解決は自動
Python向いている用途動的なロジックが必要。標準ライブラリを全て使える
KCL向いている用途動的なロジックが必要。高速でサンドボックス化されている
Pythonic向いている用途動的なロジックが必要。低レベルAPIをPythonクラスで隠したい場合

Claude Codeに書かせる場合、フィールドのパッチを列挙するだけのYAML(patch-and-transform)は差分が一目で追え、Claude Codeが書いたパッチのどこが変わったかをレビューしやすい構成です。条件分岐やループが要る構成では、YAMLの制約に直接ぶつかるため、Python・KCL・Pythonicのどれかに切り替えます。Helm風のテンプレート構文で慣れた書き方をそのまま使いたい場合は、Claude CodeでHelmチャートを作成する手順で扱っているテンプレート変数の設計が参考になります。

使う関数はKubernetesにインストールしてから参照します。fn-patch-and-transform.yamlは次の内容です。

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
kubectl apply -f fn-patch-and-transform.yaml
kubectl get function crossplane-contrib-function-patch-and-transform

INSTALLEDとHEALTHYが両方Trueになっていることを確認してから次に進みます。

ステップ3: CompositionをXRDに紐づける

CompositionはXRDが定義したXRの型に対してだけ有効です。この対応はspec.compositeTypeRefが決めます。ここが一致していないと、CrossplaneはそのCompositionをどのXRにも使いません。

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-yaml
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
    - step: create-deployment-and-service
      functionRef:
        name: crossplane-contrib-function-patch-and-transform
      input:
        apiVersion: pt.fn.crossplane.io/v1beta1
        kind: Resources
        resources:
          - name: deployment
            base:
              apiVersion: apps/v1
              kind: Deployment
              spec:
                replicas: 2
                template:
                  spec:
                    containers:
                      - name: app
                        ports:
                          - containerPort: 80
            patches:
              - type: FromCompositeFieldPath
                fromFieldPath: metadata.name
                toFieldPath: metadata.labels[example.crossplane.io/app]
              - type: FromCompositeFieldPath
                fromFieldPath: metadata.name
                toFieldPath: spec.selector.matchLabels[example.crossplane.io/app]
              - type: FromCompositeFieldPath
                fromFieldPath: metadata.name
                toFieldPath: spec.template.metadata.labels[example.crossplane.io/app]
              - type: FromCompositeFieldPath
                fromFieldPath: spec.image
                toFieldPath: spec.template.spec.containers[0].image
              - type: ToCompositeFieldPath
                fromFieldPath: status.availableReplicas
                toFieldPath: status.replicas
            readinessChecks:
              - type: MatchCondition
                matchCondition:
                  type: Available
                  status: "True"
          - name: service
            base:
              apiVersion: v1
              kind: Service
              spec:
                ports:
                  - protocol: TCP
                    port: 8080
                    targetPort: 80
            patches:
              - type: FromCompositeFieldPath
                fromFieldPath: metadata.name
                toFieldPath: metadata.labels[example.crossplane.io/app]
              - type: FromCompositeFieldPath
                fromFieldPath: metadata.name
                toFieldPath: spec.selector[example.crossplane.io/app]
              - type: ToCompositeFieldPath
                fromFieldPath: spec.clusterIP
                toFieldPath: status.address
            readinessChecks:
              - type: NonEmpty
                fieldPath: spec.clusterIP

deploymentとserviceの2つをresourcesに並べ、それぞれのpatchesでXRのmetadata.nameをラベルとセレクタへ、spec.imageをコンテナイメージへ反映させています。deployment側はspec.selector.matchLabelsとspec.template.metadata.labelsの両方にラベルを通さないと、KubernetesがPodをDeploymentの管理下として認識しません。mode: PipelineはCompositionの実行方式です。pipelineには1つ以上の関数をステップとして並べられ、後段の関数は前段の出力を書き換えられます。Crossplaneが最終的に使うのは、パイプラインの最後の関数が返した結果です。複数の関数を組み合わせる場合、順序を間違えると先に書いたパッチが後段で黙って上書きされるので注意します。

同じ種類のXRに対して複数のCompositionを作ることもできます。用途ごとにapp-yamlとapp-pythonのような別名のCompositionを用意しておき、XR個別に選ぶか既定値を決めておく運用です。個々のXRではspec.crossplane.compositionRef.nameで名前指定、またはspec.crossplane.compositionSelector.matchLabelsでラベル指定して選べます。XR側で何も指定しなかった場合の既定は、XRD側のspec.defaultCompositionRefで設定します。IaCツール自体の選定で迷う場合は、Terraform/Pulumi/CloudFormationのMCP比較がKubernetesネイティブなCrossplaneとの対比材料になります。

crossplane composition renderでクラスタなしに検証する

CompositionはCrossplane CLIのcrossplane composition renderでローカルにプレビューできます。クラスタへの接続は不要で、Docker EngineでFunctionを実行して結果を標準出力に返します。Crossplane CLI自体は次のコマンドでインストールできます。

curl -sfL "https://cli.crossplane.io/install.sh" | sh

renderには3つのファイルを渡します。ステップ3のCompositionはcomposition.yamlとして保存しておきます。Functionはステップ2で作ったfn-patch-and-transform.yamlをそのまま使い、加えてXR本体をxr.yamlとして用意します。

apiVersion: example.crossplane.io/v1
kind: App
metadata:
  namespace: default
  name: my-app
spec:
  image: nginx
crossplane composition render xr.yaml composition.yaml fn-patch-and-transform.yaml

出力はXR本体に続けて、Compositionが作成する各リソースのYAMLです。Claude CodeにComposition本体を編集させた直後は、次のように依頼すると出力を読ませた上でクラスタへ適用する前にパッチの結果を確認できます。実行にはDockerが必要です。

「compositionを編集したらcrossplane composition render xr.yaml composition.yaml fn-patch-and-transform.yamlを実行して、Deployment側のimageとラベルが意図通りパッチされているか差分を報告して」

このコマンドはクラスタの状態を一切変更しないため、.claude/settings.jsonのpermissions.allowにBash(crossplane composition render *)を追加しておくと、確認のたびに実行許可を求められずに済みます。実際にクラスタへ反映するkubectl applyは許可リストに入れず、都度確認する運用のままにします。使うFunctionのパッケージ名とバージョンはCLAUDE.mdに書いておくと、セッションをまたいでもClaude Codeが生成するfunctionRefがぶれません。

ステップ4: XRを作成してクラスタで確認する

renderでパッチの結果を確認したら、renderで使ったxr.yamlをapp.yamlとしてそのままクラスタへ適用してXRを作成します。

kubectl apply -f app.yaml
kubectl get -f app.yaml

出力のSYNCED列とREADY列が両方Trueになれば、AppはComposition経由でDeploymentとServiceを作り終えています。COMPOSITION列には実際に使われたComposition名が表示されるので、同じXRに複数のCompositionを用意している場合はここで見分けます。DeploymentとServiceの実体は次のコマンドで直接確認できます。

kubectl get deploy,service -l example.crossplane.io/app=my-app

Appのspec.imageを書き換えると、Crossplaneは紐づくDeploymentのimageも追従させます。Appを削除すると、そのAppが作ったDeploymentとServiceも連鎖して削除される点は次のつまずきでも触れます。

よくあるつまずき

  • XRDとCompositionのapiVersionを混同する: XRDはapiextensions.crossplane.io/v2、Compositionはapiextensions.crossplane.io/v1です。既存のYAMLをコピーして書くと、どちらかが古いバージョンのまま残ります。
  • YAML(patch-and-transform)にループや条件分岐を書こうとする: この言語は小さな静的構成向けで、ループも条件分岐もサポートしません。動的なロジックが必要ならPython・KCL・Pythonicに切り替えます。
  • パイプラインの順序でパッチが上書きされる: 複数の関数を並べると、後段の関数が前段の出力を書き換えられます。最終結果は最後の関数の戻り値なので、意図した順序になっているか確認します。
  • provider未インストールの種類のリソースを作ろうとする: Crossplaneのサービスアカウントは、providerがインストールした種類とXRDが定義した種類のリソースに加えて、Deploymentのように自身の動作に必要な一部のコアKubernetesリソースにも既定でアクセスできます。本記事のDeployment・Serviceはこの既定権限の範囲内なので、追加のRBACは不要です。それ以外の種類(未インストールのproviderが管理するリソース等)を作る場合は、rbac.crossplane.io/aggregate-to-crossplane: "true"のラベルを付けたClusterRoleを別途作成し、CrossplaneのClusterRoleに集約させます。
  • XRを削除したら子リソースごと消える: Appを削除すると、Compositionが作ったDeploymentとServiceも一緒に削除されます。検証中に何度もXRを作り直す運用では、削除のタイミングに注意します。

まとめ

Crossplaneのcompositionは、APIのスキーマ(XRD)と生成ロジック(Composition)を分けて設計する仕組みです。Claude Codeに書かせる場合、パッチの列挙で済むYAMLはレビューしやすく、条件分岐が要る構成はPython・KCL・Pythonicが向きます。編集のたびにcrossplane composition renderをローカルで実行させれば、クラスタに触れる前にパッチの結果を確認できます。Kubernetesリソースを直接YAMLで書く基本操作は、Claude CodeでKubernetesマニフェストを書く手順で扱っています。

この記事を共有:XはてブLinkedIn