プラットフォームAPIを設計し囲いを立てる
目標
AppClaimというセルフサービスAPIをCRDで設計して適用し、スキーマとCELで誤ったリクエストを拒否するようにし、RBACでテナントにできることとできないことを分けたうえで、払い出す個数に上限をかけます。
なぜ重要なのか
プラットフォームAPIとセルフサービスは、CNPEで扱う中核のドメインです。そして、このドメインで問われるのは、CRDを作れるかどうかではなく、何をAPIサーバーに拒否させるかを決められるかです。
この判断が重要なのは、拒否する場所が2か所しかないからです。APIサーバーが拒否すれば、ユーザーはすぐに理由を見られます。コントローラーがあとで失敗すると、ユーザーは受け付けられたと信じて待ちます。前の段階でかけられるものを後ろに回すと、その分だけ人が迷います。
このラボには、AppClaimを実際のアプリに変えるコントローラーがありません。しかし、kube-apiserverは本物なので、CRDの登録、OpenAPIの検証、CELの評価、RBACの判定、ResourceQuotaのアドミッションが、すべて実際に動作します。そのため、ここで確認する拒否と許可は、実際のクラスターで起きることと同じです。
作業ディレクトリは/root/cnpe-apiで、テナントのネームスペースはtenant-blueです。
ステップ
/root/cnpe-api/appclaim-crd.yamlにCRDを書きます。グループはplatform.labhub.io、種類はAppClaim、複数形はappclaims、スコープはNamespacedです。バージョンはv1alpha1とv1の2つで、どちらもservedであり、保存バージョンはv1です。v1alpha1とv1のどちらにも、statusサブリソースと、.spec.tierを表示する出力カラムを置きます。2つのバージョンのルートでspecを必須にし、spec内でtier・replicas・maxReplicasを必須にします。tierは文字列、2つの数量は1以上の整数です。書き終えたらapplyします。v1alpha1とv1のそれぞれのspecに、検証を加えます。tierはbronze、silver、goldだけを許可し、CELルールでreplicasがmaxReplicasを超えないようにします。拒否メッセージには、maxReplicasというフィールド名が含まれている必要があります。2つのバージョンで、bronze(1/1)・silver(2/6)・gold(3/3)の正常なリクエストは許可され、spec全体の欠落・null・空のオブジェクト、必須フィールドの欠落、誤ったデータ型、0・負の数は拒否されることを、サーバーdry-runで確認します。括弧内はreplicas/maxReplicasです。- ネームスペース
tenant-blueを作成し、/root/cnpe-api/claim-checkout.yamlにAppClaimcheckoutを書きます。tierはsilver、replicasは2、maxReplicasは6です。applyしたあと、2つのバージョンで参照したUIDとspecが同じであることを確認します。 v1alpha1とv1のそれぞれのspecに遷移ルールを加えて、tierを不変にします。ルールはoldSelfを参照する必要があり、拒否メッセージにはtierが含まれている必要があります。tierをそのままにしてreplicasだけを変更する操作は、引き続き通過する必要があります。/root/cnpe-api/tenant-rbac.yamlにServiceAccountblue-dev、Roleappclaim-author、RoleBindingappclaim-authorを書きます。RoleはAppClaimに対してget、list、watch、create、update、patchを許可します。バインディングは、そのServiceAccountを指している必要があります。- Roleにdeleteがなく、resourcequotasとrolesとrolebindingsを作成できず、secretsを読めないことを、
kubectl auth can-iで確認します。AppClaim statusのpatch・updateと、kube-systemのAppClaim listも、拒否される必要があります。正常なgetがyesであることを対照し、通信・認証のエラーをnoと解釈しないでください。Roleのverbs、resources、apiGroupsのどこにも、*がない必要があります。 - AppClaim
searchをもう1つ作成し、/root/cnpe-api/claim-quota.yamlにResourceQuotablue-claimsを書きます。count/appclaims.platform.labhub.ioの上限は2です。クォータのstatus.usedが2で埋まることを確認し、空なら、CRDのEstablished・API discovery・クォータコントローラーを診断して再確認したうえで、3つ目のAppClaimが実際に止められることを確認します。 /root/cnpe-api/api-report.txtに、storage_version、claims、quota_hard、tenant_can_deleteの4行を키=값の形式(プレースホルダーはキーと値です)で書きます。4つの値はすべて、クラスターから直接参照したものである必要があります。
参考
-
python3 /opt/fixtures/cnpe_input_contract.py matrixは、44個のリクエストを保存せずに検査するヘルパーです。クライアント側の事前検証をオフにして、サーバーに直接問い合わせるため、ローカル検証の成功と区別できます。正常なリクエストまで止まるなら、安全なAPIではなく、使えないAPIです。 -
必須値とデータ型は、公式CRD検証ドキュメントを参考にしてください。spec以下のルールは、親のspecがなければ代わりに実行されることはありません。
-
2つのservedバージョンの両方で、容量・tier・不変ルールを検査してください。保存バージョンがv1であっても、v1alpha1のリクエストのスキーマ検証の代わりにはなりません。
-
公式CRDバージョン管理と権限判定を参考にしてください。
-
サーバーが実際に拒否するかを見るときは、
kubectl apply --dry-run=serverを使ってください。検証経路をそのまま通りながら、クラスターには何も残しません。 -
クォータのキーにはドットが含まれているため、jsonpathで読むのは面倒です。
kubectl get resourcequota blue-claims -n tenant-blue -o json | jqで読むほうがよいです。 -
CRDを適用した直後は、
kubectl wait --for=condition=Established crd/...で、登録が終わるのを待ってください。 -
よくある間違いの1つは、Roleだけを作ってRoleBindingを忘れることです。Roleは権限の定義にすぎず、付けなければ何の権限も生まれません。
-
もう1つは、
self == oldSelfでspec全体をくくってしまうことです。それは不変ではなく凍結であり、レプリカも変更できなくなります。
CRDの骨格と2つのバージョンを決める
/root/cnpe-api/appclaim-crd.yamlにCRDを書きます。グループはplatform.labhub.io、種類はAppClaim、複数形はappclaims、スコープはNamespacedです。バージョンはv1alpha1とv1の2つで、どちらもservedであり、保存バージョンはv1です。v1alpha1とv1のどちらにも、statusサブリソースと、.spec.tierを表示する出力カラムを置きます。2つのバージョンのルートでspecを必須にし、spec内でtier・replicas・maxReplicasを必須にします。tierは文字列、2つの数量は1以上の整数です。書き終えたらapplyします。
保存バージョンは1つのバージョンだけがtrueです。古いバージョンもservedにしておかなければ、そのバージョンのリクエストを受け付けません。spec内のrequiredはspecそのものを必須にしないため、ルートのrequiredも必要です。statusサブリソースは、状態の書き込みを別の経路に分離します。
値の集合とフィールド間の関係を縛る
v1alpha1とv1のそれぞれのspecに、検証を加えます。tierはbronze、silver、goldだけを許可し、CELルールでreplicasがmaxReplicasを超えないようにします。拒否メッセージには、maxReplicasというフィールド名が含まれている必要があります。2つのバージョンで、bronze(1/1)・silver(2/6)・gold(3/3)の正常なリクエストは許可され、spec全体の欠落・null・空のオブジェクト、必須フィールドの欠落、誤ったデータ型、0・負の数は拒否されることを、サーバーdry-runで確認します。括弧内はreplicas/maxReplicasです。
値1つの形はenumが、2つのフィールドの関係はCELが押さえます。ルールはspecプロパティの下に、x-kubernetes-validationsで付けます。拒否メッセージは、ユーザーが実際に読む唯一の文なので、フィールド名を入れてください。
最初のセルフサービスリクエストを受け付ける
ネームスペースtenant-blueを作成し、/root/cnpe-api/claim-checkout.yamlにAppClaimcheckoutを書きます。tierはsilver、replicasは2、maxReplicasは6です。applyしたあと、2つのバージョンで参照したUIDとspecが同じであることを確認します。
2つのバージョンが別のオブジェクトを作るわけではありません。UIDとspecを比較してください。statusサブリソースを有効にすると、通常の作成・変更リクエストのstatusは無視されます。このラボにはAppClaimコントローラーがないため、受付の成功を、実際のアプリのデプロイ完了と解釈しないでください。
一度決めたら変更できないフィールドを作る
v1alpha1とv1のそれぞれのspecに遷移ルールを加えて、tierを不変にします。ルールはoldSelfを参照する必要があり、拒否メッセージにはtierが含まれている必要があります。tierをそのままにしてreplicasだけを変更する操作は、引き続き通過する必要があります。
ルールがoldSelfを参照すると、そのルールは変更のときだけ評価されます。ただし、spec全体をoldSelfと比較すると、レプリカも変更できなくなり、凍結になります。縛るフィールドを名前で書いてください。
テナントが自分でできることを決める
/root/cnpe-api/tenant-rbac.yamlにServiceAccountblue-dev、Roleappclaim-author、RoleBindingappclaim-authorを書きます。RoleはAppClaimに対してget、list、watch、create、update、patchを許可します。バインディングは、そのServiceAccountを指している必要があります。
Roleは権限の定義にすぎず、バインディングを付けて初めて権限が生まれます。作ったあとは、必ずkubectl auth can-iで判定を確認してください。Roleを読むだけでは、最終的な結果はわかりません。
閉じた側を判定で確認する
Roleにdeleteがなく、resourcequotasとrolesとrolebindingsを作成できず、secretsを読めないことを、kubectl auth can-iで確認します。AppClaim statusのpatch・updateと、kube-systemのAppClaim listも、拒否される必要があります。正常なgetがyesであることを対照し、通信・認証のエラーをnoと解釈しないでください。Roleのverbs、resources、apiGroupsのどこにも、*がない必要があります。
開けたものは前のステップで見たので、ここでは閉じた側だけを見ます。削除・クォータ・Role・Secret・statusの変更・別のネームスペースの参照は、すべて明示的なnoである必要があり、Roleのどこにもアスタリスクがあってはいけません。アスタリスクが1つあれば、上の判定がすべてひっくり返ります。
払い出しに上限をかけて、止まるかを見る
AppClaimsearchをもう1つ作成し、/root/cnpe-api/claim-quota.yamlにResourceQuotablue-claimsを書きます。count/appclaims.platform.labhub.ioの上限は2です。クォータのstatus.usedが2で埋まることを確認し、空なら、CRDのEstablished・API discovery・クォータコントローラーを診断して再確認したうえで、3つ目のAppClaimが実際に止められることを確認します。
RBACは「できるか」に答えますが、「いくつまで」には答えません。個数はクォータが数えます。status.usedが空だという事実だけで、原因を断定しないでください。登録直後は反映を待ち、空のままなら、Established・API discovery・コントローラーの状態を確認します。やみくもにクォータを削除すると、制限を取り除くことになります。
プラットフォームAPIの現在の状態を値として残す
/root/cnpe-api/api-report.txtに、storage_version、claims、quota_hard、tenant_can_deleteの4行を키=값の形式(プレースホルダーはキーと値です)で書きます。4つの値はすべて、クラスターから直接参照したものである必要があります。
4つの値とも、参照して書いてください。保存バージョンはCRDから、AppClaimの数と上限はネームスペースから、削除できるかどうかは権限の判定から得られます。採点ツールが同じ値を再計算して突き合わせます。