🛡️

Common Expression Language(CEL)でKubernetesのマニフェストのValidationを行う

2024/09/09に公開

こんにちは。@yashirookです。

2024年の4月にリリースされたKubernetes v1.30 で、Common Expression LanguageによるAdmission Control がGAになりました
業務で行なっているKubernetesのクラスタ管理において魅力的な機能だったので、簡単に試してみた結果を記事にすることにしました。

KubernetesのAPIリソース作成時のValidationについて

従来は、KubernetesのAPIリソースに対する変更のValidationは、ValidatingAdmissionWebhookを使って実現されていました。

ValidatingAdmissionWebhookを利用する場合、Validationの処理を行うために自身で開発したアプリケーションや、Open Policy Agent(OPA)などを準備し、Webhookで送信されてきたHTTPリクエストからapplyされたObjectを取り出し、適切にレスポンスを返却する必要がありました。

ValidatingAdmissionPolicyが登場したことにより、上記のような追加のコンポーネントを用意することなく、Common Expression Language(CEL)を使ったValidationを行うことができるようになりました。

ValidatingAdmissionPolicyはKubernetes v1.26.0で alpha の機能として初めてリリースされました。以降、4回のリリースサイクルを経てGAとなり、比較的早期に昇格した機能だと感じています。

私個人としては、Kubernetes Projectの公式リソースにを使ってValidationを実現可能になったことで、以下の利点があると考えています。

  • 信頼性
    • Kubernetesが公式にサポートする機能であり、かつGAされたことで、本番導入時の保守・運用性の向上が見込めます
    • Validationを行う際に他のコンポーネントが不要になり、通信や依存コンポーネントの障害による影響を受けにくい状態になりました
  • 実装コスト
    • Webhookを受け取りレスポンスを返却するために必要な要件を把握し、実装することはそれなりにボリュームがあり、クイックに機能を利用できることは魅力的だと思います
  • 学習コスト
    • 後に具体例を記載しますが、CELは比較的シンプルな文法で作成しようとするAPIのリソースのの内容を検証することができ、たとえばOpen Policy Agentで利用するRegoと比較すると学習コストは低いと感じます

それでは、実際に動作を検証する準備をしていきます。

環境構築

以下の検証環境で実施しました。

  • OS: MacOS
  • Docker Engine: 26.1.4
  • Kubernetes: kind v0.24.0 go1.22.6 darwin/arm64

kind の構築

--imageオプションを利用し、Kubernetesのバージョンを指定してクラスタを作成します。

~ ❯ kind create cluster --image=kindest/node:v1.31.0
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.31.0) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a nice day! 👋

~ kind-kind/❯ kubectl get node
NAME                 STATUS   ROLES           AGE   VERSION
kind-control-plane   Ready    control-plane   76s   v1.31.0

~ kind-kind/❯ kubectl version
Client Version: v1.31.0
Kustomize Version: v5.4.2
Server Version: v1.31.0

APIリソースの確認

~ kind-kind/❯ kubectl api-resources | grep validating
validatingadmissionpolicies                      admissionregistration.k8s.io/v1   false        ValidatingAdmissionPolicy
validatingadmissionpolicybindings                admissionregistration.k8s.io/v1   false        ValidatingAdmissionPolicyBinding
validatingwebhookconfigurations                  admissionregistration.k8s.io/v1   false        ValidatingWebhookConfiguration

validatingadmissionpolicies, validatingadmissionpolicybindingsのAPIがそれぞれ存在し、admissionregistration.k8s.ioグループにおいてv1になっていることが確認できました。

従来のvalidatingwebhookconfigurationsも残っています。

Deploymentのレプリカ数を制限する

まず、簡単な例として Deploymentのレプリカ数を5以下に制限するValidatingAdmissionPolicyを適用していきます。また以降、簡単のためにValidatingAdmissionPolicyによる制限のルールをポリシーと記載します。

APIリソースを確認したときに表示されていた、ValidatingAdmissionPolicy, ValidatingAdmissionPolicyBindingを利用してポリシーを適用していきます。

ValidatingAdmissionPolicyの作成

ValidatingAdmissionPolicyは、CELによる評価式(spec.validation)や、評価を適用するAPIリソース(spec.matchConstraints)などを定義するクラスタスコープのリソースです。

