KyvernoによるKubernetesのPolicy as Code:設定とセキュリティチェックを自動化する

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

午前2時47分、アラートが鳴り響いた。また開発者がリソース制限なしでDeploymentをプッシュしたのだ。Podが暴走してノードのCPUをすべて食いつぶし、他の3つのサービスまで道連れにした。この1ヶ月で同じ会話を6回繰り返していた。ポストモーテムは毎回同じ結論で終わる。「もっとガードレールが必要だ。」

その夜、私はようやく腰を据えて、何週間も先送りにしてきた作業に取りかかった。KyvernoによるPolicy as Codeの実装だ。それから6ヶ月、同種のインシデントはゼロ。実際に本番運用して学んだことをここにまとめる。

手動レビューがスケールしない理由

多くのチームはコードレビューをセーフティネットとして使い始める。誰かがマージ前にYAMLを目で確認する。エンジニアが2人でクラスターが1つなら、それで十分だ。しかし15人のエンジニアが4つの環境にデプロイするようになると、すぐに破綻する。

根本的な問題はこうだ。レビュー時のポリシー適用はあくまで勧告であり、強制力がない。深夜にCIがグリーンになっているからと、疲れた状態でPRを承認してしまう。securityContextの漏れが見逃される。本番環境でPodがrootとして動き続ける。Git Hooksのような自動チェックでコミット前に問題を弾くアプローチも有効だが、Kubernetesレイヤーへの到達を防ぐことはできない。

Policy as Codeはポリシーの適用をクラスター自体に移す。APIサーバーがスケジューリングされる前に非準拠のリソースを拒否する。例外なし、疲労なし、深夜の驚きなし。

アプローチ比較:OPA/Gatekeeper vs Kyverno vs Admission Webhook

選択肢を評価し始めたとき、主に3つのアプローチが浮かび上がった。それぞれ同じ問題を異なる方法で解決している。

Open Policy Agent (OPA) + Gatekeeper

OPAは歴史が長く、実績のある選択肢だ。専用のポリシー言語であるRegoと、Kubernetesとの統合レイヤーであるGatekeeperを使用する。ポリシーのエコシステムは成熟しており、専任のプラットフォームチームを持つ大企業に支持されている。

ただし、Regoの学習曲線は急峻だ。リソース制限を必須にするルールを書くだけで20行ほどのロジックになり、多くのエンジニアには直感的でない。失敗したポリシーのデバッグはRegoのトレースを読むことを意味する。専任のプラットフォームエンジニアがいないチームでは、すぐにボトルネックになる。

カスタムAdmission Webhook

GoやHTTPを話せる任意の言語で独自のWebhookを書くこともできる。完全な柔軟性と外部依存ゼロ。ただし、そのコードのすべての行を自分たちで管理することになる。バグ、TLS証明書のローテーション、可用性要件も含めて。Webhookが壊れると、すべてのデプロイがブロックされる。

このアプローチがうまく機能するのを見たのは、6人のプラットフォームチームを持つ会社で一度きりだ。それ以外の場所では、誰も触りたがらない技術的負債になっていった。

Kyverno

KyvernoはKubernetesネイティブだ。ポリシーはKubernetesリソースであり、YAMLで書かれたCRDとして定義される。Deploymentマニフェストを読めるチームなら、Kyvernoのポリシーも読める。新しい言語を覚える必要はない。

チームが実際に必要とするケースの90%をカバーする3つのユースケースに対応している。バリデーション(不正な設定を拒否)、ミューテーション(リソースを自動的に修正または補完)、ジェネレーション(関連リソースを自動作成)だ。

メリットとデメリット:率直な評価

Kyvernoのメリット

  • 参入障壁が低い — ポリシーはYAMLなので、チームの誰でも読める
  • Auditモード — 適用前にAuditモードで何が失敗するか確認できる
  • ミューテーションのサポート — サイドカーの自動注入、ラベルの追加、デフォルト値の設定が可能
  • ポリシーレポート — 名前空間をまたいだコンプライアンス状況を表示する組み込みCRD
  • 活発な開発 — CNCFインキュベーティングプロジェクトで頻繁にリリースされる

Kyvernoのデメリット

  • 複雑なロジックが書きにくい — 深くネストした条件分岐はYAMLで書くと煩雑になる
  • Regoの方が表現力が高い — 高度なロジックが必要なポリシーではOPAが優れている
  • Webhookの可用性が重要 — KyvernoはWebhookとして動作するため、本番環境ではHA構成が必要
  • 比較的新しい — OPAよりコミュニティ提供のポリシーコンテンツは少ないが、差は急速に縮まっている

私の見解

ほとんどのチーム、特に専任のプラットフォームエンジニアがいないチームにとって、Kyvernoは正しいデフォルト選択だ。私たちは週に約200回のデプロイをさばく4つのクラスターで運用している。展開に要したのは2日間。6ヶ月間、コアのセットアップに一度も手を加えていない。セキュリティツールに求める「退屈なほどの安定性」がここにある。

本番環境向け推奨セットアップ

ポリシーを1つも書く前に、デプロイを正しく行うことが先決だ。KyvernoのWebhookがダウンすると、そのスコープ内のすべてのAPIサーバーリクエストがブロックされる。これは致命的だ。

本番環境では最低3レプリカで実行すること:

# Helmでインストール(本番環境推奨)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

