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

CNPA — 云原生平台工程助理

策略一开启,紧急补丁就被拦下了

在 TT Lab 中继续学习

目标

在真实的 k3s 和 Kyverno 上,把一条平台规则(所有者标签和资源请求)推行到三个团队。先用审计模式统计,通过报告找出违规,再切换为拦截模式,亲身体会哪些操作会被挡住;最后对无法修复的对象授予有期限的例外,并计算合规率。

为什么重要

策略引擎是用代码强制落实平台护栏的工具。但如果一开始就以拦截模式启用规则,连已经违反规则的工作负载的紧急补丁也会被挡住,故障响应就会停滞。因此治理通常按这样的顺序推进:审计 → 通过报告掌握现状 → 各团队修复 → 拦截 → 范围窄且有期限的例外。 本实验要确认,在这个顺序的每个阶段,集群实际会如何反应,以及用什么机制避免例外悄悄变成永久放行。

步骤

  1. 编写并应用 /root/cnpa-pol/tenants.yaml,其中包含三个命名空间和四个 Deployment。给命名空间 team-pay、team-legacy、team-vendor 分别添加标签 platform.labhub.io/tenant,取值依次为 pay、legacy、vendor。所有 Deployment 的 replicas 均为 1,容器名称为 app,镜像为 registry.k8s.io/pause:3.10,元数据和选择器标签为 app.kubernetes.io/name: <이름>(占位符为 Deployment 名称)。team-pay/checkout 同时具有元数据标签 app.kubernetes.io/owner: pay 和 requests(cpu 10m,memory 16Mi);team-legacy/report-gen 只有 requests;team-legacy/batch-sync 只有 owner 标签(legacy);team-vendor/vendor-agent 两者都没有。
  2. 编写并应用 /root/cnpa-pol/policy.yaml,其中是 ValidatingPolicy(policies.kyverno.io/v1)tenant-deploy-baseline。设置 validationActions: [Audit] 和 evaluation.background.enabled: true;matchConstraints 针对 apps/v1 deployments 的 CREATE 和 UPDATE,并用 namespaceSelector 只选中带有标签 platform.labhub.io/tenant 的(Exists)命名空间。按以下顺序设置两条验证。① 元数据标签中存在 app.kubernetes.io/owner,消息为 Deployment 에 app.kubernetes.io/owner 라벨이 필요합니다(韩文,意为“Deployment 需要 app.kubernetes.io/owner 标签”);② 所有容器都有 requests 的 cpu 和 memory,消息为 모든 컨테이너에 cpu·memory requests 가 필요합니다(韩文,意为“所有容器都需要 cpu 和 memory 的 requests”)。
  3. 读取 Kyverno 生成的 PolicyReport,把 tenant-deploy-baseline 结果为 fail 的 Deployment 以数组形式写入 /root/cnpa-pol/violations.json。每个元素包含 namespace、name、uid(报告的 scope.uid)和 message(该结果的消息)。
  4. 把策略的 validationActions 改为 [Deny]。然后模拟 legacy 团队的紧急补丁,执行 kubectl -n team-legacy set image deploy/batch-sync app=registry.k8s.io/pause:3.9,并把输出(包括标准错误)保存到 /root/cnpa-pol/blocked.txt。接着执行 kubectl -n team-legacy scale deploy/batch-sync --replicas=2,并在 /root/cnpa-pol/deny-effects.json 中写入 image_update_denied、scale_allowed(布尔值)和 batch_sync_image(batch-sync 当前的镜像)。
  5. 让 legacy 团队的两个 Deployment 符合规则。给 report-gen 添加元数据标签 app.kubernetes.io/owner: legacy,给 batch-sync 的容器 app 加上 requests(cpu 50m,memory 64Mi)。然后重新执行第 4 步中被拦截的 set image ... app=registry.k8s.io/pause:3.9,使其通过,并等待这两个 Deployment 的 PolicyReport 结果变为 pass。
  6. vendor-agent 是供应商提供的清单,暂时无法修改。首先把 kyverno-admission-controller Deployment 的参数 --enablePolicyException=false 改为 --enablePolicyException=true,并等待滚动更新完成。然后在 /root/cnpa-pol/exception.yaml 中,于 team-vendor 命名空间创建 PolicyException(policies.kyverno.io/v1)vendor-agent-requests。policyRefs 指向 ValidatingPolicy tenant-deploy-baseline,matchConditions 只匹配名称为 vendor-agent 的对象,expiresAt 为从现在起 30 天之后(RFC3339,UTC)。应用之后,给 vendor-agent 添加注解 platform.labhub.io/reviewed=true。
  7. 在 team-legacy 中创建 PolicyException old-cron-requests,保存到 /root/cnpa-pol/expired.yaml。目标是名称为 old-cron 的对象,策略为 tenant-deploy-baseline,expiresAt 为比现在早一天。然后在 team-legacy 中用服务端 dry-run 创建一个既没有 owner 标签也没有 requests 的 Deployment old-cron(镜像 registry.k8s.io/pause:3.10),并把输出(包括标准错误)保存到 /root/cnpa-pol/expired.txt。
  8. 统计三个租户命名空间的 PolicyReport 中 tenant-deploy-baseline 的结果,写入 /root/cnpa-pol/compliance.json。键为 pass、fail、skip(数量)、rate_pct(pass / (pass + fail) × 100,保留一位小数,分母为 0 时为 100.0)和 exceptions(当前尚未过期的 PolicyException,写成 네임스페이스/이름 字符串(占位符依次为命名空间与名称),并排序的数组)。

