Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Kubernetesのクラスター内検証を簡単に:Validating Admission Policies入門

By Eyal ZekariaMay 30, 202310 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

Validating Admission PoliciesとCELで実現するKubernetesリソース検証の効率化

クラスター内検証には実用的な使い道が数多くあります。たとえば、リソースの誤削除や悪意ある削除の防止、Deploymentのレプリカ数を制限してリソース管理を最適化すること、特定のアノテーション・ラベル・環境変数の付与(または不在)を必須にすることなどです。Kubernetesクラスター内でリソースを検証する仕組み自体は至ってシンプルで、条件に合致するリクエストがAPIに届くたびに、許可するか拒否するかを判定するための一連のポリシーが実行されるだけです。

これは、開発者などのクラスター利用者に対して特定の標準やルールを徹底させたいクラスター運用者にとって非常に有用です。多くのお客様の関心が集まるテーマとなっているのも当然で、Kubernetesリソース向けの検証ルールを作成・実行するプロセスを簡素化する製品やサービスも次々と登場しています。

本記事では、検証プロセスにおけるDynamic Admission Control(DAC)の役割と、クラスター運用者が直面する課題について整理します。そのうえで、Validating Admission Policies(VAP)、Common Expression Language(CEL)、そしてRBAC風のシンプルな構文がもたらすメリットをよりはっきりと感じていただけるはずです。こうした背景を踏まえれば、ご自身のKubernetes環境にこれらの新機能を取り入れるかどうかを、より的確に判断できるでしょう。

具体的なコードや例をすぐに確認したい方は、Validating Admission Policiesのセクションへお進みください。より高度なユースケースは、続編記事Validating Admission Policies in Kubernetes: Advanced Use Casesをご覧ください。

Dynamic Admission Controlを実装するうえでの課題

