Kubernetes 策略工具的语言天然带有“阻断”色彩:Gatekeeper 像一道门,准入控制器负责拦截,策略失败通常意味着拒绝请求;Kyverno 的 validationFailureAction: Enforce 虽然听起来更中性,最终结果仍然是部署失败。
问题不在于阻断本身,而在于平台团队是否把“拒绝”当成了唯一手段。成熟的策略体系更像公路护栏:默认帮助团队保持在安全路径上,提前暴露偏差,只在风险足够明确时才真正封路。
策略不应该只有允许和拒绝
准入阶段的强制策略适合处理确定性高、后果严重且修复方式清晰的问题,例如:
- 禁止特权容器;
- 禁止使用明显危险的宿主机挂载;
- 限制生产环境可使用的镜像仓库;
- 阻止创建违反组织安全基线的公网入口。
但很多治理要求没有这么清晰。成本标签缺失、推荐副本数、镜像版本规范、资源配额建议等问题,通常更适合先报告和提醒。如果一开始就阻断,平台团队很容易制造三类副作用:
- 误报直接变成事故。 策略规则或资源匹配范围有误时,所有部署都可能停摆。
- 开发者只看到错误,不知道下一步。 一句“admission denied”不能代替迁移文档、修复命令和责任人信息。
- 例外转入地下。 当正式豁免流程太慢,团队会复制资源、换命名空间,甚至寻找绕过控制面的路径。
因此,可以把策略分成三个级别:
| 级别 | 行为 | 适合的问题 |
|---|---|---|
| Observe | 记录、生成报告和指标 | 新规则、资产盘点、影响未知的问题 |
| Warn | 在 CI、CLI 或部署界面提示,但不阻断 | 有明确建议、短期允许不合规的问题 |
| Enforce | 在准入阶段拒绝请求 | 高风险、低误报、已有修复路径的问题 |
策略从 Observe 升级到 Enforce,应该是一次有数据支持的发布,而不是在 YAML 中改一个字段就直接上线。
用 Kyverno 演示“先审计,后强制”
下面是一个可以改造的最小示例。假设集群已经安装 Kyverno,目标是在 policy-demo 命名空间中要求 Pod 包含 app.kubernetes.io/name 和 owner 标签。示例使用常见的 ClusterPolicy 写法;不同 Kyverno 版本的字段可能有变化,上线前可运行 kubectl explain clusterpolicy.spec 核对当前 CRD。
先创建测试命名空间:
kubectl create namespace policy-demo
保存以下内容为 required-labels.yaml。关键点是先使用 Audit,让不合规资源被记录,而不是被拒绝:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-workload-labels
annotations:
policies.kyverno.io/title: Require workload ownership labels
policies.kyverno.io/category: Platform Governance
spec:
validationFailureAction: Audit
background: true
rules:
- name: check-required-labels
match:
any:
- resources:
kinds:
- Pod
namespaces:
- policy-demo
validate:
message: >-
Pods must define app.kubernetes.io/name and owner labels.
See the internal workload onboarding guide or contact the platform team.
pattern:
metadata:
labels:
app.kubernetes.io/name: '?*'
owner: '?*'
应用策略并创建一个不合规的 Pod:
kubectl apply -f required-labels.yaml
kubectl run unlabeled \
--namespace policy-demo \
--image=nginx:1.27 \
--restart=Never
kubectl get pod -n policy-demo
kubectl get policyreport -n policy-demo
在审计模式下,Pod 应该仍能创建,违规信息则进入策略报告。平台团队可以先观察:有多少工作负载不合规、涉及哪些团队、规则是否误伤自动生成的 Pod,以及修复成本有多高。
确认规则稳定并完成通知后,再切换为强制模式:
kubectl patch clusterpolicy require-workload-labels \
--type=json \
-p='[{"op":"replace","path":"/spec/validationFailureAction","value":"Enforce"}]'
现在可以分别验证失败和成功路径:
kubectl delete pod unlabeled -n policy-demo --ignore-not-found
# 预期:被策略拒绝
kubectl run unlabeled \
--namespace policy-demo \
--image=nginx:1.27 \
--restart=Never
# 预期:创建成功
kubectl run labeled \
--namespace policy-demo \
--image=nginx:1.27 \
--restart=Never \
--labels=app.kubernetes.io/name=demo,owner=platform
实际推广时,不要直接把匹配范围从测试命名空间扩大到整个集群。可以按非生产环境、低风险业务、生产业务的顺序逐步启用,并为每一步设置回滚条件。
Guardrail 还需要一条“铺好的路”
策略错误消息再清楚,也只是指出哪里不能走。平台团队还需要提供一条成本更低的合规路径,例如:
- 在 Helm chart 或项目模板中默认生成必需标签;
- 在 CI 中提前运行策略测试,不等请求到达集群才报错;
- 给错误信息附上修复示例、文档位置和支持渠道;
- 在内部开发者平台中展示策略报告,而不是只留在 Kubernetes CRD 中;
- 对常见例外提供有时限、可审计的申请流程。
一个好的例外至少应包含以下信息:
exception:
policy: require-workload-labels
namespace: legacy-payments
owner: payments-team
reason: migration tool cannot add labels yet
ticket: SEC-1842
expiresAt: 2025-06-30
这段 YAML 是一种流程数据模型示例,并非某个策略引擎的通用 API。重点是例外必须有负责人、原因、追踪记录和到期时间。永久豁免如果没有复查机制,最终会把策略掏空。
强制策略也是生产依赖
一旦策略进入准入链路,它就不再只是安全配置,而是 Kubernetes API 可用性路径的一部分。平台团队需要像维护生产服务一样考虑:
- 策略评估增加了多少准入延迟;
- webhook 不可用时采用 fail-open 还是 fail-closed;
- 策略升级失败时如何快速回滚;
- 是否会匹配系统命名空间、控制器生成的资源或紧急修复任务;
- 策略报告和拒绝次数是否进入监控;
- 紧急豁免是否有审计记录。
failurePolicy: Fail 能在策略服务不可用时维持严格控制,却可能阻塞整个集群的资源变更;Ignore 能优先保证可用性,但会留下短暂的控制空窗。选择取决于策略保护的风险等级,不能为所有 webhook 使用同一个答案。
建议至少跟踪这些指标:
- 每条策略的审计命中数与拒绝数;
- 按命名空间或团队统计的违规分布;
- 例外数量、平均存续时间和过期数量;
- admission webhook 的错误率与 P95 延迟;
- 策略从 Audit 升级到 Enforce 的周期;
- 强制启用后部署失败率是否异常上升。
上线前的检查清单
平台策略可以阻断,但不应以阻断为设计起点。准备将一条规则升级为强制策略时,可以逐项确认:
- 规则解决的是明确风险,而不只是风格偏好;
- 已在 Audit 或 dry-run 模式中运行足够时间;
- 已处理控制器生成资源、系统命名空间等边界情况;
- 拒绝消息包含具体原因和修复方法;
- 项目模板和 CI 已提供合规默认值;
- 例外有负责人、审批记录和失效时间;
- 有监控、回滚命令和紧急处置流程;
- 受影响团队知道何时开始强制执行。
真正有效的 guardrail 并不追求“拒绝了多少部署”,而是减少开发者走错路的机会。只有那些高风险、低歧义并且已有清晰替代路径的问题,才值得在准入阶段成为一道真正的门。