ValidatingAdmissionPolicyリソースのマニフェストの用意

validatingadmissionpolicy/replicas-under-5-policy.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: replicas-under-5
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        resources: ["deployments"]
        operations: ["CREATE", "UPDATE"]
  validations:
    - expression: "object.spec.replicas <= 5"
      message: "Replicas must be less than or equal to 5"

spec.validations[0].expressionで記載されているのがValidationを行うための評価式です。objectがYAMLファイルで定義したAPIリソースの内容に相当し、.つなぎで必要な要素を取り出して評価を行うことができます。

今回は特定の要素(int32)の値を比較する単純なルールですが、CELの組み込み関数やKubernetesのCELライブラリが使えるようになっており、多少複雑な評価式であっても記述できるようになっています。

また、spec.failurePolicyはドキュメントにあまり記載されておらず、意味を掴みにくかったので、admissionregistration.k8s.ioのAPI定義を確認したところ、評価が何らかの原因で失敗した場合にどのように扱うかを定義するもののようで、デフォルトはFailになっていました。

ValidatingAdmissionPolicyリソースの作成

先ほど作成したリソースをクラスタに適用します。

~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ kubectl apply -f replicas-policy.yaml
validatingadmissionpolicy.admissionregistration.k8s.io/replicas-under-5 created
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ kubectl get
 validatingadmissionpolicies.admissionregistration.k8s.io 
NAME               VALIDATIONS   PARAMKIND   AGE
replicas-under-5   1             <unset>     41s

ValidatingAdmissionPolicyBindingの作成

ValidatingAdmissionPolicyBindingは、ValidatingAdmissionPolicyで定義したポリシーを適用するリソースを定義するためのクラスタスコープのリソースです。

先ほど、Deploymentのレプリカ数に対するポリシーを作成しましたが、このリソースが作成されただけでは、実際のクラスタでValidationが行われることはありません。

今回は、environment: testのラベルが付いたnamespaceに対してvalidationを行うような設定を定義したいと思います。

ValidatingAdmissionPolicyBindingリソースのマニフェストの作成

validatingadmissionpolicy/replicas-under-5-binding.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: replicas-under-5-binding
spec:
  policyName: replicas-under-5
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: test

簡単にそれぞれ説明します。

  • policyName
    • 適用するポリシールール
    • 先ほど作成したValidatingAdmissionPolicyのリソース名を指定
  • validationActions
    • 失敗時のアクション
    • Deny, Warn, Auditが選択可能で、今回は実際リクエストが拒否されることを確認するためDenyに設定
  • matchResources
    • ポリシーを適用するリソースの指定

ValidatingAdmissionPolicyリソースの作成

先ほど作成したリソースをクラスタに適用します。

~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ kubectl apply -f replicas-under-5-binding.yaml                               
validatingadmissionpolicybinding.admissionregistration.k8s.io/replicas-under-5-binding created

~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ kubectl get validatingadmissionpolicybindings.admissionregistration.k8s.io   
NAME                       POLICYNAME         PARAMREF   AGE
replicas-under-5-binding   replicas-under-5   <unset>    3s

ポリシーが適用されることの検証

今回は、default namespaceでDeploymentを作成し、検証を実施しようと思います。

事前準備

ルールが適用されるようにするために、まずラベルの付与を行なっておきます。

~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ kubectl label ns default environment=test
namespace/default labeled

~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ kubectl get ns default --show-labels 
NAME      STATUS   AGE   LABELS
default   Active   94m   environment=test,kubernetes.io/metadata.name=default

無効なリクエストをapplyしたときの検証

レプリカ数が10のDeploymentの作成を試みます。今回はレプリカ数が5を超えるDeploymentの作成や更新は拒否している想定なので、このリソースの作成は失敗することが期待されます。

invalid-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: invalid-deployment
  namespace: default
spec:
  replicas: 10
  selector:
    matchLabels:
      app: invalid-deployment
  template:
    metadata:
      labels:
        app: invalid-deployment
    spec:
      containers:
      - name: invalid-deployment
        image: nginx:latest
        ports:
        - containerPort: 80

このDeploymentリソースを作成しようとすると、期待通りエラーメッセージが出力されました。