Admission Controllerのフェーズ(出典

クラスター内でリクエスト検証を実装する作業は、決して一筋縄ではいきません。ここで使うDACは強力な仕組みである一方、クラスター運用者にとって必ずしも扱いやすいツールとは言えないからです。

DACはKubernetesの検証プロセスにおいて中核を担うコンポーネントです。クラスター管理者はAPIリクエストをインターセプトし、あらかじめ定義したポリシーに基づいて許可または拒否することで、クラスターへのリソース受け入れを制御できます。これにより、クラスター内で作成・更新・削除されるリソースをきめ細かく管理し、特定の標準やルールへの準拠を担保できます。

ただし現状では、DACの利用はその複雑さゆえにクラスター運用者にとってハードルが高いのも事実です。具体的には、admission webhookサーバーの実装、webhookリソースの作成、必要に応じたAPIサーバーへの認証設定、TLS用証明書の管理といった作業が必要になります。さらに運用者には、Kubernetesの概念やセキュリティのベストプラクティスへの深い理解に加え、検証ロジックを構築・保守できるプログラミング言語のスキルも求められます。

OPA Gatekeeperのようなツールでこのプロセスを効率化することは可能ですが、それでもクラスター運用者は自らのクラスター上で稼働するworkloadsをデプロイ・管理しなければなりません。しかしご安心ください。Kubernetesではv1.26からアルファ版としてValidating Admission Policies(VAP)という新しいリソースタイプが導入され、よりシンプルな解決策が登場しました。次のセクションでお見せするとおり、そのシンプルさによって、VAPはクラスター内検証のデファクトスタンダードになっていく可能性が高いと言えます。

VAPの登場——リソース検証における運用者の頼れる味方

VAPはGoogleのCELを活用し、条件に合致するAPIリクエストに対して実行する検証式を定義します。CEL式はAPIサーバー上で直接評価されるため、ポリシー評価のためにカスタムのworkloadsを実装したりデプロイしたりする必要はありません。これにより、先述のValidating Admission Webhooksなどの代替手段と比べて、ポリシーの作成が格段に手軽で取り組みやすくなります。

「APIに過大な負荷をかけて他の処理に影響しないか」と心配される方もご安心ください。CELにはリソース制約が組み込まれており、暴走を防ぐ仕組みが備わっています。詳しくは、Kubernetes公式ドキュメントのCELリソース制約に目を通しておくことをおすすめします。

CELサポートは、Custom Resource Definitions(CRD)のインライン検証用としてKubernetes v1.23で初めて導入され、v1.25以降はベータ版となっています。これによりKubernetes内でのCEL活用の可能性が大きく広がりました。KubernetesにおけるCELの背景と将来性についてさらに知りたい方には、Cici Huang氏の講演「The Path to Self Contained CRDs」をおすすめします。私たちがValidating Admission Policiesを掘り下げるきっかけとなった講演です。

VAPとCELで実現する効率的な検証

CELとVAPを使う最大のメリットの一つは、そのシンプルさにあります。検証ロジックはAPIサーバー上で直接評価されるため、カスタムworkloadsによる追加のオーバーヘッドが発生せず、クラスターの拡大に合わせて検証プロセスをスケールさせるのも容易です。さらにCELは、検証ルールを軽量かつ効率的に表現できる手段を提供し、パフォーマンスを高めながらAPIサーバーへの過負荷リスクも抑えます。組み込みのリソース制約により、検証プロセスの安定性と効率も担保されます。

Validating Admission PoliciesとCELを活用すれば、クラスター運用者はよりシンプルかつスケーラブルな検証プロセスを実現でき、他の手法よりも効率的かつ効果的にクラスター内で標準やルールを徹底できます。使いやすさ、スケーラビリティ、パフォーマンスの面から見て、VAPとCELはKubernetesのクラスター内検証を効率化したい運用者にとって極めて有力な選択肢になるはずです。

Validating Admission Policies

この機能を使い始めるには、主に次の2つのコンポーネントが必要です。

  1. ValidatingAdmissionPolicy — failurePolicy、リクエストのマッチ条件、CEL検証式を定義します。つまりポリシー本体です。
  2. ValidatingAdmissionPolicyBinding — ポリシーの適用範囲を定義し、マッチしたリソース群にポリシーをバインドします。

本機能はv1.26からアルファ版として提供されていますが、一部の機能はv1.27以降でのみ利用可能です(audit annotationvalidation actions)。そのため、以下のマニフェストはすべてv1.27.1で稼働するKubernetesクラスター(アルファ機能とValidatingAdmissionPolicyのfeature gateを有効化済み)でテストしています。

本記事のすべての例は、この目的のために用意したGitHubリポジトリでも参照できます。

最初の例は公式ドキュメントからそのまま引用したものです。本記事のすべての例で使うdemo namespaceを作成しておきましょう。

$ echo 'apiVersion: v1
kind: Namespace
metadata:
  labels:
    environment: demo
  name: demo' | k apply -f-
namespace/demo created

$ k config set-context --current --namespace demo

次に、Deploymentのレプリカ数を5以下に制限するポリシーを作成します。

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
 name: "demo-policy.example.com"
spec:
 failurePolicy: Fail
 matchConstraints:
   resourceRules:
     - apiGroups:   ["apps"]
       apiVersions: ["v1"]
       operations:  ["CREATE", "UPDATE"]
       resources:   ["deployments"]
 validations:
   - expression: "object.spec.replicas <= 5"

failurePolicyフィールドには次の値を設定できます。

  • FailValidatingAdmissionPolicyの呼び出し時にエラーが発生した場合、admissionが失敗しAPIリクエストは拒否されます。
  • IgnoreValidatingAdmissionPolicyの呼び出し時にエラーが発生しても無視され、APIリクエストはそのまま処理されます。

matchConstraintsフィールドは受信リクエストのマッチに用いられ、上記の設定ではapps/v1 APIのDeploymentに対するCREATEまたはUPDATEリクエストにのみポリシーが適用されます。

最後に、validationsフィールドにはマッチしたAPIリクエストに対して実行されるCEL式を記述します。リクエストが受け入れられるためには、すべての式がtrueと評価される必要があります。

設定を完成させるため、次のbindingを作成します。

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "demo-binding-test.example.com"
spec:
  policyName: "demo-policy.example.com"
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: demo

先ほど作成したポリシーを、ラベルenvironment=demoが付与された任意のnamespaceにバインドしています。これにより、ポリシー検証を適用する対象を柔軟に指定できます。

namespaceのマッチングには他にもさまざまなオプションがあります。よりきめ細かなマッチングを行うmatchExpressionsのほか、特定リソースを除外するexcludeResourceRules、特定オブジェクトをマッチさせるobjectSelector(開発者がラベルを省略するだけで監査を回避できてしまうため非推奨)といった設定も可能です。さらに踏み込んだオプションは、後続の例で取り上げます。

これらのマニフェストをクラスターに適用すると、ポリシーに違反する新規Deploymentの作成は想定どおりエラーになります。

$ k create deployment nginx — image=nginx — replicas=10
error: failed to create deployment: deployments.apps "nginx" is forbidden: ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

リソースのdiff取得時にも同様に動作します。

$ k create deployment nginx --image=nginx --replicas=10 --dry-run=client -oyaml | k diff -f -
The deployments "nginx" is invalid: : ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

複数の検証式を扱う

リソースに対して複数の検証を行いたい場面もあるでしょう。Deploymentsに対する次のような検証式を考えてみます。

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups:   ["apps"]
        apiVersions: ["v1"]
        operations:  ["CREATE", "UPDATE"]
        resources:   ["deployments"]
  validations:
    # Deployments can't have more than 3 replicas
    - expression: "object.spec.replicas <= 3"
    # Deployment containers must be using images hosted in europe-west1 Artifact Registry in project test-eyal
    - expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('europe-west1-docker.pkg.dev/test-eyal/'))"
    # Deployment cannot use emptyDir volumes
    - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))"