helm install kyverno kyverno/kyverno \
  --namespace kyverno \
  --create-namespace \
  --set replicaCount=3 \
  --set admissionController.replicas=3 \
  --set backgroundController.replicas=2 \
  --set cleanupController.replicas=2

インストール後にWebhookの設定を確認すること。デフォルトでKyvernoはfailurePolicy: Failに設定されており、Kyvernoに到達できない場合はすべてのリソース作成が失敗する。初期展開時はfailurePolicy: Ignoreへの切り替えを検討し、Kyvernoが安定していることを確認した後に厳格化するとよい。

# すべてのPodが起動していることを確認
kubectl get pods -n kyverno

# WebhookのConfig確認
kubectl get validatingwebhookconfigurations | grep kyverno
kubectl get mutatingwebhookconfigurations | grep kyverno

実装ガイド:まずAuditから、次にEnforce

いきなりEnforceモードで始めるのは、チームの信頼を失う最速の方法だ。重要なデプロイが悪いタイミングでポリシーにブロックされると、取り組み全体が台無しになる。まずAuditから始めよう。

ステップ1:リソース制限を必須にする(そもそものインシデントの発端)

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
  annotations:
    policies.kyverno.io/title: リソース制限を必須にする
    policies.kyverno.io/description: すべてのコンテナはCPUとメモリの制限を定義しなければならない。
spec:
  validationFailureAction: Audit  # まずここから始め、後でEnforceに変更する
  background: true
  rules:
    - name: check-resource-limits
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "すべてのコンテナにCPUとメモリの制限が必要です。"
        pattern:
          spec:
            containers:
              - name: "*"
                resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"
kubectl apply -f require-resource-limits.yaml

# 失敗するものを確認(Auditモード)
kubectl get policyreport -A
kubectl get clusterpolicyreport

ステップ2:特権コンテナをブロックする

rootまたは特権モードで動作するコンテナは典型的な攻撃ベクターであり、本番環境では想像以上によく見かける。CI/CDパイプラインにおけるシークレット管理と合わせて、このポリシーでコンテナのセキュリティを強化する:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
spec:
  validationFailureAction: Audit
  background: true
  rules:
    - name: check-privileged
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "特権コンテナは許可されていません。"
        pattern:
          spec:
            containers:
              - =(securityContext):
                  =(privileged): "false"

ステップ3:ミューテーションでラベルを自動注入する

ミューテーションはバリデーション以上にKyvernoの真価が発揮される場面だ。ラベルが欠けているDeploymentを拒否する代わりに、自動的に追加してしまえばよい:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-labels
spec:
  rules:
    - name: add-team-label
      match:
        any:
          - resources:
              kinds:
                - Deployment
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              +(managed-by): "kyverno"
              +(environment): "production"

ステップ4:ポリシーレポートを確認する

# クラスター全体のすべての違反を確認
kubectl get policyreport -A -o wide

# 特定の名前空間の詳細レポート
kubectl describe policyreport -n default

# 失敗のみをフィルタリング
kubectl get policyreport -A -o json | \
  jq '.items[].results[] | select(.result == "fail")'

Enforceに移行する前に、Auditレポートを1週間眺めること。ワークロードの既存の違反を修正する。その後、ポリシーを1つずつEnforceに切り替える。絶対に一括で変更してはいけない:

# ポリシーをAuditからEnforceへ変更
kubectl patch clusterpolicy require-resource-limits \
  --type=merge \
  -p '{"spec":{"validationFailureAction":"Enforce"}}'

ステップ5:Kyvernoポリシーライブラリを活用する

カスタムポリシーを書く前に、公式ライブラリを確認すること。Pod Security Standards、CISベンチマーク、一般的なベストプラクティスはすでにカバーされている:

# コミュニティポリシーの参照と適用
kubectl apply -f https://raw.githubusercontent.com/kyverno/policies/main/pod-security/baseline/disallow-host-namespaces/disallow-host-namespaces.yaml

ポリシーを骨抜きにしない例外処理

一部のワークロードは正当に例外を必要とする。特権アクセスが必要なノードレベルの監視エージェントはその典型例だ。KyvernoはexcludeブロックとPolicyExceptionリソース(Kyverno 1.9以降で利用可能)を使ってこれをきれいに処理する。

apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
  name: allow-monitoring-privileged
  namespace: monitoring
spec:
  exceptions:
    - policyName: disallow-privileged-containers
      ruleNames:
        - check-privileged
  match:
    any:
      - resources:
          kinds:
            - Pod
          namespaces:
            - monitoring
          names:
            - node-exporter-*

例外はKubernetesリソースであり、バージョン管理され、レビュー可能で、監査可能だ。深夜にオンコール担当者が行った文書化されていないバイパスの決定は、もう存在しない。

6ヶ月後の景色

あの最初のデプロイ?今ならAPIサーバーに到達することすらない。開発者は何が足りないかを明確に説明したエラーメッセージを受け取る。修正してもう一度プッシュすれば動く。3時間の障害とポストモーテムの代わりに、2分で完結する。

ポリシーレポートはコンプライアンス監査への対応も変えた。「すべての本番ワークロードがrootアクセスなしで動作しているか?」という問いへの回答が、1週間の手作業レビューから30秒に短縮された。

コードレビューだけを唯一の強制レイヤーとして本番でKubernetesを動かすのはリスクだ。今週、Kyvernoをauditモードでインストールしてみてほしい。セットアップは10分で終わり、最初のポリシーレポートには十中八九、想定外の何かが映し出されるはずだ。

Share: