10.4 K8s 策略引擎入门——Kyverno 与 OPA Gatekeeper

预计阅读时间:14 分钟

📖 目录

K8s RBAC 控制「谁能做什么」,但不控制「资源必须满足什么条件」。策略引擎(Policy Engine)在 Admission 阶段拦截不符合规则的资源创建——比如禁止用 latest 标签、必须设置资源限制、镜像必须来自可信仓库。

学习目标

学完本章,你将能够:

  • 理解 Kubernetes Admission 控制机制与策略引擎的拦截原理
  • 掌握 Kyverno ClusterPolicy 的 Validate / Mutate / Generate 策略编写
  • 掌握 OPA Gatekeeper 的 ConstraintTemplate 与 Rego 策略语法
  • 理解 Enforce 与 Audit 两种执行模式的差异及切换策略
  • 掌握 PolicyReport 审计结果的查看与策略排错方法
  • 理解「策略即代码」的 Git 管理与 CI/CD 部署方式

前置知识

方案对比

KyvernoOPA Gatekeeper
语言YAML(声明式)Rego(策略语言)
学习曲线低(K8s 原生 YAML)高(需学 Rego)
策略类型Validate / Mutate / Generate / VerifyImagesValidate / Mutate(Audit / Enforce)
生态K8s 原生,CNCF 毕业项目CNCF 毕业项目,多平台通用
适用K8s 为主的团队多平台(K8s + API 网关 + 数据库)
选型建议 如果策略主要针对 K8s 资源,优先选 Kyverno——YAML 写策略更直观。如果需要跨平台策略(K8s + API + 数据库),OPA 更通用。

准入控制原理——策略引擎在哪里生效

Kubernetes API Server 对每个写请求(create/update/delete)都要经过 Admission 阶段。策略引擎以 Admission Webhook 方式注册到 API Server,请求在资源持久化之前被拦截检查:

用户 / kubectl / 控制器
      │
      ▼
API Server ──► 认证(Authentication) ──► 授权(Authorization) ──► 准入(Admission)
                                                                     │
                                     MutatingAdmissionWebhook(Kyverno 变更/默认值注入)
                                                                     │
                                     ValidatingAdmissionWebhook(Kyverno / OPA 校验)
                                                                     │
                                     ┌─ 通过 ──► 持久化到 etcd ──► 返回响应
                                     └─ 拒绝 ──► 返回 403(含策略名与消息)

Webhook 收到的是 AdmissionReview 请求:包含 request.object(待创建资源的完整 JSON)、request.userInfo(操作者身份)、request.operation(CREATE/UPDATE/DELETE)。策略引擎据此返回 allowed: true/false。关键认知:策略只在写入时生效,运行中的容器被改造(如进入 privileged 模式)不会触发复核;对存量资源需靠 Audit 定期扫描。

1. Kyverno 安装与配置

# 安装 Kyverno
helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno -n kyverno --create-namespace

# 验证
kubectl get pods -n kyverno

Validate Policy——拦截不合规资源

# 禁止使用 latest 标签
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce  # Enforce=拦截 / Audit=仅记录
  rules:
  - name: require-image-tag
    match:
      any:
      - resources:
          kinds: ["Pod"]
    validate:
      message: "镜像标签不能使用 'latest',请指定具体版本"
      pattern:
        spec:
          containers:
          - image: "!*:latest"
---
# 所有容器必须设置资源限制
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-resource-limits
    match:
      any:
      - resources:
          kinds: ["Pod"]
    validate:
      message: "每个容器必须设置 resources.limits"
      pattern:
        spec:
          containers:
          - resources:
              limits:
                memory: "?*"
                cpu: "?*"

Mutate Policy——自动修改资源

# 自动为所有 Pod 注入安全上下文
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-security-context
spec:
  rules:
  - name: set-security-context
    match:
      any:
      - resources:
          kinds: ["Pod"]
    mutate:
      patchStrategicMerge:
        spec:
          containers:
          - (name): "*"
            securityContext:
              runAsNonRoot: true
              readOnlyRootFilesystem: true
              allowPrivilegeEscalation: false

