Validating Admission Policy は何ができるのか?

執筆者 : 浜島 大介


はじめに

今年4月に行われた KubeCon + CloudNativeCon Europe 2023 のライトニングトークの1つ 「Tricks for Enforcing Conventions for Your Kubernetes Cluster Using Only YAML」の中で webhook を使わずに Kubernetes の validation (検証) を実現する Validating Admission Policy を紹介していた。
ここで言う "validation" とは Kubernetes へのオブジェクト作成の際に事前定義されたルールに則っているかをチェックするというものである。
(以降 本ブログで用いる "validation" は上記の意味で使用する)

Validating Admission Policy 自体は Kubernetes の リリースノートで目にしたことがある程度で実際に使ったことがなかったので、この機会に触ってみることにした。

※ KubeCon + CloudNativeCon Europe 2023 については、弊社ブログ「KubeCon + CloudNativeCon Europe 2023 レポート」(著:岩本 俊弘) をご覧ください。

Kubernetes の validation の仕組み

Kubernetes には Admission Controller(s) というプラグイン群が用意されており、kube-apiserver が受け付けたオブジェクト作成のリクエストをインターセプトして、リクエスト内容をチェックしたり、リクエスト内容を変更したりすることができる。

その中の ValidatingAdmissionWebhook プラグインは自身ではvalidationのルールを持っておらず、任意の webhook に問い合わせを行う。ユーザ側はカスタムルールを webhook に実装することにより柔軟な validation を実現できる。
同様のコンセプトで MutatingAdmissionWebhook プラグインは webhook に実装されたカスタムルールに基づいてリクエストを変更 (mutate) する。

参考までに 下図が Admission Controller のフローとなる。
(A Guide to Kubernetes Admission Controllers より 転載)

Validating Admission Policy

Validating Admission Policy は Admission Controller プラグインの1つで、v1.26 より alpha 版が提供されている。
Validating Admission Policy は 前項で説明した ValidatingAdmissionWebhook プラグイン と同様、 カスタムルールに基づいた validation を行うものだが、大きな特徴は validation のカスタムルールを webhook ではなく、カスタムリソース(ValidatingAdmissionPolicy)に定義することで kube-apiserver 内部で validation を完結できることである。

なお、validationルールは CEL (Common Expression Language) で定義する。
この CEL という言語についてもどういったルール定義ができるのかを紐解いていく。

webhook が不要になったことによるメリット

validation の前提要素として webhook が不要となったことの主なメリットとしては下記が考えられる。

  • システム構成の単純化/設定の簡易化
    • webhook のための web サービスを別途用意する必要がなくなりシステム構成が単純になる
    • web サービスとの通信を行うための設定や証明書関連の準備も不要となる
  • レイテンシ性能の向上
    • web サービスへの通信が不要となるため、ネットワーク遅延がなくなったぶんのレイテンシ性能が向上する
  • システムの信頼性の向上
    • web サービス使用不可時の考慮が不要となり、システム障害となる要素が減るためシステムの信頼性が向上する

webhook が不要になったことによるデメリット

webhook が不要になったことによる明確なデメリットというのは思い付かなかった・・。
(強いて上げるのであれば kube-apiserver に処理が集中することによるパフォーマンスの懸念が上げられるか?)

CEL (Common Expression Language)

CEL は Google によって開発された軽量で高速な式言語で、いわゆるCや java 等のプログラミング言語とは異なり式評価に特化した言語で、他のプログラミング言語 (アプリケーション) に組み込んで使用することを前提としている。

CEL の特徴

CEL の特徴 (利点) として以下のような点が挙げられる。

  • CEL の評価エンジンは高速/軽量に動作する (具体的なパフォーマンス指標やベンチマークのような情報はオフィシャルドキュメント上に記載はなかったが、一般的には高速で軽量な評価エンジンとして認識されている模様)
  • 式評価に特化しており、安全性が高い
  • 他のプログラミング言語への組み込みが容易であり、拡張性/柔軟性が高い

CEL による validation ルール設定

詳細なルール設定方法については後述するが、ルール設定の基本は以下となる。

  • リソースを構成する任意のフィールド値に対する評価式 (算術比較, 文字列比較, パターンマッチング 等) を記載する

以下の例は .spec.replicas の値を 3以上に強制するルールとなる。

expression: object.spec.replicas > 2

単純な値の比較以外にも以下のようなものがルールの要素として記載できる。

  • 条件分岐
    • 基準となる評価式が true,false の結果に応じてそれぞれ異なる評価式を用いて判定するといった記述が可能
  • フィールドの存在確認
    • リソースに任意のフィールドが指定可能かどうかを判定する
    • 評価対象のフィールドの存在有無を事前に確認し、評価中の不要なエラーを防止するといったことが可能
  • マップ/リスト型のフィールドの全ての設定値に対する評価
    • たとえば Pod リソースに複数のコンテナが定義されている場合、.spec.containers 配下の 全ての images の設定値をチェックするといったことが可能

ルール設定のサンプルが下記URLにまとめられているので、参考情報として記載する。

CEL では設定できない (と思われる) ルール

