Kyverno

Kyverno变更资源

2023-01-06  本文已影响0人  王勇1024

修改资源配置。

mutate 规则可用于修改匹配资源,并以 RFC 6902 JSON 补丁或策略性合并补丁的形式编写。

通过使用 JSONPatch - RFC 6902格式的补丁,您可以对正在创建的资源进行精确更改。战略合并补丁对于控制带有列表的元素的合并行为很有用。无论采用哪种方法,当需要以给定方式修改对象时,都会使用 mutate 规则。

资源变更发生在验证之前,因此验证规则不应与变更部分执行的更改相矛盾。要更改除受 AdmissionReview 请求约束的现有资源之外的现有资源,请使用 mutateExisting 策略。

如果镜像的 tag 是latest,则此策略将 imagePullPolicy 设置为 IfNotPresent:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: set-image-pull-policy
spec:
  rules:
    - name: set-image-pull-policy
      match:
        any:
        - resources:
            kinds:
            - Pod
      mutate:
        patchStrategicMerge:
          spec:
            containers:
              # 匹配以 :latest 结尾的镜像
              - (image): "*:latest"
                # 设置 imagePullPolicy 为 "IfNotPresent"
                imagePullPolicy: "IfNotPresent"

RFC 6902 JSON补丁

JSON Patch 实现为称为 patchesJson6902 的变更方法,提供了一种精确的资源变更方法并支持以下操作(在 op 字段中):

使用 Kyverno, addreplace 具有相同的行为(即,两个操作都将添加或替换目标元素)。

当需要特定变更而 patchesStrategicMerge 无法满足需求时,patchesJson6902 方法会很有用。例如,当需要对数组中的特定对象进行变更时,可以将索引指定为 patchesJson6902 变更规则的一部分。

patchesJson6902 和其它变更方式的差异之一是,patchesJson6902 不支持使用条件锚(conditional anchors)。可以使用 preconditions代替。patchesJson6902 变更会直接作用到 Pod 上,并不会通过 auto-gen feature将规则转到更高级别的控制器上,例如 Deployments 和 StatefulSets。因此,在为 Pod 编写此类变异规则时,可能需要创建多个规则来覆盖所有相关的 Pod 控制器。

此补丁策略添加或替换任何命名空间中名称为 config-game 的 ConfigMap 中的条目。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: policy-patch-cm
spec:
  rules:
    - name: pCM1
      match:
        any:
        - resources:
            names:
              - config-game
            kinds:
              - ConfigMap
      mutate:
        patchesJson6902: |-
          - path: "/data/ship.properties"
            op: add
            value: |
              type=starship
              owner=utany.corp
          - path: "/data/newKey1"
            op: add
            value: newValue1          

如果您的 ConfigMap 是空数据,则以下策略会在 config-game 中添加一个条目。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: policy-add-cm
spec:
  rules:
    - name: pCM1
      match:
        any:
        - resources:
            names:
              - config-game
            kinds:
            - ConfigMap
      mutate:
        patchesJson6902: |-
          - path: "/data"
            op: add
            value: {"ship.properties": "{\"type\": \"starship\", \"owner\": \"utany.corp\"}"}          

这是从 Secret 中删除标签的补丁示例:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: policy-remove-label
spec:
  rules:
    - name: "Remove unwanted label"
      match:
        any:
        - resources:
            kinds:
            - Secret
      mutate:
        patchesJson6902: |-
          - path: "/metadata/labels/purpose"
            op: remove          

此策略规则将元素添加到列表中。 在这种情况下,它添加了一个新的 busybox 容器和一个命令。请注意,因为 path 语句是一个精确的 schema 元素,所以这仅适用于 direct Pod,而不适用于更高级别的对象,例如 Deployment。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: insert-container
spec:
  rules:
  - name: insert-container
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchesJson6902: |-
        - op: add
          path: "/spec/containers/1"
          value: {"name":"busybox","image":"busybox:latest"}
        - op: add
          path: "/spec/containers/1/command"
          value:
          - ls        

注意:就像之前提到的,作用于 Pod 上的 patchesJson6902 变更并不会转到高级别的 Pod 控制器上。