Generate Policy——自动生成资源

# 为每个新命名空间自动创建默认 NetworkPolicy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: default-network-policy
spec:
  rules:
  - name: generate-default-deny
    match:
      any:
      - resources:
          kinds: ["Namespace"]
      generate:
      synchronize: true
      apiVersion: networking.k8s.io/v1
      kind: NetworkPolicy
      name: default-deny
      namespace: "{{request.object.metadata.name}}"
      data:
        spec:
          podSelector: {}
          policyTypes:
          - Ingress
          - Egress

VerifyImages——校验镜像签名与来源

# 校验镜像必须来自内网仓库(Validate + VerifyImages 组合)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-trusted-registry
spec:
  validationFailureAction: Enforce
  rules:
  - name: only-internal-registry
    match:
      any:
      - resources:
          kinds: ["Pod"]
    verifyImages:
    - imageReferences: ["harbor.internal.example/*"]
      required: false
    validate:
      message: "镜像必须来自 harbor.internal.example"
      pattern:
        spec:
          containers:
          - image: "harbor.internal.example/*"

# 用 cosign 公钥校验镜像签名(与 cosign01 的签名流程联动)
# CI 中 cosign sign 签名 → 准入时校验签名 + 仓库白名单双保险
#   verifyImages:
#   - imageReferences: ["*"]
#     required: true
#     mutateDigest: true
#     attestors:
#     - entries:
#       - keys:
#           publicKeys: |-
#             -----BEGIN PUBLIC KEY-----
#             MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
#             -----END PUBLIC KEY-----

排除系统命名空间

# 在每条策略的 rules 中声明 exclude,避免拦截集群自身组件
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-privileged
    match:
      any:
      - resources:
          kinds: ["Pod"]
    exclude:
      any:
      - resources:
          namespaces: ["kube-system", "kube-public", "kyverno", "gatekeeper-system"]

策略排错:从 webhook 日志看拦截详情

策略不生效或误拦时,先看 webhook 端到端链路:请求是否到达引擎、引擎是否匹配、匹配后结果如何。三个排查点:

# 1. 确认 webhook 已注册且健康
kubectl get validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg
kubectl get mutatingwebhookconfiguration kyverno-resource-mutating-webhook-cfg

# 2. 看引擎日志中的 AdmissionReview 处理记录
kubectl logs -n kyverno -l app.kubernetes.io/name=kyverno -f | grep -i reject
# 拦截时日志含 "REJECT" 或 "allow=false";误放行时查 "did not match"

# 3. 用审计模式复现问题(不改任何集群状态)
# Kyverno:策略切 Audit 后 apply 一个测试资源,看 PolicyReport 结果
# Gatekeeper:直接本地跑 Rego,与引擎判定完全一致
opa eval -i pod.yaml -d template.rego "data.k8sallowedrepos.violation"
# -i 输入资源、-d 策略文件,输出 violation 数组
先本地后集群 Rego 类策略先在本地 opa eval / opa test 验证,YAML 类策略先用 kyverno test 验证——能本地确认的不要反复 apply 到集群(每一次 Enforce 误拦都会卡住业务发布)。

2. OPA Gatekeeper

# 安装 Gatekeeper
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper gatekeeper/gatekeeper \
  -n gatekeeper-system --create-namespace

ConstraintTemplate——定义策略逻辑

# 禁止 latest 标签的 ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdisallowlatesttag
spec:
  crd:
    spec:
      names:
        kind: K8sDisallowLatestTag
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdisallowlatesttag

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          endswith(container.image, ":latest")
          msg := sprintf("容器 '%v' 使用了 latest 标签,请指定具体版本", [container.name])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not contains(container.image, ":")
          msg := sprintf("容器 '%v' 未指定镜像标签,请使用具体版本", [container.name])
        }
---
# 应用策略
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowLatestTag
metadata:
  name: pod-no-latest
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]

带参数的 ConstraintTemplate

