Kyverno變更資源

修改資源配置。

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 字段中):

  • add

  • replace

  • remove

使用 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 方法的其他一些功能包括:

  • 添加不存在的路徑

  • 添加不存在的數組

  • 給數組尾部添加一個元素 (使用負索引 -1)

策略性合并補丁

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 聲明中可以包含(可選)以下聲明:

  • Context: 添加僅在每個循環迭代中可用的額外外部數據。

  • Preconditions: 控制何時跳過循環迭代。

對應 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}} 變量用雙引號指定。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 并發控制 并發控制指的是當多個用戶同時更新運行時,用于保護數據庫完整性的各種技術。并發機制不正確可能導致臟讀、幻讀...
    王勇1024閱讀 649評論 0 0
  • 策略和規則 了解 Kyverno 政策和規則的運作方式。 一個 Kyverno 策略是一組規則的集合。每個規則包含...
    王勇1024閱讀 699評論 0 1
  • Kyverno(希臘語為“治理”)是專為 Kubernetes 設計的策略引擎。它的許多功能中的一些包括: 策略作...
    王勇1024閱讀 2,202評論 0 1
  • 策略配置 策略中所有規則的通用配置。 策略包含一個或多個規則,以及適用于策略中所有規則的以下常見設置: valid...
    王勇1024閱讀 403評論 0 1
  • 大型組織應用GitOps難免會遇到在多環境中部署的問題,本文分析了應用環境分支策略會遇到到問題,介紹了應用文件夾策...
    DeepNoMind閱讀 1,018評論 0 0