以下のような制御や機能は CEL にはないと思われる。

  • for, while のようなループ制御
    • CEL のマクロ機能 (後述) により、リスト/マップ型変数をループで回して個々の値を評価するといったロジックは代替可能である
  • echo, print のような出力機能、プリントデバッグ機能
    • 任意のタイミングで文字列を出力することはできないが、ルールに違反した場合に任意の文字列 (メッセージ) を出力することは可能である。また、messageExpression(後述) を用いてメッセージ中に CEL の評価式を埋め込むこともできるため、ある程度のプリントデバッグも代替可能である
  • try-catch のような例外処理機能やエラーハンドリング

上記が必要な場合は ValidatingAdmissionPolicy ではなく ValidatingAdmissionWebhook プラグインを使用して、Go などのプログラミング言語で実装する。

実践してみる (Validation Admission Policy による validationルール設定)

実際に Validating Admission Policy を用いた validationのルール設定を行う。

事前準備

--feature-gates=ValidatingAdmissionPolicy=true
--runtime-config=admissionregistration.k8s.io/v1alpha1=true

ルール設定の基本

ルールの設定とその有効化には ValidatingAdmissionPolicyValidatingAdmissionPolicyBinding の 2つのリソースをセットで作成する必要がある。

ValidatingAdmissionPolicy

validation ルールを設定するリソース。
主な設定要素は以下となる。

  • validation の対象リソース
  • 上記リソースに対する validationルール (許可条件)
  • ルールに違反した場合のメッセージ

ValidatingAdmissionPolicy 定義例

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: sample-rule-01
spec:
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
  - expression: "object.spec.replicas > 2"
    message: "spec.replicas must be greater than 2"

.spec.matchConstraints.resourceRules:validation ルールの対象リソースを設定する。
上記例では deployment リソースの作成/更新時 にルールを適用するように設定している。
本フィールドには pod,deployment 等の builtin リソースだけでなくカスタムリソースを指定することも可能。
.spec.validations.expression:validation ルールを設定する。上記例では.spec.replicas の値が 3以上でない場合は deployment リソースの作成を行うことはできない。
評価式中の object は API リクエストのオブジェクト (上記例の場合は deployment) を表す CEL 変数として扱われる。
.spec.validations.messageexpression の評価式が false の場合の出力メッセージを設定する。本フィールドを定義しない場合はデフォルトフォーマットのメッセージが設定される。

ValidatingAdmissionPolicyBinding

ルール (ValidatingAdmissionPolicy) を有効化するリソース。
本リソースに紐づいていないルールは無効となる。
主な設定要素は以下となる。

  • 有効化するルール名 (ValidatingAdmissionPolicy名)
  • ルールの適用範囲
    • nemespaceSelector , objectSelector 等

ValidatingAdmissionPolicyBinding 定義例

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: sample-rulebind-01
spec:
  policyName: sample-rule-01
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: "default"

.spec.policyName:有効化する ValidatingAdmissionPolicy名を設定する。
.spec.validationActionsDeny を指定した場合はルール違反のリクエストは拒否される動作となる。他には Warn,Audit が指定できる。
.spec.matchResources:ルールの適用範囲を設定する。上記例では namespaceSelector を用いて "default" namespace に限定している。本フィールドを指定しない場合は Kubernetes クラスタ全体に対してルールが適用される。

実際にルール違反 (replicas = 1) となる deployment リソースのマニフェストをデプロイしたところ validation によりデプロイが拒否される動作となった。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: default
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
$ kubectl apply -f deployment.yaml
The deployments "nginx-deployment" is invalid: : ValidatingAdmissionPolicy 'sample-rule-01' with binding 'sample-rulebind-01' denied request: spec.replicas must be greater than 2

以上が Validating Admission Policy による基本的な validation ルールの設定方法となる。
以降ではより実用的な使い方について説明する。

メッセージ出力設定 (messagemessageExpression) について

ValidatingAdmissionPolicyリソースの message にてルール違反時の出力メッセージを定義することができるが messageExpression を用いることにより出力メッセージにCELの評価式を埋め込むことができる。固定文字列と評価式の値は "+" にて連結できる。
注意点としては、messageExpression が扱えるのは"文字列"であるため、CEL評価式の値が数値の場合は string() にて文字列型にキャストする必要がある。

messageExpression: "'spec.replicas must be greater than 2. (Curent setting value is ' + string(object.spec.replicas) + ')'"

出力メッセージは以下のようになる。

The deployments "nginx-deployment" is invalid: : ValidatingAdmissionPolicy 'sample-rule-01' with binding 'sample-rulebind-01' denied request: spec.replicas must be greater than 2. (Curent setting value is 1)

複数のルールを設定する場合

単純に expression を複数エントリした場合は AND 条件として扱われる。
下記例は expression を 2つ並べて記載しているので、replicas の値が 2より大きく、かつ ラベル env に "prod","test" のいずれかの文字列が設定されている場合に許可されるルールとなる。

validations:
- expression: "object.spec.replicas > 2"
- expression: "object.metadata.labels['env'] in ['prod','test']"

