「Crossplane 2.0」深掘り: インフラの合成エンジンから組織のAPIの土台へ
第4回の今回は、「Crossplane 2.0」のComposition機構とFunctionの実行モデルを深掘りし、KubeVelaからの移行検証で見えた設計思想の違いについて解説します。
6:30
はじめに
第3回ではKubeVelaを深掘りし、逆方向のアプローチとしてCrossplane 2.0を予告しました。KubeVelaがApplicationからインフラへ広げるのに対し、CrossplaneはインフラからApplicationへ広げてきました。OAM(Open Application Model)のSpecが2021年6月のv0.3.0で止まった筆者チームにとって(経緯は第1回)、次の土台の最初の候補でもあります。
Crossplane 2.0とは結局何で、これは引き受けて回せる代物なのか。本稿では、公式ドキュメントの原文、内部のソースコード、kind上の実測で確かめます。まとめの手前にはおまけとして、筆者チームのKubeVela資産を移せるかの検証を置きます。着手前の見積もりは「似た部品が揃っているのだから、書き写す作業だろう」でした。
Crossplaneとは何か
control planeという出発点
Crossplaneは、Kubernetesを「アプリを動かす場所」から「あらゆるリソースのcontrol plane」へ拡張するOSSです(2025年11月にCNCF Graduatedへ昇格)。望ましい状態を持ち続け、現実がズレたら自動で戻します(reconcile)。
公式ドキュメントは、Crossplaneを4つの主要コンポーネントに整理しています。
- Composition:組織独自のAPIを作る仕組みで、本稿の主役です。ここには4つが属します。スキーマを定義するXRD(CompositeResourceDefinition)、実リソースへ展開するレシピのComposition(紛らわしいですが、コンポーネント名と同名のリソースです)、レシピを実行するFunction、開発者が実際に作るインスタンスのXR(Composite Resource)です。
- Managed resources(MR):クラウドAPIをCRDにした、出来合いのカスタムリソースです。RDSInstanceをapplyすると、実際のRDSインスタンスが作られます。
- Operations:Function pipelineで運用タスクを走らせる、v2で加わった仕組みです。
- Package manager:ProviderやFunctionを配布・導入する仕組みです。
4つすべてを使う必要はなく、必要なものだけ選べます。MRだけをTerraform代替として使う組織もあれば、Compositionまで組んで独自APIを作る組織もあります。
2.0で何が変わったか
「2.0」と呼んできたのには理由があります。v1とv2では、この道具の守備範囲が違うのです。Crossplane 2.0は2025年8月12日GAで、執筆時点の最新はv2.3.3です。変更点をv1ではどうだったかとセットで並べます。
| 変わったこと | v1ではこうだった | v2で期待できること |
| XRとMRのnamespaced化 | XRはcluster-scoped。開発者がnamespaceから使うにはClaimという代理リソースを介す二重構造だった | Claimなしで直接XRを書ける。学ぶ概念と権限設計が1段減る |
| 素のKubernetesリソースの合成 | DeploymentなどはObjectというラッパーCRD越しにしか合成できなかった | ラッパー抜きでアプリのリソースまで合成対象になる |
| Composition Functionsへの一本化 | インラインのpatch-and-transform記法が主流(v1.17で非推奨) | レシピの書き方が1つに揃う。P&TもFunctionの1つとして存続 |
1行目のClaimについては、設計者Nic Cope氏自身がv2 proposalで「プラットフォーム利用者の不満と混乱の源であり続けた」と振り返っています。旧APIはLegacyClusterモードとして残り、既存のClaimは動き続けるので、「廃止」ではなく「不要化」です。
3つの変更は同じ方向を向いています。インフラの合成エンジンとして出発したCrossplaneを、アプリのリソースまで含めて、開発者がnamespaceの中で完結して使える道具に近づけようとしています。
コア機能を深く理解する
Composition機構 ーXRD・Composition・XR
まず、プラットフォームチームがXRDで「うちのWebAppはこういう項目を持つ」という型を宣言します。決まり文句を除くと、設計するのは次の部分です。
# XRD抜粋
versions:
- name: v1
referenceable: true # Compositionから参照される版の印
schema:
openAPIV3Schema:
# ...スキーマまでの入れ子は定型...
properties:
image: { type: string } # (A) 開発者に見せる項目
serviceType: { type: string, enum: [http, grpc] } # (A)。enumの制約は(B)の検証
required: [image, serviceType] # (B) 入口の検証同じチームが、Compositionで「WebAppから何をどう作るか」というレシピを書きます。そして開発者が書くのは、型のインスタンスであるXRだけです。
apiVersion: example.org/v1
kind: WebApp
metadata:
name: order-api
namespace: team-a
spec:
image: ghcr.io/example/order-api:v1.2.0 # (A) 宣言した項目に、実際の値を与えるレシピを含めた3つのYAMLの全体と対応関係を図2にまとめます。
読み方は、グレーの決まり文句と、(A)〜(D)の4色です。グレーはどのXRD・Compositionでもほぼ同じで、写経で足ります。
(A)は開発者の知る名前です。XRDがimageを宣言し、CompositionがfromFieldPathで受け取り、XRが実際の値を与えます。同じ名前が3つのファイルを貫きます。
(B)は雛形と検証です。baseの雛形、値の行き先、requiredやenumによる入口の検証は、スキーマとレシピの側だけが知っています。
(C)は型とレシピの接続点で、XRDのreferenceableとCompositionのcompositeTypeRefが対になります。
(D)はpipelineの骨組みで、stepを並べ、各stepの実行エンジンをfunctionRefで指名します。
条件分岐やループのような複雑なロジックは、(D)のinputを解釈するFunctionの実装側に書きます。このWebAppをapplyするとCompositionが受け取り、DeploymentとServiceが生成されます。DB(MR)を足したければ、レシピに1リソース追加するだけです。開発者が触るのはXRだけ、という体験です。
Functionの実行モデルー全量を受け取り、全量を返す
Functionは、OCIパッケージとして配布される小さなサーバです。独立プロセスとして動き、CrossplaneからgRPCで呼ばれ、pipelineの一段として初めて仕事をします。
導入の単位は、リソースの種類ではなく、レシピを解釈する実行エンジンです。function-kclのようなFunctionを1つ導入し、複数のCompositionから使い回します。何を生成できるかを決めるのはFunctionではなく、クラスタに入っているAPIの側です。組み込みリソース、ProviderのMR、導入済みのCRDが、その範囲を決めます。
XRのreconcileが走るたびに、Crossplane coreがpipelineを実行します。coreは各stepのFunctionへ、観測された現状(XRと、すでに存在する生成リソース)と、前段までが組み立てたdesired stateをgRPCで渡します。Functionはそれを読んで手を入れ、desired stateの全体を返します。
次のstepはその全体を受け取り、また全体を返します。最後のstepが返した全体が「正解」となり、coreが現実との差分を計算して適用します。差分を計算するのはFunctionではなくcoreの仕事、というのが要点です。
ただし、間違いの壊れ方は非対称です。Functionがエラーを返すとreconcileはその場で中断され、適用も削除も起きません(composition_functions.goを読むと、pipelineの失敗は適用処理へ進む前に返ります)。
落ちる間違いは、安全側に倒れます。黙って通るのは、成功しながらリソースを欠落させる間違いと、間違った値のまま成功してしまう間違いです。欠落は削除に直結するぶん一段危険ですが、function-kclのような既製の実行エンジンは前段の結果の引き継ぎを自動で行うため、これが生で起きるのは自作Functionを書くときです。
この「各段が常に全体を受け取り、全体を返す」ルールを、公式ドキュメントはfull desired stateの受け渡しと呼びます。
運用の視点では、この設計は「レシピの実行が、XR数×step数×reconcile頻度のgRPC呼び出しになる」ことを意味します。筆者の計測では、6 stepのpipelineは1回のreconcileでちょうど6回のgRPC呼び出しを起こし、新規applyがSynced=Trueに収束するまでには5回のreconcile、計30回の呼び出しが走りました。変更がなくてもpoll間隔ごとに6回が定常的に発生します。Function Podは全Compositionが共有する部品なので、その可用性と応答時間は、プラットフォーム全体の可用性と収束速度そのものです。
リソース間の情報伝搬ークラウドの採番を待って配線する
図4を眺めると、reconcileのたびに観測された現状を集め直すのは、無駄な仕事に見えます。テンプレートなら一度展開すれば済むはずです。この一見無駄な再実行にこそ、テンプレートエンジンには真似できない能力が載っています。実行時にしか決まらない値を、リソースからリソースへ配線できるのです。
パナソニック コネクト様がCloudNative Days Winter 2025の登壇で示した実例が分かりやすいので借ります。
XServiceAccountというXRを作ると、CompositionがAzureのUserAssignedIdentity(MR)とKubernetesのServiceAccountを合成します。workload identityを成立させるにはAzureが採番するclientIdをServiceAccountのannotationへ書き込む必要がありますが、この値はapplyの時点では存在しません。クラウドが実体を作って初めて、MRのstatus.atProvider.clientIdに現れます。
動きはこうなります。最初のreconcileでpipelineはUserAssignedIdentityを出し、まだclientIdがないのでServiceAccountは保留します。Providerが実体を作りstatusへ書き戻すと、生成リソースをwatchするcore(beta機能、既定で有効)が次のreconcileを起こします。
今度はpipelineが観測済みのMRからclientIdを読み、annotationへ焼き込んだServiceAccountを出します。この間、人が宣言を書き直すことはありません。この2回の往復を図5にまとめます。
Terraformならresource間の参照とoutputで書く配線が、ここでは「観測を入力に再実行され続けるレシピ」として動きます。合成が1回きりの展開ではなくreconcileの一部であることの意味は、この伝搬にあります。
MR・Provider・Package manager
AWSやGCPのProviderパッケージを導入すると対応するMRのCRDが生え、Providerがクラウドの認証情報を持って実リソースとのreconcileを担います。
Provider本体もFunctionもxpkgと呼ばれるOCIパッケージとして配布され、Package managerが導入と依存解決を担います。MRはManaged Resource Activation Policy(MRAP)で、使う分だけ有効化できます。
Operationsー宣言に混ぜない運用タスク
Operationsは、FunctionのpipelineをJobのように「完了まで一度だけ」実行する仕組みです(ops.crossplane.io/v1alpha1、まだalphaです)。きっかけは手動のOperation、定期実行のCronOperation、リソース変化駆動のWatchOperationの3通りです。バックアップやrolling upgradeのような「何度でもやり直す」性質になじまない運用タスクの受け皿です。
KubeVelaのWorkflow(デリバリの手順を宣言する機能)に似た姿ですが、置き場所が違います。WorkflowがApplicationのデリバリ本体に組み込まれて使われるのが標準形なのに対し、OperationsはXRのreconcileから切り離された独立のリソースです。Compositionと共有しているのはpipelineという実行の枠組みまでで、ルールは別物です。
公式の比較によれば、Composition Functionが観測されたXRを入力にdesired stateを返して継続的にreconcileされるのに対し、Operation Functionは宣言しておいた必要リソースを入力に、任意のリソースへ一回性の変更をowner referenceなしでforce applyします。入出力・所有権・ライフサイクルが異なる、別のルールです。共通するのは、運用タスクをreconcileに混ぜないという設計意図です。
保守運用の条件
回している組織
公式のADOPTERS.mdには、Nike、SAP、Akamaiなど70を超える組織が載っています(自己申告のリストです)。執筆時点で、このリストに日本企業名は見当たりません。CNCF Graduationの発表によれば、contributorは3,000人超、参加組織は450超です。規模は大きい一方、公開事例の多くはインフラのself-serviceです。
また、執筆時点で日本語の一次資料からproduction運用を確認できたのは2社でした。hacomono様はマルチプロダクトのSaaS基盤にCrossplane v2とFunctionを採用し、マネージドサービスをガードレール込みの独自リソースとして提供しています(techblog.hacomono.jp、2025年10月・12月)。約20種類のカスタムリソースの管理コスト、Function間gRPC通信の不安定なエラー、「抽象化には時間がかかり、柔軟性も下がる」という率直な辛みも語られています。
登壇資料はさらに具体的で、実際に起きた設定ミスとして、失敗してもRunningし続けるカスタムリソース、クラウドとの差分が埋まらない無限reconcile、消したくても消えない永続層、Crossplane側のバグで期待した更新がされないケースが挙げられています。
パナソニック コネクト様は、Azure/AKS上のマルチテナントSaaS基盤で、4手順(Bicep→Helm→GitHub→ArgoCD)にまたがっていたプロビジョニングを1手順に集約しました(CloudNative Days Winter 2025)。管理するランタイムは約5個(2024年6月)から約30個(2025年11月)へ拡大中です。
海外では、金融のMacquarie Group様が、クラウドごとに分断されたプロビジョニングを統一control planeへ作り替え、オンボーディングを数ヶ月から数週間へ短縮したとKubeCon EU 2025で発表しています。ただしv1時代からの積み上げで、2.0の新機能の実証ではありません。このほかにも、Elastic様が数百クラスタ規模の採用をKubeCon EU 2024で語っています。
初期導入段階で決まる条件
では、何を引き受ければ回るのか。最初の一群は、初期導入段階で決まり、後から動かすのが重い条件です。
1つ目はCRDの初期設計です。Nic Cope氏による2022年の計測では、API ServerはCRD 1個あたり4MiB強のメモリを消費し、1,900個規模で顕著に不安定化します。パナソニック コネクト様の実測では、Azure Providerのデフォルト導入でCRDが495個登録されAPI Serverのメモリ使用率が18%に達し、不要なMRを無効化して36個へ絞ると1%まで下がったといいます(同社の導入はv1系の時期で、絞り込みの仕組みは現在と同じではありません)。
対処の方向は今も変わらず「使うリソースだけ有効化」です。v2系では各MRがManaged Resource Definitions(MRD)という定義の形で届き、前節で触れたMRAPが、どれを有効化するかを指定します。有効化を後から足すことはできますが、逆は利きません。動かし始めたMRを無効化へ戻す操作はなく、事後に絞り直すなら導入設定の組み直しになります。重いのは広げる方向ではなく、絞る方向です。
APIを将来どう廃止するかも、最初に決めておくべきです。XRDを削除すると、CrossplaneはまずそのAPIで作られた全インスタンスを削除し、それからCRDを消します。APIの廃止は全利用者のリソース削除と同義です。
またXRDごとに専用のcontrollerが起動するため、APIを増やすことはcontrollerを増やすことでもあります。XRDの寿命管理には、データベースのスキーマ運用と同じ重さがあります。
負荷と影響半径
Crossplaneは組織のリソース全体を握るcontrol planeです。1つのアプリ宣言から生まれるリソースは1桁では収まりません(筆者の縮約した試作でも6〜7個です)。コア機能の節で実測した通り、1回のreconcileにはstep数ぶんのgRPC往復が乗るので、reconcileの総量そのものが問題になります。多数のプロジェクトを抱えるDeutsche Bahn様は、共有リソースへの過剰なAPI負荷を自作のcontrollerで補ったと講演で語っています。
暴走には安全弁もあります。v2.1で導入されたCircuit breakerはXRごとにwatchイベントの頻度を監視し、現行v2.3系の既定ではバースト100・毎秒1回の補充を超えると、そのXRのreconcileをほぼ遮断します(30秒に1回だけ回復を試します)。守られるのはクラスタ側で、遮断されたXRの収束は止まります。XR単位の仕組みなので、これでは多数のXRが刻む定常負荷は減りません。
Providerは複数チーム分のクラウド権限を集中して持つため、control planeの誤りは即座に実リソースへ伝播します。Functions導入初期のVSHN様は、部分的な結果を返したFunctionでproductionリソースが削除された事故を報告しています(2023年、alpha期の教訓です)。この事故が深刻なのは、この集中構造の上で起きたからです。
検証とデバッグ
Terraformのplanに相当する「現在との差分」を出す道具はありません。crossplane renderコマンドが描くのは望ましい状態までで、hacomono様も登壇でこの不在を運用課題の筆頭に挙げています。renderの先にも、スキーマ検証・admission・権限と壁が続きます。筆者の試作でも、renderを通過した誤りが2つ、実クラスタで初めて弾かれました。
デバッグも同じです。「動いているcontrol planeの中のロジックのデバッグは歴史的に難しかった」と公式ブログ自身が認めています。v2系でrender・validate・traceと道具は揃ってきましたが、pipelineの中を覗くinspectorはまだalphaです。hacomono様が対策として挙げるのは、Crossplane内部のソースコード(reconciler等)まで読み込む「完全理解」でした。本稿がここまでソースコードを根拠に引いてきたのは、回している組織に実際に要求される理解が、その水準だからです。
ただし、公平のために筆者の実感を添えます。これはCrossplane固有の穴ではありません。KubeVelaを運用してきた筆者の日常も同じで、vela dry-runが描くのは望ましい状態までです。現在との差分は出ず、Definitionのテストの枠組みもなく、最後は実クラスタへ流すE2Eで確かめるしかありません。宣言を抽象するレイヤはどれも、まだplanの水準に届いていません。この弱点でツールを選ぶ理由にはならない、というのが筆者の判断です。
レシピの変更と保守
特に踏みやすいのが、リソース名の変更です。生成リソースの同一性はComposition内で付けた名前(図2のname: deployment。内部ではcomposition-resource-name annotation)で管理されるため、名前を変えることは「旧名の削除と新名の新規作成」の宣言になります。
kind上で確かめると、名前だけを変えて生成物のmetadata.nameを保っても、uidは改名のたびに変わりました。Deploymentなら作り直しで済みますが、RDSのようなMRでは削除ポリシー次第で実体の再作成になりえます。レシピのリファクタリングは、リソースの寿命に触る操作です。
裏を返せば、Composition資産の保守の形も見えています。renderの出力をシナリオごとに期待値として保存すれば、Compositionの変更はスナップショットテストで差分検知できます。先ほどの改名も、この差分には削除と新規作成として現れます。KCLの生成ロジックは、通常のコードレビューに載ります。そこに、admissionまで届く適用試験をどう足すか。資産を回し続けられるかは、この検証の仕組みを整えられるかに懸かっています。
ツールの外で決まる条件
最後の一群は、ツールの仕様ではなく、組織と市場の側で決まる条件です。Functionの言語選定には正解がありません。go-templating、KCL、Pythonなどが横並びです。GitHubのstar数は当てになりません。OCIパッケージで配布されるため、starは利用規模を反映しないからです。
サポート範囲の線引きも同じです。カスタムリソースが支える機能を増やすほど、その機能ぶんの知識と運用コストをプラットフォームチームが引き受けます。かといってサポート外を開発者の別IaC管理へ逃がすと、インフラ管理の一貫性が崩れます。hacomono様はこのジレンマを「Terraform module配布の方が『オプションで組み合わせ可能』を達成しやすい」とまで率直に語り、多くを支える運用体制を組むか、サポート外の明示と対策方針を固めるか、のどちらかが要ると結論しています。抽象の面を提供するとは、その裏にある全機能の面倒を見ると宣言することです。
商用の境界は動いています。v2系のOfficial Packagesは、Upboundのディストリビューション(無償のCommunity Editionまたは商用版)を要求する方向へ変わりました(2025年9月発表)。無償版でproduction利用は可能ですが、境界線が単一ベンダーの判断で引き直されうる構造には注意が要ります。
OAM・KubeVelaとの比較
狙いは同じ、概念の対応も付く
ここまでの道具立てに、筆者は既視感を覚えました。プラットフォームチームが型とレシピを用意し、開発者はそのインスタンスを書くだけ。この分業は、OAMが2019年に目指したものと同じです。
OAMは、MicrosoftとAlibaba Cloudが提唱した、アプリの抽象を仕様として定義する試みでした。アプリを構成するワークロードの単位をComponent、スケーリングやIngressのような運用特性を後から合成する部品をTraitと呼びます。KubeVelaはその代表実装で、各Definitionの実体をCUEテンプレートとして書きます。
Crossplane 2.0のGA発表で、メンテナ自身がv2を「the kind of full-stack self-service APIs that platform teams have been asking for(プラットフォームチームが求め続けてきた類のフルスタックself-service API)」と説明しています。概念の対応も素直に付きます。
| KubeVela(OAM)側 | Crossplane側 |
| ComponentDefinitionのparameter部 (開発者に見せるスキーマ) | XRD |
| Applicationが持つcomponent (型のインスタンス) | XR |
| 同じComponentDefinitionのtemplate部 (実リソースへの展開) | Composition |
| Trait(運用特性の後付け) | pipelineのstep(後付けの体験は残らない) |
| Workflow(apply時の手順) | Operations(reconcile外で同型のpipelineを回す) |
| Addon(Definitionの配布) | xpkg |
本質の違いは、部品が前提にする分業
概念が対応するのに、なぜ話はそれで終わらないのか。いまのPlatform Engineeringでは、Application Modelに責任を持つのはプラットフォームチームです。ComponentDefinitionもTraitも書くのは同じチームで、アプリ開発者が自分でTraitを選んで足す運用は一般的ではありません。筆者のチームも同じです。
この現実を頭に置いてTraitの作りを見直すと、見え方が変わります。Traitは自分のcomponentの描画結果(context.output)を受け取り、自分の寄与分(patchと自分のoutputs)だけを返す部品です。全体を組み立てるのはvelaエンジンの仕事です。
部品が寄与分しか返さないからこそ、マージの規約(patchKeyや適用順)が要り、他のTraitが積んだ変更を読んで展開し直すメタ的なTraitのような調整部品も要ります。この機構一式は、OAMが2019年に想定した分業、つまりアプリ開発者・アプリ運用者・インフラ運用者が別人で、運用者が既存のアプリへ後からTraitを貼る、という体制のための設計です。
差分(patch)だけを返すこのルールは、お互いを知らない複数の書き手の寄与を、衝突させずに合成するための調整機構です。
Crossplaneのstepのルールは、逆の前提で書かれています。各stepは前段までの組み立て結果を丸ごと受け取り、自分の変更を織り込んだ全体を返します。全体に触れる部品を、見知らぬ他人へ無防備に開放はできません。
このルールは、レシピ全体を1つのチームが責任を持って書くことを前提にしています。だからマージの規約がありません。部品どうしの調整は、同じ書き手の頭の中とコードレビューで済むからです。
つまり、両者の違いは機能の優劣ではありません。部品を何人で書くかという想定の違いです。Traitのマージ規約は他人の書いたTraitと衝突しないための決まりで、書き手が複数いて初めて意味を持ちます。想定どおりに役割が分かれている組織なら、この決まりは価値です。
筆者のチームのように1つのチームが全部品を書いているなら、衝突する他人がいないのに決まりを守り続けることになり、コストだけが残ります。OAMがComponentやTraitという語彙を仕様で固定したのも、組織をまたいで通じさせるためでした。自分の組織の中だけで使うなら、固定の恩恵はありません。
能力の対応表
では、KubeVelaにできることはCrossplaneにもできるのでしょうか。
| KubeVelaの能力 | Crossplaneでの受け皿 |
| Component:typeひとつでワークロード一式を生成 | XRD + Compositionで可能 |
| Trait:運用特性を後付け | XRDのフィールド + 注入stepで再現可能。後付けする体験は不可 |
| Workflow:apply時の手順・通知・待機 | 一回性はOperations(alpha)。副作用系はCD層へ外出し |
| healthPolicy:health判定 | auto-ready(readyを判定する既製Function)等でXRのstatusへ明示的に集約 |
| Addon:Definitionの配布 | xpkgとして配布可能 |
対応が付かない能力は、ほぼありません。ただし右列の多くは「同じ体験の再現」ではなく「別の機構への置き換え」です。Traitの後付け体験は、全量を返す世界に存在せず、stepの構成はプラットフォームチームが固定し、開発者はXRのフィールドでon/offを選ぶ形になります。
KubeVelaのDefinitionはCrossplaneへ移せるのか
移す資産のプロファイル
筆者チームがKubeVelaで提供しているのは、typeひとつで標準構成のワークロード一式が出てくるComponentDefinition群と、9種のTraitです。CUEにして123ファイル、2万行弱の資産です。
最大のComponentにはパラメータが約100個あります。3種類のprobeがプロトコルごとに分岐し、HPAはレプリカ数を固定しない場合だけ生成され、サービスメッシュ関連のリソース群はフラグひとつで出たり消えたりします。canaryデプロイを選ぶと、ワークロードの種類自体がArgo RolloutsのRolloutに切り替わります。
移す前にー両者の作りはどう違うか
棚卸しを終えて手を動かし始めると、書き写す作業という想定が最初に崩れました。移行の作業量と保守の形を決めるのは、機能の一覧ではなく、両者の「作りの違い」だったからです。
| 観点 | KubeVela | Crossplane |
| 記述の置き場所 | parameter(スキーマ)とtemplate(展開)を1つのCUEファイルに | XRD(OpenAPI+CEL)とFunction(KCLなど)に分離 |
| パラメータを1つ足す | 1ファイル・1言語で完結 | スキーマと実装の2箇所を触る |
| 入口の検証 | CUEの型と制約 | Kubernetes標準のOpenAPI+CELでadmissionが弾く |
| 開発者への見せ方 | vela showがドキュメントを自動生成 | kubectl explainは効く。専用の面は自作 |
| 展開ロジックの言語 | CUE固定 | Functionごとに選べる(KCL / Go templateなど) |
| ローカルの検証 | vela dry-run | crossplane render |
表の根にあるのは1行目、スキーマと展開ロジックの距離です。KubeVelaのComponentDefinitionは、開発者に見せるparameter(スキーマ)と展開のtemplateを、1つのCUEファイルに同じ言語で書きます。
Crossplaneでは同じ役割が2つの成果物に割れます。スキーマはXRDにOpenAPIとCELで、展開ロジックはFunctionにKCLで書き、パラメータを1つ足すたびに両方を触ります。筆者の試作でも、25フィールドのXRDとKCL側の入力処理は別々に書きました。
記述を1箇所に束ねる密度では、KubeVelaのCUE統合の方が上です。言語を差し替えられる自由とスキーマと実装の二重管理は、Crossplaneの同じ設計から出た表と裏です。
何がどこに移るか
この「束ねるKubeVela、分けるCrossplane」を頭に置くと、移し先は3層に整理できます。対応を図8にまとめます。
第1層は開発者に見せる面です。
約100個のパラメータはXRDのスキーマへ写し、KubeVelaでCUEの型システムが担っていた「入口で間違いを弾く」役割は、CELの検証としてadmissionに残せます。試作では構造上の難所を25フィールドに絞り、CEL制約を3本入れました(レプリカ固定とスケーリングの同時指定の禁止など)。ただし約100個分を書けば、スキーマは相応の長さになります。
第2層は展開ロジックです。
Compositionのpipelineを6つのstepに割りました。中心になるのは、workload(canary指定ならRolloutへ出し分け)・networking(mesh有効ならVirtualServiceなども)・policies(replicas未指定ならHPA)・inject-config・auto-readyの5つです。
分岐とループは、各stepのFunction実装の中の本物のifとforです。言語にはKCLを選びました。既存資産のCUEと同じ型付き・制約ベースの言語で、頭の切り替えが小さいという判断です。それでもCUEの資産は、自動変換ではなく言語ごとの書き直しになります。
Componentが抱えていた複雑な変換も、Traitと同じようにFunctionの中へ移ります。違いは読む入力だけです。workloadやpoliciesのような生成系のstepは、XRの入力(oxr)だけを読んで組み立てます。前段の結果(dcds)まで読む必要があるのは、inject-configのように、他のstepが作ったものへ後から手を入れる部品だけです。
第3層がTraitです。
移してみると、対応は3通りに割れました。ServiceやSecurityGroupPolicyのように、リソースを1つ追加で生やすだけのTraitは、生成系のstepへそのまま移せます。ConfigMapやSecretのマウント、initContainer、書き込み用volumeのように、できあがったworkloadへ後から差し込むTraitは、stepへの書き換えが必要です。
そして1つだけ特殊なTraitがありました。他のTraitのpatchがすべて当たった後の最終形を読み取り、その結果を材料に別のリソースを組み立てるものです。patch方式では、各Traitは自分のpatchしか知らず、最終形はどのTraitからも見えません。だから「最後に走って、他のTraitの結果を読む」専用の部品が必要になります。移行で一番の難所になると身構えていたのは、この部品です。
ところが、Crossplane側には対応物を作る必要がありませんでした。stepは前段までの組み立て結果を丸ごと受け取るので、「最終形を読む」ことは専用部品の仕事ではなく、後ろに置いたstepにとっての普通の入力だからです。
書き換えの核ーTraitはstepになる
3通りのうち、移行の成否を握るのはworkloadへ後から差し込む注入系のTraitです。試作のinject-config stepの実物を図9に解剖します。
function-kclの入出力仕様では、前段までの組み立て中の結果をdcds(desired composed resources)、クラスタから観測した現実をocds(observed composed resources)という別のmapで参照します。
Traitの部品は、それぞれ形を変えて生き残ります。parameterは(A)のXR読み取りに、Traitが手を入れる「対象」は(B)のdcds参照に、patchは(C)のdeep mergeになり、(D)同じリソース名を名乗ることで新規作成ではなく上書きとして扱われます。
設定がなければ(E)何も出しませんが、workloadは前段のstepがdesired stateへ入れているので消えません。「どのstepがそのリソースをdesiredへ入れているか」が、削除の境目になります。
KCLという言語の選択
すでに2万行弱のCUE資産があるのに、なぜCUEのまま行かないのか。この反問には答えておく必要があります。KCLがCUEより高機能だから、ではありません。CUEにも条件式や内包表記があり、同種の条件生成は書けます。差が出るのは書き味です。
KCLはifとforで「この条件ならこのリソースを出す」と逐次に読めます。この書き味の実物が、図9のinject-configです。CUEは値と制約を順序に依存せずunificationする言語で、公式解説が強調する通りoverrideを許さないため、schema・data・policyを1つのモデルで厳密に扱えます。生成ロジックの読みやすさを取るか、整合性検証の厳密さを取るかの違いで、後者ではCUEの方が強い場面があります。
| 観点 | KCL | CUE |
| 書き味 | if・forで逐次に読む | 制約のunificationで宣言する |
| 強み | 生成・加工ロジックの読みやすさ | スキーマと整合性検証の厳密さ |
もう1つの後押しは、KCLの勢いです。設定をYAMLのテンプレートではなく、型・IDE補完・パッケージ配布というプログラミング言語の道具立てで書く流れがあり、KCLはそれをKubernetes向けに揃えた言語です。
公式のKubernetesガイドが示す通り、既存のmanifestやCRDの型をimportして型付きの設定に変え、moduleとして配布できます。既存リソースの型をimportできることは、CUE資産を書き直すときの型補完の支えにもなります。
2023年にCNCF Sandboxへ入り、Crossplaneでもfunction-kclが継続的にリリースされています。ただし、広くproduction検証済みと評価された段階ではありません。
検証の結果
条件生成と注入という難所を含む縮約モデルで、書き換えが成立することが分かりました。crossplane render(v2.3.3)で5シナリオ(基本、HPA切替、mesh無効化、canary、設定注入)が、すべて期待どおりのリソースを生成しました。
まとめー結局、Crossplane 2.0とは何なのか
Crossplane 2.0は、全量を返すルールでレシピを回し、観測を入力に再実行され、クラウドの採番までリソース間で配線するcontrol planeです。その代償が、gRPC往復の常時コストとrenderの先に何層も続く検証、ソースコードを読む水準の理解です。
これらを引き受けた組織には、Application Modelを作るための強力な土台になります。ただし、組織にそのまま配れる完成品ではありません。XRDとCompositionで作った独自APIは、第1回の定義(意図を宣言し、インフラの詳細から分離する抽象)を確かに満たします。足りないのはモデルではなく体験の部分です。
KubeVelaからCrossplaneへの移行では「似た部品が揃っているのだから、書き写す作業だろう」と見積もっていましたが、半分しか当たりませんでした。受け皿は揃っていましたが、部品が前提にする分業が逆向きで、成立したのは移植ではなく、patchを返す部品から全量を返すstepへの書き換えです。
第3回の予告「KubeVela 2.0とCrossplane 2.0はどこで重なるのか」への答えもここにあります。重なるのは型とレシピから組織のAPIを作る合成の層で、能力の対応はほぼ全面に付きます。Application Modelツールの決め手は機能の対応ではなく、部品のルールが前提にする分業と組織の実態が合っているかでした。だとすると、道具の側ではなく組織の側から問い直す仕事が残っています。この時代のPlatform EngineeringにふさわしいApplication Modelとは何か。この問いは連載の総括へ残します。
次回は、AWS・Google・Microsoftが共同で開発するもう1つの合成エンジン、Kroを深掘りします。
この記事をシェアしてください
