CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト
ポリシーを有効にしたら緊急パッチが止められた
目標
本物のk3sとKyvernoで、プラットフォームのルール1つ(オーナーラベルとリソースのrequests)を3つのチームに導入します。まず監査で数え、レポートで違反を洗い出し、 ブロックに切り替えたときに何が止まるのかを体験したうえで、直せない対象に期限付きの例外を与え、準拠率を計算します。
なぜ重要なのか
ポリシーエンジンは、プラットフォームのガードレールをコードとして強制するツールです。ところが、ルールを最初からブロックで有効にすると、すでにルールを破っているワークロードの緊急パッチまで止まってしまい、 障害対応が止まります。そのためガバナンスは、通常、監査 → レポートで現状把握 → チームごとの修正 → ブロック → 狭く期限付きの例外、の順序で進みます。 このラボは、その順序の各段階でクラスターが実際にどう反応するのか、そして例外が静かな永久許可に変わらないようにする仕組みが何なのかを確認します。
ステップ
/root/cnpa-pol/tenants.yamlに、3つのネームスペースと4つのDeploymentを作成して適用してください。ネームスペースteam-pay・team-legacy・team-vendorには、ラベルplatform.labhub.io/tenantをそれぞれpay・legacy・vendorとして付けます。Deploymentはすべて、replicas 1、コンテナ名app、イメージregistry.k8s.io/pause:3.10、メタデータとセレクターのラベルapp.kubernetes.io/name: <이름>(プレースホルダーはDeploymentの名前です)です。team-pay/checkoutはメタデータラベルapp.kubernetes.io/owner: payとrequests(cpu10m、memory16Mi)の両方を持ち、team-legacy/report-genはrequestsのみ、team-legacy/batch-syncはownerラベル(legacy)のみ、team-vendor/vendor-agentはどちらもありません。/root/cnpa-pol/policy.yamlにValidatingPolicy(policies.kyverno.io/v1)tenant-deploy-baselineを作成して適用してください。validationActions: [Audit]、evaluation.background.enabled: true、matchConstraintsはapps/v1のdeploymentsのCREATE・UPDATEで、namespaceSelectorでラベルplatform.labhub.io/tenantが存在する(Exists)ネームスペースだけを選びます。検証を2つ、この順序で置きます。① メタデータラベルにapp.kubernetes.io/ownerがあるかを検証し、メッセージはDeployment 에 app.kubernetes.io/owner 라벨이 필요합니다(韓国語の文は「Deploymentにはapp.kubernetes.io/ownerラベルが必要です」という意味です)にします。② すべてのコンテナにrequestsのcpuとmemoryがあるかを検証し、メッセージは모든 컨테이너에 cpu·memory requests 가 필요합니다(韓国語の文は「すべてのコンテナにcpu・memoryのrequestsが必要です」という意味です)にします。- Kyvernoが残したPolicyReportを読み、
tenant-deploy-baselineの結果がfailのDeploymentを、/root/cnpa-pol/violations.jsonに配列として書いてください。各要素には、namespace、name、uid(レポートのscope.uid)、message(その結果のメッセージ)を置きます。 - ポリシーの
validationActionsを[Deny]に変更してください。続いて、legacyチームの緊急パッチをまねて、kubectl -n team-legacy set image deploy/batch-sync app=registry.k8s.io/pause:3.9を実行し、その出力(標準エラー出力を含む)を/root/cnpa-pol/blocked.txtに保存してください。さらにkubectl -n team-legacy scale deploy/batch-sync --replicas=2を実行し、/root/cnpa-pol/deny-effects.jsonにimage_update_denied、scale_allowed(ブール値)、batch_sync_image(現在のbatch-syncのイメージ)を書いてください。 - legacyチームの2つのDeploymentをルールに合わせてください。
report-genにメタデータラベルapp.kubernetes.io/owner: legacyを、batch-syncのコンテナappにrequests(cpu50m、memory64Mi)を入れます。そのあと、ステップ4でブロックされたset image ... app=registry.k8s.io/pause:3.9をもう一度実行して通し、2つのDeploymentのPolicyReportの結果がpassに変わるまで待ってください。 - vendor-agentは業者から提供されるマニフェストなので、今すぐには直せません。まず
kyverno-admission-controllerDeploymentの引数--enablePolicyException=falseを--enablePolicyException=trueに変更し、ロールアウトが終わるのを待ってください。そのあと、/root/cnpa-pol/exception.yamlにPolicyException(policies.kyverno.io/v1)vendor-agent-requestsをteam-vendorネームスペースに作成します。policyRefsはValidatingPolicytenant-deploy-baseline、matchConditionsは名前がvendor-agentのオブジェクトだけ、expiresAtは今から30日後(RFC3339、UTC)です。適用後、vendor-agentにアノテーションplatform.labhub.io/reviewed=trueを付けてください。 /root/cnpa-pol/expired.yamlにPolicyExceptionold-cron-requestsをteam-legacyに作成してください。対象は名前がold-cronのオブジェクト、ポリシーはtenant-deploy-baseline、expiresAtは今より1日前です。そのあと、ownerラベルもrequestsもないDeploymentold-cron(イメージregistry.k8s.io/pause:3.10)を、team-legacyにサーバーdry-runで作成してみて、その出力(標準エラー出力を含む)を/root/cnpa-pol/expired.txtに保存してください。- 3つのテナントネームスペースのPolicyReportから、
tenant-deploy-baselineの結果を数えて、/root/cnpa-pol/compliance.jsonに書いてください。キーはpass、fail、skip(件数)、rate_pct(pass / (pass + fail) × 100、小数第1位、分母が0なら100.0)、exceptions(現在期限切れになっていないPolicyExceptionを、네임스페이스/이름(プレースホルダーはネームスペースと名前です)の文字列で、ソートした配列)です。
参考
- VM内にk3sとKyverno 1.19がインストールされています。ワークロードのイメージは
registry.k8s.io/pauseだけを使います。 - ポリシーの準備確認:
kubectl get validatingpolicy tenant-deploy-baseline -o jsonpath='{.status.conditionStatus.ready}'。 - レポート:
kubectl get policyreport -Aと-o json。レポート名は、対象リソースのuidです。 - よくある間違い: 例外機能がオフのままPolicyExceptionを作成することです。
kubectl applyは成功して警告が1行出るだけで、アドミッションは引き続き拒否します。 - 例外の期限フィールドの説明:
kubectl explain policyexception.spec.expiresAt --api-version=policies.kyverno.io/v1。 - よくある間違い:
matchConditionsなしで例外を作成することです。そのネームスペースのすべてのDeploymentが、ルールの対象外になります。 - Kyverno ValidatingPolicy・Kyverno Policy Exceptions・Kyverno Reporting・Kubernetes CEL・CNCF Platforms White Paper
ポリシー導入前の3つのチーム
/root/cnpa-pol/tenants.yamlに、3つのネームスペースと4つのDeploymentを作成して適用してください。ネームスペースteam-pay・team-legacy・team-vendorには、ラベルplatform.labhub.io/tenantをそれぞれpay・legacy・vendorとして付けます。Deploymentはすべて、replicas 1、コンテナ名app、イメージregistry.k8s.io/pause:3.10、メタデータとセレクターのラベルapp.kubernetes.io/name: <이름>(プレースホルダーはDeploymentの名前です)です。team-pay/checkoutはメタデータラベルapp.kubernetes.io/owner: payとrequests(cpu 10m、memory 16Mi)の両方を持ち、team-legacy/report-genはrequestsのみ、team-legacy/batch-syncはownerラベル(legacy)のみ、team-vendor/vendor-agentはどちらもありません。
ポリシーエンジンを導入する前のクラスターには、すでにルールを破っているワークロードが混ざっています。このステップでは何もブロックしないので、4つすべてが作成されるはずです。複数のオブジェクトを---でつないで、1つのファイルに置けます。
ブロックせず、まず数える
/root/cnpa-pol/policy.yamlにValidatingPolicy(policies.kyverno.io/v1) tenant-deploy-baselineを作成して適用してください。validationActions: [Audit]、evaluation.background.enabled: true、matchConstraintsはapps/v1のdeploymentsのCREATE・UPDATEで、namespaceSelectorでラベルplatform.labhub.io/tenantが存在する(Exists)ネームスペースだけを選びます。検証を2つ、この順序で置きます。① メタデータラベルにapp.kubernetes.io/ownerがあるかを検証し、メッセージはDeployment 에 app.kubernetes.io/owner 라벨이 필요합니다(韓国語の文は「Deploymentにはapp.kubernetes.io/ownerラベルが必要です」という意味です)にします。② すべてのコンテナにrequestsのcpuとmemoryがあるかを検証し、メッセージは모든 컨테이너에 cpu·memory requests 가 필요합니다(韓国語の文は「すべてのコンテナにcpu・memoryのrequestsが必要です」という意味です)にします。
ValidatingPolicyの検証はCEL式で、objectがリクエストされたオブジェクトです。キーがあるかは'키' in 맵(プレースホルダーはキーとマップです)で、存在しないかもしれないフィールドはhas()で先に確認します。リストのすべての要素に対する条件は.all(c, ...)です。Auditは、拒否せずに結果だけを残す動作です。ポリシーのstatus.conditionStatus.readyがtrueになるまで待ってください。採点ツールは、ポリシーが後のステップでDenyに変わった場合も受け入れます。
知らなかった違反がレポートで明らかになる
Kyvernoが残したPolicyReportを読み、tenant-deploy-baselineの結果がfailのDeploymentを、/root/cnpa-pol/violations.jsonに配列として書いてください。各要素には、namespace、name、uid(レポートのscope.uid)、message(その結果のメッセージ)を置きます。
バックグラウンド評価は、ポリシーが準備できてから数秒以内に、ネームスペースごとにPolicyReportを作成します。レポート1つがリソース1つに対応し、scopeに対象が、resultsにポリシーごとの結果があります。kubectl get policyreport -Aで概要を見て、-o jsonで詳しく読んでください。両方を破ったリソースのメッセージは、先に失敗した検証のものです。
ポリシーを有効にすると緊急パッチが止まる
ポリシーのvalidationActionsを[Deny]に変更してください。続いて、legacyチームの緊急パッチをまねて、kubectl -n team-legacy set image deploy/batch-sync app=registry.k8s.io/pause:3.9を実行し、その出力(標準エラー出力を含む)を/root/cnpa-pol/blocked.txtに保存してください。さらにkubectl -n team-legacy scale deploy/batch-sync --replicas=2を実行し、/root/cnpa-pol/deny-effects.jsonにimage_update_denied、scale_allowed(ブール値)、batch_sync_image(現在のbatch-syncのイメージ)を書いてください。
Denyは、新しく作るものだけでなく、すでにルールを破っているオブジェクトの更新リクエストも評価します。更新されたオブジェクト全体がルールを守っていて初めて受け入れられます。scaleはDeployment本体ではなくscaleサブリソースへのリクエストなので、ルールが選んだリソースとは異なります。すでに起動しているPodは、アドミッションを再度通りません。
ルールに合わせると、同じパッチが通る
legacyチームの2つのDeploymentをルールに合わせてください。report-genにメタデータラベルapp.kubernetes.io/owner: legacyを、batch-syncのコンテナappにrequests(cpu 50m、memory 64Mi)を入れます。そのあと、ステップ4でブロックされたset image ... app=registry.k8s.io/pause:3.9をもう一度実行して通し、2つのDeploymentのPolicyReportの結果がpassに変わるまで待ってください。
拒否されたリクエストは保存されていないので、先にルールに合わせる変更を入れる必要があります。その変更自体がルールを守るオブジェクトを作るので、Denyの下でも受け入れられます。requestsを入れる最も短い方法はkubectl set resourcesです。レポートは、リソースが変わると再評価されます。名前がリソースのuidなので、同じレポートが更新されます。
直せない外部マニフェストに期限付きの例外を与える
vendor-agentは業者から提供されるマニフェストなので、今すぐには直せません。まずkyverno-admission-controller Deploymentの引数--enablePolicyException=falseを--enablePolicyException=trueに変更し、ロールアウトが終わるのを待ってください。そのあと、/root/cnpa-pol/exception.yamlにPolicyException(policies.kyverno.io/v1) vendor-agent-requestsをteam-vendorネームスペースに作成します。policyRefsはValidatingPolicy tenant-deploy-baseline、matchConditionsは名前がvendor-agentのオブジェクトだけ、expiresAtは今から30日後(RFC3339、UTC)です。適用後、vendor-agentにアノテーションplatform.labhub.io/reviewed=trueを付けてください。
このインストールでは、例外機能がデフォルトでオフになっています。オフの状態で例外を作成しても、警告が出るだけで、アドミッションは相変わらず拒否します。JSONパッチでargs配列のその要素を変更すればよいです(jqでインデックスを探せます)。例外は、必要な対象だけを狭く選び、期限を設けておかないと、原則が静かに崩れます。日付はdate -u -d '+30 days' +%Y-%m-%dT%H:%M:%SZで作れます。採点ツールは、同じネームスペースの別の名前が、引き続き拒否されるかも確認します。
期限切れの例外は何も守ってくれない
/root/cnpa-pol/expired.yamlにPolicyException old-cron-requestsをteam-legacyに作成してください。対象は名前がold-cronのオブジェクト、ポリシーはtenant-deploy-baseline、expiresAtは今より1日前です。そのあと、ownerラベルもrequestsもないDeployment old-cron(イメージregistry.k8s.io/pause:3.10)を、team-legacyにサーバーdry-runで作成してみて、その出力(標準エラー出力を含む)を/root/cnpa-pol/expired.txtに保存してください。
期限切れとは、例外オブジェクトが消えることではなく、アドミッションがその例外をもう使わなくなることです。オブジェクトは残るので、誰がいつまで何を許可したかが記録として残ります。dry-runは保存しませんが、アドミッションWebhookは通ります。このステップでold-cronが実際に作成されてはいけません。
準拠率をレポートから計算する
3つのテナントネームスペースのPolicyReportから、tenant-deploy-baselineの結果を数えて、/root/cnpa-pol/compliance.jsonに書いてください。キーはpass、fail、skip(件数)、rate_pct(pass / (pass + fail) × 100、小数第1位、分母が0なら100.0)、exceptions(現在期限切れになっていないPolicyExceptionを、네임스페이스/이름(プレースホルダーはネームスペースと名前です)の文字列で、ソートした配列)です。
例外でスキップされたリソースは、failではなくskipとして報告されます。準拠率でskipをどう扱うかは組織が決めることですが、ここでは分母から除きます。期限切れかどうかは、expiresAtを現在時刻と比べて判断します。レポートの更新が遅れることがあるので、vendor-agentがskipに変わるまで待ってから数えてください。採点ツールは、同じレポートをもう一度数えます。