CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト
CRDでプラットフォームAPIを作る
目標
Kubernetes APIを拡張してWebServiceというプラットフォームAPIを自分で作り、スキーマ検証・サーバーサイドのデフォルト値・テナントの境界・セルフサービスのRBACまでを一式、実際のクラスターで完成させます。
なぜ重要なのか
プラットフォームAPIを社内のWebアプリとして作ると、状態の保存、並行制御、認証・認可、監査、watchをすべて実装し直す必要があります。CRDを登録すれば、それらがすべてついてきます。だからKubernetes APIがプラットフォームの共通言語になりました。特にOpenAPIスキーマは、セルフサービスの3つの条件のうち「速いフィードバック」を最も安く実装する仕組みです。maximum: 10の1行で、誤ったリクエストが、30分後のパイプラインログではなく即座に、人が読める文で拒否されます。defaultも同様に、「安全なデフォルト値」をサーバーが強制してくれます。そして、CRDだけではプラットフォームは完成しません。テナントが互いを侵害できないという境界(ネームスペース・クォータ・RBAC)がそろって初めて、チケットなしで権限を与えられます。このラボのリソースはすべてKubernetes組み込みのリソースなので、実際に適用してkubectlで採点します。
ステップ
/root/cnpa-platform/crd.yamlにCustomResourceDefinitionを作成して適用してください。metadata.name: webservices.platform.labhub.io、spec.group: platform.labhub.io、spec.scope: Namespacedとし、spec.namesはkindがWebService、pluralがwebservices、singularがwebservice、shortNamesの最初の項目がwsです。バージョンv1alpha1はserved: true、storage: trueにしてください。- 同じCRDの
v1alpha1スキーマを完成させてください。specオブジェクトにimage(string、必須)、replicas(integer、default: 2、minimum: 1、maximum: 10)、public(boolean、default: false)を定義します。さらにadditionalPrinterColumnsに、名前Image(jsonPath.spec.image、type string)とReplicas(jsonPath.spec.replicas、type integer)の2つの列を追加してください。 - ネームスペース
tenant-blueを作成してください。ラベルはplatform.labhub.io/tenant: blueとpod-security.kubernetes.io/enforce: baselineです。 tenant-blueにResourceQuotatenant-blue-quotaを作成してください。requests.cpu: "2"、requests.memory: 4Gi、limits.cpu: "4"、limits.memory: 8Gi、pods: "10"を設定します。同じネームスペースにLimitRangetenant-blue-limitsを作成してください。typeはContainer、defaultはcpu200m/ memory256Mi、defaultRequestはcpu100m/ memory128Miです。tenant-blueにWebServiceshopを作成してください。spec.image: ghcr.io/labhub/shop:1.0.0だけを書き、replicasとpublicは書かないでください。作成したら、もう一度読み出して、2つの値が補われていることを確認してください。- スキーマ違反を確認してください。
spec.replicas: 20のWebServicebadをtenant-blueに作成してみて、その失敗の出力(標準エラー出力を含む)を/root/cnpa-platform/reject.txtに保存してください。badはクラスターに残っていてはいけません。 - ServiceAccount
blue-devをtenant-blueに作成し、同じネームスペースにRolewebservice-editor(apiGroupsplatform.labhub.io、resourceswebservices、verbsget,list,watch,create,update,patch,delete)とRoleBindingblue-devs(そのRoleをblue-devServiceAccountに結び付けるもの)を作成してください。クォータを変更する権限は与えないでください。 - 同じパターンで2つ目のテナントを作成してください。ネームスペース
tenant-green(ラベルplatform.labhub.io/tenant: green)、ResourceQuotatenant-green-quota(pods: "10"を含む)、そしてWebServiceapi(spec.image: ghcr.io/labhub/api:1.0.0、replicasは指定しない)です。tenant-blueのblue-devがtenant-greenにはWebServiceを作成できない状態にしてください。
参考
- CRDを適用した直後は、
kubectl get ws -n tenant-blueのような短縮形もすぐに動作する必要があります。 - 権限の確認は、
kubectl auth can-i <verb> <resource> --as=system:serviceaccount:tenant-blue:blue-dev -n <네임스페이스>で行います(プレースホルダーはネームスペース名です)。 - 失敗した出力をファイルに保存するには、標準エラー出力も一緒にリダイレクトする必要があります。
- よくある間違い1: CRDの
metadata.nameをwebservice.platform.labhub.ioのように単数形で書いてしまうことです。必ず複数形にする必要があります。 - よくある間違い2:
defaultは、specオブジェクト自体ではなく各プロパティの中に入れる必要がある、という点です。そしてrequiredはプロパティ名の配列です。
CRDの登録: グループ・スコープ・名前
/root/cnpa-platform/crd.yamlにCustomResourceDefinitionを作成して適用してください。metadata.name: webservices.platform.labhub.io、spec.group: platform.labhub.io、spec.scope: Namespacedとし、spec.namesはkindがWebService、pluralがwebservices、singularがwebservice、shortNamesの最初の項目がwsです。バージョンv1alpha1はserved: true、storage: trueにしてください。
CRDの名前は<복수형>.<그룹>の形式でなければなりません(プレースホルダーは複数形とグループです)。テナントが作るリソースなので、スコープの選択に注意してください。
スキーマとプリンターカラム
同じCRDのv1alpha1スキーマを完成させてください。specオブジェクトにimage(string、必須)、replicas(integer、default: 2、minimum: 1、maximum: 10)、public(boolean、default: false)を定義します。さらにadditionalPrinterColumnsに、名前Image(jsonPath .spec.image、type string)とReplicas(jsonPath .spec.replicas、type integer)の2つの列を追加してください。
OpenAPI v3スキーマは、versions配列の中の各バージョンに付けます。required、minimum/maximum、defaultをそれぞれどこに書くのかを区別してください。プリンターカラムもバージョンごとに定義します。
テナントのネームスペース
ネームスペースtenant-blueを作成してください。ラベルはplatform.labhub.io/tenant: blueとpod-security.kubernetes.io/enforce: baselineです。
ネームスペース自体がテナントの境界です。所属を示すラベルと、Pod Security Standardsのラベルを一緒に付けてください。
ResourceQuotaとLimitRange
tenant-blueにResourceQuota tenant-blue-quotaを作成してください。requests.cpu: "2"、requests.memory: 4Gi、limits.cpu: "4"、limits.memory: 8Gi、pods: "10"を設定します。同じネームスペースにLimitRange tenant-blue-limitsを作成してください。typeはContainer、defaultはcpu 200m / memory 256Mi、defaultRequestはcpu 100m / memory 128Miです。
2つは役割が違います。1つはネームスペース全体の総量の上限で、もう1つは個々のコンテナのデフォルト値と範囲です。両方そろって初めて、「requestを指定していないPod」がクォータを通過します。
CRの作成とサーバーサイドのデフォルト値
tenant-blueにWebService shopを作成してください。spec.image: ghcr.io/labhub/shop:1.0.0だけを書き、replicasとpublicは書かないでください。作成したら、もう一度読み出して、2つの値が補われていることを確認してください。
replicasをまったく書かずに作成してみてください。保存されたオブジェクトを読み直すと、値が入っているはずです。
スキーマ違反が拒否されることの確認
スキーマ違反を確認してください。spec.replicas: 20のWebService badをtenant-blueに作成してみて、その失敗の出力(標準エラー出力を含む)を/root/cnpa-platform/reject.txtに保存してください。badはクラスターに残っていてはいけません。
範囲外の値で作成してみて、そのとき出るエラーメッセージをファイルに残してください。標準エラー出力も一緒に保存する必要があります。
セルフサービスの権限とガードレール
ServiceAccount blue-devをtenant-blueに作成し、同じネームスペースにRole webservice-editor(apiGroups platform.labhub.io、resources webservices、verbs get,list,watch,create,update,patch,delete)とRoleBinding blue-devs(そのRoleをblue-dev ServiceAccountに結び付けるもの)を作成してください。クォータを変更する権限は与えないでください。
新しいリソースにも、既存のRBACがそのまま適用されます。ルールのapiGroupsとresourcesに何を書くかを確認し、クォータには触れられないままにしておいてください。
2つ目のテナントと分離の証明
同じパターンで2つ目のテナントを作成してください。ネームスペースtenant-green(ラベルplatform.labhub.io/tenant: green)、ResourceQuota tenant-green-quota(pods: "10"を含む)、そしてWebService api(spec.image: ghcr.io/labhub/api:1.0.0、replicasは指定しない)です。tenant-blueのblue-devがtenant-greenにはWebServiceを作成できない状態にしてください。
同じパターンをもう一度適用するのがプラットフォームです。そして、最初のテナントの権限が2つ目のテナントには及ばないようにしてください。