把可变值(如允许的仓库前缀)声明为 parameters,同一模板可被多个 Constraint 以不同参数复用:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sallowedrepos
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRepos
      validation:
        openAPIV3Schema:
          type: object
          properties:
            repos:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sallowedrepos

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          repo := container.image
          not startswith(repo, input.parameters.repos[_])
          msg := sprintf("镜像 '%v' 不在允许的仓库列表中", [repo])
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: pod-allowed-repos
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    namespaces: ["prod"]
  parameters:
    repos:
    - "harbor.internal.example"
  enforcementAction: deny   # deny=拦截 / dryrun=仅审计
enforcementAction Constraint 上的 enforcementAction: deny 拦截、dryrun 只记录(对应 Kyverno 的 Enforce/Audit)。审计结果可通过 kubectl get constraint pod-allowed-repos -o yaml 查看 status.violations 字段。

Rego 速查:五分钟读懂策略语言

概念写法说明
package k8sallowedrepos每个 .rego 文件第一行,与模板名对应
输入input.review.objectAdmissionReview 中的资源对象
迭代container := input.review.object.spec.containers[_][_] 遍历数组,命中即绑定
违规输出violation[{"msg": msg}]引擎收集所有 violation 并拒绝(deny 模式)
取反not startswith(...)条件不成立时继续匹配
参数input.parameters.reposConstraint 传入的参数
# 读 Rego 的套路:violation 里是一组"必须同时成立的子句"
violation[{"msg": msg}] {
  container := input.review.object.spec.containers[_]   # 1. 遍历容器
  repo := container.image                                # 2. 取镜像
  not startswith(repo, input.parameters.repos[_])        # 3. 不在白名单
  msg := sprintf("镜像 '%v' 不在允许列表", [repo])        # 4. 组装消息
}
# 任何子句失败 → 这条 violation 不成立 → 策略放行
# 所有容器都满足 → 无 violation → 策略放行

3. 策略审计与报告

# Kyverno:查看策略违规
kubectl get policyreport -A
kubectl get clusterpolicyreport

# 查看特定策略的违规详情
kubectl describe clusterpolicy disallow-latest-tag

# OPA Gatekeeper:查看审计结果
kubectl get constraints
kubectl get constrainttemplates

# Gatekeeper 审计模式(不拦截,仅记录)
# 在 ConstraintTemplate 的 spec.targets[0].rego 中添加:
#   annotation[{"msg": msg}] { ... }  # 用 annotation 替代 violation

审计结果接入监控

PolicyReport 只躺在 etcd 里没有价值,把违规计数接进监控才有人看:

# 按策略统计违规数(Kyverno)
kubectl get clusterpolicyreport -o json \
  | jq -r '.items[0].results[] | "\(.policy) \(.result)"' \
  | sort | uniq -c

# 巡检脚本定期执行,配合 cron + 告警:
# Enforce 策略出现违规 → 策略被绕过(误配或新版绕行),立即人工处理
# 违规数持续增长 → 策略覆盖了错误的资源集合,回退审计模式复查

# Gatekeeper 审计指标(/metrics 端点直接暴露 Prometheus 格式)
gatekeeper_constraints_violations{enforcement_action="deny"}
# 接 Prometheus + Grafana 看板,按 Constraint 分维度展示

4. 生产策略清单

策略类型说明
禁止 latest 标签Validate强制镜像版本可追溯
必须设置资源限制Validate防止资源耗尽
必须设置安全上下文Mutate自动注入 runAsNonRoot 等
镜像必须来自可信仓库Validate只允许内网 registry
禁止 privileged 容器Validate安全基线
Pod 必须有标签 appValidate便于监控和管理
新命名空间自动创建 NetworkPolicyGenerate零信任网络

5. 策略测试——上线前验证

策略必须像代码一样可测试。Kyverno 官方提供 kyverno test,OPA 生态使用 conftestopa test

# Kyverno:目录结构(策略 + 用例)
policies/
├── disallow-latest-tag.yaml        # 被测策略
└── test/
    └── disallow-latest-tag/
        ├── good-pod.yaml           # 应通过的资源
        └── bad-pod.yaml            # 应被拦截的资源

