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

KCA — Kyverno認定アソシエイト

Deployment は作成されたのに Pod が 1 つもない

TT Labで続きを見る

目標

Kyvernoポリシーの記述ツール(preconditions、apiCallコンテキスト、変数のデフォルト値、autogen、JSONパッチ、foreach、CEL、バックグラウンドスキャン)が、実際のリクエストで何を拒否し、何を書き換えるのかを、本物のKyverno 1.19.1で確認します。

なぜ重要なのか

ポリシーの文法が正しいことと、意図どおりに判定することは、別のものです。matchはリソースの種類と場所だけで選ぶので、リクエストの内容に応じてルールをオン・オフするにはpreconditionsが、リクエストにない情報が必要ならコンテキストが必要です。変数が解釈されないと、ルールは判定の代わりにエラーを出し、Enforceではそのエラーが拒否として見えるので、原因を見当違いの場所に探すことになります。Podルールは、デフォルトでコントローラー用のルールとして自動生成されますが、これを切ると、Deploymentは成功したように見えるのに、Podだけが静かに作られません。書き換えルールは、リクエストごとに違う値を入れたり、リストの一部だけを直したりできなければならず、すでにクラスターにあったリソースは、アドミッションを再び通らないので、バックグラウンドスキャンでしか明らかになりません。

ステップ

  1. ネームスペースkca-preを作成し、/root/kca-write/01-limits.yamlにClusterPolicy limits-for-prod(background false)を書いて適用してください。ルールmemory-limitはkca-preのPodをmatchし、preconditionsで、ラベルtierがprodのリクエストにだけ適用され(ラベルがなければ空文字列として扱います)、すべてのコンテナにresources.limits.memoryを要求します(Enforce)。matchにはラベルセレクターを使いません。
  2. ネームスペースkca-prod(ラベルenv=prod)とkca-dev(ラベルenv=dev)を作成し、/root/kca-write/02-team.yamlにClusterPolicy team-in-prod-ns(background false)を適用してください。ルールteam-when-prodは、2つのネームスペースのPodについて、コンテキストnsenvをapiCallで/api/v1/namespaces/{{request.namespace}}から読み取り(ラベルenv、なければnone)、envがprodでteamラベルがないときに拒否します(Enforce)。そのあと、kca-devのenvを一時的にprodに変えて、ラベルのないPodをサーバーdry-runで送った出力全体を/root/kca-write/flip.txtに保存し、envをdevに戻してください。
  3. ネームスペースkca-varに、ClusterPolicy no-platform-team(background false、ルールnot-platform)を適用してください。teamラベルがplatformなら拒否するdeny条件ですが、最初はkeyを、デフォルト値なしで{{ request.object.metadata.labels.team }}と書きます。ラベルのないPodをサーバーdry-runで送り、拒否の出力全体を/root/kca-write/missing.txtに保存してください。そのあと、/root/kca-write/03-no-platform.yamlのkeyにデフォルト値(空文字列)を加えて再び適用し、ラベルのないPodは受け入れ、team=platformは拒否するようにしてください。
  4. ネームスペースkca-autoとkca-noautoを作成し、各ネームスペースのPodにreadinessProbeを要求する(Enforce、background false、ルールneed-probe)ClusterPolicy probes-autoとprobes-noautoを、/root/kca-write/04-probes.yamlに書いて適用してください。probes-noautoにだけ、アノテーションpod-policies.kyverno.io/autogen-controllers: noneを付けます。そのあと、2つのネームスペースでkubectl create deployment web --image=nginx:1.27-alpineを実行し、kca-auto側の出力全体を/root/kca-write/auto.txtに保存してください。
  5. ネームスペースkca-mutを作成し、/root/kca-write/05-requested-by.yamlにClusterPolicy requested-by(ルールannotate-user)を書いてください。kca-mutのPodに、patchesJson6902のadd操作でアノテーションkca.io/requested-byを入れ、値は{{request.userInfo.username}}です。まずspec.backgroundを書かずに適用して、拒否される出力全体を/root/kca-write/bg-denied.txtに保存したあと、backgroundをfalseにして適用してください。既存のアノテーションは保存されている必要があります。
  6. /root/kca-write/06-pull-policy.yamlにClusterPolicy pull-policy(ルールlatest-always)を書いて適用してください。kca-mutのPodについて、foreachでコンテナごとにイメージが:latestで終わるかを要素単位のpreconditionsで見て、そのコンテナだけをpatchStrategicMergeでimagePullPolicy: Alwaysに変えます。タグを固定したコンテナのimagePullPolicyは、そのままである必要があります。
  7. ネームスペースkca-celを作成し、/root/kca-write/07-no-priv-esc.yamlに、policies.kyverno.io/v1のValidatingPolicy no-priv-escを適用してください。validationActionsは[Deny]、matchConstraintsはcore v1 podsのCREATE・UPDATEと、namespaceSelector kubernetes.io/metadata.name: kca-celです。CEL変数escalatingに、securityContext.allowPrivilegeEscalationが明示的にfalseではないコンテナ名のリストを入れ、そのリストが空であれば通過し、拒否メッセージ(messageExpression)には、その名前をカンマでつないで表示します。
  8. ネームスペースkca-bgに、ラベルのないPod old-nolabelと、ラベルteam=aのPod old-labelを先に作成したあと、/root/kca-write/08-audit-team.yamlにClusterPolicy audit-team(background true、Audit、ルールteam、kca-bgのPodにteamラベルを要求)を適用してください。PolicyReportに2つのPodの結果が現れるまで待ってから、/root/kca-write/report.jsonに、fail・passキーで、それぞれの結果のPod名のリストを書きます。