~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl apply -f deployment/invalid-deployment.yaml
The deployments "invalid-deployment" is invalid: : ValidatingAdmissionPolicy 'replicas-under-5' with binding 'replicas-under-5-binding' denied request: Replicas must be less than or equal to 5

また、ValidatingAdmissionPolicyspec.validations[0].messageで定義した以下のエラーメッセージが含まれていることも確認できました。

Replicas must be less than or equal to 5

有効なリクエストをapplyしたときの検証

念の為、レプリカ数が5のDeploymentの作成を試みます。今回はレプリカ数が5を超えるDeploymentの作成や更新を拒否している想定なので、このリソースの作成は成功することが期待されます。

valid-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: valid-deployment
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: valid-deployment
  template:
    metadata:
      labels:
        app: valid-deployment
    spec:
      containers:
      - name: valid-deployment
        image: nginx:latest
        ports:
        - containerPort: 80

applyすると成功し、Podが起動していることを確認できました。

~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl apply -f deployment/valid-deployment.yaml
deployment.apps/valid-deployment created

~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl get po
NAME                               READY   STATUS    RESTARTS   AGE
valid-deployment-57d6875f7-2l6tq   1/1     Running   0          11s
valid-deployment-57d6875f7-8fzmq   1/1     Running   0          11s
valid-deployment-57d6875f7-dz6tw   1/1     Running   0          11s
valid-deployment-57d6875f7-nhplm   1/1     Running   0          11s
valid-deployment-57d6875f7-t7nxp   1/1     Running   0          11s

上記の検証から、定義したポリシーが期待通り動作し、Validationが実現できたことが確認できました。

次節以降では、他にもよくありそうなユースケースとして、livenessProbeなどのヘルスチェックが作成されていること、privilegedオプションがされていないことを担保するポリシーをそれぞれ定義してみようと思います。

livenessProbeが設定されていないコンテナを制限する

livenessProbeを設定し、何かしらの形でコンテナプロセスに対するヘルスチェックが行われていることを担保したいというユースケースを想定して、ポリシーを実装していこうと思います。

リソースの作成

ValidatingAdmissionPolicy

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: probe-must-be-set
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        resources: ["deployments"]
        operations: ["CREATE", "UPDATE"]
  variables:
    - name: containers
      expression: "object.spec.template.spec.containers"
  validations:
    - expression: "variables.containers.all(c, has(c.livenessProbe))"
      message: "All containers must have a liveness probe"

ここで、**spec.variablesと、spec.validations[0].expressionで現れるcontainers.all**内容について、それぞれ簡単に解説しておきます。

variables
expressionの可読性を高めるためにvariablesを定義することができます。今回は、containersをvariablesとして定義して、spec.validations[0].expressionにおいてvariables.contaienrsでアクセスしています。

階層が深くなるリソースなどでは、関心領域を絞るために有効活用できるように思います。

containers.all
containersにはPodで作成しようとするそれぞれのコンテナの定義内容を含むobjectが含まれた配列になっており、配列に対してはall関数でそれぞれの要素に対する検証を行うことができます。

variables.containers.all(c, has(c.livenessProbe))におけるcは配列中の個々のオブジェクトに対応します。この動きは、JavaScriptなどで配列に対して利用するmap関数のようなものと思っていただいて差し支えないと思います。

全てのcに対して、第2引数で設定した関数の真偽値がtrueになった場合のみ、validationが通過することになります。

これを利用することで、Podで起動するコンテナの数によらず、可読性を保ちつつシンプルな方法でValidationが実現することができます。

ValidatingAdmissionPolicyBinding

以下のマニフェストを使用します。spec.policyNameを変更しています。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: privileged-binding
spec:
  policyName: privileged
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: test

ポリシーが適用されていることの検証

以下のような、2つのコンテナを含むPodを起動するDeploymentの起動を試みます。app, sidecarコンテナを起動しますが、前者はlivenessProbeが設定されており、後者はされておりません。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: no-probe-deployment
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: no-probe-deployment
  template:
    metadata:
      labels:
        app: no-probe-deployment
    spec:
      containers:
      - name: app
        image: nginx:latest
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /
            port: 80
      - name: sidecar
        image: nginx:latest
        ports:
        - containerPort: 8000