# test/bad-pod.yaml——声明期望结果
apiVersion: kyverno.io/v1
kind: Policy
metadata:
  name: disallow-latest-tag
  annotations:
    pod-policies.kyverno.io/autogen-controllers: none
results:
- policy: disallow-latest-tag
  rule: require-image-tag
  resource: bad-pod
  kind: Pod
  result: fail

# 运行测试
kyverno test policies/

# OPA:conftest 直接检查 YAML/JSON 文件
conftest test pod.yaml -p policy.rego --namespace k8sallowedrepos

# OPA 原生单元测试(测试函数直接在 Rego 中编写)
opa test policy.rego policy_test.rego -v
测试是策略发布的前置门禁 在 CI 中加入 kyverno test / opa test,策略合并到 main 前必须全部用例通过,否则改坏策略会静默放行恶意资源。

6. 策略发布与 GitOps 管理

生产集群的策略应纳入 Git 仓库,走与代码相同的评审与发布流程:

# 仓库目录结构
policy-repo/
├── cluster/                      # 集群级策略(ClusterPolicy / Constraint)
│   ├── baseline/                 # 基线级——所有集群强制执行
│   │   └── disallow-latest-tag.yaml
│   ├── medium/                   # 中等——大部分业务命名空间
│   │   └── require-resource-limits.yaml
│   └── high/                     # 高级——合规敏感命名空间
│       └── verify-image-signature.yaml
├── namespaces/                   # 命名空间级策略(按团队隔离)
│   ├── payments/...
│   └── checkout/...
└── tests/                        # kyverno test 用例

# CI 流水线(GitHub Actions 片段)
# 1. kyverno test 策略测试
# 2. kubectl diff --server-side -f cluster/(预览集群差异)
# 3. 人工评审后合并到 main
# 4. ArgoCD / Flux 自动同步到集群(与 GitOps 章节一致)

策略分级管理

级别示例策略执行模式适用命名空间
基线(Baseline)禁止 privileged、必须 runAsNonRoot先 Audit 4 周,后 Enforce全部
中等(Medium)资源限制、可信镜像仓库Audit 观察后 Enforce大部分业务
高级(High)镜像签名校验、禁止 hostPath直接 Enforce + 异常审批支付、合规敏感业务
变更爆炸半径 每条策略先按「分级 + Audit」灰度:先在 baseline 命名空间试点,再逐步扩大到全集群。一次全量 Enforce 多个新策略是生产事故的常见来源。

7. 策略引擎的韧性与性能

Admission Webhook 是写入路径上的「串行检查」,它的可用性与延迟直接影响整个集群的写入能力:

  • Webhook 超时:API Server 等待 webhook 响应的默认超时为 10s,策略执行超时会直接拒绝请求——把 timeoutSeconds 调大到 20-30s,避免大资源在复杂策略下超时。
  • failurePolicyFail 时 webhook 不可用 = 相关写操作全部失败;Ignore 时 webhook 挂掉 = 策略形同虚设。生产建议保持 Fail,但给引擎配 HA(多副本 + leader election)与监控告警。
  • 策略数量:每个请求要执行所有匹配的策略,数百条策略时写入延迟明显上升——用 match 的 namespace 范围限制覆盖面,定期清理冗余策略。
  • Mutation 顺序:Mutate 在 Validate 之前执行,Kyverno 的 mutate 结果会再触发一次 admission 检查——保证 mutate 注入的值能通过自己的 validate,否则形成死循环拦截。
策略故障 = 发布故障 把策略引擎的可用性、延迟、webhook 拒绝率接进 Prometheus 监控。拒绝率突增通常不是攻击,而是策略写坏了——这也是「策略必须进 Git + CI 测试」的根本原因。