当需要将对象附加到对象数组时,例如在 pod.spec.tolerations 中,在路径末尾使用破折号 (-)。

mutate:
  patchesJson6902: |-
    - op: add
      path: "/spec/tolerations/-"
      value: {"key":"networkzone","operator":"Equal","value":"dmz","effect":"NoSchedule"}    

JSON Patch 使用 JSON Pointer 来引用键,带有波浪号 (~) 和正斜杠 (/) 字符的键需要分别用 ~0 和 ~1 进行转义。下面的示例是添加一个 key 为 config.linkerd.io/skip-outbound-ports,value 为 "8200" 的 annotation。

- op: add
  path: /spec/template/metadata/annotations/config.linkerd.io~1skip-outbound-ports
  value: "8200"

patchesJson6902 方法的其他一些功能包括:

策略性合并补丁

kubectl 命令使用带有特殊指令的策略性合并补丁来控制元素合并行为。Kyverno 支持这种类型的补丁来变更资源。patchStrategicMerge 会覆盖一个局部资源定义

该策略向 Pod 添加一个新容器,设置 imagePullPolicy,添加一个命令,并添加一个 label,其中 label 的 key 是 “name”,value 来自 AdmissionReview 中的 Pod name。同样的,这次的覆盖行为也只作用于 Pod,而不会作用于高级别的 Deployment。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: strategic-merge-patch
spec:
  rules:
  - name: set-image-pull-policy-add-command
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            name: "{{request.object.metadata.name}}"
        spec:
          containers:
            - name: "nginx"
              image: "nginx:latest"
              imagePullPolicy: "Never"
              command:
              - ls

注意,当使用 patchStrategicMerge 修改 pod.spec.containers[] 数组是,name 关键字必须指定为一个条件锚(即 (name): "*"),为了在其他字段上发生合并。

使用锚点的条件逻辑

validate 规则一样,mutate 规则也支持条件锚。有关条件的更多信息,请参阅锚点部分

anchor 字段,由括号和可选的前导字符标记,允许对变更进行条件处理。

mutate 规则支持两种类型的锚点:

锚点 Tag 行为
Conditional () 使用 tag 和 value 作为 “if” 条件
Add if not present +() 如果 tag 不存在,则添加 tag 值
Global <() 全局锚点为true 时添加 pattern

锚点值支持通配符

      • 匹配零个或多个字母数字字符
  1. ? - 匹配单个字母数字字符

只有 patchStrategicMerge 变更方法中支持条件锚。

Conditional anchor

如果锚标记存在并且值与指定值匹配,则条件锚评估为 true。如果 tag 不存在或值不匹配,则处理停止。一旦处理停止,列表中的任何子元素或任何剩余的兄弟元素都不会被处理。

下面的示例会将所有 name 有值并以“secure”开头的端口的 port 字段添加或替换为 6443,

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: policy-set-port
spec:
  rules:
  - name: "Set port"
    match:
      any:
      - resources:
          kinds :
            - Endpoints
    mutate:
      patchStrategicMerge:
        subsets:
        - ports:
          - (name): "secure*"
            port: 6443

如果锚点的 tag 值是一个对象或数组,整个对象或数组都必须匹配。换句话说,整个对象或数组成为“if”子句的一部分。不支持条件锚 tag 嵌套。

Add if not present anchor

该锚点的作用是,如果一个字段尚未定义,则添加该字段值。这是通过使用 add 锚(“add if not present”锚的缩写)和 tag 的符号 +(...) 来完成的。

add 锚会应用为变更的一部分。通常,每个非锚 tag 值都应用为变更的一部分。如果 tag 上设置了 add 锚,tag 和 value 仅在资源中不存在时才应用。

例如,此策略匹配并变更具有 emptyDir 卷的 pod,以添加 safe-to-evict 注解(如果未指定)。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-safe-to-evict
  annotations:
    pod-policies.kyverno.io/autogen-controllers: none
spec:
  rules:
  - name: "annotate-empty-dir"
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        metadata:
          annotations:
            +(cluster-autoscaler.kubernetes.io/safe-to-evict): true
        spec:
          volumes:
          - <(emptyDir): {}

