TT Lab
はじめる
学ぶ 学習パス コース

CRDとオペレータ

WebService型を定義しAPIに登録する

TT Labで続きを見る

目標

apps.labhub.io/v1グループにWebServiceという新しいリソース型を定義してAPIサーバーに登録し、スキーマ・カスタムカラム・サブリソース・複数バージョンまで備えた、実際に使えるCRDを完成させます。

なぜ重要なのか

CRDを作成するということは、「フィールドを並べる」ことではなく、検証の責任をどこに置くかを決めるということです。スキーマにminimum: 1と書けば、そのルールはapply時点でAPIサーバーが強制し、エラーメッセージがユーザーに直接届きます。逆にコントローラーのコードに入れると、誤ったオブジェクトがすでに保存された後になってはじめて、ログで発見されます。サブリソースも、便利機能ではありません。statusを別のパスに分けると、statusを書いてもmetadata.generationが上がらないので、コントローラーは「ユーザーがspecを変更した」ことと「自分が今statusを書いた」ことを区別できるようになります。この区別がないと、コントローラーが自分のstatus書き込みにまた反応する無限ループに陥ります。最後に、バージョンは最初から2つで始めてみるのがよいです。ストレージバージョンがちょうど1つでなければならないというルールと、status.storedVersionsの存在を体で覚えておけば、あとで古いバージョンを消してデータを読めなくする事故を避けられます。

ステップ

  1. /root/crd/crd.yamlを作成してください。apiVersion: apiextensions.k8s.io/v1、kind: CustomResourceDefinition、metadata.name: webservices.apps.labhub.io、spec.group: apps.labhub.ioにする必要があります。
  2. 同じファイルのspec.namesに、plural: webservices、singular: webservice、kind: WebService、listKind: WebServiceList、shortNames: [ws]、categories: [labhub]を入れ、spec.scope: Namespacedにしてください。
  3. spec.versionsにname: v1を置き、schema.openAPIV3Schemaを書いてください。最上位はtype: object、properties.spec.type: object、properties.spec.required: [image]とし、properties.spec.propertiesの下に、image(type string)、replicas(type integer、minimum: 1、maximum: 10、default: 1)、tier(type string、enum: [dev, stage, prod]、default: dev)の3つのフィールドを定義してください。
  4. kubectl apply -f /root/crd/crd.yamlで適用し、Established条件がTrueであることを確認したうえで、kubectl api-resources --api-group=apps.labhub.ioの出力を/root/crd/out/api-resources.txtに保存してください。
  5. v1バージョンにadditionalPrinterColumnsを追加してください。Image(type string、jsonPath .spec.image)、Replicas(type integer、jsonPath .spec.replicas)、Tier(type string、jsonPath .spec.tier)、Age(type date、jsonPath .metadata.creationTimestamp)の4つのカラムです。
  6. v1バージョンでsubresources.status: {}とsubresources.scaleを有効にしてください。scaleはspecReplicasPath: .spec.replicas、statusReplicasPath: .status.replicas、labelSelectorPath: .status.selectorです。同時に、スキーマのproperties.statusをtype: objectで定義し、その下に、replicas(integer)、selector(string)、observedGeneration(integer)、conditions(type array、itemsはtype objectで、type・status・reason・messageはstring、lastTransitionTimeはstring)を入れてください。スキーマにないstatusフィールドは切り落とされ、保存されません。
  7. spec.versionsにname: v1alpha1を追加してください。v1alpha1はserved: true、storage: false、v1はserved: true、storage: trueです。v1alpha1にもスキーマが必要なので、v1のスキーマをそのままコピーしてください。再適用した後、kubectl get crd webservices.apps.labhub.io -o jsonpath='{.status.storedVersions}'にv1があることを確認してください。
  8. kubectl create ns crd-labでネームスペースを作成し、/opt/lab/fixtures/crd/sample-cr.yamlを適用してsampleを作成してください(spec.imageはタグまで含める必要があります)。そのうえで、kubectl get webservice -n crd-labの出力を/root/crd/out/get-ws.txtに、kubectl get --raw /apis/apps.labhub.io/v1/namespaces/crd-lab/webservices/sample/statusの出力を/root/crd/out/status.jsonに保存してください。

参考

CRDの骨組みと名前のルールを合わせる

/root/crd/crd.yamlを作成してください。apiVersion: apiextensions.k8s.io/v1、kind: CustomResourceDefinition、metadata.name: webservices.apps.labhub.io、spec.group: apps.labhub.ioにする必要があります。