参考

策略之前的三个团队

编写并应用 /root/cnpa-pol/tenants.yaml,其中包含三个命名空间和四个 Deployment。给命名空间 team-pay、team-legacy、team-vendor 分别添加标签 platform.labhub.io/tenant,取值依次为 pay、legacy、vendor。所有 Deployment 的 replicas 均为 1,容器名称为 app,镜像为 registry.k8s.io/pause:3.10,元数据和选择器标签为 app.kubernetes.io/name: <이름>(占位符为 Deployment 名称)。team-pay/checkout 同时具有元数据标签 app.kubernetes.io/owner: pay 和 requests(cpu 10m,memory 16Mi);team-legacy/report-gen 只有 requests;team-legacy/batch-sync 只有 owner 标签(legacy);team-vendor/vendor-agent 两者都没有。

在引入策略引擎之前,集群里已经混有违反规则的工作负载。这一步不会拦截任何东西,所以四个 Deployment 都应创建成功。多个对象可以用 --- 连接,放在同一个文件中。

先统计,不拦截

编写并应用 /root/cnpa-pol/policy.yaml,其中是 ValidatingPolicy(policies.kyverno.io/v1)tenant-deploy-baseline。设置 validationActions: [Audit] 和 evaluation.background.enabled: true;matchConstraints 针对 apps/v1 deployments 的 CREATE 和 UPDATE,并用 namespaceSelector 只选中带有标签 platform.labhub.io/tenant 的(Exists)命名空间。按以下顺序设置两条验证。① 元数据标签中存在 app.kubernetes.io/owner,消息为 Deployment 에 app.kubernetes.io/owner 라벨이 필요합니다(韩文,意为“Deployment 需要 app.kubernetes.io/owner 标签”);② 所有容器都有 requests 的 cpu 和 memory,消息为 모든 컨테이너에 cpu·memory requests 가 필요합니다(韩文,意为“所有容器都需要 cpu 和 memory 的 requests”)。

ValidatingPolicy 的验证是 CEL 表达式,object 是被请求的对象。用 '키' in 맵(占位符依次为键与映射)判断键是否存在,对可能不存在的字段要先用 has() 检查。针对列表中所有元素的条件用 .all(c, ...)。Audit 是不拒绝、只留下结果的行为。请等待策略的 status.conditionStatus.ready 变为 true。如果策略在后续步骤中被改为 Deny,评分器也会接受。

原本不知道的违规在报告中显现了

读取 Kyverno 生成的 PolicyReport,把 tenant-deploy-baseline 结果为 fail 的 Deployment 以数组形式写入 /root/cnpa-pol/violations.json。每个元素包含 namespace、name、uid(报告的 scope.uid)和 message(该结果的消息)。

策略就绪后,后台评估会在几秒内为每个命名空间生成 PolicyReport。一份报告对应一个资源,scope 中是目标对象,results 中是各策略的结果。先用 kubectl get policyreport -A 查看摘要,再用 -o json 读取详细内容。两条规则都违反的资源,其消息来自先失败的那条验证。

一启用策略,紧急补丁就被拦住了