Deploymentの作成を試みます。livenessProbeが設定されていないコンテナを含むため、拒否されることが期待されます。

~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl apply -f deployment/no-probe-deployment.yaml             
The deployments "no-probe-deployment" is invalid: : ValidatingAdmissionPolicy 'probe-must-be-set' with binding 'probe-must-be-set-binding' denied request: All containers must have a liveness probe

想定通り、リソースの作成が拒否されることが確認できました。もちろん、2つ目のコンテナに livenessProbeを追加するとリソースは問題なく作成できるようになります。

やや冗長になるため、トグル以下に記載しているので、興味のある方は見てみてください。

両方のコンテナprobeを設定した場合

以下のように、Probeを追加したマニフェストを用意します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: probe-deployment
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: probe-deployment
  template:
    metadata:
      labels:
        app: probe-deployment
    spec:
      containers:
      - name: app
        image: nginx:latest
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /
            port: 80
      - name: sidecar
        image: nginx:latest
        ports:
        - containerPort: 8000
        livenessProbe:
          httpGet:
            path: /
            port: 8000

上記の内容を適用すると、正常にリソースが作成されます。

~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k apply -f deployment/probe-deployment.yaml 
deployment.apps/probe-deployment created

privilegedオプションが付与されているコンテナを制限する

不適切にprivilegedオプションが設定され、セキュリティ上の大きなリスクとなるワークロードが作成されないことを担保したいというユースケースを想定して、ポリシーを実装していこうと思います。

リソースの作成

ValidatingAdmissionPolicy

以下のようにポリシーを定義しました。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: privileged
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        resources: ["deployments"]
        operations: ["CREATE", "UPDATE"]
  variables:
    - name: containers
      expression: object.spec.template.spec.containers
    - name: securityContexts
      expression: 'variables.containers.map(c, c.?securityContext)'
  validations:
    - expression: "variables.securityContexts.all(sc, sc.?privileged != optional.of(true))"
      message: "Containers must not allow privileged mode"

今回は、securityContextという存在しない可能性のある(Optionalな)要素にアクセスしているため、少し工夫が必要です。

まず、map(c, c.?securityContext)all(sc, sc.?privileged != optional.of(true))などmap関数が使われていますが、配列要素のオブジェクトがもつフィールドにアクセスするときに?が指定されています。このような表記をすることで、Optionalなフィールドをの値が評価できます。

そして、privilegedオプションの値をoptional.of(true)と比較しています。Optionalな値を比較するためには、単純にtrueと比較するのでなく、このような形にする必要があるとのことでした。

詳しくは、CEL - Optional ValuesのProposalに記載されています。

ValidatingAdmissionPolicyBinding

以下のマニフェストを使用します。spec.policyNameを変更しています。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: privileged-binding
spec:
  policyName: privileged
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: test

ポリシーが適用されていることの検証

以下のDeploymentを作成する

apiVersion: apps/v1
kind: Deployment
metadata:
  name: privileged-deployment
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: privileged-deployment
  template:
    metadata:
      labels:
        app: privileged-deployment
    spec:
      containers:
      - name: app
        image: nginx:latest
        ports:
        - containerPort: 80
        securityContext:
          privileged: true
      - name: sidecar
        image: nginx:latest
        ports:
        - containerPort: 8000
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl apply -f deployment/privileged-deployment.yaml                                               
The deployments "privileged-deployment" is invalid: : ValidatingAdmissionPolicy 'privileged' with binding 'privileged-binding' denied request: Containers must not allow privileged mode

appコンテナに、privilegedオプションが有効化されているので、リソースの作成が失敗することを確認できました。

簡単なValidationであれば実現できるイメージが湧いてきましたね。次は、運用面を想定し、取得できるメトリクスを確認してみようと思います。

ValidatingAdmissionPolicyに違反した時のアクション

前節までの検証結果で、簡単なValidationについては問題なく実装できることがわかりました。
適切なポリシーを組み込んでいくことで、ワークロードやクラスタの信頼性は高められるでしょう。

個人的には、Validationのルールを新たに導入する際には、鶴の一声的にいきなりさまざまなポリシーを有効化し、既存のワークフローに対して突然大きな変更をかけるよりは、段階的にチームが自己組織的に変更の必要性に気づき、チームの意思で対応が進んでいくような姿を好みます。

