痛点
在 Kubernetes 集群中实施安全策略(禁止特权容器、强制资源限制、镜像来源白名单等),长期以来的标准方案是 OPA Gatekeeper 或 Kyverno。它们功能强大,但也带来了额外的运维负担:
- 额外组件部署:Gatekeeper 本身是一个 Webhook + Controller,需要独立维护、升级、监控
- Rego 学习曲线:OPA 的策略语言 Rego 对大多数运维工程师来说并不直观,排查策略问题时效率低
- Webhook 故障影响面大:Admission Webhook 挂掉可能阻塞整个集群的资源创建(failurePolicy=Fail 时)
- 资源开销:Gatekeeper 的 Audit + Webhook 进程在大集群中 CPU/内存消耗不低
从 Kubernetes 1.30 开始,ValidatingAdmissionPolicy(VAP)正式 GA。它是内置在 API Server 中的策略引擎,使用 CEL(Common Expression Language)编写规则,无需任何外部组件。
方案
用 Kubernetes 原生的 ValidatingAdmissionPolicy + CEL 表达式,在 API Server 层面直接执行准入策略,彻底消除对外部 Webhook 的依赖。
核心优势:
| 维度 | OPA Gatekeeper | ValidatingAdmissionPolicy |
|---|---|---|
| 部署复杂度 | 需要额外 Deployment + Webhook | 零额外组件,API Server 内置 |
| 策略语言 | Rego(专用语言) | CEL(类 Go 表达式,简洁直观) |
| 延迟 | Webhook 网络调用(1-5ms) | 进程内执行(<0.1ms) |
| 故障影响 | Webhook 不可用时可能阻塞集群 | 无额外故障点 |
| 审计能力 | 内置 Audit Controller | 通过 Audit Annotation 实现 |
| 适用版本 | K8s 1.16+ | K8s 1.30+(GA) |
实操步骤
第一步:确认集群版本和功能启用
# 确认 Kubernetes 版本 >= 1.30
kubectl version --short
# 验证 ValidatingAdmissionPolicy API 已可用
kubectl api-resources | grep ValidatingAdmissionPolicy
输出应包含:
validatingadmissionpolicies admissionregistration.k8s.io/v1 false ValidatingAdmissionPolicy
validatingadmissionpolicybindings admissionregistration.k8s.io/v1 false ValidatingAdmissionPolicyBinding
第二步:编写策略 — 禁止特权容器
创建一个策略,禁止所有 namespace 中运行特权容器(securityContext.privileged: true):
# deny-privileged-containers.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: deny-privileged-containers
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments", "replicasets", "daemonsets", "statefulsets"]
validations:
- expression: >-
object.spec.?containers.orValue([]).all(c,
!c.?securityContext.?privileged.orValue(false)
)
message: "特权容器被禁止。请移除 securityContext.privileged: true"
reason: Forbidden
- expression: >-
object.spec.?initContainers.orValue([]).all(c,
!c.?securityContext.?privileged.orValue(false)
)
message: "特权 initContainer 被禁止。请移除 securityContext.privileged: true"
reason: Forbidden
- expression: >-
object.spec.?template.?spec.?containers.orValue([]).all(c,
!c.?securityContext.?privileged.orValue(false)
)
message: "Workload 中特权容器被禁止"
reason: Forbidden
第三步:创建策略绑定
策略本身不会生效,需要通过 Binding 绑定到具体的目标范围:
# deny-privileged-binding.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: deny-privileged-binding
spec:
policyName: deny-privileged-containers
validationActions:
- Deny
matchResources:
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "monitoring"] # 排除系统 namespace
应用策略:
kubectl apply -f deny-privileged-containers.yaml
kubectl apply -f deny-privileged-binding.yaml
第四步:验证策略生效
# 尝试创建一个特权 Pod — 应该被拒绝
kubectl run test-privileged --image=nginx \
--overrides='{"spec":{"containers":[{"name":"nginx","image":"nginx","securityContext":{"privileged":true}}]}}'
# 预期输出:
# Error from server (Forbidden): admission webhook denied the request:
# 特权容器被禁止。请移除 securityContext.privileged: true
实用示例:强制镜像来源白名单
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: restrict-image-registries
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
object.spec.containers.all(c,
c.image.startsWith("harbor.internal.com/") ||
c.image.startsWith("gcr.io/my-project/")
)
message: "镜像必须来自 harbor.internal.com 或 gcr.io/my-project"
reason: Forbidden
实用示例:强制设置资源 requests
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-resource-requests
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: >-
object.spec.template.spec.containers.all(c,
has(c.resources) && has(c.resources.requests) &&
has(c.resources.requests.cpu) && has(c.resources.requests.memory)
)
message: "所有容器必须设置 resources.requests.cpu 和 resources.requests.memory"
reason: Forbidden
避坑指南
1. CEL 表达式中 Optional 字段处理
CEL 中访问可能不存在的字段时,必须用 ? 操作符(Optional chaining)配合 .orValue() 提供默认值,否则在字段不存在时会报运行时错误:
# ❌ 错误写法 — securityContext 不存在时会报错
object.spec.containers.all(c, !c.securityContext.privileged)
# ✅ 正确写法 — 安全处理 Optional 字段
object.spec.containers.all(c, !c.?securityContext.?privileged.orValue(false))
2. 策略生效范围要精确
validationActions 支持三种模式:
- Deny:直接拒绝请求
- Warn:允许请求但返回警告信息(适合灰度上线)
- Audit:仅记录到审计日志
建议上线流程:先用 Warn 观察 1-2 周,确认无误报后再切换为 Deny。
3. 与 Pod Security Admission (PSA) 的关系
PSA 提供了 privileged/baseline/restricted 三个预设级别,适合快速落地基础安全基线。ValidatingAdmissionPolicy 适合实现更精细的自定义策略。两者可以共存互补:
- PSA → 覆盖基础安全基线(namespace 级别)
- VAP → 实现组织特定的合规规则(镜像白名单、标签规范、资源限制等)
迁移建议:从 Gatekeeper 到 VAP
如果你的集群已在使用 OPA Gatekeeper,建议分阶段迁移:
- 盘点现有策略:
kubectl get constrainttemplate列出所有 Rego 模板 - 按复杂度分类:简单策略(字段校验、白名单)优先迁移到 VAP;复杂策略(需要跨资源关联查询)暂时保留在 Gatekeeper
- 并行运行:新策略用 VAP 的
Warn模式部署,同时保留 Gatekeeper 策略 - 验证等效性:对比两者的拦截结果,确认一致后移除 Gatekeeper 策略
- 最终清理:所有可迁移策略完成后,评估是否可以完全卸载 Gatekeeper
注意:VAP 目前不支持 Mutation(修改请求),如果你依赖 Gatekeeper 的 Assign/ModifySet 功能,仍需保留或等待 MutatingAdmissionPolicy(预计 K8s 1.32+ Beta)。
总结
- ValidatingAdmissionPolicy 是 K8s 1.30+ 的原生策略引擎,零外部依赖、低延迟、CEL 语法直观
- 适合替代 OPA Gatekeeper 处理 80% 的常见准入策略场景(字段校验、白名单、合规检查)
- 上线建议:先
Warn观察,再Deny执行;与 PSA 配合实现分层安全 - 复杂场景(跨资源关联、Mutation)仍需 Gatekeeper/Kyverno,但覆盖面会随版本迭代持续扩大
- 对于新建集群,优先考虑 VAP + PSA 的原生方案,减少第三方组件运维负担