Deployment 创建成功了,却一个 Pod 都没有
目标
在真实的 Kyverno 1.19.1 中确认 Kyverno 策略的各种编写手段(preconditions、apiCall 上下文、变量默认值、autogen、JSON 补丁、foreach、CEL、后台扫描) 在实际请求中会拒绝什么、修改什么。
为什么重要
策略的语法正确,与按预期做出判定,是两回事。match 只按资源种类和位置来选择,所以要根据请求内容开关规则,就需要 preconditions; 需要请求中没有的信息时,就需要上下文。变量无法解析时,规则不会给出判定而是报错,而在 Enforce 下, 这个错误看起来就是拒绝,于是会在错误的地方找原因。Pod 规则默认会自动生成面向控制器的规则,把它关掉之后,Deployment 看上去成功了,却只有 Pod 悄悄地没有生成。mutate 规则必须能为每个请求写入不同的值,也能只修改列表中的一部分, 而集群中早已存在的资源不会再次经过准入,只能通过后台扫描才能暴露出来。
步骤
- 创建命名空间
kca-pre,并在/root/kca-write/01-limits.yaml中编写并应用 ClusterPolicylimits-for-prod(background false)。规则memory-limit匹配kca-pre中的 Pod,通过preconditions使它只作用于标签tier为prod的请求(没有标签时按空字符串处理),并要求所有容器都有resources.limits.memory(Enforce)。不要在 match 中使用标签 selector。 - 创建命名空间
kca-prod(标签env=prod)和kca-dev(标签env=dev),并在/root/kca-write/02-team.yaml中应用 ClusterPolicyteam-in-prod-ns(background false)。规则team-when-prod针对这两个命名空间中的 Pod,通过apiCall以上下文nsenv从/api/v1/namespaces/{{request.namespace}}读取(标签 env,没有则为none),当 env 为 prod 且没有 team 标签时拒绝(Enforce)。然后把kca-dev的 env 暂时改成 prod,用服务器端 dry-run 发送一个没有标签的 Pod,把输出的全部内容保存到/root/kca-write/flip.txt,再把 env 改回 dev。 - 在命名空间
kca-var中应用 ClusterPolicyno-platform-team(background false,规则not-platform)。这是在 team 标签为platform时拒绝的 deny 条件,起初 key 不带默认值,写成{{ request.object.metadata.labels.team }}。用服务器端 dry-run 发送一个没有标签的 Pod,把拒绝输出的全部内容保存到/root/kca-write/missing.txt。然后给/root/kca-write/03-no-platform.yaml的 key 加上默认值(空字符串)后重新应用,让没有标签的 Pod 被接受,而 team=platform 被拒绝。 - 创建命名空间
kca-auto和kca-noauto,在/root/kca-write/04-probes.yaml中编写并应用 ClusterPolicyprobes-auto和probes-noauto,它们分别要求各自命名空间中的 Pod 具有 readinessProbe(Enforce,background false,规则need-probe)。只给probes-noauto加上注解pod-policies.kyverno.io/autogen-controllers: none。然后在两个命名空间中运行kubectl create deployment web --image=nginx:1.27-alpine,把kca-auto一侧输出的全部内容保存到/root/kca-write/auto.txt。 - 创建命名空间
kca-mut,并在/root/kca-write/05-requested-by.yaml中编写 ClusterPolicyrequested-by(规则annotate-user)。通过 patchesJson6902 的 add 操作,给kca-mut中的 Pod 写入注解kca.io/requested-by,值为{{request.userInfo.username}}。先不写 spec.background 直接应用,把被拒绝时输出的全部内容保存到/root/kca-write/bg-denied.txt,然后把 background 设为 false 再应用。已有的注解必须保留。 - 在
/root/kca-write/06-pull-policy.yaml中编写并应用 ClusterPolicypull-policy(规则latest-always)。针对kca-mut中的 Pod,通过 foreach 逐个容器用元素级 preconditions 判断镜像是否以:latest结尾,只把这样的容器用 patchStrategicMerge 改为imagePullPolicy: Always。固定了标签的容器的 imagePullPolicy 必须保持不变。 - 创建命名空间
kca-cel,并在/root/kca-write/07-no-priv-esc.yaml中应用policies.kyverno.io/v1的 ValidatingPolicyno-priv-esc。validationActions 为[Deny],matchConstraints 为 core v1 pods 的 CREATE、UPDATE 以及 namespaceSelectorkubernetes.io/metadata.name: kca-cel。CEL 变量escalating保存 securityContext.allowPrivilegeEscalation 没有被明确设为 false 的容器名称列表,该列表必须为空才算通过,并且在拒绝消息(messageExpression)中用逗号连接显示这些名称。 - 在命名空间
kca-bg中先创建没有标签的 Podold-nolabel和带标签team=a的 Podold-label,然后在/root/kca-write/08-audit-team.yaml中应用 ClusterPolicyaudit-team(background true,Audit,规则team,要求 kca-bg 中的 Pod 带有 team 标签)。等到 PolicyReport 中出现这两个 Pod 的结果之后,在/root/kca-write/report.json中以fail、pass为键,写入各结果对应的 Pod 名称列表。
参考
- VM 中有 k3s 和 Kyverno v1.19.1。在这个版本中,
kyverno.io/v1 ClusterPolicy会给出弃用警告,但可以正常工作。 - 不保存、直接测试策略:
kubectl -n <ns> run t --image=nginx:1.27-alpine --dry-run=server(要查看 mutate 的结果,加上-o yaml)。 - 确认策略就绪:
kubectl get clusterpolicy <이름> -o jsonpath='{.status.conditionStatus.ready}'(占位符为名称),ValidatingPolicy 用.status.conditionStatus.ready。 - 常见错误:应用策略后立刻发送请求。Webhook 生效需要几秒钟。
- 常见错误:在同一个命名空间里叠放多个步骤的策略。本实验每个步骤使用各自的命名空间。
- Preconditions · External Data Sources(apiCall) · Variables · Auto-Gen Rules · Mutate Rules · ValidatingPolicy · Reporting
只对 prod 的 Pod 要求设置 limit
创建命名空间 kca-pre,并在 /root/kca-write/01-limits.yaml 中编写并应用 ClusterPolicy limits-for-prod(background false)。规则 memory-limit 匹配 kca-pre 中的 Pod,通过preconditions使它只作用于标签 tier 为 prod 的请求(没有标签时按空字符串处理),并要求所有容器都有 resources.limits.memory(Enforce)。不要在 match 中使用标签 selector。
preconditions 的 key 是 JMESPath 变量。在没有标签的请求中变量无法解析时会怎样,第 3 步会看到。评分器会用服务器端 dry-run 发送没有 tier、tier 为 dev、tier 为 prod 的 Pod。
一个命名空间标签就能启用规则
创建命名空间 kca-prod(标签 env=prod)和 kca-dev(标签 env=dev),并在 /root/kca-write/02-team.yaml 中应用 ClusterPolicy team-in-prod-ns(background false)。规则 team-when-prod 针对这两个命名空间中的 Pod,通过apiCall以上下文 nsenv 从 /api/v1/namespaces/{{request.namespace}} 读取(标签 env,没有则为 none),当 env 为 prod 且没有 team 标签时拒绝(Enforce)。然后把 kca-dev 的 env 暂时改成 prod,用服务器端 dry-run 发送一个没有标签的 Pod,把输出的全部内容保存到 /root/kca-write/flip.txt,再把 env 改回 dev。
把命名空间名称硬编码进策略的话,不修改策略就改变不了判定结果。apiCall 会针对每个请求读取 API 服务器当前的值。dry-run 用 kubectl run ... --dry-run=server。
没有标签的 Pod 因莫名其妙的原因被拒绝了
在命名空间 kca-var 中应用 ClusterPolicy no-platform-team(background false,规则 not-platform)。这是在 team 标签为 platform 时拒绝的 deny 条件,起初 key 不带默认值,写成 {{ request.object.metadata.labels.team }}。用服务器端 dry-run 发送一个没有标签的 Pod,把拒绝输出的全部内容保存到 /root/kca-write/missing.txt。然后给 /root/kca-write/03-no-platform.yaml 的 key 加上默认值(空字符串)后重新应用,让没有标签的 Pod 被接受,而 team=platform 被拒绝。
用 JMESPath 读取不存在的键时,变量替换会失败,Kyverno 会把该规则当作无法判定的错误来处理。请读输出中的文字,看 Enforce 规则的错误会让请求变成什么结果。|| 运算符可以提供默认值。
Deployment 创建出来了,却一个 Pod 都没有
创建命名空间 kca-auto 和 kca-noauto,在 /root/kca-write/04-probes.yaml 中编写并应用 ClusterPolicy probes-auto 和 probes-noauto,它们分别要求各自命名空间中的 Pod 具有 readinessProbe(Enforce,background false,规则 need-probe)。只给 probes-noauto 加上注解 pod-policies.kyverno.io/autogen-controllers: none。然后在两个命名空间中运行 kubectl create deployment web --image=nginx:1.27-alpine,把 kca-auto 一侧输出的全部内容保存到 /root/kca-write/auto.txt。
Kyverno 会自动把 Pod 规则扩展成针对 Deployment、ReplicaSet 等带有 Pod 模板的资源的规则。不扩展的话,Deployment 会通过,而拒绝发生在 ReplicaSet 控制器创建 Pod 的时候。请查看 kubectl describe rs 的条件和事件。
把请求者的名字写进 Pod
创建命名空间 kca-mut,并在 /root/kca-write/05-requested-by.yaml 中编写 ClusterPolicy requested-by(规则 annotate-user)。通过 patchesJson6902 的 add 操作,给 kca-mut 中的 Pod 写入注解 kca.io/requested-by,值为 {{request.userInfo.username}}。先不写 spec.background 直接应用,把被拒绝时输出的全部内容保存到 /root/kca-write/bg-denied.txt,然后把 background 设为 false 再应用。已有的注解必须保留。
在 JSON 指针中,/ 要写成 ~1。请求者信息只存在于准入请求中,重新扫描已保存资源的后台处理中没有。在服务器端 dry-run 上加 -o jsonpath,就能看到 mutate 之后的结果。
只让 latest 标签的容器每次都重新拉取镜像
在 /root/kca-write/06-pull-policy.yaml 中编写并应用 ClusterPolicy pull-policy(规则 latest-always)。针对 kca-mut 中的 Pod,通过 foreach 逐个容器用元素级 preconditions 判断镜像是否以 :latest 结尾,只把这样的容器用 patchStrategicMerge 改为 imagePullPolicy: Always。固定了标签的容器的 imagePullPolicy 必须保持不变。
在 foreach 中,当前元素是 element。要只修改列表中的某个容器,就用条件锚点 (name) 匹配名称。JMESPath 有 ends_with 函数。
一行 CEL 连 Deployment 也拦住了
创建命名空间 kca-cel,并在 /root/kca-write/07-no-priv-esc.yaml 中应用 policies.kyverno.io/v1 的 ValidatingPolicy no-priv-esc。validationActions 为 [Deny],matchConstraints 为 core v1 pods 的 CREATE、UPDATE 以及 namespaceSelector kubernetes.io/metadata.name: kca-cel。CEL 变量 escalating 保存 securityContext.allowPrivilegeEscalation 没有被明确设为 false 的容器名称列表,该列表必须为空才算通过,并且在拒绝消息(messageExpression)中用逗号连接显示这些名称。
用 CEL 的 filter、map 构造列表,并先用 has() 检查不存在的字段。这个版本的 ValidatingPolicy 也会把 Pod 规则自动生成为针对带有 Pod 模板的资源的规则(status.autogen)。评分器也会用 dry-run 发送 Deployment。
先于策略存在的 Pod 也出现在了报告中
在命名空间 kca-bg 中先创建没有标签的 Pod old-nolabel 和带标签 team=a 的 Pod old-label,然后在 /root/kca-write/08-audit-team.yaml 中应用 ClusterPolicy audit-team(background true,Audit,规则 team,要求 kca-bg 中的 Pod 带有 team 标签)。等到 PolicyReport 中出现这两个 Pod 的结果之后,在 /root/kca-write/report.json 中以 fail、pass 为键,写入各结果对应的 Pod 名称列表。
Audit 只记录,不拦截请求。已经保存的资源不会再次经过准入,所以必须有 background 才会被评估。结果会汇集到命名空间的 PolicyReport 中(每个资源一个)。