初めに
前回の記事では、Crossplane を Platform API の観点から整理しました。
Crossplane を OCI リソース作成ツールとしてだけ見るのではなく、Kubernetes API を使って利用者向けの宣言的 API と外部クラウドをつなぐ control plane として見る、という話でした。
今回は、その次の段階として、Crossplane が実際にどのように動くのかを整理します。
中心になるのは、次の三つです。
API Server
Reconcile
status
Crossplane を初めて触ると、kubectl apply したあとに何が起きているのか、READY と SYNCED をどう読めばよいのか、status.atProvider は何を意味しているのかで迷いやすいです。
この記事では、OCI Provider の Bucket demo に入る前に、Crossplane の状態合わせの見方を整理します。
この記事のゴール
この記事のゴールは、細かい Provider の設定に入る前に、次の流れを自分の言葉で説明できるようになることです。
YAML を apply する
-> Kubernetes API Server に desired state が保存される
-> Provider controller がそれを watch する
-> 外部 API の状態を observe する
-> 差分があれば act する
-> 結果を status / Conditions に返す
ここを押さえると、後で OCI Object Storage Bucket を作るときにも、どこで詰まっているのかを切り分けやすくなります。
Crossplane は一回実行して終わる CLI ではない
まず大事なのは、Crossplane を一回だけ実行する CLI として見ないことです。
Terraform のような IaC ツールに慣れていると、どうしても次のように考えがちです。
設定を書く
コマンドを実行する
クラウドリソースが作られる
Crossplane でも kubectl apply は使います。
ただし、kubectl apply が直接 OCI API を呼ぶわけではありません。
kubectl apply は、Kubernetes API Server に Kubernetes object を登録します。
その object を Provider controller が見続け、外部 API の状態と合わせていきます。
全体像は、次のように見ると分かりやすいです。
ユーザー / GitOps ツール
|
v
Kubernetes API Server
|
v
Crossplane Provider controller
|
v
OCI API
|
v
OCI Resource
Crossplane の公式 README でも、Crossplane は cloud native control plane を作るための framework と説明されています。
この "control plane" という見方が、Crossplane の動作を理解する入口になります。
API Server は desired state を保持する場所
Kubernetes API Server は、利用者や GitOps ツールから受け取った object を保存します。
たとえば、OCI Object Storage Bucket を Kubernetes resource として扱う場合、概念的には次のような manifest になります。
apiVersion: objectstorage.oci.m.upbound.io/v1alpha1
kind: Bucket
metadata:
name: bucket1
namespace: namespace1
spec:
forProvider:
name: bucket1
namespace: <object-storage-namespace>
compartmentIdSelector:
matchLabels:
testing.upbound.io/example-name: compartment_label_1
providerConfigRef:
kind: ProviderConfig
name: default
ここで spec.forProvider に書かれている内容は、「OCI 側をこうしたい」という desired state です。
一方で、この時点ではまだ OCI 側の実体ができているとは限りません。
Kubernetes API Server に desired state が保存され、そのあと Provider が reconcile します。
つまり、最初に見るべき流れはこうです。
manifest
-> Kubernetes API Server
-> desired state
Reconcile は状態を合わせ続ける loop
Crossplane の動きは、reconcile loop として見ると理解しやすいです。
この loop は、ざっくり次の四つに分けられます。
Observe
外部 API から現在の状態を読む
Diff
Kubernetes 側の spec と外部状態を比べる
Act
必要なら作成、更新、削除を行う
Report
結果を status と Conditions に返す
図にすると、次のような流れです。
Kubernetes spec
|
v
Provider reconcile loop
|
+--> Observe OCI
|
+--> Diff spec vs observed state
|
+--> Act if needed
|
+--> Report status
ここで重要なのは、reconcile は一回だけではないということです。
Provider は対象 resource を watch し、必要に応じて何度も状態を合わせます。
そのため、外部側で変更された drift も、Provider が観察できる範囲で次の reconcile の対象になります。
Desired / Observed / Actual を分ける
Crossplane の status を読むときは、次の三つを分けると理解しやすくなります。
| 観点 | どこにあるか | 意味 |
|---|---|---|
| Desired |
spec / spec.forProvider
|
利用者が望む状態 |
| Observed |
status / status.atProvider
|
Provider が外部 API から観察した結果 |
| Actual | OCI resource | OCI 側に実在するリソース |
たとえば Bucket の場合、利用者は manifest に desired state を書きます。
Provider は OCI API を呼び、実際の Bucket の状態を読みます。
その結果が Kubernetes 側の status.atProvider や conditions に返ります。
この三つを混同すると、問題の切り分けが難しくなります。
YAML が間違っているのか
ProviderConfig が間違っているのか
OCI IAM が足りないのか
OCI 側の resource 作成で失敗しているのか
この判断をするために、spec、status.atProvider、Conditions を分けて読みます。
Provider は外部 API と話す controller
Crossplane では、Provider が外部 API と話します。
OCI Provider の場合、Provider は OCI API を呼び出し、OCI の resource を Kubernetes object として扱えるようにします。
Provider をインストールすると、主に次のものが増えます。
CRD
Managed Resource の API 型を Kubernetes に追加する
Controller
Managed Resource を watch し、外部 API と状態を合わせる
ProviderConfig
認証情報や接続設定を Provider に渡す
ここで大事なのは、ProviderConfig はクラウドリソースそのものではないということです。
ProviderConfig は、Provider が OCI API を呼ぶための認証と接続の境界です。
同じ Bucket manifest でも、参照する ProviderConfig が変われば、使う資格情報、region、OCI IAM で許可される操作範囲が変わります。
そのため、ProviderConfig は status を読むときにも重要です。
Managed Resource は外部リソースの Kubernetes 表現
Managed Resource は、外部リソースを Kubernetes object として扱うための入口です。
OCI Object Storage Bucket であれば、概念的には次の関係になります。
Kubernetes object
Bucket Managed Resource
OCI external resource
Object Storage Bucket
Provider は Managed Resource の spec を読み、OCI API を呼び、結果を status に返します。
トラブルシュートのときも、まず Managed Resource を見ます。
kubectl get managed --all-namespaces
kubectl get bucket.objectstorage.oci.m.upbound.io -A
kubectl describe bucket.objectstorage.oci.m.upbound.io bucket1 -n namespace1
OCI Provider の Quick Start でも、Bucket を作ったあとに READY / SYNCED を確認し、False や空欄の場合は kubectl describe で理由を見る流れになっています。
READY と SYNCED は最初の信号
Crossplane の resource を見ると、よく READY と SYNCED が出てきます。
これは、Kubernetes の status.conditions から表示される状態です。
Crossplane の実装でも、Ready と Synced は system condition として扱われています。
ざっくり見ると、次のように読めます。
| 表示 | 見るポイント |
|---|---|
SYNCED=True |
直近の reconcile が成功したか |
SYNCED=False |
reconcile 中にエラーが起きた可能性がある |
READY=True |
resource が利用可能な状態として観察されているか |
READY=False |
外部 resource がまだ作成中、利用不可、またはエラーの可能性がある |
ここで注意したいのは、READY と SYNCED だけで原因を決めつけないことです。
READY=False や SYNCED=False を見たら、次に Conditions の Reason と Message を読みます。
kubectl describe bucket.objectstorage.oci.m.upbound.io bucket1 -n namespace1
たとえば OCI Provider の Quick Start では、ProviderConfig の名前が合っていない場合に、Synced condition が False になり、message に参照できない ProviderConfig が出る例が紹介されています。
つまり、READY / SYNCED は結論ではなく、原因を見る入口です。
status.atProvider は Provider が観察した外部状態
status.atProvider は、Provider が外部 API から観察した情報が返る場所です。
イメージとしては、次のようになります。
status:
atProvider:
namespace: id8pypxcqtqs
name: bucket1
# OCI API から観察された値
conditions:
- type: Synced
status: "True"
reason: ReconcileSuccess
- type: Ready
status: "True"
reason: Available
spec.forProvider は「こうしたい」です。
status.atProvider は「Provider が外部 API から見た結果」です。
そのため、status.atProvider は手で編集する場所ではありません。
OCI 側で実際にどの名前になったのか、どの OCID が返ったのか、どの region や namespace と対応しているのかを確認するために読みます。
失敗したときの読み順
Crossplane のトラブルシュートでは、いきなり Provider の log を見る前に、次の順番で読むと整理しやすいです。
1. kubectl get
READY / SYNCED を見る
2. kubectl describe
Conditions、Reason、Message、Events を見る
3. spec.providerConfigRef
参照している ProviderConfig の名前と kind を見る
4. ProviderConfig / Secret
認証情報と namespace を見る
5. OCI 側
compartment、namespace、IAM policy、resource lifecycle を見る
6. Provider log
それでも分からない場合に controller の log を見る
特に初学者にとって大事なのは、READY=False だけを見て「Bucket 作成失敗」と決めつけないことです。
認証が失敗している場合もあります。
ProviderConfig の参照名が違う場合もあります。
OCI IAM の権限が足りない場合もあります。
入力 field が provider release と合っていない場合もあります。
だからこそ、status.conditions と Events を読む習慣が重要になります。
GitOps と相性がよい理由
この動作モデルは、GitOps とも相性がよいです。
GitOps ツールは manifest を Kubernetes API Server に apply します。
Crossplane Provider は、その desired state を watch して外部 API と状態を合わせます。
Git repository
|
v
GitOps tool
|
v
Kubernetes API Server
|
v
Crossplane Provider
|
v
OCI
ここで GitOps が担当するのは、Kubernetes API Server に desired state を届けるところです。
Crossplane が担当するのは、その desired state と外部クラウドの状態を合わせるところです。
この分担が分かると、Crossplane を「GitOps の後ろで動く control plane」として理解しやすくなります。
この段階で覚えておきたいこと
この section で覚えておきたいことは、次の三点です。
1. API Server は desired state を保持する
2. Provider は observe / diff / act / report を繰り返す
3. READY / SYNCED / status は、原因を見るための信号である
Crossplane では、kubectl apply した瞬間にすべてが終わるわけではありません。
Kubernetes API Server に保存された desired state を、Provider が外部 API と継続的に合わせます。
そして、その結果が status と Conditions に返ります。
この流れが分かると、OCI Provider の Bucket demo でも、単に YAML を貼るのではなく、状態の変化として読めるようになります。
まとめ
この記事では、Crossplane の基本動作を API Server、Reconcile、status の三つで整理しました。
ポイントは以下です。
API Server は desired state を保存する
Provider は外部 API と状態を合わせる controller
Reconcile は observe / diff / act / report の継続 loop
spec.forProvider は望ましい状態
status.atProvider は Provider が観察した外部状態
READY / SYNCED は原因を見るための最初の信号
ProviderConfig は認証と接続の境界
次に学ぶべきことは、Managed Resource をそのまま使うだけでなく、利用者向けの抽象 API に変える部品です。
つまり、XRD、XR、Composition を使って、OCI の細かい resource 定義を Platform API としてどう見せるかに進みます。
参考
-
Crossplane GitHub
https://github.com/crossplane/crossplane -
Oracle Crossplane Provider for OCI GitHub
https://github.com/oracle/crossplane-provider-oci -
Oracle Crossplane Provider for OCI Quick Start
https://github.com/oracle/crossplane-provider-oci/blob/main/docs/quickstart.md -
Crossplane Provider for Oracle Cloud Infrastructure (OCI) 入門
https://qiita.com/engchina/items/3188e3d88665c81d151c -
ローカルKubernetesから始める Crossplane と OCI Provider 学習計画
https://qiita.com/engchina/items/59645196f86566b79cd0