そんな個人的な嗜好性の上では、いきなりリクエストを拒否するのでなく、段階的にリリースするために取りうる手段が気になるところなので、以下に関連して調べてみたことを記載していきます。

validationActions

Validationに違反した際の挙動は、ValidatingAdmissionPolicyBindingspec.validationActionsで指定することができます。

これまでの検証ではすべて[Deny]に設定しており、実際にポリシーに違反するリソースの作成は弾かれ失敗していました。

spec.validationActionsには、以下の値が設定できます。(詳しくは、公式ドキュメントを参照)

validationActions 動作
Deny リクエストを拒否し、クライアントにエラーを返す
Warn リクエストを受け付け、クライアントにWarningを表示する
Audit Validationの結果をAuditとして記録する。リクエストの受け付けには関与しない。

Denyの他にも、WarnAuditで運用をスタートすることも可能なようでした。

Warnは第一歩としては良いように思うのですが、多くの場合リリースはCI/CDのワークフローの中で実行されるので、Warnのメッセージに気づけるかどうかはCI/CDの実装とメッセージに対する意識に依存する問題は残るように思います。利用しているCI/CDの使用を踏まえ、運用設計が必要だと感じました。

次に、Auditについては、単純に有効にしただけでは、kube-apiserverのログの出力内容や、eventに変化は見られませんでした。

おそらく、kube-apiserverのauditを有効にしている場合にログに出力されるのではないかと思うのですが、今回の記事のスコープからは省略させていただきます。興味はあるので、どこかで追記もしくは別記事を行いたいと思います。

今回調べた範囲では、Validationを有効化していくにあたり、対象になるNamespaceやワークロードのスコープを小さくしたり、WarnモードやAuditモードを活用することが段階的に適用するための手段になりうるように感じました。

ValidatingAdmissionPolicyに関連するメトリクス

次に、よりマクロな観点で運用状況を把握するため、ValidatingAdmissionPolicyに関連して使用できるメトリクスを調べてみます。

公式ドキュメントによると、Kubernetes v1.30.0時点では以下のメトリクスが利用可能で、いずれもαです。(v1.31でのメトリクスのドキュメントは2024年9月9日現在ではまだ用意されていないようでしたが、新しいメトリクスが追加されていたり、早いペースで変更が加えられているように思います)

metrics 説明
apiserver_validating_admission_policy_check_duration_seconds ポリシーの検証に要した時間
apiserver_validating_admission_policy_check_total ポリシーの検証が行われた回数
apiserver_validating_admission_policy_definition_total 作成されているポリシーの数

実際に、クラスタにPrometheusとkube-state-metricsをインストールしてブラウザからメトリクスを参照してみましょう。今回のインストール方法は、以下の折りたたみの中に記載しました。

Prometheusとkube-state-metricsのインストール

Prometheus

helmを使ってインストールします。

~ kind-kind/❯ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
"prometheus-community" has been added to your repositories

~ kind-kind/❯ helm repo update

Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "ub-helm-repo" chart repository
...Successfully got an update from the "prometheus-community" chart repository
Update Complete. ⎈Happy Helming!⎈

~ kind-kind/❯ helm install prometheus prometheus-community/kube-prometheus-stack --namespace monitoring --create-namespace
NAME: prometheus
LAST DEPLOYED: Sat Sep  7 09:20:31 2024
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
kube-prometheus-stack has been installed. Check its status by running:
  kubectl --namespace monitoring get pods -l "release=prometheus"

Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus 
instances using the Operator.

~ kind-kind/❯ kubectl get po -n monitoring
NAME                                                     READY   STATUS    RESTARTS   AGE
alertmanager-prometheus-kube-prometheus-alertmanager-0   2/2     Running   0          62s
prometheus-grafana-7f7b8c64d-c2qgq                       3/3     Running   0          78s
prometheus-kube-prometheus-operator-648f8c94ff-k92zs     1/1     Running   0          78s
prometheus-kube-state-metrics-5694684fbc-z76jg           1/1     Running   0          78s
prometheus-prometheus-kube-prometheus-prometheus-0       2/2     Running   0          62s
prometheus-prometheus-node-exporter-hclrs                1/1     Running   0          78s

