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, add
和 replace
具有相同的行为(即,两个操作都将添加或替换目标元素)。
当需要特定变更而 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 |
锚点值支持通配符:
- 匹配零个或多个字母数字字符
-
? - 匹配单个字母数字字符
只有 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
锚点处理流程
变更条件的锚点处理行为如下:
-
首先,处理所有条件锚。当第一个条件锚返回 false 时,处理停止。只有所有条件锚都返回true时,才会处理变更。注意,对于具有复杂 tag(对象或数组)值的条件锚,整个值(子)对象被视为条件的一部分,如上所述。
-
接下来,处理所有没有锚的标签值和所有添加锚标签以应用于变更。
变更现有资源
与通过 AdmissionReview 实现的标准变更策略不同,Kyverno 1.7.0+ 支持用 patchesStrategicMerge
和 patchesJson6902
变更现有资源。变更集群中现有的资源的策略会在后台运行。和传统的变更策略一样,这些变更现有资源的策略也是由 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}}
变量用双引号指定。