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

CRDとオペレータ

APIサーバに検証を押し付ける

TT Labで続きを見る

目標

わざとルールに違反したリソースを投入して、APIサーバーが何をどのように拒否するかを自分で集め、デフォルト値の注入・pruning・CEL検証までスキーマ1つで処理されることを確認します。

なぜ重要なのか

検証をスキーマに移す本当の利点は「拒否する」ことではなく、「拒否しながら、何がなぜだめなのかをユーザーに伝える」ことです。enumを入れると拒否メッセージに許可リストが一緒に表示され、maximumを入れると上限がそのまま文言に入ります。ユーザーはWikiを探し回らなくても、エラーを読むだけで直せます。pruningは最初は戸惑います。タイプミスしたフィールドが、エラーも出ずに黙って消えるからです。しかし、これが「スキーマがそのまま契約」であることを強制する仕組みであり、だから任意のキーを受け取る必要がある箇所は、例外的に、その箇所だけを開けなければなりません。最後に、CELは状況を変えました。以前は、「prodならレプリカは2つ以上」のようなフィールド間の制約のために、検証Webhookサーバーを立て、証明書を管理し、そのWebhookが落ちるとクラスターが麻痺するリスクまで負う必要がありました。今ではスキーマの1行です。

ステップ

開始前の準備: ラボのPodはラボごとに新しく起動するため、前のラボで作ったクラスターの状態は残っていません。kubectl get crd webservices.apps.labhub.ioの結果が空なら、前のラボで使ったCRDを/root/crd/crd.yamlとして書き直して適用し、kubectl create ns crd-labも実行してください。このラボが要求するスキーマは、spec.required: [image]、replicas(integer、minimum 1、maximum 10、default 1)、tier(string、enum [dev, stage, prod]、default dev)で、ステップ6とステップ7で、ここにspec.extraとCELルールを加えることになります。

  1. /root/crd/validate/no-image.yamlに、metadata.name: no-image(ネームスペースcrd-lab)で、specにimageがないWebServiceを書いて、適用してみてください。失敗の出力を標準エラー出力まで含めて、/root/crd/validate/out/err-required.txtに保存してください。出力にspec.imageと必須の値に関する文言があり、no-imageがクラスターに残っていてはいけません。
  2. /root/crd/validate/bad-type.yamlに、metadata.name: bad-type、spec.image: nginx:1.27、spec.replicas: "three"を書いて適用し、失敗の出力を/root/crd/validate/out/err-type.txtに保存してください。
  3. /root/crd/validate/bad-tier.yamlに、metadata.name: bad-tier、spec.image: nginx:1.27、spec.tier: qaを書いて適用し、失敗の出力を/root/crd/validate/out/err-enum.txtに保存してください。出力に、許可される値も一緒に表示されている必要があります。
  4. /root/crd/validate/too-many.yamlに、metadata.name: too-many、spec.image: nginx:1.27、spec.replicas: 50を書いて適用し、失敗の出力を/root/crd/validate/out/err-range.txtに保存してください。
  5. /root/crd/validate/defaulted.yamlに、metadata.name: defaultedとspec.imageだけを書いてください。spec.replicasとspec.tierは絶対に書かないでください。適用した後、保存されたオブジェクトにreplicas: 1、tier: devが埋められているか確認してください。
  6. /root/crd/crd.yamlのv1スキーマに、properties.spec.properties.extraをtype: objectとx-kubernetes-preserve-unknown-fields: trueで追加し、CRDを再適用してください。そのうえで、/root/crd/validate/pruned.yamlに、metadata.name: pruned、spec.image、そしてスキーマにないspec.bogus: anythingを入れて適用し(保存されたオブジェクトからbogusが消えている必要があります)、/root/crd/validate/preserved.yamlに、metadata.name: preserved、spec.image、spec.extra.custom: keptを入れて適用してください(この値は残っている必要があります)。
  7. v1スキーマのproperties.specの直下に、x-kubernetes-validations配列を追加してください。最初のルールのruleはself.tier != 'prod' || self.replicas >= 2で、messageはprod 계층은 복제본이 2개 이상이어야 합니다(韓国語の文は「prod階層ではレプリカが2つ以上必要」という意味です)です。再適用した後、/root/crd/validate/cel-violation.yamlに、metadata.name: cel-violation、spec.image、spec.tier: prod、spec.replicas: 1を入れて適用し、失敗の出力を/root/crd/validate/out/err-cel.txtに保存してください。その出力に、上のmessageの文言がそのまま含まれている必要があります。
  8. /root/crd/validate/out/matrix.jsonを作成してください。最上位のキーはcasesで、配列の各要素はname、expected(rejectedまたはaccepted)、actual、ruleの4つのキーを持ちます。拒否5件(no-image/required、bad-type/type、bad-tier/enum、too-many/maximum、cel-violation/cel)と、通過3件(defaulted/default、pruned/pruning、preserved/preserve-unknown-fields)をすべて入れ、すべてのケースでexpectedとactualが同じである必要があります。

