饮墨

子安饮墨馀三斗,留与卿儿作赋来

Kubernetes ValidatingAdmissionPolicy:用 CEL 原生策略引擎替代 OPA Gatekeeper

痛点

在 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,建议分阶段迁移:

  1. 盘点现有策略kubectl get constrainttemplate 列出所有 Rego 模板
  2. 按复杂度分类:简单策略(字段校验、白名单)优先迁移到 VAP;复杂策略(需要跨资源关联查询)暂时保留在 Gatekeeper
  3. 并行运行:新策略用 VAP 的 Warn 模式部署,同时保留 Gatekeeper 策略
  4. 验证等效性:对比两者的拦截结果,确认一致后移除 Gatekeeper 策略
  5. 最终清理:所有可迁移策略完成后,评估是否可以完全卸载 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 的原生方案,减少第三方组件运维负担
您还没有登录,请登录后发表评论。