常见错误

  • 策略不生效(Enforce 模式下资源仍可创建):检查 validationFailureAction 是否设为 Enforce 而非 Audit;确认策略 matchkindsapiGroups 正确匹配目标资源类型。
  • Kyverno 策略导致所有 Pod 创建被拦截:策略范围过广或匹配了系统命名空间。添加 exclude 规则排除 kube-system 等系统命名空间,或先用 Audit 模式观察。
  • OPA Gatekeeper Rego 语法报错violation 规则必须返回 msg 字段,注意 Rego 缩进和引号匹配。用 opa test 本地调试策略逻辑。
  • Generate 策略未自动创建资源:确认 synchronize: true 已设置,检查目标命名空间是否存在标签冲突,查看 Kyverno Pod 日志中的 webhook 错误。
  • Audit 模式下 PolicyReport 为空:新部署的策略需要等待审计周期(默认几分钟),可手动触发审计或检查 background: true 是否开启。
  • Mutate 注入的值被自己的 Validate 拦截:Mutate 先于 Validate 执行,注入值必须满足本策略的校验规则(如注入 runAsNonRoot 的同时 validate 也要求它),否则形成死循环拦截。
  • Webhook 超时导致大资源写入失败:复杂策略 + 大对象超出 webhook 响应时限,调大 timeoutSeconds,并避免对超大资源(如大型 ConfigMap)做全量遍历检查。

最佳实践

  • 先 Audit 后 Enforce:新策略先用 Audit 模式运行一段时间,确认无误后再切换到 Enforce 拦截模式。
  • 排除系统命名空间:为所有策略添加 exclude.namespaces: [kube-system, kube-public],避免影响集群核心组件。
  • 策略即代码:将策略 YAML 纳入 Git 仓库管理,通过 CI/CD 流水线自动部署,确保变更可追溯。
  • 分层策略管理:集群级策略用 ClusterPolicy,命名空间级用 Policy,实现不同团队的策略隔离。
  • 定期审查策略:使用 PolicyReportConstraint 审计结果定期清理过时或冗余的策略。
  • 给策略引擎配 HA 与监控:Kyverno / Gatekeeper 多副本部署,把拒绝率、webhook 延迟接入 Prometheus 告警。
  • 控制策略数量与覆盖面:用 namespace 范围限制 match 覆盖面,每季度用 PolicyReport 清理冗余策略,保持写入路径低延迟。

练习题

  1. 编写一个 Kyverno ClusterPolicy,要求所有 Pod 必须设置 app 标签,且标签值不能为空。先用 Audit 模式验证,再切换到 Enforce
  2. 使用 OPA Gatekeeper 创建一个 ConstraintTemplate,禁止创建 CPU 资源限制超过 4 核的 Pod,并应用到生产命名空间。
  3. 对比 Kyverno 和 OPA 的 Mutate 能力:分别为同一个 Pod 编写自动注入 securityContext 的策略,记录两种方案的配置复杂度差异。
  4. 故意把一条 Validate 策略写错(如 pattern 语法错误),部署到测试集群,用 webhook 日志与本地 kyverno test 对比定位问题,记录排错路径。
  5. 设计「策略上线五步流程」:Audit 观察 → 试点命名空间 → 全量 Enforce → 监控拒绝率 → 定期审查,写成一页纸 SOP。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 OPA/Rego 的策略即代码原理尝试向他人讲解
命令操作能不查文档完成 OPA 安装和 Rego 策略编写在终端实际执行
原理掌握能说出 OPA 的策略评估引擎和数据模型原理画出流程图
故障排查能独立排查 OPA 策略评估结果不正确的问题模拟故障并修复
最佳实践能说明为什么需要为基础设施即代码配置策略准入控制对比不同方案

本章总结

RBAC 控制「谁能做什么」,策略引擎控制「资源必须满足什么条件」,二者在 Admission 阶段互补。Kyverno 用 YAML 贴近 K8s 生态,OPA Gatekeeper 用 Rego 服务多平台。无论选哪个,都应先 Audit 观察再 Enforce 拦截,并将策略纳入版本管理。

延伸阅读

  • container_sec01 Trivy 镜像漏洞扫描(构建时安全)
  • container_sec02 Falco 运行时安全监控(运行时安全)
  • k8s04 K8s NetworkPolicy 网络策略
  • k8s06 Service Mesh 入门(Istio mTLS)
↑ 回到顶部