使用 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 httpbinimage 字段修改为 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 还提供了复制对象的方法,例如每个新命名空间中都应该复制一个名为 connConfigmap,就可以使用如下策略:

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/
Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
下一页
上一页

相关