TT Lab
开始
学习 学习路径 课程

KCA — Kyverno 认证助理

Deployment 创建成功了,却一个 Pod 都没有

在 TT Lab 中继续学习

目标

在真实的 Kyverno 1.19.1 中确认 Kyverno 策略的各种编写手段(preconditions、apiCall 上下文、变量默认值、autogen、JSON 补丁、foreach、CEL、后台扫描) 在实际请求中会拒绝什么、修改什么。

为什么重要

策略的语法正确,与按预期做出判定,是两回事。match 只按资源种类和位置来选择,所以要根据请求内容开关规则,就需要 preconditions; 需要请求中没有的信息时,就需要上下文。变量无法解析时,规则不会给出判定而是报错,而在 Enforce 下, 这个错误看起来就是拒绝,于是会在错误的地方找原因。Pod 规则默认会自动生成面向控制器的规则,把它关掉之后,Deployment 看上去成功了,却只有 Pod 悄悄地没有生成。mutate 规则必须能为每个请求写入不同的值,也能只修改列表中的一部分, 而集群中早已存在的资源不会再次经过准入,只能通过后台扫描才能暴露出来。

步骤

  1. 创建命名空间 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。
  2. 创建命名空间 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。
  3. 在命名空间 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 被拒绝。
  4. 创建命名空间 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。
  5. 创建命名空间 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 再应用。已有的注解必须保留。
  6. 在 /root/kca-write/06-pull-policy.yaml 中编写并应用 ClusterPolicy pull-policy(规则 latest-always)。针对 kca-mut 中的 Pod,通过 foreach 逐个容器用元素级 preconditions 判断镜像是否以 :latest 结尾,只把这样的容器用 patchStrategicMerge 改为 imagePullPolicy: Always。固定了标签的容器的 imagePullPolicy 必须保持不变。
  7. 创建命名空间 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)中用逗号连接显示这些名称。
  8. 在命名空间 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 名称列表。

参考

只对 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 中(每个资源一个)。