validate — 模式、deny,以及匹配范围
一句话总结
验证策略的质量不取决于条件表达式,而取决于匹配范围和失败消息。
为什么需要它
第一次编写策略的人,往往先考虑如何表达条件。但真正引发事故的通常不是条件。条件写错会在测试中暴露,**匹配范围写错却可能什么都不发生,因此无人察觉。**面向整个集群的策略可能拦截 kube-system 的系统 Pod,妨碍节点启动;反过来,也可能遗漏一个命名空间,让那里连续几个月处于规则之外。
因此,编写验证策略的顺序不是先写条件,而是先确定范围:要作用于哪些种类(kinds)、哪些命名空间(namespaces)、哪些标签(selector)。还必须同时回答一个配套问题:**要排除什么(exclude)。**集群组件、策略引擎本身,以及尚未完成整理的遗留命名空间,最好从一开始就排除。
它如何运作
验证规则有多种表达条件的方式,表达能力与可读性通常成反比。
| 方式 | 使用场景 | 特点 |
|---|---|---|
validate.pattern |
字段必须存在/值必须符合某种形式 | 写法与对象形状相同,容易阅读 |
validate.deny.conditions |
列表比较、字符串匹配等无法用模式表达的条件 | 明确拒绝。any 表示任一为真,all 表示全部为真 |
validate.cel |
需要计算的条件 | 表达能力强,但必须确保整个团队都能读懂 |
validate.foreach |
对容器等列表中的每一项 | 逐项判断,消息也按项给出 |
模式可以附带运算符。?* 表示“任意非空值”,* 表示“任意值(也可以不存在)”,X|Y 表示二选一,!X 表示否定,也支持 >=256Mi 这样的数值比较。在 metadata.labels 下写 team: "?*",就表示“必须有 team 标签,且值不能为空”。
锚点是改变模式含义的前缀。条件锚点 () 表示“仅当这个值匹配时才检查下面的内容”,相等锚点 =() 表示“如果这个键存在,其值必须相同”,否定锚点 X() 表示“不得存在这个键”。尤其常见的错误,是把否定锚点误认为值比较。X(privileged) 并不是要求 privileged 为 false,而是要求该键不存在。
preconditions 和 deny.conditions 形状相似,因此经常被混淆。两者的区别不在结果,而在于是否进行评估。如果 preconditions 为假,该规则根本不会执行,而是跳过(skip)。如果 deny.conditions 为真,则表示规则已经执行,结果是拒绝。它们在报告中的表现也不同——前者不留痕迹,后者会记录为 fail。因此,“此规则只应用于 CREATE 请求”之类的条件应写在 preconditions 中,而“此镜像不允许使用”则应写成 deny。
最后是强制级别。过去由策略级别的 spec.validationFailureAction 统一支配策略中的所有规则。现在正逐步迁移到规则级别的 validate.failureAction,可选值有 Enforce(阻止违规请求)和 Audit(允许通过,但记录在报告中)。采用规则级别后,同一策略中可以让一部分规则已经强制执行,另一部分仍只作观察。这意味着无需拆分策略,就能悄悄加入新规则并观察数天。
在实际工作中会遇到的情况
**第一,消息占策略的一半。**被拒绝的人无法阅读策略 YAML,他看到的只有 kubectl apply 输出的一行文字。“Validation error”这样的消息会制造咨询,而“Pod 必须有 team 标签——请填写所属团队”这样的消息能让他自行修正。消息质量就是策略的运营成本。
**第二,什么都匹配不到的策略。**匹配块中的拼写错误或错误的命名空间,常会让整个策略形同虚设。发现它只有一种办法:**同时提交应当通过的资源和应当被拦截的资源。**只验证通过情形,无法区分“什么都匹配不到的策略”和“正常运行的策略”。
第三,有时例外必须先于策略准备。把新规则应用到已经运行的工作负载,必然会发现违规项。如果等到全部修复后再启用,策略可能永远无法落地。更好的做法是用 PolicyException 将例外精确限定到策略名、规则名和资源名,把例外作为文档保留下来,并管理这个列表逐步缩短。为整个策略设置例外,等同于关闭策略。
后续实验要做什么
在 /root/policy/validate/require-labels.yaml 中编写要求必需标签的 ClusterPolicy,并明确指定匹配和排除范围。然后分别提交可通过的 Pod 与会被拦截的 Pod,确认判定结果确实不同。接着编写限制镜像仓库的 deny 规则和 preconditions,最后创建一次检查多个资源的 PolicyReport,以及范围很窄的 PolicyException。本环境中的集群不会替你拦截请求,因此要在本地运行 kyverno apply 来确认判定结果。