Prometheusとともに、kube-state-metricsもデプロイできていることを確認できました。

NodePort経由でServiceにアクセスできるようにします。

prometheus-nodeport-svc
apiVersion: v1
kind: Service
metadata:
  name: prometheus-nodeport
  namespace: monitoring
spec:
  type: NodePort
  ports:
  - port: 9090
    targetPort: 9090
    nodePort: 30090
  selector:
    app.kubernetes.io/name: prometheus
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl apply -f monitoring/prometheus-nodeport-svc.yaml
service/prometheus-nodeport created

~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ kubectl get svc -n monitoring | grep NodePort
prometheus-nodeport                       NodePort    10.96.194.39    <none>        9090:30090/TCP               2m

~ kind-kind/❯ kubectl get nodes -o wide
NAME                 STATUS   ROLES           AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION                        CONTAINER-RUNTIME
kind-control-plane   Ready    control-plane   11h   v1.31.0   192.168.247.2   <none>        Debian GNU/Linux 12 (bookworm)   6.10.6-orbstack-00249-g92ad2848917c   containerd://1.7.18

http://<INTERNAL-IP>:30090/ にブラウザからアクセスすると、PrometheusのUIが参照できます。
PrometheusのWeb UI

ポリシーの違反が見られた回数を可視化する

PrometheusのWebUIでたとえば以下のようなクエリを実行すると、validationのチェックが行われた数の時系列データを確認することができます。

作成したポリシーや、enforcement_actionのlabelがついており、それらで集計することができました。

sum by (policy, enforcement_action, namespace) (increase(apiserver_validating_admission_policy_check_total[1m]))

apiserver_validating_admission_policy_check_totalの時系列データ

ドキュメントを読むと評価(check)が行われた回数が表示されるように見えるのですが、時系列データの挙動としてはvalidationが失敗した場合に限りカウンタがインクリメントされるような挙動が観測されました。

クエリの問題なのか、まだalpha版なのでkube-state-metricsの利用バージョンとKubernetes Metricsのドキュメントの間に差異があるのかもしれません(取れているLabelの値)。

より欲しい時系列データとしてはポリシーに抵触したリクエストが発生している回数になるので、欲しいものが見られているようには思えるのですが、やはりドキュメントの記載内容とギャップがあるのは気持ち悪さがあります。検証方法や環境起因による勘違いの可能性もあるので、有識者がいらっしゃいましたらご指摘いただけますと幸いです。

いずれにせよ、運用目線ではどんなリソースがどれほどvalidationに引っかかっているのか把握をしてアクションを打てるようにしておきたいですが、現状では実際に弾かれているリソースを直接特定できるような情報はLabelとして含まれていないようでした。

ただ、メトリクスもv1.30->v1.31の間で色々変更は入っていそうなので、この辺りの動向は注視して、アップデートがあれば加筆修正をしたいと思います。

まとめ

CELを使って、Kubernetesリソース作成・変更時のValidationを行う検証をしました。これまでのWebhookと比較して、かなり簡便かつシンプルにValidationを実現できていることを実感できたので、個人的にはCELで補える範囲のValidationではどんどんこちらを使っていきたいと思いました。

CELの機能や有効になっているライブラリの全体感はまだ把握できていないのですが、かなり多くのことはCELで実現できる印象を持っており、採用される事例も増えていくだろうなという印象を持ちました。

ただし、運用の面でより使いやすくなるための課題も見えました。

  • ポリシーに抵触しているリクエストを特定し、クラスタ管理者側で対策できるようにする仕組みを用意する必要があるように感じました
    • この方法として、今のところkube-apiserverのAuditログが目的にマッチしているのではないかという仮説をもっているので、これは近いうちに検証をしたいと思いました。
  • CELのテストはCEL Playgroundなどを利用してできますが、実際に適用するマニフェストベースでE2E的にテストを実行できるツールなどが出てくると良いなと感じました。

ちなみに、MutatingAdmissionPolicyKEPでリクエストが上がってきており、1.32にalphaリリースがマイルストーンとして設定されているようです。

そのうち、Mutating(リクエストの書き換え)はValidationよりいくらか複雑に思いますが、こちらもCELを用いて実現できるようになるのかもしれません。

こちらの動向も楽しみです。

Discussion