使用 Kyverno 定义 Kubernetes 策略
Kubernetes 的日常使用过程中,在对象提交给集群之前,我们会有很多机会,很多方法对资源的 Yaml 定义进行检查和处理。很多读者应该也会知道,资源提交之后,还有机会使用 Admission Controller 对资源动动手脚,这其中其实有很多可以提炼出来的标准动作,可以用统一的控制器来进行处理,Kyverno 就是这样一个工具。有了 Kyverno 的帮助,YAML 程序员可以根据条件对资源进行筛选,符合条件的资源可以:
验证资源:对资源定义进行检查,不符合条件的资源拒绝创建,从而保证集群资源的合规性。
修改资源:在资源定义中进行注入,强制资源部分行为的一致性。
生成资源:在资源创建时,同时创建相关的资源。
安装
安装过程是很简单的,安装清单文件位于 https://github.com/nirmata/kyverno/raw/master/definitions/install.yaml
,使用 kubectl 直接部署即可:
$ kubectl create -f https://github.com/nirmata/kyverno/raw/master/definitions/install.yaml
customresourcedefinition.apiextensions.k8s.io/policies.kyverno.io created
namespace/kyverno created
service/kyverno-svc created
serviceaccount/kyverno-service-account created
clusterrolebinding.rbac.authorization.k8s.io/kyverno-admin created
deployment.extensions/kyverno created
有一点需要注意的是资源的类型范围,可以在主进程的命令行参数中设定不需要处理的资源类型,缺省设置为:
containers:
- name: kyverno
image: nirmata/kyverno:latest
args: ["--filterKind","Node,Event,APIService,Policy,TokenReview,SubjectAccessReview"]
ports:
策略定义
安装完成后,就可以编写策略了,策略的规则不算复杂,具体格式可以从 install.yaml
中的 CRD 定义里面推断出来。
apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
name: sample-policy
spec:
rules: # 规则数组,spec 的唯一下级
- name: check-rule-1
resource: # 定义选择条件,限制生效范围
kinds: # 生效对象类型数组,必要字段
- Deployment
- StatefulSet
namespace: default # 命名空间
name: "*" # 资源名称
selector: # 用更加复杂一点的方式来定义选择方式
matchLabels: # 精确匹配标签
app: some-app
matchExpressions: # 表达式匹配标签
key: "operator"
operator: In
values:
- v2
- v3
validate:
...
mutate:
...
generate:
...
resource
部分是固定的,而 validate
mutate
generate
三个动作则各有各的结构。
下面用几个例子来演示一下他的功能。
验证资源(validate)
定义一个限制特定命名空间下镜像地址的策略如下:
apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
name: check-registries
spec:
rules:
- name: check-registries
resource:
kinds:
- Deployment
namespace: default
validate:
message: "Registry is not allowed"
pattern:
spec:
template:
spec:
containers:
- name: "*"
image: "docker.io/citizenstig/*"
这个策略文件中,pattern
部分和我们要处理的 deployment
文档结构一致,其中支持通配符,可以用它来对目标进行校验,这里我们要求 default
命名空间中的 Deplyment
对象,containers
下的 image
字段必须符合 docker.io/citizenstig/*
的通配符要求。
例如下面的的 Deployment 就无法创建:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
containers:
- name: httpbin
image: citizenstig/httpbin
imagePullPolicy: IfNotPresent
$ kubectl apply -f httpbin.yaml
Error from server: error when creating "httpbin.yaml": admission webhook "nirmata.kyverno.validating-webhook" denied the request:
Policy check-registries failed with following rules;rulename: check-registries;Rule check-registries: Validation has failed, err Failed to validate value citizenstig/httpbin with pattern docker.io/citizenstig/*. Path: /spec/template/spec/containers/0/image/.
但是如果我们换个命名空间就没问题了:
$ kubectl create ns free
kunamespace/free created
$ kubectl apply -f httpbin.yaml -n free
deployment.extensions/httpbin created
又或者我们不用 Deployment,直接创建 Pod:
apiVersion: v1
kind: Pod
metadata:
name: static-httpbin
spec:
containers:
- name: httpbin
image: citizenstig/httpbin
果然就能够创建成功了:
$ kubectl apply -f pod.yaml
pod/static-httpbin created
这样的绕过自然是我们不想要的,但是可以改变策略,把限制做到 Pod 上:
rules:
- name: check-registries
resource:
kinds:
- Pod
namespace: default
validate:
message: "Registry is not allowed"
pattern:
spec:
containers:
- name: "*"
image: "docker.io/citizenstig/*"
这样更新之后,不管是 Deployment 还是静态 Pod 都无法通过了。
如果使用 kubectl edit deploy httpbin
把 image
字段修改为 docker.io/citizenstig/httpbin
,就能看到 deployment
能够正常工作了。
或者我们可以要求所有 Pod 都必须指定 CPU 限制:
validate:
message: "resources/limits is needed."
pattern:
spec:
template:
spec:
containers:
- resources:
limits:
cpu: "*"
这个策略提交之后,上面的 Deploy 就再次无法部署了:
$ kubectl apply -f httpbin.yaml
Error from server: error when creating "httpbin.yaml": admission webhook "nirmata.kyverno.validating-webhook" denied the request:
...
Path: /spec/template/spec/containers/0/resources/limits/. Expected map[string]interface {}, found <nil>.
修改清单,加入资源限制,即可满足条件。
修改资源(mutate)
这里也可以做类似自动注入的内容,例如我们可以要求所有 default
命名空间中的 Deployment,如果 deployment
标签中有 io=heavy
,则分配到 ssd=true
的节点上。
apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
name: assign-ssd
spec:
rules:
- name: assign-ssd
resource:
kinds:
- Deployment
namespace: default
selector:
matchLabels:
io: heavy
mutate:
overlay:
spec:
template:
spec:
nodeSelector:
ssd: true
修改一下上面的 Deployment
,加上标签:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: httpbin
labels:
io: heavy
spec:
...
提交到集群之后,查看变更结果:
$ kubectl get deployments httpbin -o yaml
apiVersion: extensions/v1beta1
kind: Deployment
...
spec:
...
dnsPolicy: ClusterFirst
nodeSelector:
ssd: "true"
看到多出来的 nodeSelector
字段,如果查看 Pod 信息,也会发现这个 Deployment 的所有 Pod 都分配到了指定的节点上。
创建资源(generate)
有时候我们在 Kubernetes 上创建资源的时候,可能希望同时提供一些缺省资源,例如一个新的命名空间,我们希望其中包含缺省的 Configmap 或者 SA 或者资源限制。
例如我们要在新建 test-n
的命名空间的同时,创建名为 dummy
的 sa。
apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
name: auto-sa
spec:
rules:
- name: auto-sa
resource:
kinds:
- Namespace
name: "test-*"
generate:
kind: ServiceAccount
name: dummy
data:
spec: {}
metadata:
labels:
source: "webhook"
这个策略生效后,每次我们创建形如 test-*
的命名空间,其中都会生成对应的名为 dummy
的 ServiceAccount,并且有标签:source=webhook
。
Generate 还提供了复制对象的方法,例如每个新命名空间中都应该复制一个名为 conn
的 Configmap
,就可以使用如下策略:
apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
name: auto-cm
spec:
rules:
- name: auto-cm
resource:
kinds:
- Namespace
name: "test-*"
generate:
kind: ConfigMap
name: conn
clone:
namespace: default
name: conn
随意验证一下:
$ kubectl create configmap conn \
--from-literal=mysql=mysql \
--from-literal=mongodb=mongodb
configmap/conn created
$ kubectl create ns test-6
namespace/test-6 created
$ kubectl get cm,sa -n test-6
NAME DATA AGE
configmap/conn 2 6s
NAME SECRETS AGE
serviceaccount/default 1 7s
serviceaccount/dummy 1 6s
这里会发现,随着新的命名空间的创建,新的 SA 和 CM 也都出现了。
结论
相对于其他的类似工具,Kyverno 在灵活、强大和易用之间取得了一个很好的平衡,不需要太多学习时间,就能够提供相当方便的功能,官网提供了大量的针对各种场景的样例,非常值得一看。
参考链接
- 项目主页:
https://kyverno.io/