0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Crossplane の Composition 実装:Patch & Transform、Functions を使い分ける

0
Posted at

初めに

前回の記事では、Crossplane の抽象 API を XRD、XR、Composition の三つで整理しました。

XRD は利用者向け API の型とスキーマを定義し、XR は利用者の要求を表し、Composition はその要求を実装 resource へ変換するレシピ、という話でした。

今回は、その Composition の中身に入ります。

中心になるのは、次の三つです。

Patch & Transform
Composition Functions
Pipeline mode

Composition を初めて読むと、compositeTypeRefmode: PipelinefunctionRefinputresourcespatches が一気に出てきます。

ここを YAML の細部から読むと迷いやすいです。

まずは、Composition が何をしていて、どこから先は Provider の仕事なのかを分けて見ると理解しやすくなります。


この記事のゴール

この記事のゴールは、次の流れを自分の言葉で説明できるようになることです。

XR の spec
  -> Composition pipeline
    -> Function が desired resources を作る
      -> Crossplane が composed resources を apply する
        -> Provider が Managed Resource を reconcile する
          -> OCI resource が作られる、または更新される

特に、次の判断軸を持てるようにします。

単純な field mapping
  -> Patch & Transform で十分

条件分岐、繰り返し、fan-out、複雑な生成 logic
  -> Composition Functions を検討する

ここで重要なのは、Composition Functions が OCI API を直接操作するわけではない、という点です。

Function は desired resources を作ります。

その後、Managed Resource を Provider が reconcile します。


Composition は「外部 API を呼ぶ処理」ではない

まず、Composition の位置づけを確認します。

Composition は、XR を受け取り、そこからどの Kubernetes resource を作るかを決めます。

たとえば、利用者が次のような XR を作ったとします。

apiVersion: platform.example.org/v1alpha1
kind: AppBucket
metadata:
  name: demo
spec:
  team: team-a
  tier: standard
  objectStorageNamespace: my-oci-namespace
  compartmentId: ocid1.compartment.oc1..example

この XR は、利用者向けの小さな要求です。

これを OCI Object Storage Bucket の Managed Resource に変換するのが Composition の役割です。

ただし、Composition が OCI API を直接呼ぶわけではありません。

Composition が作るのは、あくまで Kubernetes resource としての desired state です。

Composition
  -> Managed Resource の desired state を作る

Provider
  -> Managed Resource を見て OCI API と状態合わせする

この境界を分けておくと、debug するときにも迷いにくくなります。

Composition で見るのは「どんな resource を作ろうとしているか」です。

Provider で見るのは「その resource が外部 API と同期できているか」です。


Pipeline mode は Function を順番に呼ぶ形

現在の Composition では、mode: Pipeline を使って Function を呼び出す形が中心です。

概念的には、次のようになります。

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: appbucket-oci
spec:
  compositeTypeRef:
    apiVersion: platform.example.org/v1alpha1
    kind: AppBucket
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: bucket
        base:
          apiVersion: objectstorage.oci.upbound.io/v1alpha1
          kind: Bucket
          spec:
            forProvider:
              compartmentId: ""
              name: ""
              namespace: ""

ここで最初に見るべきなのは、compositeTypeRef です。

compositeTypeRef は、この Composition がどの XR に対応するかを示します。

次に、mode: Pipelinepipeline を見ます。

pipeline の各 step は Function を呼び出します。

functionRef.name が、呼び出す Function の名前です。

XR
  |
  v
step 1: function-patch-and-transform
  |
  v
desired resources

Function が複数ある場合は、定義された順番に呼び出されます。

各 Function は、前の Function が作った desired state を受け取り、必要に応じて追加や更新をして次に渡します。


Function は事前に install する

Composition で functionRef を書くだけでは、Function が使えるようになるわけではありません。

Function は Crossplane の package として install します。

Patch & Transform を使う場合は、たとえば次のような Function resource を用意します。

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:<version>

実際に使う version は、利用している Crossplane と公式ドキュメントに合わせて確認してください。

ここで大事なのは、Composition の functionRef.name と Function resource の metadata.name が対応していることです。

Composition 側
  functionRef.name: function-patch-and-transform

Function 側
  metadata.name: function-patch-and-transform

Composition を読んでいて functionRef が出てきたら、まずその Function が install 済みか、HEALTHY になっているかを確認します。


Patch & Transform は XR の値を Managed Resource へ写す

Patch & Transform は、XR の field を composed resource の field へコピーし、必要なら値を変換するための仕組みです。

公式ドキュメントでも、patch は一つの resource から別の resource へ値をコピーし、transform は patch 前に値を加工すると説明されています。

たとえば、XR の spec.compartmentId を OCI Bucket の spec.forProvider.compartmentId へ渡す場合は、次のように読みます。

patches:
- type: FromCompositeFieldPath
  fromFieldPath: spec.compartmentId
  toFieldPath: spec.forProvider.compartmentId

読み方は、そのままです。

Composite Resource の spec.compartmentId から値を取り、
composed resource の spec.forProvider.compartmentId へ入れる

OCI Bucket の例にすると、次のような対応になります。