このポリシーにマッチするDeploymentがクラスターに受け入れられるためには、すべての式がtrueと評価される必要があります。

注意したいのは、検証は順次実行される点です。いずれかの式が失敗した時点で、その失敗がただちにクライアントへ返されます。つまり、リソースが複数の検証式に違反している場合は、拒否される→違反を修正する→再度試す、という反復プロセスになります。

検証メッセージをカスタマイズする

検証失敗時にクライアントへ返すメッセージは、任意の内容にカスタマイズできます。必要に応じてmessageExpressionを使えば、文字列補間も可能です。message expressionからはobjectoldObjectrequestparamsにアクセスできます。

先ほどのポリシーを、コメントの代わりにカスタムメッセージを使う形に書き換えてみましょう。

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups:   ["apps"]
        apiVersions: ["v1"]
        operations:  ["CREATE", "UPDATE"]
        resources:   ["deployments"]
  validations:
    - expression: "object.spec.replicas <= 3"
      messageExpression: "'Deployments cannot have more than 3 replicas, this one has ' + string(object.spec.replicas)"
    - expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('europe-west1-docker.pkg.dev/test-eyal/'))"
      message: "Deployment containers must be using images hosted in europe-west1 Artifact Registry in project test-eyal"
    - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))"
      messageExpression: "'Deployment cannot use emptyDir volumes, change the following volume: ' + object.spec.template.spec.volumes.filter(v, has(v.emptyDir)).map(v, v.name)[0]"

注意点として、messageExpressionフィールド内で補間する値はすべてstring型である必要があります。そうでない場合、メッセージの代わりに評価失敗のエラーが返されてしまいます。

カスタムメッセージを設定しない場合、emptyDirボリュームを持つDeploymentは次のように失敗します。

The deployments "nginx" is invalid: : ValidatingAdmissionPolicy ‘demo-policy.example.com’ with binding ‘demo-binding-test.example.com’ denied request: failed expression: !has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))

これではクライアント側から見て内容を理解しづらいかもしれません。カスタムのmessage expressionを使えば、出力は次のようになります。

The deployments "nginx" is invalid: : ValidatingAdmissionPolicy ‘demo-policy.example.com’ with binding ‘demo-binding-test.example.com’ denied request: Deployment cannot use emptyDir volumes, change the following volumes: test

なお、ここではCELの表現力を示すために通常のmessageではなくmessageExpressionを使っています。できるからといって、必ずしもそうすべきとは限らない点にご注意ください。

参考リソース

Kubernetesは進化を続けており、CELサポートも今後のリリースでさらに魅力的な機能をもたらしていくはずです。Validating Admission Policiesをぜひ実際に試し、この強力な機能に慣れ親しんでみてください。クラスター運用者にとって定番のツールになっていくことでしょう。

Validating Admission Policyのfeature gateは現在アルファ版のため、General Availabilityに向けて変更や改善が加わることが予想されます。最新動向を把握するには、Kubernetesのチェンジログとドキュメントの更新を継続的にチェックしてください。

本機能は進化の途上にあるため、本記事の内容も時間とともに更新が必要になる可能性があります。動作しなくなった例や、すでに正確でなくなった記述を見つけられた場合は、ぜひご連絡ください。内容を確認のうえ、適宜更新いたします。