在 Kubernetes 准入控制体系中,ValidatingAdmissionPolicy 是从 1.30 起正式 GA 的“原生策略”能力:它用 CEL 表达式在请求写入 etcd 之前拦截并校验,无需再为 OPA/Kyvernus 部署额外 Webhook。本文用四个生产级示例,带你把集群的边界真正守起来。
一、为什么需要准入控制
RBAC 管的是“谁能调哪个 API”,而准入控制管的是“请求长什么样才算合法”。一个拥有 create pod 权限的用户,依然可能提交一个 privileged: true 的特权容器、一个没有资源限制的“饿死邻居”Pod,或来自未知仓库的镜像。这类风险 RBAC 看不住,只能靠准入层在请求抵达 etcd 前拦下来。关于 Pod/Deployment 的基础对象模型,可回顾 Kubernetes 入门:Pod、Deployment、Service。
过去做这件事几乎只有一条路:写(或引入)一个 ValidatingWebhook,自己起服务、管证书、扛可用性。Kubernetes 1.30 之后,ValidatingAdmissionPolicy 把最常用的“字段校验”沉淀成控制面原生能力,用 CEL 表达规则,零额外组件。
二、ValidatingAdmissionPolicy 是什么
它由两条资源组成:ValidatingAdmissionPolicy 定义“校验什么、怎么校验”,ValidatingAdmissionPolicyBinding 定义“对谁生效”。两者解耦,意味着一条策略可以被多个 Binding 复用(比如“禁止特权容器”同时绑定到业务命名空间和预发命名空间)。
2.1 CEL:策略就是一行表达式
CEL(Common Expression Language)是一种安全、无副作用的表达式语言,Kubernetes 把请求对象暴露为 request.object,你写一段布尔表达式,返回 true 即通过。它不支持循环外的副作用,因此天然比 Webhook 轻、比 Rego 好上手。
2.2 匹配规则 matchConstraints
用 matchConstraints.resourceRules 声明对哪些 Group/Version/Resource、哪些操作(CREATE/UPDATE)生效,避免策略误伤系统组件。
2.3 审计还是强制
Binding 的 validationActions 决定动作:["Deny"] 直接拒绝;["Warn","Audit"] 只告警并记录、不阻断。新策略强烈建议先 Audit 观察再切 Deny。
三、实战一:禁止特权容器
最经典的一条规则。注意 CEL 里要用 has() 判断字段是否存在,避免 securityContext 未设置时访问字段报错。
# no-privileged.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: no-privileged
spec:
matchConstraints:
resourceRules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
request.object.spec.containers.all(c,
!has(c.securityContext.privileged) || c.securityContext.privileged != true)
message: "Privileged containers are not allowed."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: no-privileged-binding
spec:
policyName: no-privileged
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels:
env: prod
四、实战二:限制镜像只能来自内网仓库
供应链安全的第一道闸:只允许来自内部 registry 的镜像,杜绝开发者随手 docker run 来的未知镜像。镜像建议统一由 Docker 编排 流水线构建并推送到内网仓库。
# only-internal-registry.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: only-internal-registry
spec:
matchConstraints:
resourceRules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
request.object.spec.containers.all(c,
c.image.startsWith("registry.internal.example.com/"))
message: "Only images from registry.internal.example.com are allowed."
五、实战三:强制每个容器都设资源 requests/limits
没有资源限制的 Pod 会触发节点资源抢占,是线上“邻居雪崩”的常见根因。用一条策略强制 CPU/内存的 requests 与 limits 全部存在。
# require-resources.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-resources
spec:
matchConstraints:
resourceRules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
request.object.spec.containers.all(c,
has(c.resources.requests.cpu) && has(c.resources.requests.memory) &&
has(c.resources.limits.cpu) && has(c.resources.limits.memory))
message: "Every container must set cpu/memory requests and limits."
六、实战四:按命名空间放行
策略与 Binding 解耦的价值在这里体现:同一条“禁止特权容器”,可以只绑定到带 env=prod 标签的命名空间,而 pre 环境放开做实验。再结合 excludedNamespaces 把 kube-system 等系统命名空间排除,避免误伤。
# binding 排除系统命名空间
spec:
policyName: no-privileged
validationActions: ["Deny"]
matchResources:
excludedNamespaces: ["kube-system", "kube-public"]
namespaceSelector:
matchLabels:
env: prod
七、与 OPA、Kyverno、Webhook 怎么选
原生策略不是要取代一切,而是覆盖“80% 的简单校验”。复杂策略(需要查外部数据、模板复用、跨资源关联)仍适合 OPA/Kyverno。
| 方案 | 额外部署 | 策略语言 | 适合场景 |
|---|---|---|---|
| ValidatingAdmissionPolicy | 否(控制面内置) | CEL | 常规字段校验、替代简单 Webhook |
| OPA Gatekeeper | 是 | Rego | 复杂策略、模板复用、外部数据 |
| Kyverno | 是 | 类 YAML 策略 | 不想学 Rego、策略即 YAML |
| 自定义 Webhook | 是 | 任意 | 需调外部系统/自研逻辑 |
八、生产落地检查清单
| 检查项 | 建议 |
|---|---|
| 特性门(<1.30) | 开启 ValidatingAdmissionPolicy 特性门 |
| 先 Audit 后 Deny | 新策略先用 [“Audit”,”Warn”] 观察再切 Deny |
| Binding 范围 | 用 namespaceSelector 限定生效命名空间 |
| 排除系统组件 | excludedNamespaces 排除 kube-system |
| 失败策略 | failurePolicy 设 Fail,避免漏放 |
| 版本兼容 | 确认 kubectl/控制面 ≥ 1.30 |
九、上线前必做的排错
策略不生效多半是 Binding 没匹配到、或 CEL 表达式对字段判断少了 has()。用下面几条命令自测:
# 查看策略与绑定是否就绪
kubectl get validatingadmissionpolicy
kubectl get validatingadmissionpolicybinding
# 看某条策略的详情与状态
kubectl describe validatingadmissionpolicy no-privileged
# 故意提交一个特权 Pod,观察拒绝信息
kubectl apply -f privileged-pod.yaml
# Error from server: admission webhook "..." denied the request: Privileged containers are not allowed.
建议把这些 YAML 纳入 Git,配合 ArgoCD GitOps 做版本化管理,并在 GitHub Actions 的 CI 里对 YAML 做基本 lint,避免策略随手写随丢。需要更细的网络层隔离时,可叠加 Cilium 的 eBPF 网络策略。
十、小结
ValidatingAdmissionPolicy 用控制面内置的 CEL,把“禁特权容器、限镜像来源、强制资源限制”这类高频校验做到了零额外组件、低延迟、易版本化。它是 Kubernetes 准入控制从“自建 Webhook”走向“平台原生”的关键一步:先用它覆盖 80% 的常规规则,把 OPA/Kyverno 留给真正复杂的场景,集群边界就能既稳又轻。