参考

prodのPodだけにlimitを要求する

ネームスペースkca-preを作成し、/root/kca-write/01-limits.yamlにClusterPolicy limits-for-prod(background false)を書いて適用してください。ルールmemory-limitはkca-preのPodをmatchし、preconditionsで、ラベルtierがprodのリクエストにだけ適用され(ラベルがなければ空文字列として扱います)、すべてのコンテナにresources.limits.memoryを要求します(Enforce)。matchにはラベルセレクターを使いません。

preconditionsのkeyは、JMESPath変数です。ラベルのないリクエストで変数が解釈されないとどうなるかは、ステップ3で見ます。採点ツールは、tierがない・dev・prodのPodを、サーバーdry-runで送ってみます。

ネームスペースのラベル1つでルールがオンになる

ネームスペースkca-prod(ラベルenv=prod)とkca-dev(ラベルenv=dev)を作成し、/root/kca-write/02-team.yamlにClusterPolicy team-in-prod-ns(background false)を適用してください。ルールteam-when-prodは、2つのネームスペースのPodについて、コンテキストnsenvをapiCallで/api/v1/namespaces/{{request.namespace}}から読み取り(ラベルenv、なければnone)、envがprodでteamラベルがないときに拒否します(Enforce)。そのあと、kca-devのenvを一時的にprodに変えて、ラベルのないPodをサーバーdry-runで送った出力全体を/root/kca-write/flip.txtに保存し、envをdevに戻してください。

ネームスペース名をポリシーに埋め込むと、ポリシーを直さなければ判定が変わりません。apiCallは、リクエストごとにAPIサーバーの現在の値を読み取ります。dry-runは、kubectl run ... --dry-run=serverです。

ラベルのないPodが、見当違いの理由で拒否された

ネームスペースkca-varに、ClusterPolicy no-platform-team(background false、ルールnot-platform)を適用してください。teamラベルがplatformなら拒否するdeny条件ですが、最初はkeyを、デフォルト値なしで{{ request.object.metadata.labels.team }}と書きます。ラベルのないPodをサーバーdry-runで送り、拒否の出力全体を/root/kca-write/missing.txtに保存してください。そのあと、/root/kca-write/03-no-platform.yamlのkeyにデフォルト値(空文字列)を加えて再び適用し、ラベルのないPodは受け入れ、team=platformは拒否するようにしてください。

JMESPathで存在しないキーを読むと、変数の置換が失敗し、Kyvernoはそのルールを、判定できなかったエラーとして扱います。Enforceルールのエラーがリクエストをどうするかを、出力の文言を読んで確認してください。||演算子が、デフォルト値を与えます。

