VAP、Gatekeeper 与 Kyverno 选型迁移工程
一个平台团队为了降低 webhook 延迟,把二十多条 Gatekeeper 约束“迁移”为原生 VAP。YAML 都创建成功,第一轮发布也没有报错,但依赖 inventory 的命名空间配额规则被简化成只检查当前对象,存量 audit 也在删除 Gatekeeper 后消失。团队完成的是语法重写,不是控制能力迁移:执行位置、可用数据、历史发现和失败证据都发生了变化。
另一支团队从旧 Kyverno ClusterPolicy 迁到 CEL 新 API,选择让新旧规则同时 Deny,以为这样最安全。两个执行器对同一对象给出不同消息,一个先返回后遮住另一个;旧规则的 deny 条件迁移时又忘了反转成新 validation 的允许表达式,发布因此大面积失败。应急人员删除新策略后,自动生成的 VAPBinding 又把它重建。迁移失败的根因不是 CEL 语法,而是没有规定事实源、字段 owner、影子动作和撤回顺序。
先比较一次请求经历了什么
原生 ValidatingAdmissionPolicy 在 kube-apiserver 进程内运行 CEL。Kubernetes Validating Admission Policy 文档定义了 Policy、Binding、Param 与 validation action 的当前语义。它没有额外 webhook 网络、Service 与证书依赖,适合确定性的准入验证;代价是表达式计算进入 API Server CPU 预算,而且它不做 mutation、外部 HTTP、任意 inventory、周期存量扫描或报表控制器。
Gatekeeper 以 ConstraintTemplate/Constraint 为策略模型。Gatekeeper 官方文档分别描述 admission、audit、gator、mutation、External Data 与 VAP 管理能力。Rego 或 K8sNativeValidation CEL 可以在 validating webhook、周期 audit 与 gator 中执行;referential inventory、mutation 和 External Data 扩大了表达能力,也增加缓存、RBAC、TLS、网络和控制器责任。Gatekeeper 还能生成原生 VAP/VAPBinding,但该管理面仍是 beta,不能与稳定的 CEL 本地求值成熟度混为一谈。
Kyverno 用 Kubernetes 风格 CRD 组织验证、变更、生成、删除、镜像策略、报告与例外。Kyverno Policy Types 文档给出了当前 CEL 资源族及其成熟度入口。新项目应从 policies.kyverno.io/v1 的离散 CEL 对象评估;admission、reports、background 和 cleanup 控制器有不同权限、队列和伸缩语义。能力集中会减少工具数量,也会扩大一个平台的 RBAC 与故障半径。
选型不是统计功能勾选,而是确认哪条执行链承担哪项责任。RBAC 决定主体能否调用 API,准入决定获准请求携带的对象能否写入;三种方案都不能替代 Pod Security、镜像信任、运行时威胁检测、备份恢复或应用内授权。
用策略契约代替产品名称做比较
迁移前先把每条规则写成可执行契约。以“Deployment 必须有合法 owner”为例,至少记录:匹配 GVK 与 operation、namespace/object selector、参数来源、合法与非法 fixture、更新旧对象、参数缺失、执行器错误、允许/拒绝消息、存量扫描期望、延迟与可用性预算、例外 owner、回滚开关和证据保留位置。
policyId: deployment-owner
revision: r1
match:
apiGroups: [apps]
apiVersions: [v1]
resources: [deployments]
operations: [CREATE, UPDATE]
parameters:
allowedSuffix: "@example.com"
cases:
- name: valid-owner
expected: allow
- name: missing-owner
expected: violation
- name: invalid-suffix
expected: violation
- name: missing-parameter
expected: execution-failure
- name: evaluator-unavailable
expected: availability-policy这不是某种控制器的配置,而是迁移双方必须满足的验收输入。violation 与 execution-failure 必须分开:前者是规则正常求值后发现不合规,后者是表达式错误、参数不存在、webhook 超时或依赖不可达。若目标平台只能通过删掉一个 case 才“等价”,那就是能力降级,应由架构决策显式接受,而不是藏在转换脚本里。
同一契约还要记录证据键:policyId、策略 revision、resource UID、request audit ID、执行器、动作和结果。客户端错误、Kubernetes audit、Gatekeeper audit、Kyverno PolicyReport 分属不同证据面,没有这些关联键就无法判断双轨差异来自哪条策略版本。
三种执行路径的关键取舍
| 决策事实 | 原生 VAP | Gatekeeper | Kyverno |
|---|---|---|---|
| 准入位置 | API Server 内 CEL | webhook;可选生成原生 VAP | admission webhook;可选生成原生 VAP |
| 策略模型 | Policy + Binding + 可选 Param | Template + Constraint + Config | 按动作拆分的 CEL CRD |
| 存量发现 | 无周期扫描;Audit 仅标注当前请求 | 周期 audit、Constraint status、日志与指标 | background scan、AdmissionReport/BackgroundScanReport/PolicyReport |
| 主动改变对象 | 不支持 | mutation webhook | mutate、mutate-existing、generate、scheduled delete |
| 外部与跨对象数据 | Param、namespaceObject、authorizer;无任意网络 | sync inventory、External Data | 扩展 CEL/HTTP、上下文与后台控制器能力 |
| 提交前入口 | 自建 fixture 与 API dry-run 流程 | gator test/verify | Kyverno CLI 与策略测试 |
| 主要故障域 | API Server CPU、CEL cost、错误策略 | Service、Endpoint、证书、网络、缓存、Provider | 多类 controller、webhook、reports、队列、registry/HTTP |
| 退出成本 | 迁出 Policy/Binding/Param | 迁出 Template/Constraint、sync、mutator、生成 VAP | 迁出多类策略、report/exception、主动协调状态 |
表格只是入口,真正的取舍发生在业务约束里。纯对象字段验证、低延迟硬拒绝、目标集群至少支持稳定 admissionregistration.k8s.io/v1 时,原生 VAP 通常具有最短故障链。需要 Rego 资产、同一约束的 admission + audit + gator、referential inventory 或受控 External Data 时,Gatekeeper 的运行模型更贴合。需要验证之外的 mutation、generate、delete、PolicyReport、PolicyException 和 Kubernetes 风格一体化工作流时,Kyverno 更有优势。
“能力更多”不等于“默认更合适”。每多一个主动控制器,就多一组写权限、leader election、队列、重试、幂等和回滚责任;每少一个 webhook,也可能把计算成本转移进 kube-apiserver。架构决策要按目标集群 QPS、对象大小、策略数量、存量规模、团队 Rego/CEL 能力、审计要求、可用性预算与退出成本共同判断。
建立可重复的三执行器实验台
准备一个可重置的 Kubernetes 测试集群、Helm、kubectl、目标版本的 gator 与 Kyverno CLI。原生 VAP 使用稳定 API;Gatekeeper 与 Kyverno 固定 release/Chart 版本,不使用 latest、开发分支或未经审核的远程 manifest。三套策略各自放入独立 namespace 与目录,避免同名对象和字段 owner 争用。
policy-migration/
contract/policy.yaml
fixtures/good.yaml
fixtures/missing-owner.yaml
fixtures/invalid-suffix.yaml
vap/
gatekeeper/
kyverno/
expected/results.yaml
evidence/安装后先证明执行器健康,再装业务规则:
kubectl api-resources --api-group=admissionregistration.k8s.io \
| grep validatingadmission
kubectl -n gatekeeper-system get deploy,pod,svc,endpointslice
kubectl -n kyverno get deploy,pod,svc,endpointslice
kubectl get validatingwebhookconfiguration预期原生 VAP 的 Policy 与 Binding API 可发现,两个第三方控制器的 Service 有 Endpoint,webhook CA 非空。若健康基线都不成立,后续 allow/deny 结果会把安装故障误判为策略差异。
三份策略使用完全相同的 owner 参数与 fixture。原生 VAP 先让 Binding 使用 [Warn, Audit];Gatekeeper Constraint 使用 dryrun 或 scoped audit;Kyverno ValidatingPolicy 使用 Audit。观察阶段必须只有旧执行器承担 Deny,新执行器不得提前成为第二个阻断点。
把结果归一成可审计 diff
不同产品的原始输出不能直接逐字符串比较。VAP 返回 Warning/Audit annotation,Gatekeeper 产生 violation/status/log,Kyverno 产生 report result;先归一为一个最小结果模型:
{
"policyId": "deployment-owner",
"revision": "r1",
"fixture": "missing-owner",
"executor": "gatekeeper-webhook",
"phase": "admission",
"decision": "violation",
"action": "observe",
"reason": "owner-missing"
}decision 至少区分 allow、violation、skip 与 error;phase 区分 shift-left、admission、inventory-audit 和 reconciliation;action 区分 observe、warn、deny 与 mutate。消息可以不同,但 reason 必须稳定映射。这样才能识别“两个执行器都返回 HTTP 2xx,但一个正常允许、一个因错误 fail-open”的根本差异。
对每个 fixture 依次运行提交前测试、服务端 dry-run 或观察请求,并采集对象是否存在:
gator verify gatekeeper/suite.yaml
kubectl apply --dry-run=server -f fixtures/good.yaml
kubectl apply --dry-run=server -f fixtures/missing-owner.yaml
kubectl apply -f fixtures/missing-owner.yaml
kubectl get deployment owner-bad -n policy-lab -o yamlKyverno CLI 与 VAP harness 应对同一渲染产物执行,而不是各自使用手写的近似 YAML。预期观察阶段中,违规对象可以进入集群,但三个新路径都产生可归一的 violation;合法对象不产生误报。若某一路 skip,先比较 GVK、operation、namespace selector、matchConditions 和参数;若为 error,按 failure policy 单独处理,不能把它算成等价 allow。
正向实验要证明允许链没有被破坏
合法 fixture 应覆盖 CREATE、UPDATE 与控制器常见更新,而不是只创建一次静态对象。先创建有 owner 的 Deployment,再更新镜像和副本数,最后让 Deployment controller 正常写入 ReplicaSet。预期各执行器允许请求,业务对象 generation 递增,控制器没有因主体或 oldObject 差异被误拦。
kubectl apply -f fixtures/good.yaml
kubectl -n policy-lab set image deployment/owner-good \
pause=registry.k8s.io/pause:3.10
kubectl -n policy-lab scale deployment/owner-good --replicas=2
kubectl -n policy-lab rollout status deployment/owner-good --timeout=90s主体相关规则尤其容易在这里暴露问题:人工 kubectl、GitOps controller 与内建 controller 的 userInfo 不同。若策略真正约束的是对象状态,应避免把人类用户名写成允许条件;若必须约束请求主体,就要为各类控制器制作 AdmissionReview fixture,并接受存量扫描无法重建历史主体这一事实。
允许链还要覆盖接近 schema 上限的大对象和批量更新,观察 API Server admission 延迟、CPU、webhook duration 与控制器重试。原生 VAP 消除了网络跳转,却不会消除 CEL cost;Gatekeeper/Kyverno 增加副本可以改善接管与并发,但昂贵表达式、过宽匹配和外部依赖仍会放大尾延迟。
反向实验要拆开五类失败
第一类是正常违规:缺 owner、后缀错误。观察阶段预期请求成功并留下 warning/report/violation;阻断阶段预期请求被拒绝,对象不存在。
第二类是参数缺失。VAP 的 parameterNotFoundAction、Gatekeeper Constraint 参数/Template Schema、Kyverno 上下文或参数解析具有不同语义。删除参数后要记录它是 deny、skip 还是 error,不能假设三者默认一致。
第三类是表达式错误。让 CEL 安全访问改为直接索引不存在的 label,或让 Rego 读取错误路径。预期结果由各自 failure policy 决定;正常 validation false 与执行错误必须使用不同 reason。把 Ignore 当成“忽略所有不合规”会造成永久放行漏洞。
第四类是执行器不可达。缩容 Gatekeeper/Kyverno admission controller 或让 Service Endpoint 为空,分别在 Ignore 与 Fail 下提交同一反例。预期错误消息是 webhook 调用失败,而不是 owner violation。原生 VAP 没有该网络故障,但要用高成本表达式和高请求量验证 API Server CPU 与 cost budget。
第五类是存量差异。在策略启用前创建反例,再启动 Gatekeeper audit 或 Kyverno background scan。预期 Gatekeeper Constraint status/日志与 Kyverno PolicyReport 出现违规,原生 VAP 不会生成存量列表。若迁移目标删除了原有存量发现能力,必须先建立另一条扫描与报告链,否则不是等价迁移。
所有实验都只在隔离集群或批准窗口执行。文中的结果是判据;团队只有保存命令输出、Status、report、audit event、Endpoint、指标和对象状态后,才能记录某次环境验收完成。
双轨迁移的六个阶段
阶段一:冻结事实源和版本
导出旧策略、参数、生成对象、webhook、报告、例外、Helm values 和指标基线。每条策略分配稳定 policy ID 与 revision,指定一个仓库和一个控制器为字段 owner。若 Gatekeeper/Kyverno 正在自动生成 VAP,不允许 GitOps 同时以不同模板管理同名对象。
阶段二:建立 fixture 契约
把历史拒绝样本、合法对象、旧对象更新、参数缺失、执行错误和控制器主体都变成 fixture。先在旧执行器重放并记录归一结果,确认测试能重现现有语义;无法重现的规则不能直接迁移,因为团队甚至不知道旧行为是什么。
阶段三:新执行器只观察
旧规则保持唯一 Deny。VAP 用 Warn/Audit,Gatekeeper 用 dryrun/warn 或 scoped action,Kyverno 用 Audit;对同一流量比较命中集合、scope、reason、延迟与错误。观察窗口要覆盖日常发布、控制器更新、批处理、参数变化和一次执行器故障,而不是只跑几条手工 YAML。
阶段四:逐条迁移阻断权
对差异归零的一条策略,先把新执行器改为 Deny,再立即把旧规则退到 observe;不要长期双 Deny。先迁移低风险、确定性字段约束,再迁移依赖 oldObject、主体、inventory 或外部数据的规则。每次切换都保留独立回滚开关,避免一次变更同时删除旧规则和旧控制器。
阶段五:验证存量与主动协调
如果旧平台承担 audit/report、mutation、generate、cleanup 或 exception,分别迁移这些运行面。原生 VAP 只能接管验证,不能接管其余能力。Kyverno legacy 到 CEL 不是机械改 apiVersion:旧对象中的 validate/mutate/generate/verifyImages 可能拆成多个新 CRD,旧 deny 条件还要反转成“允许条件”,无直接映射字段必须重新设计并做结果 diff。
阶段六:撤旧并证明不会重建
先删除旧 Constraint/Policy 或取消其执行点,观察至少一个完整发布与扫描周期;再缩容旧控制器,确认 GitOps、operator 和生成器不再重建对象;最后撤销 RBAC、证书、Secret、Service、CRD 与监控。退出完成的证据包括旧 webhook 消失、新路径稳定、存量报告有归属、延迟回到预期、凭证已撤销、审计历史仍可追溯。
三条常见迁移路线
Gatekeeper 到原生 VAP
适合把无 inventory、无 External Data、无 mutation 的确定性 CEL 约束移到 API Server 内。若源 Template 仍是 Rego,先用 fixture 将语义改写为 CEL;不要假设语法翻译等价。新 VAP 以 Warn/Audit 影子运行,比较 Gatekeeper webhook 与 VAP 的 scope、DELETE 语义、namespace exclusion、参数和错误消息,再迁移 Deny。
Gatekeeper v3.23 的 --sync-vap-enforcement-scope 已有弃用警告,后续移除后会固定合并多类作用域。若迁移借助 Gatekeeper VAP manager,升级前必须比较生成的 VAP/VAPBinding;完成迁移后应决定保留 Gatekeeper 为 audit owner,还是将 VAP 改由 GitOps 直接管理。不能长期让 beta 生成器与仓库共同争夺字段。
原生 VAP 到 Gatekeeper 或 Kyverno
通常由新增需求驱动:存量扫描、外部数据、inventory、mutation、generate、报告或例外。保留 VAP 作为低延迟硬拒绝时,新平台先只承担 audit/report,避免重复 Deny;若新平台要全面接管,先验证 webhook Fail/Ignore、HA、证书、网络和恢复路径,再转移阻断权。新增能力也要独立 owner,例如 report controller 失败不能伪装成 admission 失败。
Kyverno legacy 到 CEL 新 API
新 API 使用 policies.kyverno.io/v1,按 ValidatingPolicy、MutatingPolicy、GeneratingPolicy、DeletingPolicy 与 ImageValidatingPolicy 拆分。旧 Policy/ClusterPolicy 和 CleanupPolicy 家族已进入弃用路径,但计划移除时间必须随目标 release notes 复核,不能把计划写成已经发生。
迁移脚本先拆 rule,再对相同 fixture 比较 pass/fail/skip/error、mutation patch、generated resource、report 与删除生命周期。spec.background 与 reports scan、mutate-existing reconciliation 不是一个开关;generate 的 synchronize/orphan 组合会改变下游对象删除语义;DeletingPolicy 是破坏性执行器,必须在隔离 namespace 验证 selector、schedule 与 propagation。只有这些差异归零或被决策接受,才可移除 legacy 对象。
回退不是把旧 YAML 再 apply 一遍
迁移期间始终保留“谁正在 Deny”的单一答案。新策略造成误拦时,第一步是把新 Binding/Constraint/Policy 退到 Warn/Audit/dryrun 或删除执行绑定,恢复业务写入;第二步确认旧观察规则仍能重新承担 Deny;第三步再回滚策略代码或控制器版本。直接回滚控制器而不处理生成对象,可能让新 VAPBinding 继续拒绝。
回退控制器版本前要检查 CRD storage version、conversion、已写入的新字段和 CEL 库。较新 Kubernetes API Server 接受的 CEL 函数,在较旧控制平面中可能无法重新创建或更新;因此控制平面回滚测试必须包含“导出后在旧版本重建”,不能只确认原地对象还在求值。
故障关闭的回退还需要恢复命名空间、跨故障域副本、PDB、timeout、证书轮换、control-plane 网络和受审计的短时应急授权。只把 failurePolicy 改为 Ignore 会恢复请求,却也可能形成无声放行窗口;恢复后必须运行存量扫描并关联该窗口的 API audit event。
证据、权限与敏感数据治理
策略仓库不保存真实集群名、用户名、组织邮箱、内网地址、token 或应急凭据。fixture 使用 example.com 与一次性 namespace;AdmissionReview 中的 userInfo、audit event、PolicyReport 和 External Data 响应在进入集中日志前脱敏。统一 policy ID 不应编码个人或客户信息。
安装控制器、修改 Template/Policy、修改参数、审批例外和查看审计应拆分权限。平台团队拥有执行器与 SLO,治理团队拥有策略意图,业务团队拥有合法 fixture 和修复责任,安全团队审核高风险例外;生成 VAP、mutation、generate 和 delete 的 ServiceAccount 只获得对应 GVK 与 verb。迁移临时权限设置自动到期,完成后用 kubectl auth can-i --as=<service-account> 和 RBAC 导出证明已回收。
成本不仅是 Pod 数量。原生 VAP 消耗 API Server CPU;Gatekeeper 消耗 webhook、audit list、inventory、status 与日志;Kyverno 还可能消耗 report CRD、background queue、UpdateRequest、registry/HTTP 和主动协调写入。策略数量增长时要跟踪每请求命中数、对象大小、尾延迟、错误率、缓存基数、扫描周期、队列年龄、etcd 对象膨胀与日志保留费用。选型评审应给这些成本明确 owner 和容量预算。
故障排查从差异类型开始
新旧结果不一致时,先判定是 match、data、decision、action 还是 evidence 差异。match 差异查 GVK、operation、selector、排除和自动生成 scope;data 差异查参数版本、inventory 新鲜度、HTTP/Provider 和 namespaceObject;decision 差异查 CEL/Rego 逻辑、oldObject、DELETE 与空值语义;action 差异查 Deny/Warn/Audit/dryrun、failure policy 与例外;evidence 差异查 audit backend、Gatekeeper audit 完成状态、Kyverno report controller 和日志采集。
“对象放行但没有报告”不能直接归为策略没执行:VAP Audit 只进入 Kubernetes audit event,Gatekeeper audit 需要完整周期,Kyverno background report 依赖独立控制器与 CRD。“对象被拒绝但找不到哪条规则”通常来自双 Deny、消息映射缺失或生成 VAP 与源策略 revision 不一致,应先按 request audit ID 列出所有执行器结果。
“升级后作用域扩大”优先检查 Gatekeeper VAP scope 同步弃用变化、Kyverno autogen 与 webhook selector、原生 VAP 的多 Binding/多参数 AND 收敛,以及 managedFields 的 owner 争用。不要用强制 apply 压过漂移;先确定哪个控制器应该拥有该字段,再删除另一条写入路径。
清理实验并保留迁移证据
清理顺序先移除阻断绑定,再删除策略和参数,最后卸载执行器。示意命令需要按实际对象名调整:
kubectl delete validatingadmissionpolicybinding deployment-owner-shadow \
--ignore-not-found
kubectl delete validatingadmissionpolicy deployment-owner-shadow \
--ignore-not-found
kubectl delete k8srequiredowner deployment-owner-shadow \
--ignore-not-found
kubectl delete validatingpolicy deployment-owner-shadow \
--ignore-not-found
kubectl delete namespace policy-lab --ignore-not-found删除前导出脱敏的策略 revision、归一 diff、拒绝 Status、audit/report、指标基线和回退演练结果。清理后确认 webhook、Binding、生成对象、RBAC、Secret 与 Service Endpoint 均不再存在,GitOps 不会重建旧对象,合法请求恢复,存量违规转交到明确的新 owner。
好的 Kubernetes 策略迁移不是让新工具“也能拒绝”,而是让每条策略的匹配、数据、决定、动作、证据和恢复责任在新路径中都有对应位置。用统一 fixture 证明语义,用单一 Deny owner 控制风险,用影子结果发现差异,再把回退开关和退出证据留到最后,团队才能改变执行器而不丢失治理能力。