参考

必須フィールドの欠落が拒否されるのを見る

/root/crd/validate/no-image.yamlに、metadata.name: no-image(ネームスペースcrd-lab)で、specにimageがないWebServiceを書いて、適用してみてください。失敗の出力を標準エラー出力まで含めて、/root/crd/validate/out/err-required.txtに保存してください。出力にspec.imageと必須の値に関する文言があり、no-imageがクラスターに残っていてはいけません。

わざと失敗させるステップです。拒否メッセージは標準エラー出力に出るため、ファイルに残すには標準エラー出力まで一緒に受け取る必要があります。拒否されたリソースがクラスターに残っていてはいけません。

型の不一致が拒否されるのを見る

/root/crd/validate/bad-type.yamlに、metadata.name: bad-type、spec.image: nginx:1.27、spec.replicas: "three"を書いて適用し、失敗の出力を/root/crd/validate/out/err-type.txtに保存してください。

YAMLで数値を引用符で囲むと文字列になります。スキーマがintegerを期待しているときにどんな文言が出るかを保存してください。

列挙値の違反と、許可リストの案内を見る

/root/crd/validate/bad-tier.yamlに、metadata.name: bad-tier、spec.image: nginx:1.27、spec.tier: qaを書いて適用し、失敗の出力を/root/crd/validate/out/err-enum.txtに保存してください。出力に、許可される値も一緒に表示されている必要があります。

enum違反のメッセージは、拒否するだけでなく、何が可能かも教えてくれます。そのリストが出力に入っている必要があります。

範囲の超過が拒否されるのを見る

/root/crd/validate/too-many.yamlに、metadata.name: too-many、spec.image: nginx:1.27、spec.replicas: 50を書いて適用し、失敗の出力を/root/crd/validate/out/err-range.txtに保存してください。

maximumを超える値を指定してください。メッセージに上限がそのまま示されます。

書かなかったフィールドにデフォルト値が埋められるのを確認する

/root/crd/validate/defaulted.yamlに、metadata.name: defaultedとspec.imageだけを書いてください。spec.replicasとspec.tierは絶対に書かないでください。適用した後、保存されたオブジェクトにreplicas: 1、tier: devが埋められているか確認してください。

マニフェストに値を書くと、デフォルト値が埋められたのかどうかわかりません。2つのフィールドを空にしておき、保存されたオブジェクトを読み直して比較してください。

未知のフィールドの切り落としと、例外的な保存

/root/crd/crd.yamlのv1スキーマに、properties.spec.properties.extraをtype: objectとx-kubernetes-preserve-unknown-fields: trueで追加し、CRDを再適用してください。そのうえで、/root/crd/validate/pruned.yamlに、metadata.name: pruned、spec.image、そしてスキーマにないspec.bogus: anythingを入れて適用し(保存されたオブジェクトからbogusが消えている必要があります)、/root/crd/validate/preserved.yamlに、metadata.name: preserved、spec.image、spec.extra.custom: keptを入れて適用してください(この値は残っている必要があります)。

スキーマにないフィールドは、保存される前に切り落とされます。任意のキーを受け取る必要がある箇所は、スキーマにオブジェクトとして定義したうえで、未知のフィールドを保存するよう指定する必要があります。その指定は、x-で始まる拡張キーです。

フィールド間の制約をCELで表現する

v1スキーマのproperties.specの直下に、x-kubernetes-validations配列を追加してください。最初のルールのruleはself.tier != 'prod' || self.replicas >= 2で、messageはprod 계층은 복제본이 2개 이상이어야 합니다(韓国語の文は「prod階層ではレプリカが2つ以上必要」という意味です)です。再適用した後、/root/crd/validate/cel-violation.yamlに、metadata.name: cel-violation、spec.image、spec.tier: prod、spec.replicas: 1を入れて適用し、失敗の出力を/root/crd/validate/out/err-cel.txtに保存してください。その出力に、上のmessageの文言がそのまま含まれている必要があります。

1つのフィールドだけを見ても判断できないルールです。specオブジェクトのレベルにルールの配列を置き、ルールの中ではselfで現在のオブジェクトを指します。messageはユーザーが目にする文言なので、そのままエラーに表示されます。

検証マトリクスを作り、実際の結果と照合する

/root/crd/validate/out/matrix.jsonを作成してください。最上位のキーはcasesで、配列の各要素はname、expected(rejectedまたはaccepted)、actual、ruleの4つのキーを持ちます。拒否5件(no-image/required、bad-type/type、bad-tier/enum、too-many/maximum、cel-violation/cel)と、通過3件(defaulted/default、pruned/pruning、preserved/preserve-unknown-fields)をすべて入れ、すべてのケースでexpectedとactualが同じである必要があります。

前のステップで作ったケースを表に整理します。各ケースに名前・予想・実際・引っかかったルールを書き、予想と実際が1つでも違ってはいけません。