Global Anchor

与验证规则类似,变更规则可以使用全局锚。当使用全局锚时,锚内部的条件为 true 时,意味着无论它与全局锚点如何相关,都将应用 pattern 的其余部分。

例如,下面的策略会添加一个名为 my-secret 的 imagePullSecret 到任何容器镜像以 corp.reg.com 开头的 Pod中。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-imagepullsecrets
spec:
  rules:
  - name: add-imagepullsecret
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        spec:
          containers:
          - <(image): "corp.reg.com/*"
          imagePullSecrets:
          - name: my-secret

下面的 Pod 符合此条件,因此添加了名为 my-secret 的 imagePullSecret。

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
spec:
  containers:
    - name: web
      image: corp.reg.com/nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP

锚点处理流程

变更条件的锚点处理行为如下:

  1. 首先,处理所有条件锚。当第一个条件锚返回 false 时,处理停止。只有所有条件锚都返回true时,才会处理变更。注意,对于具有复杂 tag(对象或数组)值的条件锚,整个值(子)对象被视为条件的一部分,如上所述。

  2. 接下来,处理所有没有锚的标签值和所有添加锚标签以应用于变更。

变更现有资源

与通过 AdmissionReview 实现的标准变更策略不同,Kyverno 1.7.0+ 支持用 patchesStrategicMergepatchesJson6902 变更现有资源。变更集群中现有的资源的策略会在后台运行。和传统的变更策略一样,这些变更现有资源的策略也是由 AdmissionReview 触发的,但适用于现有的、甚至不同的资源。它们也可以配置为更新策略本身。

要定义这样的策略,需要在 match 中指定触发资源。在每个变更规则的 mutate.targets 中指定目标资源(要在后台变更的资源)。注意,单个规则中的所有目标资源必须使用相同的 schema。例如,如果一个变更现有资源策略的规则是变更 Pod 和 Deployment,这个策略就会失败,因为它们的 OpenAPI V3 schema不同(除了 metadata)。

注意:要给 Kyverno ServiceAccount 授予合适的权限。你需要创建一个有合适权限的 ClusterRole,并通过 ClusterRoleBinding 将其绑定到 Kyverno ServiceAccount。

这个策略,当命名空间 staging 中名为 dictionary-1 的 ConfigMap 类型的触发资源发生变更时,它会给同样在 staging 命名空间中的名为 secret-1 的目标资源设置 label foo=bar。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: "mutate-existing-secret"
spec:
  rules:
    - name: "mutate-secret-on-configmap-event"
      match:
        any:
        - resources:
            kinds:
            - ConfigMap
            names:
            - dictionary-1
            namespaces:
            - staging
      mutate:
        targets:
        - apiVersion: v1
          kind: Secret
          name: secret-1
          namespace: "{{ request.object.metadata.namespace }}"
        patchStrategicMerge:
          metadata:
            labels:
              foo: bar

默认情况下,安装时不会应用上述策略。可以通过 mutateExistingOnPolicyUpdate 属性配置此行为。如果你设置 mutateExistingOnPolicyUpdate 为 true,Kyverno 会在收到 AdmissionReview CREATE 或 UPDATE 事件时,变更策略中指定的、现有的 secret。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: "mutate-existing-secret"
spec:
  mutateExistingOnPolicyUpdate: true
  rules:
    - name: "mutate-secret-on-configmap-event"
      match:
        any:
        - resources:
            kinds:
            - ConfigMap
            names:
            - dictionary-1
            namespaces:
            - staging
...

引用目标资源的变量

要引用目标资源中的数据,您可以定义变量 target,后跟所需属性的路径。例如,使用 target.metadata.labels.env 引用目标资源中的标签 env。

这个策略复制 ConfigMaps 中 target.data.key 的值到它的标签 env。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: sync-cms
spec:
  mutateExistingOnPolicyUpdate: false
  rules:
  - name: concat-cm
    match:
      any:
      - resources:
          kinds:
          - ConfigMap
          names:
          - cmone
          namespaces:
          - foo
    mutate:
      targets:
        - apiVersion: v1
          kind: ConfigMap
          name: cmtwo
          namespace: bar
        - apiVersion: v1
          kind: ConfigMap
          name: cmthree
          namespace: bar
      patchesJson6902: |-
        - op: add
          path: "/metadata/labels/env"
          value: "{{ target.data.key }}"          

