KubernetesのCELによるValidationを試す
kindでKubernetesクラスタ v1.31.0 (2024年9月時点で最新)を作成
~ ❯ kind version
kind v0.24.0 go1.22.6 darwin/arm64
~ ❯ 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/❯ k get node
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 76s v1.31.0
~ kind-kind/❯ k version
Client Version: v1.31.0
Kustomize Version: v5.4.2
Server Version: v1.31.0
validationに関連するapi-resource
~ kind-kind/❯ k 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になっていることがわかる。
試しにValidatingAdmissionPolicy
とValidatingAdmissionPolicyBinding
を作ってみる。
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"
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ k apply -f replicas-policy.yaml
validatingadmissionpolicy.admissionregistration.k8s.io/replicas-under-5 created
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ k get
validatingadmissionpolicies.admissionregistration.k8s.io
NAME VALIDATIONS PARAMKIND AGE
replicas-under-5 1 <unset> 41s
demo
namespaceでpolicyを適用するようにした。
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
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ k 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/❯ k get validatingadmissionpolicybindings.admissionregistration.k8s.io
NAME POLICYNAME PARAMREF AGE
replicas-under-5-binding replicas-under-5 <unset> 3s
default
namespaceにラベルをつけて、有効/無効なDeployment
リソースをapplyしてみる。
ラベルを付与
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ k label ns default environment=test
namespace/default labeled
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ k get ns default --show-labels
NAME STATUS AGE LABELS
default Active 94m environment=test,kubernetes.io/metadata.name=default
有効なDeployment
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すると成功することを確認。
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k apply -f deployment/valid-deployment.yaml
deployment.apps/valid-deployment created
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k 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
無効なDeployment
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
applyすると失敗することを確認。
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k 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
namespaceに付与したラベルを外すと適用可能なことを確認する。
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k label ns default environm
ent-
namespace/default unlabeled
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k apply -f deployment/invalid-deployment.yaml
deployment.apps/invalid-deployment created
以降も検証を続けるので、再度ラベルを付与しておく
~/workspace/test-kubernetes-validating-admission-policy/validatingadmissionpolicy kind-kind/❯ k label ns default environment=test
namespace/default labeled
ValidatingAdmissionPolicy
の適用状況をモニタリングできるよう、Prometheusとkube-state-metricsをインストールしていく。
Prometheus
~ 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/❯ k 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にアクセスできるようにします。
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/❯ k apply -f monitoring/prometheus-nodeport-svc.yaml
service/prometheus-nodeport created
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k 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が参照できます。
kube-apiserverのログを確認してみる。
デフォルトでログが十分でなかったので、起動引数に -v=5
オプションを変更してみる
I0907 04:28:15.287521 1 queueset.go:417] QS(global-default): Dispatching request &request.RequestInfo{IsResourceRequest:true, Path:"/apis/apps/v1/namespaces/default/deployments/invalid-deployment", Verb:"get", APIPrefix:"apis", APIGroup:"apps", APIVersion:"v1", Namespace:"default", Resource:"deployments", Subresource:"", Name:"invalid-deployment", Parts:[]string{"deployments", "invalid-deployment"}, FieldSelector:"", LabelSelector:""} &user.DefaultInfo{Name:"kubernetes-admin", UID:"", Groups:[]string{"kubeadm:cluster-admins", "system:authenticated"}, Extra:map[string][]string(nil)} from its queue
I0907 04:28:15.287615 1 handler.go:153] kube-aggregator: GET "/apis/apps/v1/namespaces/default/deployments/invalid-deployment" satisfied by nonGoRestful
I0907 04:28:15.287632 1 pathrecorder.go:250] kube-aggregator: "/apis/apps/v1/namespaces/default/deployments/invalid-deployment" satisfied by prefix /apis/apps/v1/
I0907 04:28:15.287649 1 handler.go:143] kube-apiserver: GET "/apis/apps/v1/namespaces/default/deployments/invalid-deployment" satisfied by gorestful with webservice /apis/apps/v1
I0907 04:28:15.288943 1 httplog.go:134] "HTTP" verb="GET" URI="/apis/apps/v1/namespaces/default/deployments/invalid-deployment" latency="1.714279ms" userAgent="kubectl/v1.31.0 (darwin/arm64) kubernetes/9edcffc" audit-ID="3d677934-99da-4858-bc78-778370bb4e53" srcIP="192.168.247.1:42544" apf_pl="global-default" apf_fs="global-default" apf_iseats=1 apf_fseats=0 apf_additionalLatency="0s" apf_execution_time="1.324031ms" resp=404
I0907 04:28:15.290341 1 queueset.go:417] QS(global-default): Dispatching request &request.RequestInfo{IsResourceRequest:true, Path:"/apis/apps/v1/namespaces/default/deployments", Verb:"create", APIPrefix:"apis", APIGroup:"apps", APIVersion:"v1", Namespace:"default", Resource:"deployments", Subresource:"", Name:"", Parts:[]string{"deployments"}, FieldSelector:"", LabelSelector:""} &user.DefaultInfo{Name:"kubernetes-admin", UID:"", Groups:[]string{"kubeadm:cluster-admins", "system:authenticated"}, Extra:map[string][]string(nil)} from its queue
applyしたリソースがどのpolicyに抵触しているのかについてはログで出力されていないようだ。
PrometheusのWebUIでたとえば以下のようなクエリを実行すると、validationのチェックが行われた数の時系列データが見られる
sum by (policy, enforcement_action, namespace) (increase(apiserver_validating_admission_policy_check_total[1m]))
運用目線では、どんなリソースがどれほどvalidationに引っかかっているのか把握をしてアクションを打てるようにしておきたい気持ちはあるが、メトリクスに付与されたラベルからは特定することは難しそうに見えた。
コンテナをProduction Readyに運用するためには、コンテナに対するProbeを設定することが望ましいので、Liveness Probeを例に取り、LivenessProbeが設定されていないコンテナを含むDeploymentを作成しようとした時にValidationにより失敗するルールを作ってみる。
ValidatingAdmissionPolicy をつくる
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"
ここで、2点解説しておく
- variables
- 繰り返しアクセスする、もしくはexpressionの可読性を高めるためにvariablesを定義できる。今回は、containersをvariablesとして定義して、validationで
variables.validation
でアクセスしている。
- 繰り返しアクセスする、もしくはexpressionの可読性を高めるためにvariablesを定義できる。今回は、containersをvariablesとして定義して、validationで
- containers.all
- containersは配列が入るので、allメソッドを呼び出すことでそれぞれの配列要素に対して検証を実行し、全てがpassした場合のみtrueを返すように書くことができる。Podで起動するコンテナの数によらず適用できるようにした。
ValidatingAdmissionPolicyBinding
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: probe-must-be-set-binding
spec:
policyName: probe-must-be-set
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: test
検証
以下のDeployment
を作成する
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
~/workspace/test-kubernetes-validating-admission-policy kind-kind/❯ k 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つコンテナを起動するPodを起動するような設定内容になっていますが、2つ目のsidecar
コンテナに livenessProbe が設定されていないため、applyが弾かれるようになっています。
2つ目のコンテナに livenessProbeを追加すると、applyが通るようになります。
次はprevilegedコンテナの起動を防ぐようにしてみます。
ValidatingAdmissionPolicy をつくる
ValidatingAdmissionPolicy
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: previleged
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.?allowPrivilegeEscalation != optional.of(true))"
message: "Containers must not allow privilege escalation"
今回は、optionalなプロパティの値のvalidationを行った。
optional.of(bool)を使うと、実現可能なのでそう構成した。
ValidatingAdmissionPolicyBinding
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: previleged-binding
spec:
policyName: previleged
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: test
検証
以下のDeployment
を作成する
apiVersion: apps/v1
kind: Deployment
metadata:
name: previleged-deployment
namespace: default
spec:
replicas: 5
selector:
matchLabels:
app: previleged-deployment
template:
metadata:
labels:
app: previleged-deployment
spec:
containers:
- name: app
image: nginx:latest
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
securityContext:
allowPrivilegeEscalation: true
- 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/previleged-deployment.yaml
The deployments "previleged-deployment" is invalid: : ValidatingAdmissionPolicy 'previleged' with binding 'previleged-binding' denied request: Containers must not allow privilege escalation
app
コンテナに、allowPrivilegeEscalation
が有効化されているので、リクエストが拒否されていることを確認できました。
一通り試して、シュッとKubernetes Officialの機能のみを利用してリクエストのValidationが行えることを確認した。
運用面で気になったこと
- メトリクスから原因になっているリソースを追うことは現状難しそう
- 軽く調べた限り、Kubernetesのマニフェストベースでテストをするフレームワーク的なものは存在していないように見えた。CEL Playbookは使ってテストして、それをマニフェストに記載するのが現時点ではお手軽かなと思いましたが、やはりCIには組み込みたいなと感じた
| メトリクスから原因になっているリソースを追うことは現状難しそう
これは validationActions
にAudit
が存在しているので、kube-apiserverのauditを有効にするといい感じにログベースでモニタリングはできるかもしれないと感じた。
また検証してみたいトピックなので、取り組んだ際は何かしらの形で公開したいなと思います。