Deploymentは作られたのに、Podが1つもない

ネームスペースkca-autoとkca-noautoを作成し、各ネームスペースのPodにreadinessProbeを要求する(Enforce、background false、ルールneed-probe)ClusterPolicy probes-autoとprobes-noautoを、/root/kca-write/04-probes.yamlに書いて適用してください。probes-noautoにだけ、アノテーションpod-policies.kyverno.io/autogen-controllers: noneを付けます。そのあと、2つのネームスペースでkubectl create deployment web --image=nginx:1.27-alpineを実行し、kca-auto側の出力全体を/root/kca-write/auto.txtに保存してください。

Kyvernoは、PodルールをDeployment・ReplicaSetなど、Podテンプレートを持つリソース用のルールに自動的に拡張します。拡張しなければ、Deploymentは通過し、拒否はReplicaSetコントローラーがPodを作るときに起きます。kubectl describe rsの条件とイベントを見てください。

リクエストした人の名前をPodに刻む

ネームスペースkca-mutを作成し、/root/kca-write/05-requested-by.yamlにClusterPolicy requested-by(ルールannotate-user)を書いてください。kca-mutのPodに、patchesJson6902のadd操作でアノテーションkca.io/requested-byを入れ、値は{{request.userInfo.username}}です。まずspec.backgroundを書かずに適用して、拒否される出力全体を/root/kca-write/bg-denied.txtに保存したあと、backgroundをfalseにして適用してください。既存のアノテーションは保存されている必要があります。

JSON Pointerでは、/を~1と書きます。リクエスターの情報は、アドミッションのリクエストにしかなく、すでに保存されているリソースをもう一度走査するバックグラウンド処理にはありません。サーバーdry-runに-o jsonpathを付けると、書き換えられた結果を見られます。

latestタグのコンテナだけを毎回新しく取得させる

/root/kca-write/06-pull-policy.yamlにClusterPolicy pull-policy(ルールlatest-always)を書いて適用してください。kca-mutのPodについて、foreachでコンテナごとにイメージが:latestで終わるかを要素単位のpreconditionsで見て、そのコンテナだけをpatchStrategicMergeでimagePullPolicy: Alwaysに変えます。タグを固定したコンテナのimagePullPolicyは、そのままである必要があります。

foreachの中で、現在の要素はelementです。リストから特定のコンテナだけを直すには、条件アンカー(name)で名前を合わせます。JMESPathには、ends_with関数があります。

CELの1行が、Deploymentまで止めた

ネームスペースkca-celを作成し、/root/kca-write/07-no-priv-esc.yamlに、policies.kyverno.io/v1のValidatingPolicy no-priv-escを適用してください。validationActionsは[Deny]、matchConstraintsはcore v1 podsのCREATE・UPDATEと、namespaceSelector kubernetes.io/metadata.name: kca-celです。CEL変数escalatingに、securityContext.allowPrivilegeEscalationが明示的にfalseではないコンテナ名のリストを入れ、そのリストが空であれば通過し、拒否メッセージ(messageExpression)には、その名前をカンマでつないで表示します。

CELのfilter・mapでリストを作り、has()で存在しないフィールドを先に確認します。このバージョンのValidatingPolicyも、Podルールを、Podテンプレートを持つリソースに自動生成します(status.autogen)。採点ツールは、Deploymentもdry-runで送ってみます。

ポリシーより先にあったPodも、レポートに載った

ネームスペースkca-bgに、ラベルのないPod old-nolabelと、ラベルteam=aのPod old-labelを先に作成したあと、/root/kca-write/08-audit-team.yamlにClusterPolicy audit-team(background true、Audit、ルールteam、kca-bgのPodにteamラベルを要求)を適用してください。PolicyReportに2つのPodの結果が現れるまで待ってから、/root/kca-write/report.jsonに、fail・passキーで、それぞれの結果のPod名のリストを書きます。

Auditは、リクエストを止めずに記録だけします。すでに保存されているリソースは、アドミッションを再び通らないので、backgroundがあってはじめて評価されます。結果は、ネームスペースのPolicyReport(リソースごとに1つ)に集まります。