使用 {{ @ }} 特殊变量以引用目标资源的内联值。

这个策略将 foo 命名空间中名为 cmone 的触发器 ConfigMap 中 keyone 的值添加到目标 ConfigMaps 中,并作为 data 中 keynew 的前缀。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: sync-cms
spec:
  mutateExistingOnPolicyUpdate: false
  rules:
  - name: concat-cm
    match:
      any:
      - resources:
          kinds:
          - ConfigMap
          names:
          - cmone
          namespaces:
          - foo
    mutate:
      targets:
        - apiVersion: v1
          kind: ConfigMap
          name: cmtwo
          namespace: bar
        - apiVersion: v1
          kind: ConfigMap
          name: cmthree
          namespace: bar
      patchStrategicMerge:
        data:
          keynew: "{{request.object.data.keyone}}-{{@}}"

一旦一个变更已有资源的策略成功应用,会有一个事件和注解被添加到目标资源中。

$ kubectl describe deploy foobar
...
Events:
  Type     Reason             Age                From                   Message
  ----     ------             ----               ----                   -------
  Normal   PolicyApplied      29s (x2 over 31s)  kyverno-mutate         policy add-sec/add-sec-rule applied

$ kubectl get deploy foobar -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    ...
    policies.kyverno.io/last-applied-patches: |
      add-sec-rule.add-sec.kyverno.io: added /spec/template/spec/containers/0/securityContext

要对策略应用程序失败进行故障排除,您可以检查 UpdateRequest 自定义资源以获取详细信息。Kyverno 会自动清理成功的 UpdateRequest。

例如,如果没有给 Kyverno 授予相应的权限,你会在 updaterequest.status 中看到如下错误信息:

$ kubectl get ur -n kyverno
NAME       POLICY    RULETYPE   RESOURCEKIND   RESOURCENAME   RESOURCENAMESPACE   STATUS   AGE
ur-swsdg   add-sec   mutate     Deployment     foobar         default             Failed   84s

$ kubectl describe ur ur-swsdg -n kyverno
Name:         ur-swsdg
Namespace:    kyverno
...
Status:
  Message:  deployments.apps "foobar" is forbidden: User "system:serviceaccount:kyverno:kyverno-service-account" cannot update resource "deployments" in API group "apps" in the namespace "default"
  State:    Failed

变更规则排序(级联)

在某些情况下,可能需要将多级变异规则应用于传入的资源。规则A中的 match 声明会对资源应用一个变更,变更的结果会触发规则B中的 match 声明,从而应用第二个。这种情况下,Kyverno 可以适应更复杂的变更规则,但是规则排序对于保证一致的结果很重要。

例如,假设您希望为每个传入的 Pod 分配一个标签,描述它包含的应用程序类型。对那些 image 中包含 cassandra 或 mongo 字符串的,希望使用标签 type=database。可以通过下面的策略实现:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: database-type-labeling
spec:
  rules:
    - name: assign-type-database
      match:
        any:
        - resources:
            kinds:
            - Pod
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              type: database
          spec:
            (containers):
            - (image): "*cassandra* | *mongo*"

此外,假设需要为某些应用程序类型定义备份策略。对那些 type=database 的应用程序,需要指定另外一个标签 backup-needed 值为 yes 或 no。仅当尚未指定标签时才会添加标签,因为操作员可以选择是否需要保护。 该策略的定义如下。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: database-backup-labeling
spec:
  rules:
    - name: assign-backup-database
      match:
        any:
        - resources:
            kinds:
              - Pod
            selector:
              matchLabels:
                type: database
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              +(backup-needed): "yes"