XR 側 Managed Resource 側
spec.compartmentId spec.forProvider.compartmentId
spec.objectStorageNamespace spec.forProvider.namespace
spec.teammetadata.name spec.forProvider.name

Bucket 名のように、複数の値を組み合わせたい場合は CombineFromComposite が使えます。

patches:
- type: CombineFromComposite
  combine:
    variables:
    - fromFieldPath: spec.team
    - fromFieldPath: metadata.name
    strategy: string
    string:
      fmt: "%s-%s"
  toFieldPath: spec.forProvider.name

この例では、team-ademo から team-a-demo のような名前を作り、Bucket の name に渡すイメージです。

実際の命名規則では、長さ、利用可能文字、重複、環境名なども考慮します。

そのため、ここでは仕組みを理解するための例として見てください。


Transform は値を整えるために使う

Patch は値を渡す操作です。

Transform は、渡す前に値を整える操作です。

代表的には、次のような使い方があります。

Transform 使いどころ
map standard を具体的な設定値へ変換する
match 文字列や pattern に応じて値を選ぶ
string format を使って名前や tag を作る
convert 型を変換する
math 数値を加工する

たとえば、利用者には standardpremium のような tier だけを選んでもらい、実装側では provider 固有の値に変換したい場合があります。

そのような場面では、map transform が読みやすいです。

次の YAML は、Transform の考え方を示すための概念例です。

patches:
- type: FromCompositeFieldPath
  fromFieldPath: spec.tier
  toFieldPath: spec.forProvider.exampleTier
  transforms:
  - type: map
    map:
      standard: standard-value
      premium: premium-value

このように、利用者向け API の言葉と provider 側の field 値を分けられるのが Patch & Transform の良いところです。

ただし、provider の実際の field 名や許容値は release によって変わる可能性があります。

実際の manifest では、必ず provider の CRD、examples、quickstart を確認してください。


Patch & Transform が向いている場面

Patch & Transform は、単純な対応関係を宣言的に書く場合に向いています。

たとえば、次のような処理です。

XR の region を Managed Resource の region に渡す
XR の team と name から resource 名を作る
XR の tier を provider 固有の値に map する
Managed Resource の値を XR の status に戻す

この範囲であれば、YAML を読めば何が起きているか追いやすいです。

platform team 以外の人が review するときも、field path と transform を見れば意図を確認できます。

一方で、Patch & Transform に何でも詰め込むと、Composition が読みづらくなります。

特に、次のような兆候が出てきたら注意が必要です。

条件分岐が増える
配列を見て resource 数を変えたい
複数 resource をまたいだ判断が増える
同じ pattern の resource を大量に生成したい
validation だけでは表現しにくい business rule が増える

このあたりから、Composition Functions を検討する価値が出てきます。


Composition Functions は複雑な生成 logic の逃がし先

Composition Functions は、Composition の処理を pipeline step として拡張する仕組みです。

Crossplane は Function に RunFunctionRequest を送り、Function は RunFunctionResponse で desired state を返します。

公式ドキュメントでは、Function が受け取る主な情報として、observed state、desired state、Function input、pipeline context が説明されています。

イメージは次のようになります。

Crossplane
  -> observed state
  -> desired state
  -> input
  -> context

Function
  -> updated desired state

ここでいう observed state は、XR と既存の composed resources の現在状態です。

desired state は、pipeline が最終的に作りたい resource 群です。

Function の主な仕事は、この desired state を更新して Crossplane に返すことです。

その後、Crossplane が desired state を Kubernetes API Server に apply します。

OCI API と状態合わせするのは、その後の Provider です。

Function
  -> desired resources を作る

Crossplane
  -> desired resources を apply する

Provider
  -> Managed Resource を OCI API と reconcile する

この切り分けを押さえておくと、Function を「クラウド API を直接叩く plugin」と誤解しにくくなります。


Pipeline では desired state が積み上がる

Function pipeline では、各 step が desired state を受け取り、必要な変更を加えて次に渡します。

たとえば、次のような構成が考えられます。

step 1: resource template を作る
step 2: naming や labels を補う
step 3: readiness を調整する

公式ドキュメントでは、pipeline の desired state は積み上がるものとして説明されています。

そのため、Function を自作する場合は、自分が追加したい resource だけでなく、前の step から来た desired state をどう扱うかも重要です。

ただし、最初に学ぶ段階では、細かい SDK 実装よりも次の理解で十分です。

Pipeline は Function を順番に呼ぶ
各 Function は desired state を更新する
最後に残った desired state が apply 対象になる

Patch & Transform も、この pipeline の中で動く Function の一つとして見ると分かりやすいです。


Reference、Selector、Connection Details も設計対象になる

Composition が一つの Bucket だけを作る場合は、field mapping を見れば理解しやすいです。

しかし、実際の Platform API では、複数の resource が関係することが多いです。

たとえば、OCI Bucket を中心に考えても、次のような要素が出てきます。

Bucket
Compartment
IAM policy
Secret
tag
利用者へ返す出力

このとき、単に resource を作るだけでは足りません。