把策略的 validationActions 改为 [Deny]。然后模拟 legacy 团队的紧急补丁,执行 kubectl -n team-legacy set image deploy/batch-sync app=registry.k8s.io/pause:3.9,并把输出(包括标准错误)保存到 /root/cnpa-pol/blocked.txt。接着执行 kubectl -n team-legacy scale deploy/batch-sync --replicas=2,并在 /root/cnpa-pol/deny-effects.json 中写入 image_update_denied、scale_allowed(布尔值)和 batch_sync_image(batch-sync 当前的镜像)。

Deny 评估的不只是新建对象,也包括对已经违反规则的对象的更新请求。只有更新后的整个对象都符合规则,才会被接受。scale 请求发往的是 scale 子资源而不是 Deployment 本体,所以它与规则选中的资源不同。已经在运行的 Pod 不会再次经过准入。

符合规则之后,同样的补丁通过了

让 legacy 团队的两个 Deployment 符合规则。给 report-gen 添加元数据标签 app.kubernetes.io/owner: legacy,给 batch-sync 的容器 app 加上 requests(cpu 50m,memory 64Mi)。然后重新执行第 4 步中被拦截的 set image ... app=registry.k8s.io/pause:3.9,使其通过,并等待这两个 Deployment 的 PolicyReport 结果变为 pass。

被拒绝的请求并没有保存,所以必须先提交使其符合规则的变更。这个变更本身产生的对象是符合规则的,因此在 Deny 下也会被接受。添加 requests 最短的办法是 kubectl set resources。资源发生变化时,报告会重新评估。报告名称就是资源 uid,所以更新的是同一份报告。

对无法修复的外部清单,授予有期限的例外

vendor-agent 是供应商提供的清单,暂时无法修改。首先把 kyverno-admission-controller Deployment 的参数 --enablePolicyException=false 改为 --enablePolicyException=true,并等待滚动更新完成。然后在 /root/cnpa-pol/exception.yaml 中,于 team-vendor 命名空间创建 PolicyException(policies.kyverno.io/v1)vendor-agent-requests。policyRefs 指向 ValidatingPolicy tenant-deploy-baseline,matchConditions 只匹配名称为 vendor-agent 的对象,expiresAt 为从现在起 30 天之后(RFC3339,UTC)。应用之后,给 vendor-agent 添加注解 platform.labhub.io/reviewed=true。

这个安装默认关闭例外功能。在关闭的状态下创建例外,只会出现警告,准入仍然拒绝。用 JSON 补丁修改 args 数组中的那个元素即可(可以用 jq 找到索引)。例外要只选中必要的对象,范围要窄,并设置过期日,这样原则才不会悄悄瓦解。日期可以用 date -u -d '+30 days' +%Y-%m-%dT%H:%M:%SZ 生成。评分器还会检查同一命名空间中的其他名称是否仍被拒绝。

过期的例外什么都保护不了

在 team-legacy 中创建 PolicyException old-cron-requests,保存到 /root/cnpa-pol/expired.yaml。目标是名称为 old-cron 的对象,策略为 tenant-deploy-baseline,expiresAt 为比现在早一天。然后在 team-legacy 中用服务端 dry-run 创建一个既没有 owner 标签也没有 requests 的 Deployment old-cron(镜像 registry.k8s.io/pause:3.10),并把输出(包括标准错误)保存到 /root/cnpa-pol/expired.txt。

过期不是例外对象消失,而是准入不再使用这个例外。对象仍然保留,因此谁在何时之前允许了什么,会作为记录留存。dry-run 不会保存,但仍会经过准入 Webhook。这一步中 old-cron 不应被真正创建出来。

在报告中计算合规率

统计三个租户命名空间的 PolicyReport 中 tenant-deploy-baseline 的结果,写入 /root/cnpa-pol/compliance.json。键为 pass、fail、skip(数量)、rate_pct(pass / (pass + fail) × 100,保留一位小数,分母为 0 时为 100.0)和 exceptions(当前尚未过期的 PolicyException,写成 네임스페이스/이름 字符串(占位符依次为命名空间与名称),并排序的数组)。

因例外而被跳过的资源会报告为 skip,而不是 fail。合规率如何处理 skip 由组织决定,这里从分母中排除。是否过期,通过比较 expiresAt 与当前时间来判断。报告可能更新得较晚,请等到 vendor-agent 变为 skip 之后再统计。评分器会重新统计同一批报告。