Kyverno 策略冲突:为何 “Last-Writer-Wins” 是生产缺陷
Source: Dev.to
(请提供需要翻译的正文内容,我将为您翻译成简体中文并保留原始的格式、Markdown 语法以及技术术语。)
▶️ 介绍
Kyverno 已成为 Kubernetes 的首选策略引擎,因为它使用起来很简单:
- 策略使用 YAML 编写
- 在准入阶段运行
- 不需要自定义控制器
大多数 Kyverno 事件并非 Kyverno 本身的 bug。
它们是由于对策略执行方式的错误假设导致的。
🤔 几乎所有人都会的假设
大多数团队隐含地假设以下至少一项:
- 策略会按照它们被应用的顺序执行。
- 集群范围的策略在命名空间范围的策略之前评估。
- Kyverno 能确定性地解决冲突。
⚠️ 这些假设都不成立。
Kyverno 提供:
- 没有策略优先级。
- 跨策略没有保证的执行顺序。
- 对变更之间的冲突没有检测。
🧠 Kyverno 实际处理 Admission 请求的方式
对于 单个 admission 请求,Kyverno 会在三个不同的阶段评估策略。这些阶段的顺序是 有保证 的。
| 阶段 | 会发生什么 | 保证 |
|---|---|---|
| 1️⃣ Mutate | 执行所有匹配的 mutate 规则。 | ✅ |
| 2️⃣ Validate | 评估所有匹配的 validate 规则。 | ✅ |
| 3️⃣ Generate | 处理所有匹配的 generate 规则。 | ✅ |
不 被保证的内容
- 不同 mutate 策略之间的顺序 – Kyverno 不 定义哪个 mutate 策略先运行。
- 属于不同策略的 mutate 规则之间的顺序 – 相对执行顺序未定义。
注意:
- 在 单个策略 内,规则会 自上而下 依次处理。
- 在 多个策略 之间,顺序 明确未定义。
Source: …
📌 具体的失败模式
🧪 示例 1 – Mutate 与 Mutate:问题悄然出现
⚠️ 这些示例基于 Kyverno v1.17 编写,策略在同一时间创建。
两个 MutatingPolicy 对象匹配同一个 Pod。
策略 A
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: enforce-non-root
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
spec: Object.spec{
securityContext: Object.spec.securityContext{
runAsNonRoot: true
}
}
}
策略 B
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: force-run-as-user-0
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
spec: Object.spec{
securityContext: Object.spec.securityContext{
runAsUser: 0
}
}
}
Kyverno 的观察结果
| 观察点 | 细节 |
|---|---|
| 匹配 | 两个策略都匹配同一个 Pod。 |
| 操作 | 两者都执行变更。 |
| 结果 | Kyverno 会同时应用这两个补丁,但跨策略的执行顺序 没有 保证。 |
🧪 示例 2 – Mutate 与 Validate:自我导致的 Admission 失败
场景
一个策略对资源进行变更以强制默认值;另一个策略验证同一字段,假设它处于不同的期望状态。两个策略单独来看都是正确的。
MutatingPolicy – 添加默认标签
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: add-default-app-label
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
metadata: Object.metadata{
labels: Object.metadata.labels{
app: "default"
}
}
}
ValidatingPolicy – 要求特定标签值
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-app-frontend
spec:
validationActions:
- Deny
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- message: "Label app=frontend is required"
expression: "object.metadata.?labels.app.orValue('') == 'frontend'"
实际发生的情况
- 变更 先执行,将
app=default写入。 - 验证 随后执行。
- 验证失败,因为缺少必需的标签
app=frontend。
❗ Admission 被拒绝 – 即使原始的 Pod 本可以满足验证规则。
📉 为什么规模扩大时会更糟
当你只有一两个策略时,交互关系容易推理。随着策略数量的增长,会出现多个问题:
大规模时的常见问题
- 多个团队修改相同字段
- 校验规则编码了不同的假设
- 作用域静默重叠
典型故障
- 它们 并不总是立即发生。
- 它们往往 仅在特定工作负载下出现。
- 它们 难以追溯到策略交互,而不是策略本身的逻辑。
为什么会发生
Kyverno 独立评估每个策略。它 不会 推断意图或解决策略之间的冲突,这导致了上述问题。
💡 关键洞察
Kyverno 独立评估策略;它 不会 在策略之间推断或调和意图。当意图分散在多个策略中时,Kyverno 将强制执行 所有 匹配的规则,即使它们的组合效果相互矛盾。