这种情况下,Kyverno 能够执行级联变更,从而使传入的 Pod 在匹配到第一个规则并变更后,进而匹配到第二个规则发生第二次变更。这些情况下,规则必须按照其依赖关系的顺序从上到下排序,并存储在同一策略中。生成的策略定义如下所示:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: database-protection
spec:
  rules:
  - name: assign-type-database
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            type: database
        spec:
          (containers):
          - (image): "*cassandra* | *mongo*"
  - name: assign-backup-database
    match:
      any:
      - resources:
          kinds:
          - Pod
          selector:
            matchLabels:
              type: database
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            +(backup-needed): "yes"

通过使用 Cassandra 镜像创建 Pod 来测试级联变更策略。

$ kubectl run cassandra --image=cassandra:latest
pod/cassandra created

使用 get 或 describe 查看变更后的 Pod 的 metadata:

$ kubectl describe po cassandra
Name:         cassandra
Namespace:    default
<snip>
Labels:       backup-needed=yes
              run=cassandra
              type=database
<snip>

可以看到,根据变更规则,type=database 和 backup-needed=yes 都被添加到 Pod 中。

验证如果 Pod 中已存在标签 backup-needed=no,之后触发规则一,而不会触发规则二。

$ kubectl run cassandra --image=cassandra:latest --labels backup-needed=no

使用 get 或 describe 可以验证 backup-needed 标签并未被变更规则修改。

$ kubectl describe po cassandra
Name:         cassandra
Namespace:    default
<snip>
Labels:       backup-needed=no
              type=database
<snip>

foreach

一个 foreach 声明可以包含多个条目来处理不同的子元素,例如 一个处理容器列表,另一个处理 Pod 中的 initContainers 列表。

foreach 必须包含一个 list 属性,定义了要处理的元素列表,以及一个 patchStrategicMerge 或 patchesJson6902 声明。例如,使用 list 声明遍历 Pod 中的 containers 列表:

list: request.object.spec.containers
patchStrategicMerge:
  spec:
    containers:
      ...

当处理一个 foreach 时,Kyverno 引擎将评估 list 作为 JMESPath 表达式以检索零个或多个子元素以供进一步处理。list 字段的值也可以解析为一个简单的字符串数组,例如在上下文变量中定义的字符串。list 字段的值不应包含在大括号中,即使它是 JMESPath 表达式。

每次遍历是,一个 element 变量都会被添加到处理上下文中。可以通过 element.<name> 来引用元素中的数据,name 是属性名。例如,当 request.object 是一个 Pod 而使用 request.object.spec.containers 列表时,可以在 foreach 中使用 element.image 来引用容器镜像。

每个 foreach 声明中可以包含(可选)以下声明:

对应 foreach 声明中的 patchesJson6902 类型,会有一个叫做 elementIndex 的额外变量,该变量可以在循环中引用索引编号。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: foreach-json-patch
spec:
  rules:
    - name: add-security-context
      match:
        any:
        - resources:
            kinds:
              - Pod
      preconditions:
        any:
        - key: "{{ request.operation }}"
          operator: Equals
          value: CREATE
      mutate:
        foreach: 
        - list: "request.object.spec.containers"
          patchesJson6902: |-
            - path: /spec/containers/{{elementIndex}}/securityContext
              op: add
              value: {"runAsNonRoot" : true}            

如下是一个完整的 patchStrategicMerge 方法示例,该方法改变 image 以预先添加受信任的 registry 地址。

apiVersion : kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: prepend-registry
spec:
  background: false
  rules:
  - name: prepend-registry-containers
    match:
      any:
      - resources:
          kinds:
          - Pod
    preconditions:
      all:
      - key: "{{request.operation}}"
        operator: In
        value:
        - CREATE
        - UPDATE
    mutate:
      foreach:
      - list: "request.object.spec.containers"
        patchStrategicMerge:
          spec:
            containers:
            - name: "{{ element.name }}"           
              image: registry.io/{{ images.containers."{{element.name}}".name}}:{{images.containers."{{element.name}}".tag}}

注意,patchStrategicMerge 作用于 request.object。因此,patch 要以 spec 开始。由于容器名称中可能包含破折号(-)(必须转义),因此 {{element.name}} 变量用双引号指定。

上一篇 下一篇

猜你喜欢

热点阅读