AND ではなく OR 条件にする場合は OR 演算子 || を使用する。

validations:
- expression: "object.spec.replicas > 2 || object.metadata.labels['env'] in ['prod','test']"

三項演算子 (条件演算子) を使用することで、if ~ else 文の動作を行うことができる。

  • 条件演算子の基本書式は 評価式1 ? 評価式2 : 評価式3 で記載する
  • 評価式1 が true の場合は 評価式2 による判定が行われる
  • 評価式1 が false の場合は 評価式3 による判定が行われる

ルール定義例

  • replicas の値が 2より大きい場合は ラベル env に "prod" が設定されていれば許可される
  • replicas の値が 2以下の場合は ラベル env に "test" が設定されていれば許可される
validations:
- expression: "object.spec.replicas > 2 ? object.metadata.labels['env'] == 'prod' : object.metadata.labels['env'] == 'test'"

演算子の一覧については、CELのオフィシャルサイト に網羅されている。

マクロを用いたルール定義

CEL には特定の処理を行うためのマクロがいくつか用意されている。その中で validation ルール設定に有用と思ったものをいくつか紹介する。

all

all マクロは リスト型 の全要素に対して、任意の条件を満たしているかを判定する。

  • 基本書式は e.all(x,p) で記載する
  • "e" はリストをあらわす
  • "x" はリストe の各要素をあらわす一時的な変数
  • "p" は x が満たすべき条件式をあらわし、xの各要素が p をすべて満たした場合、allマクロは true となる

説明文だけでは分かりづらいと思うので使用例を記載する。
下記の例は (deploymentリソースの) 全てのコンテナイメージの 取得先 (パス) が "my-registry.io/" から始まる文字列かをチェックするルールとなる。
(startsWith は開始文字列をチェックするための文字列チェック演算子)

validations:
- expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('my-registry.io/'))"

exists/exists_one

all と同種のマクロとして、exists, exists_one がある。
all は リストの全ての要素が条件式を満たすことで true となるが、exists はリスト要素の 1つ以上が条件式を満たせば true となる。
exists_one はリスト要素の 1つだけが条件を満たす場合に true となる。

has

has マクロは フィールドの存在有無を判定する。 下記の例は allhas のマクロを併用して、すべてのコンテナにリソースの使用制限の設定がされているかをチェックするルールとなる。
一見すると c.resources.limits.cpuc.resources.limits.memory の2つだけを チェックすれば良さそうだが、前提となるフィールド resources, resources.limits が定義されていない場合 hasマクロ がエラーとなり、message で定義しているメッセージが出力されない動作となるため、前提となるフィールドもまとめてチェックしたほうが良い。

validations:
- expression: "object.spec.template.spec.containers.all(c, has(c.resources) && has(c.resources.limits) && has(c.resources.limits.cpu) && has(c.resources.limits.memory))"

外部リソースからの値参照

評価式内のリテラル値は外部のリソース定義から取得することができる。
例として namespace "kube-system" の deployment リソース "test-01"の .spec.replicas の値を取得する場合を考える。(test-01が作成済の前提)

ValidatingAdmissionPolicyリソースには以下を設定する。

  • 値を取得する外部リソースの種別を .spec.paramKind に設定する
  • 取得対象のリソースオブジェクトは CEL 変数 params として扱うことができるため 評価式には params.spec.replicas と定義することで参照先リソースの replicas の値が取得できる
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: sample-rule-01
spec:
  paramKind:
    apiVersion: apps/v1
    kind: Deployment
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
  - expression: "object.spec.replicas == params.spec.replicas"

ValidatingAdmissionPolicyBindingリソースには以下を設定する。

  • 実際のリソース名と namespace の情報を .spec.paramRef 配下に設定する
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: sample-rulebind-01
spec:
  policyName: sample-rule-01
  validationActions: [Deny]
  paramRef:
    name: test-01
    namespace: kube-system
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: "default"

"test-01" の deployment の replicas は "3" であるため、replicas が 3以外に設定されている deployment は default namespace に配置できないルールとなる。
(まったく実用性のないルールではあるが)

$ kubectl -n kube-system get deployments.apps test-01
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
test-01   3/3     3            3           5m27s

実際の使い方としては ConfigMap リソース等で設定値を一元管理するなどの用途が考えられる。

総括/感想

  • デメリットらしいデメリットはないので、validation を行う際は Validating Admission Policy の使用をまずは検討するのが良さそう
    • webhook 不要で 実装が手軽に行える
    • ルール定義をマニフェストファイルのみで管理できる
  • mutation (リソース設定の強制変更) のカスタムルール設定は webhook が必要なので、validation と mutation を併用するのであれば ValidatingAdmissionWebhookプラグインを使用して webhook 側でルールを一元管理するという整理もありだと思う
    • 今回の趣旨とは外れるが、mutation はマニフェストと実装がイコールとならないので、個人的には validation のみで運用するほうがシステム管理上は間違いがないと思っている
  • 今後は validation の実装として Validating Admission Policy が採用されることが増えていくと考える。そうなれば設定例やサンプルの yaml なども充実しより使い勝手が良くなるのではと期待している