CRDはapiextensions.k8s.io/v1グループのオブジェクトです。metadata.nameは自由に付けることはできず、複数形とグループをドットでつないだ形にする必要があります。フィクスチャの壊れたCRDを適用してみると、どんな文言で拒否されるかがわかります。

名前・スコープ・短い名前を決める

同じファイルのspec.namesに、plural: webservices、singular: webservice、kind: WebService、listKind: WebServiceList、shortNames: [ws]、categories: [labhub]を入れ、spec.scope: Namespacedにしてください。

spec.namesには、複数形・単数形・kind・listKindがそれぞれ別々に入ります。kindはパスカル表記で、listKindはkindの後ろにListを付けます。shortNamesとcategoriesは配列です。

OpenAPI v3スキーマでフィールドを固める

spec.versionsにname: v1を置き、schema.openAPIV3Schemaを書いてください。最上位はtype: object、properties.spec.type: object、properties.spec.required: [image]とし、properties.spec.propertiesの下に、image(type string)、replicas(type integer、minimum: 1、maximum: 10、default: 1)、tier(type string、enum: [dev, stage, prod]、default: dev)の3つのフィールドを定義してください。

requiredはspecオブジェクトの中に配列として入ります。数値フィールドにはminimum/maximum/defaultを、文字列フィールドにはenumを使えます。デフォルト値は、検証を通過する値でなければならない点に注意してください。

適用して、API一覧に現れたか確認する

kubectl apply -f /root/crd/crd.yamlで適用し、Established条件がTrueであることを確認したうえで、kubectl api-resources --api-group=apps.labhub.ioの出力を/root/crd/out/api-resources.txtに保存してください。

適用した直後にすぐ使えるわけではありません。CRDのstatusの条件のうち1つがTrueになってはじめて、APIサーバーがその型を受け付けます。新しい型が実際に登録されたかどうかは、APIリソースの一覧で確認します。

kubectl getに表示するカラムを付ける

v1バージョンにadditionalPrinterColumnsを追加してください。Image(type string、jsonPath .spec.image)、Replicas(type integer、jsonPath .spec.replicas)、Tier(type string、jsonPath .spec.tier)、Age(type date、jsonPath .metadata.creationTimestamp)の4つのカラムです。

additionalPrinterColumnsはバージョンごとに付けます。各カラムにはname、type、jsonPathの3つが必要で、時刻のカラムはtypeをdateにしないと相対時間で表示されません。

statusとscaleのサブリソースを有効にする

v1バージョンでsubresources.status: {}とsubresources.scaleを有効にしてください。scaleはspecReplicasPath: .spec.replicas、statusReplicasPath: .status.replicas、labelSelectorPath: .status.selectorです。同時に、スキーマのproperties.statusをtype: objectで定義し、その下に、replicas(integer)、selector(string)、observedGeneration(integer)、conditions(type array、itemsはtype objectで、type・status・reason・messageはstring、lastTransitionTimeはstring)を入れてください。スキーマにないstatusフィールドは切り落とされ、保存されません。

サブリソースを有効にするだけでは足りません。スキーマにstatusフィールドを定義しないと、pruningのせいで書いても保存されません。scaleには3つのパスを伝える必要があり、そのうち1つは、HPAがPodを数えるのに使います。

2つのバージョンを提供し、ストレージバージョンを1つに保つ

spec.versionsにname: v1alpha1を追加してください。v1alpha1はserved: true、storage: false、v1はserved: true、storage: trueです。v1alpha1にもスキーマが必要なので、v1のスキーマをそのままコピーしてください。再適用した後、kubectl get crd webservices.apps.labhub.io -o jsonpath='{.status.storedVersions}'にv1があることを確認してください。

apiextensions/v1では、すべてのバージョンがそれぞれスキーマを持つ必要があります。servedとstorageは別の意味で、storageがtrueのバージョンはちょうど1つでなければなりません。適用した後、CRDのstatusにストレージバージョンが記録されているか見てください。

最初のカスタムリソースを作成して参照する

kubectl create ns crd-labでネームスペースを作成し、/opt/lab/fixtures/crd/sample-cr.yamlを適用してsampleを作成してください(spec.imageはタグまで含める必要があります)。そのうえで、kubectl get webservice -n crd-labの出力を/root/crd/out/get-ws.txtに、kubectl get --raw /apis/apps.labhub.io/v1/namespaces/crd-lab/webservices/sample/statusの出力を/root/crd/out/status.jsonに保存してください。

先にネームスペースを作成する必要があります。カスタムカラムが実際に表示されるかどうかは、通常の参照の出力を保存して確認し、statusは通常の参照ではなくサブリソースのパスで別に読んでみてください。rawのAPIパスを直接呼び出す方法があります。