TOC
前言
在 K8S 1.26 版本以前,达到 K8S 准入校验策略效果的方式有两种:
- 自己实现 K8S webook
- 直接使用 CNCF 项目中以 K8S 策略展开的项目,例如
OPA
、kyverno
.
这些都是 K8S 非默认的内容,K8S 只负责将流量转发到对应的 webhook,具体是怎样一个逻辑判断是在外部处理的.而现在 K8S 支持了一种内置的准入校验方式,使用 CEL 表达式来完成 webhook 的校验逻辑,在一定程度上替代了外部 webhook.
一些常见的比较简单的校验内容可以直接使用 CEL 表达式来完成,比如比如校验当前 Deployment 副本数,校验当前资源使用的镜像是否是内网地址的镜像等等,如果校验失败则拦截操作并且返回错误提示内容.
资源文件
本文使用到的资源文件在部署到 K8S 集群中时均已在线文件方式使用,例如 kubectl apply -f https://xxxxx
,但同时都会将内容贴出来,方便对文件内容的直接查看效果.
准备环境
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.17.0/kind-linux-amd64
wget https://storage.googleapis.com/kubernetes-release/release/v1.26.0/bin/linux/amd64/kubectl
mv kind-linux-amd64 kind && chmod +x kind && mv kind /usr/local/bin
chmod +x kubectl && mv kubectl /usr/local/bin
cp /usr/local/bin/kubectl /usr/local/bin/k
注意这个版本的 kind 创建集群时需要指定 1.26 的 image,否则默认会使用 1.25.3 版本.
另一个注意点是需要开启 ValidatingAdmissionPolicy 这个功能以及开启 admissionregistration.k8s.io/v1alpha1 API.
kind 配置文件 kind.yaml
如下:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
"ValidatingAdmissionPolicy": true
runtimeConfig:
"api/alpha": "true"
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
endpoint = ["https://gcr.lank8s.cn"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
endpoint = ["https://k8s.lank8s.cn"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint = ["https://registry.lank8s.cn"]
通过 Kind 创建一个集群.
kind create cluster --config kind.yaml --image kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352
kubectl label namespace default environment=test
研究环境已经准备好了,接下来开始做示例演示.
示例
例如希望限制一个 Deployment 的最大副本数,如果是自己开发 webhook,那么你至少需要编写代码,测试代码,部署 webhook.而对于使用OPA
或kyverno
,你至少需要了解对应的语法使用.
现在呢,你只需要添加一个带有 CEL 表达式的 K8S 配置就能够做到这样的效果.
vap.yaml
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.lank8s.local"
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
这个策略配置看着也非常的直观,匹配 apps/v1 版本的 Deployment 资源的 create 和 update 操作,并且只有当副本数<=5
的情况下才能操作成功.
上述是策略的配置,还需要一个ValidatingAdmissionPolicyBinding
资源表示上述策略的校验范围.
vapb.yaml
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-test.lank8s.local"
spec:
policyName: "demo-policy.lank8s.local"
matchResources:
namespaceSelector:
matchExpressions:
- key: environment
operator: In
values: ["test"]
应用上述资源:
kubectl apply -f https://liangyuanpeng.com/files/k8s-admissionregistration-with-cel/vap.yaml
kubectl apply -f https://liangyuanpeng.com/files/k8s-admissionregistration-with-cel/vapb.yaml
上述示例配置表示,将校验策略应用到含有environment=test
标签的 namespace 下,也就是在这些匹配的 namespace 下的所有 apps/v1 版本的 deployment 资源都会被校验副本数是否<=5
.
创建好策略后接下来部署一个 Deployment 试试.
envoy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: envoy
labels:
app: envoy
spec:
selector:
matchLabels:
app: envoy
replicas: 6
template:
metadata:
labels:
app: envoy
spec:
containers:
- name: envoy
image: envoyproxy/envoy:v1.25.1
imagePullPolicy: IfNotPresent
ports:
- name: envoy
protocol: TCP
containerPort: 10000
- name: admin
containerPort: 9901
command:
- /bin/sh
- -c
- envoy -c /etc/envoy/envoy.yaml
部署 deployment 资源:
kubectl apply -f https://liangyuanpeng.com/files/k8s-admissionregistration-with-cel/envoy.yaml
应用上述配置后会 apply 失败:
The deployments "envoy" is invalid: : ValidatingAdmissionPolicy 'demo-policy.lank8s.local' with binding 'demo-binding-test.lank8s.local' denied request: failed expression: object.spec.replicas <= 5
可以从提示中看到,apply envoy 这个 Deployment 的时候失败了,导致失败的策略是 object.spec.replicas <= 5
我们还可以做到更强大的效果,将 CEL 表达式中的校验条件的校验值作为参数传递进来,比如将另一个CRD的资源字段作为最大副本数的值.
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.lank8s.local"
spec:
paramKind:
apiVersion: rules.lank8s.local/v1 # 需要有一个这样的CRD资源
kind: ReplicaLimit
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= params.maxReplicas"
从上述配置中可以看到,最大副本数从5
变成了CRD资源的maxReplicas
字段,从而让这个策略更加的灵活.
然后我们再定义一个策略绑定,并且应用到一个不同的条件(也就是说,一个策略能够被多个策略绑定所应用):
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-production.lank8s.local"
spec:
policy: "demo-policy.lank8s.local"
paramsRef:
name: "demo-params-production.lank8s.local"
matchResources:
namespaceSelector:
- key: environment,
operator: In,
values: ["production"]
---
apiVersion: rules.lank8s.local/v1
kind: ReplicaLimit
metadata:
name: "demo-params-production.lank8s.local"
maxReplicas: 100
上述配置我们为environment=production
的 namespace 应用了 Deployment 资源最大副本数为100的策略限制,然后我们还可以再写一份配置,将最大副本数应用到另一个环境,例如:
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-dev.lank8s.local"
spec:
policy: "demo-policy.lank8s.local"
paramsRef:
name: "demo-params-dev.lank8s.local"
matchResources:
namespaceSelector:
- key: environment,
operator: In,
values: ["dev"]
---
apiVersion: rules.lank8s.local/v1
kind: ReplicaLimit
metadata:
name: "demo-params-dev.lank8s.local"
maxReplicas: 2
为environment=dev
的 namespace 的资源限制了 Deployment 资源最大副本数为2.
上述示例需要当前 K8S 集群中有 ReplicaLimit 这个资源,因此这个示例只是作为效果理解,没有提供相对应的资源文件来演示效果,如果你有的兴趣的话可以自己创建一个 ReplicaLimit 资源来做研究.
下面实践一下这个参数化策略的效果,将 ReplicaLimit 资源改为使用当前 K8S 集群中已有的 Deployment 资源.
重新为 default namespace 赋予标签
kubectl label namespace default environment=production --overwrite
vap-crd.yaml
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: lank8s.local
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"
vapb-crd.yaml
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: lank8s.local
spec:
policyName: lank8s.local
paramRef:
name: "coredns"
namespace: kube-system
matchResources:
namespaceSelector:
matchExpressions:
- key: environment
operator: In
values: ["production"]
可以看到上述配置中直接使用 kube-system 这个 namespace 下的 Coredns 这个 Deployment 资源的副本数作为校验条件的值.
如果应用部署在带有标签environment=production
的 namespace,那么 Deployment 资源的副本数必须不能超过 kube-system namespace 下 coredns 这个 Deployment 的副本数.
上述资源应用:
kubectl apply -f https://liangyuanpeng.com/files/k8s-admissionregistration-with-cel/vap-crd.yaml
kubectl apply -f https://liangyuanpeng.com/files/k8s-admissionregistration-with-cel/vapb-crd.yaml
kubectl apply -f https://liangyuanpeng.com/files/k8s-admissionregistration-with-cel/envoy.yaml
报错内容:
The deployments "envoy" is invalid: : ValidatingAdmissionPolicy 'lank8s.local' with binding 'lank8s.local' denied request: failed expression: object.spec.replicas <= params.spec.replicas
这里的提示如果将 params.spec.replicas
的值也显示出来就更友好了.也许后续的 K8S 版本当中会支持.
总结
K8S 有了内置的 CEL 策略机制后对于一些简单的需求已经能够达到满足,可以看到使用起来也是非常方便的.
本文只演示了策略拦截成功的效果,对于进一步验证效果你可以修改一下 envoy deployment 资源的副本数来观察资源通过策略验证的效果.
敬请期待
我将会开源一个 CRD 用于配合 ValidatingAdmissionPolicy 做更为灵活的准入校验,参考前面的 CRD 字段作为参数传递到策略中,目前这个字段是不变的或者说是 CRD 逻辑相关的变更,但如果这个字段能够以更灵活的方式将逻辑以脚本的方式处理 K8S API 数据最终得到一个值是不是更妙呢?
你可以先想象一下如果希望用 ValidatingAdmissionPolicy 做到限制一个 namespace 下 pod 的数量的话需要怎么做?
参考资料
官方博客 https://kubernetes.io/blog/2022/12/20/validating-admission-policies-alpha/
微信公众号
扫描下面的二维码关注我们的微信公众号,第一时间查看最新内容。同时也可以关注我的Github,看看我都在了解什么技术,在页面底部可以找到我的Github。