Gatekeeper 策略准入与存量审计工程
一次集群巡检发现,几十个已经运行数月的 Deployment 都没有 owner 标签。团队此前已经部署 Gatekeeper,并确认新建违规对象会被拒绝,于是一直把“准入生效”等同于“集群合规”。真正的问题是 Gatekeeper audit 被关闭,策略只能处理新请求,既没有周期扫描存量对象,也没有人检查 Constraint 的 status.violations。准入是写入时的一次决定,audit 是对现存状态的另一条执行链,两者不能互相代替。
另一场事故发生在策略控制器升级时。验证 webhook 使用 failurePolicy: Fail,三个副本却落在同一节点,证书更新后 Service 一度没有可用 Endpoint;用于恢复 Gatekeeper 的 Pod 更新也被同一 webhook 拦住。值班人员只能紧急删除 webhook 配置,结果所有约束同时失效。故障关闭不是把一个字段从 Ignore 改成 Fail,它要求副本、拓扑、PDB、证书、网络、恢复命名空间与受控应急授权组成一条可演练的恢复路径。
一条策略为什么会有多个执行结果
Gatekeeper 是 Kubernetes 策略控制器。ConstraintTemplate 定义一种约束类型及其参数 Schema,并承载 Rego 或 K8sNativeValidation CEL 逻辑;Template 建立后,API Server 才会出现相应 Constraint CRD。Constraint 实例再提供匹配对象、参数和执行动作。业务对象经过 Gatekeeper validating webhook 时,约束可以同步拒绝或警告;audit 进程会周期性评估已经存在的对象;gator 则在工作站或 CI 中对渲染清单执行相同约束。
这几条路径共享策略意图,却不共享全部上下文。webhook 能看到 AdmissionReview 的请求身份与操作;audit 和普通 gator 对存量对象求值时没有实时 userInfo。referential policy 依赖同步进 OPA 的 inventory;External Data 又把远端 Provider 的延迟、TLS 和错误带进决策路径。一个约束在 CI 中通过,只能证明给定 fixture 在该执行点得到预期结果,不能自动证明集群准入、存量扫描和外部依赖都健康。
Gatekeeper v3.23.x 文档把 K8sNativeValidation CEL 求值标记为稳定能力,而由 Gatekeeper 创建并管理原生 VAP/VAPBinding 仍是 beta。若同一个 Template 同时包含 CEL 与 Rego,当前执行优先选择 K8sNativeValidation,不会在 CEL 失败后回退到 Rego。双语言应当用于迁移期间的等价性验证,不能当成运行时降级机制。VAP 集成说明给出了引擎优先级与生成对象的规则。
在隔离集群固定安装输入
Gatekeeper 会创建集群级 CRD、ClusterRole、webhook、Service 和证书对象,安装者需要相应集群管理权限。动手前先确认目标 context、Kubernetes 服务端版本、Helm 版本和变更窗口;不要只看本机 kubectl 版本。实验宜使用可重置的 kind、k3d 或专用测试集群,避免第一次策略错误直接影响共享控制平面。
kubectl config current-context
kubectl version
helm version
kubectl auth can-i create customresourcedefinitions.apiextensions.k8s.io
kubectl auth can-i create validatingwebhookconfigurations.admissionregistration.k8s.io后两个命令应返回 yes。它们只证明当前主体有安装权限,不证明目标版本兼容。Gatekeeper 的最低支持线跟随 Kubernetes 维护分支变化,升级前应在安装说明和目标 release notes 中核对,而不是把某个历史最低版本永久写进平台模板。
生产可审计安装应固定 Chart 版本和 values。下面以 3.23.0 为可复现实验基线;准备升级时先用 helm search repo gatekeeper/gatekeeper --versions 与 release notes 选择目标 patch,并重新渲染差异。
helm repo add gatekeeper \
https://open-policy-agent.github.io/gatekeeper/charts --force-update
helm repo update
helm show chart gatekeeper/gatekeeper --version 3.23.0
helm show values gatekeeper/gatekeeper --version 3.23.0 > gatekeeper-values-reference.yaml创建 gatekeeper-values.yaml:
replicas: 3
validatingWebhookFailurePolicy: Ignore
validatingWebhookTimeoutSeconds: 3
mutatingWebhookFailurePolicy: Ignore
mutatingWebhookTimeoutSeconds: 1
auditInterval: 60
constraintViolationsLimit: 20
auditFromCache: false
enableK8sNativeValidation: true
enableExternalData: false
pdb:
controllerManager:
minAvailable: 1replicas 是 webhook/controller-manager 的接管容量,不保证跨故障域;还要给 Pod 设置实际可满足的 topology spread 或 anti-affinity。validatingWebhookFailurePolicy 控制 webhook 不可达、超时或调用错误,不控制策略正常返回 violation。auditInterval 决定存量扫描节奏;constraintViolationsLimit 只是单个 Constraint status 保留条数,不是违规总数。auditFromCache: false 表示 audit 默认从 API Server 分页列举;改成缓存扫描后,sync 配置就成为完整性前提。没有使用 External Data 时显式关闭,减少不必要的扩展面。
先渲染,再安装:
helm template gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--version 3.23.0 \
-f gatekeeper-values.yaml > rendered-gatekeeper.yaml
helm upgrade --install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--version 3.23.0 \
-f gatekeeper-values.yaml \
--wait --timeout 5m安装后的最小证据不是“Helm 返回成功”,而是 CRD、Deployment、EndpointSlice、证书和 webhook 都形成闭环:
kubectl -n gatekeeper-system get deploy,pod,svc,endpointslice
kubectl get crd constrainttemplates.templates.gatekeeper.sh
kubectl get validatingwebhookconfiguration \
gatekeeper-validating-webhook-configuration -o yaml
kubectl -n gatekeeper-system logs deploy/gatekeeper-controller-manager \
--tail=100预期 controller-manager 的可用副本达到期望值,Service 有可用 EndpointSlice,ConstraintTemplate CRD 存在,webhook 的 clientConfig.caBundle 非空。若 Pod Ready 但 EndpointSlice 为空,先查 selector 与 readiness;若 API Server 报 x509,对比 webhook CA、Service 证书 SAN 与证书轮换日志;若 control plane 到节点端口不通,策略代码没有任何修复作用。
用 ConstraintTemplate 建立 owner 约束类型
下面的策略要求 Deployment 设置 metadata.labels.owner,并以受控后缀结尾。Template 使用 templates.gatekeeper.sh/v1,参数 Schema 会进入动态生成的 Constraint CRD。K8sNativeValidation source 显式设置 generateVAP: false,先只验证 Gatekeeper webhook、audit 与 gator;是否启用 beta VAP 管理留到完成行为对比之后。
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredowner
spec:
crd:
spec:
names:
kind: K8sRequiredOwner
validation:
openAPIV3Schema:
type: object
properties:
allowedSuffix:
type: string
minLength: 2
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: K8sNativeValidation
source:
generateVAP: false
validations:
- expression: >-
has(object.metadata.labels) &&
'owner' in object.metadata.labels &&
string(object.metadata.labels['owner'])
.endsWith(string(variables.params.allowedSuffix))
message: Deployment 必须设置符合团队后缀规则的 owner 标签保存为 policy/template.yaml 后应用:
kubectl apply -f policy/template.yaml
kubectl wait --for=condition=Established \
crd/k8srequiredowner.constraints.gatekeeper.sh --timeout=60s
kubectl get constrainttemplate k8srequiredowner -o yaml预期 Template status 显示目标创建成功,CRD 可被 discovery。若 Template 被接受但 CRD 没有 Established,查看 Template status 与 controller 日志;常见原因是 Kind 名与 metadata name 不匹配、OpenAPI Schema 非结构化,或 CEL 编译失败。Template 只是定义类型,此时还没有任何业务对象受到限制。
Rego 资产可以在 targets[].rego 中读取 input.review.object 与 input.parameters,以 violation 结果返回消息。把同一个 Template 同时放入 Rego 与 K8sNativeValidation 并不能获得 fallback:CEL 引擎优先,逻辑错误也不会自动改跑 Rego。迁移时应保留两份独立 fixture 结果,确认等价后只保留一个实际执行源,避免维护者误判哪段代码生效。
Constraint 决定对象、参数和动作
创建实验 namespace,并先放入一个违规 Deployment,随后再创建 dryrun Constraint。这样既能观察新请求,也能证明 audit 是否发现存量对象。
kubectl create namespace gatekeeper-lab
kubectl -n gatekeeper-lab create deployment owner-before-policy \
--image=registry.k8s.io/pause:3.10policy/constraint.yaml:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredOwner
metadata:
name: deployment-owner
spec:
enforcementAction: dryrun
match:
scope: Namespaced
namespaces: ["gatekeeper-lab"]
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
allowedSuffix: "@example.com"kubectl apply -f policy/constraint.yaml
kubectl get k8srequiredowner deployment-owner -o yamlmatch 是粗粒度候选集,参数是组织规则,enforcementAction 决定各执行点如何呈现 violation。默认动作是 deny,因此新策略上线时应显式写 dryrun 或 warn,不能依赖维护者记得某个默认值。参数使用示例域名,真实团队应优先用稳定团队 ID 而不是个人邮箱;谁能修改 Constraint 参数,谁就能改变集群的允许集合,RBAC 与代码评审必须覆盖该资源。
在 CI 中先让正反例都说话
目录保持 Template、Constraint、fixture 与 Suite 相互独立:
policy/
template.yaml
constraint.yaml
suite.yaml
fixtures/
good.yaml
bad.yamlgood.yaml 与 bad.yaml 只需使用不同标签:
apiVersion: apps/v1
kind: Deployment
metadata:
name: owner-good
namespace: gatekeeper-lab
labels:
owner: platform@example.com
spec:
replicas: 1
selector:
matchLabels: { app: owner-good }
template:
metadata:
labels: { app: owner-good, owner: platform@example.com }
spec:
containers:
- name: pause
image: registry.k8s.io/pause:3.10反例复制该对象并删除两个 owner 标签、把名称改为 owner-bad。Suite 把预期写成契约:
apiVersion: test.gatekeeper.sh/v1alpha1
kind: Suite
tests:
- name: deployment-owner
template: template.yaml
constraint: constraint.yaml
cases:
- name: owner-is-valid
object: fixtures/good.yaml
assertions:
- violations: no
- name: owner-is-missing
object: fixtures/bad.yaml
assertions:
- violations: 1
message: "必须设置.*owner"从目标 Gatekeeper release 下载对应 gator 二进制并校验发布方提供的摘要,避免在 CI 中使用 @master。执行:
gator version
gator verify policy/suite.yaml
gator test \
--filename=policy/template.yaml \
--filename=policy/constraint.yaml \
--filename=policy/fixtures/bad.yaml \
--output=json预期 verify 的两个 case 都满足断言。由于 Constraint 是 dryrun,gator test 仍输出 violation,但不会像 deny 那样仅因该 violation 产生拒绝语义;CI 若需要任何 violation 都失败,应解析结构化输出或使用 Suite 断言,而不是只盯退出码。gator 命令说明明确区分了求值错误、violation 与 enforcement action 对退出状态的影响。
反向实验还应故意把参数改成空字符串、把 Template Kind 改错、让 CEL 引用不存在字段,分别观察 ingest/compile error 与正常 violation。前者说明策略资产本身不可用,后者说明业务对象违反规则;把两者都压成一个 CI 红灯会失去排障线索。
从 dryrun 到 warn 再到 deny
先提交正反 fixture:
kubectl apply -f policy/fixtures/good.yaml
kubectl apply -f policy/fixtures/bad.yaml
kubectl -n gatekeeper-lab get deploy owner-good owner-bad在 dryrun 下,预期两个对象都创建成功,反例不会收到同步 warning。把 Constraint 改为 warn 后,删除并重新提交反例,预期请求仍成功,但客户端收到 Warning header 对应消息。调用方可能不展示或不保存 warning,因此观察期还要检查 API Server audit、Gatekeeper metrics 与应用发布日志,不能把“终端没显示”判定为没命中。
完成命中率与误报核对后,把动作改为 deny:
kubectl patch k8srequiredowner deployment-owner \
--type=merge -p '{"spec":{"enforcementAction":"deny"}}'
kubectl -n gatekeeper-lab delete deployment owner-bad --ignore-not-found
kubectl apply -f policy/fixtures/bad.yaml预期最后一条命令非零,Status message 含 owner 违规信息,并且 owner-bad 不存在。随后再次提交正例,确认它仍能更新。只有正例允许、反例拒绝、对象状态与错误证据同时满足,才证明同步执行链有效;kubectl apply 一次成功或失败都不足以判断策略是否正确。
audit 发现存量,但不会修复存量
最早创建的 owner-before-policy 不会因为 Constraint 改为 deny 而消失。等待至少一个完整 audit 周期,再查看:
kubectl get k8srequiredowner deployment-owner \
-o jsonpath='{.status.totalViolations}{"\n"}{.status.violations}{"\n"}'
kubectl -n gatekeeper-system logs deploy/gatekeeper-audit \
--since=10m | grep deployment-owner预期 Constraint status 或 audit 结构化日志中出现存量违规。status.violations 只代表最近一次扫描的截断样本,默认每个 Constraint 保留有限条;空数组也可能来自 audit 尚未完成、目标 Kind 未扫描、缓存未同步或 status 被截断,不能直接当合规证明。应同时观察 gatekeeper_audit_last_run_time、gatekeeper_audit_last_run_end_time、扫描时长和违规总量,并把完整历史送往有保留策略的日志或导出系统。
auditFromCache: true 会降低直接 list API Server 的压力,但事实源变成 informer cache。此时必须通过 Config.spec.sync.syncOnly 或 SyncSet 覆盖策略依赖的 GVK,并用 gator sync test 检查 Template 声明的同步要求。漏同步会表现为“扫描很快且零违规”,这是最危险的假阴性之一。Replicating Data给出了 inventory 与 sync 的对象模型。
audit 是发现控制,不执行删除、回滚或修复。修复存量应由独立变更流程根据 resource UID、owner 与风险分批执行;不要让扫描控制器在没有审批和回滚证据时直接修改业务对象。
mutation、inventory 和 External Data 会扩大责任面
Gatekeeper mutation 使用 AssignMetadata、Assign、ModifySet、AssignImage 等 CRD,由 mutation webhook 在写入阶段生成补丁。它与验证 Constraint 是不同对象、不同操作和不同失败策略;旧 --enable-mutation 已弃用且没有效果,运行面应按 operation flags 和 mutation status 管理。每条 mutator 都要做幂等性、冲突与收敛实验,尤其要确认多个 mutator 不会反复改写同一字段。
referential policy 把其他 Kubernetes 对象同步到 data.inventory。这使“Route 引用的 Service 必须存在”之类规则成为可能,也引入缓存新鲜度、RBAC、内存和 GVK 扩张成本。对 inventory 的读取不是强一致事务:请求对象与缓存对象可能处于不同时间点,涉及删除和重建时必须定义陈旧数据可接受多久。
External Data 允许策略调用 Provider,适合组织目录、镜像风险或外部登记信息,但 Provider 会进入 API 写入关键路径。当前能力仍是 beta,Provider 通信需要 TLS/mTLS;还要分别决定 Provider 的 failure policy、Gatekeeper webhook 的 failure policy和 Constraint action。超时预算应小于 webhook 总超时并留出网络与序列化空间,缓存键不得包含敏感明文,响应和日志应脱敏。若外部系统的 SLO 无法达到 API Server 写路径要求,应改用异步同步到本地数据或把规则留在观察执行点。
一个可复制的反向演练是让 Provider 分别超时、返回证书错误和业务错误,记录 webhook Status、Provider 指标、Gatekeeper trace 与最终对象状态。预期结果必须按三层失败策略分别解释,不能把所有放行都归因于 failurePolicy: Ignore。
VAP 管理不是“自动提速”开关
采用 CEL Template 后,Gatekeeper 可以生成原生 ValidatingAdmissionPolicy 与 Binding,让简单规则由 API Server 内建 CEL 执行。CEL source 的本地求值从 Gatekeeper v3.18 起标记稳定;VAP 管理由 v3.20 起仍标记 beta,两者的成熟度不同。默认生成开关和实际行为还受 Template、Constraint 与 scoped enforcement point 影响。
使用 enforcementAction: scoped 时,可以为 validation.gatekeeper.sh、audit.gatekeeper.sh、gator.gatekeeper.sh 与 vap.k8s.io 分别指定动作。例如让 VAP deny、Gatekeeper audit 保留 dry-run,同时关闭 webhook 的重复 deny。映射到原生 Binding 时,Gatekeeper 的 deny、warn、dryrun 分别对应 Deny、Warn、Audit;生成结果仍应逐字段检查,不应只看 Constraint 源对象。
v3.23 已对 --sync-vap-enforcement-scope 发出弃用警告,后续版本会移除该开关,并固定合并 webhook configuration、Gatekeeper Config 与 namespace exemption 的作用域。曾把它显式设为 false 的集群,升级前必须导出并比较:
kubectl get constrainttemplate,constraints -A -o yaml > gatekeeper-source.yaml
kubectl get validatingadmissionpolicy,validatingadmissionpolicybinding \
-o yaml > generated-vap-before.yaml
kubectl get validatingwebhookconfiguration \
gatekeeper-validating-webhook-configuration -o yaml > webhook-before.yaml先在隔离集群升级并重新导出,比较 namespace exclusion、operations、resource rules、参数、动作与 managedFields。Gatekeeper 和 GitOps 不能同时以不同期望管理同名 VAP;生成控制器应是唯一字段 owner。双执行期间先让一侧 Warn/Audit,按统一 policy ID、resource UID、request audit ID 与 rollout revision 比较命中集合;两个执行器同时 Deny 会造成重复求值、错误消息竞争和证据归因困难。
fail-open 与 fail-closed 要做破坏性演练
Chart 默认验证与变更 webhook 都使用 Ignore。当 Gatekeeper 不可达时,匹配请求可能放行,audit 只能稍后发现存量违规,不能恢复写入时原子性。改为 Fail 可以在 webhook 调用异常时拒绝,但也会把 Gatekeeper 可用性提升为 API Server 写路径依赖。
切换前至少验证:三个或更多可用副本是否跨节点/故障域,PDB 是否允许计划维护,Service 是否持续有 Endpoint,证书轮换是否在到期前完成,control plane 到 worker 8443 的网络是否通,timeout 是否低于调用链预算,Gatekeeper 自身及恢复命名空间是否不会被自锁,以及受限主体、短时授权、双人审批、完整审计、自动到期的应急流程是否能恢复执行器。
隔离集群中的反向演练可以缩容 controller-manager 到零:
kubectl -n gatekeeper-system scale deploy/gatekeeper-controller-manager \
--replicas=0
kubectl -n gatekeeper-system get endpointslice
kubectl apply -f policy/fixtures/bad.yaml默认 Ignore 下,预期请求因 webhook 不可达而放行;若改成 Fail,预期请求因调用失败被拒绝,错误不是 owner violation。恢复副本后,先确认 Endpoint 和证书健康,再重试正反例;随后用 audit 查找 fail-open 窗口进入的违规对象。演练命令会影响集群写入,必须只在隔离环境或批准窗口执行,并保留不依赖该 webhook 的恢复通道。
从现象倒推第一证据
“应该拒绝却放行”先分四类:Constraint 没命中、动作不是 deny、webhook 没被调用、webhook 失败后 Ignore。依次查看业务对象 GVK/namespace、Constraint 完整 YAML、ValidatingWebhookConfiguration、EndpointSlice 和 API Server webhook 指标。只看 Gatekeeper Pod Running 会漏掉 selector、证书、网络与 namespaceSelector 问题。
“audit 没有违规”先看 last run start/end,再看扫描日志、目标 Kind、auditFromCache 与 sync 配置、Constraint status 限额。若 last run 没结束,零结果没有意义;若缓存未包含目标 GVK,策略不会凭空发现对象。
“CI 与集群结果不同”要比较 gator 版本、Template/Constraint revision、渲染后的业务对象、AdmissionReview 元数据和 inventory。普通对象 fixture 没有实时 userInfo,依赖请求主体的规则必须用 AdmissionReview fixture 验证,而且 audit 仍无法获得当时主体。稳定组织约束应优先依赖对象状态,主体例外交给短时、审计化流程。
“延迟突然升高”先分 Gatekeeper 求值、External Data、API Server 到 webhook 网络和 audit list 压力。观察 webhook duration、拒绝/调用错误、Provider latency、CPU/内存、audit 扫描时长和 API throttling。扩大副本只改善并发与接管,昂贵 Rego/CEL、超宽 match、过量 inventory 和慢 Provider 仍会按请求放大成本。
权限、数据和成本必须有 owner
Template、Constraint、Config、SyncSet、mutator 与 Provider 都属于高权限策略资产。平台管理员负责安装与执行器 SLO,安全或治理团队负责策略意图,业务 owner 负责 fixture 和例外,发布系统只获得提交已评审对象所需的最小权限。不要让日常开发者直接修改 Constraint 参数,也不要让 Gatekeeper ServiceAccount 获得超出所需 GVK 的读取权限。
AdmissionReview、Constraint 参数、External Data 请求和 audit 日志可能包含用户名、组、标签、镜像地址与业务元数据。日志导出前应按字段脱敏,禁止记录 token、完整 Secret 或外部 Provider 凭证。Provider 凭证放入受控 Secret 并执行轮换,不能写进 Template、Constraint、Helm values 明文或公开仓库。
容量预算应同时覆盖 admission QPS、对象大小、策略数量、每个对象命中的 Constraint 数、inventory 基数、audit list 流量、status 写入和日志保留。constraintViolationsLimit 调大并不能获得完整历史,反而会增加 controller 内存与 etcd 单对象大小。团队应以目标负载基线比较引入策略前后的 API Server 尾延迟、webhook 错误率、Gatekeeper CPU/内存、audit 周期和 Provider SLO,而不是套用一个脱离集群规模的固定阈值。
升级、回滚和退出的顺序
升级前保存 Helm values、Chart 渲染结果、CRD、Template、Constraint、Config/SyncSet、mutator、生成 VAP/VAPBinding、webhook 配置和脱敏指标基线。在隔离集群先升级 CRD 与控制器,运行 gator fixture、集群正反例、audit 完整周期、Endpoint 中断和证书恢复实验,再分批进入共享集群。升级成功的判据是行为与证据保持一致,不只是 Pod Ready。
策略回滚先把执行动作退到 warn/dryrun 或禁用对应 Binding,恢复业务写入;再回滚 Template/Constraint 与控制器版本。直接删除 Template 会连带删除动态 Constraint CRD 及实例,直接删除 Gatekeeper CRD 还会删除所有模板和约束,因此卸载前必须导出并确认依赖。Helm 删除不会自动清理 CRD,这是为防止策略资产被意外删除,不是残留故障。
实验清理按“执行点、策略实例、策略类型、控制器”进行:
kubectl delete k8srequiredowner deployment-owner --ignore-not-found
kubectl delete constrainttemplate k8srequiredowner --ignore-not-found
kubectl delete namespace gatekeeper-lab --ignore-not-found
helm uninstall gatekeeper -n gatekeeper-system只有在确认没有其他 Template、Constraint、Config、mutator 与审计证据需要保留后,才考虑删除 gatekeeper.sh/system=yes 标记的 CRD。长期退出还要撤销 Provider 凭证与 RBAC、移除 webhook、确认 API Server 延迟回到基线、把仍需执行的约束迁移到原生 VAP 或其他平台,并防止 GitOps 再次创建旧对象。
Gatekeeper 的价值不是“给 Kubernetes 加一层拒绝”,而是把同一约束放进提交前测试、请求时执行与存量发现三条证据链。只有 Template、Constraint、执行点、数据依赖、失败策略和恢复责任都能被独立验证,策略才从一段规则代码变成可长期运营的控制系统。
