WebService型を定義しAPIに登録する
目標
apps.labhub.io/v1グループにWebServiceという新しいリソース型を定義してAPIサーバーに登録し、スキーマ・カスタムカラム・サブリソース・複数バージョンまで備えた、実際に使えるCRDを完成させます。
なぜ重要なのか
CRDを作成するということは、「フィールドを並べる」ことではなく、検証の責任をどこに置くかを決めるということです。スキーマにminimum: 1と書けば、そのルールはapply時点でAPIサーバーが強制し、エラーメッセージがユーザーに直接届きます。逆にコントローラーのコードに入れると、誤ったオブジェクトがすでに保存された後になってはじめて、ログで発見されます。サブリソースも、便利機能ではありません。statusを別のパスに分けると、statusを書いてもmetadata.generationが上がらないので、コントローラーは「ユーザーがspecを変更した」ことと「自分が今statusを書いた」ことを区別できるようになります。この区別がないと、コントローラーが自分のstatus書き込みにまた反応する無限ループに陥ります。最後に、バージョンは最初から2つで始めてみるのがよいです。ストレージバージョンがちょうど1つでなければならないというルールと、status.storedVersionsの存在を体で覚えておけば、あとで古いバージョンを消してデータを読めなくする事故を避けられます。
ステップ
/root/crd/crd.yamlを作成してください。apiVersion: apiextensions.k8s.io/v1、kind: CustomResourceDefinition、metadata.name: webservices.apps.labhub.io、spec.group: apps.labhub.ioにする必要があります。- 同じファイルの
spec.namesに、plural: webservices、singular: webservice、kind: WebService、listKind: WebServiceList、shortNames: [ws]、categories: [labhub]を入れ、spec.scope: Namespacedにしてください。 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つのフィールドを定義してください。kubectl apply -f /root/crd/crd.yamlで適用し、Established条件がTrueであることを確認したうえで、kubectl api-resources --api-group=apps.labhub.ioの出力を/root/crd/out/api-resources.txtに保存してください。- v1バージョンに
additionalPrinterColumnsを追加してください。Image(type string、jsonPath.spec.image)、Replicas(type integer、jsonPath.spec.replicas)、Tier(type string、jsonPath.spec.tier)、Age(typedate、jsonPath.metadata.creationTimestamp)の4つのカラムです。 - 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フィールドは切り落とされ、保存されません。 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があることを確認してください。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に保存してください。
参考
/opt/lab/fixtures/crd/broken-crd.yamlをそのまま適用してみると、名前のルールと必須フィールドに対するAPIサーバーの拒否メッセージを見られます。このファイルは修正する対象ではなく、エラーを見て確かめるためのものです。kubectl explain webservice.specで、今登録したスキーマがドキュメントのように表示されるか確認してみてください。- よくあるミス1:
metadata.nameをwebservicesとだけ書いてしまうことです。CRDの名前は必ず<복수형>.<그룹>(プレースホルダーは複数形とグループです)です。 - よくあるミス2: ステップ6でサブリソースだけを有効にして、スキーマの
properties.statusを書き忘れてしまうことです。statusをpatchしても、pruningですべて切り落とされます。 - よくあるミス3: ステップ7で、両方のバージョンを
storage: trueにしてしまうことです。ストレージバージョンはちょうど1つでなければならず、そうでないとCRDの適用自体が拒否されます。
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パスを直接呼び出す方法があります。