どの resource がどの resource を参照するのか、既存 resource をどう選ぶのか、利用者にどの情報を返すのかを決める必要があります。

Managed Resource の CRD には、xxxRefxxxSelector のような field が用意されていることがあります。

OCI Bucket の CRD でも、compartmentIdRefcompartmentIdSelector のような field が確認できます。

直接値を渡す
  compartmentId

別 resource を名前で参照する
  compartmentIdRef

label などで対象を選ぶ
  compartmentIdSelector

Connection Details は、利用者に返す接続情報や出力を整理するための出口です。

たとえば endpoint、username、password、bucket 名、namespace など、利用者が後続処理で必要とする値をどう渡すかを考えます。

ここを曖昧にすると、XR は作れても、利用者が次に何を使えばよいのか分からなくなります。

Platform API では、resource の作成だけでなく、利用までの流れを含めて Composition を読むことが重要です。


Patch & Transform と Functions の使い分け

使い分けは、次のように考えると分かりやすいです。

やりたいこと 向いている方法
XR の field を Managed Resource へ渡す Patch & Transform
tier や region を provider 固有値へ変換する Patch & Transform
複数 field から名前や tag を作る Patch & Transform
条件分岐が多い Composition Functions
配列から resource を複数生成する Composition Functions
business rule を code で表現したい Composition Functions
複数 step に処理を分けたい Composition Functions

最初から Function に寄せすぎると、YAML だけでは挙動が見えにくくなります。

逆に、Patch & Transform に複雑な logic を詰め込みすぎると、Composition が巨大な DSL のようになってしまいます。

おすすめは、単純な mapping は Patch & Transform に置き、複雑さが出てきたら Function に分けることです。

単純な値の受け渡し
  -> Patch & Transform

複雑な生成 logic
  -> Composition Functions

この境界を持っておくと、Composition の保守性を落としにくくなります。


Composition を読む順番

Composition YAML を読むときは、次の順番がおすすめです。

1. compositeTypeRef
   どの XR に対応する Composition かを見る

2. mode と pipeline
   Pipeline mode か、どの Function を呼ぶかを見る

3. Function input
   Function に渡している設定を見る

4. base resource
   どの Managed Resource を作るのかを見る

5. patches / transforms
   XR のどの値が、どの field に渡るかを見る

6. reference / selector / connection details
   resource 間のつながりと利用者への出力を見る

この順番で読むと、Composition を「長い YAML」としてではなく、「XR から composed resources を作る流れ」として追えます。

debug するときも同じです。

XR の spec は正しいか
Composition は正しい XR を対象にしているか
Function は install されているか
patch の field path は存在するか
Managed Resource は期待通り作られているか
Provider の READY / SYNCED はどうなっているか

Composition の問題なのか、Provider の問題なのかを分けることが大事です。


crossplane render で事前に見る

Composition を書くときは、実際に cluster に apply する前に、crossplane render で出力を確認できます。

公式ドキュメントでも、XR、Composition、Functions を渡して、生成される resources をローカルで preview する方法が紹介されています。

crossplane render xr.yaml composition.yaml functions.yaml

crossplane render は、Function を実行して、Composition からどの resource が出力されるかを確認するために便利です。

ただし、Function の実行には Docker が必要になる場合があります。

また、render は composed resources の形を確認するためのものです。

OCI API との実際の状態合わせ、IAM、ProviderConfig、認証、外部 resource の状態までは、実 cluster 上で確認する必要があります。

crossplane render
  -> Composition が何を作ろうとしているかを見る

kubectl get / describe
  -> cluster 上で実際に何が起きているかを見る

OCI Console / OCI CLI
  -> 外部 resource の状態を見る

この三つを分けると、調査の順番がかなり明確になります。


よくある混乱

最後に、Composition 実装で混乱しやすい点を整理します。

一つ目は、Patch & Transform は古い mode: Resources そのものではない、という点です。

現在は、mode: Pipeline の中で function-patch-and-transform を呼び出す形として見るのが自然です。

二つ目は、Function が外部クラウドを直接作るわけではない、という点です。

Function は desired resources を返し、その desired resources を Crossplane が apply し、Managed Resource を Provider が reconcile します。

三つ目は、Composition は利用者 API の裏側にある実装だ、という点です。

利用者が見るのは XR です。

Composition は、platform team が利用者向け API をどう実装するかを表す場所です。

この三つを押さえると、Composition の YAML を読んだときに、どこを見ればよいかが分かりやすくなります。


まとめ

今回は、Composition の実装として、Patch & Transform、Composition Functions、Pipeline mode を整理しました。

ポイントは、次の三つです。

Patch & Transform は単純な field mapping に向いている
Composition Functions は複雑な生成 logic を分離するために使う
Pipeline mode では Function が desired state を順番に組み立てる

Composition は OCI API を直接呼ぶ処理ではありません。

Composition は desired resources を作り、その後の Managed Resource を Provider が reconcile します。

この境界を押さえると、Composition の問題、Function の問題、Provider の問題を切り分けやすくなります。

次は、この考え方を OCI Provider の構成、認証、Bucket demo に当てはめて見ていきます。


参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?