4. Gatekeeper 核心概念:约束模板、约束与数据注入

在深入实战之前,必须透彻理解 Gatekeeper 的三个核心抽象:约束模板(ConstraintTemplate)、约束(Constraint) 以及 数据注入(data.inventory)。这三者构成了策略定义、策略实例化与策略上下文获取的完整链路。理解它们的协作关系,是编写可维护、可复用的安全基线门禁策略的前提。

4.1 约束模板(ConstraintTemplate)—— 策略的“类”

约束模板是 Gatekeeper 策略的蓝图,它使用 Rego 语言(OPA 的策略语言)定义了一类规则的逻辑骨架。你可以将其类比为面向对象编程中的类(Class):它描述了“要检查什么”以及“如何判断违规”,但不包含具体的参数值(如哪些命名空间、哪些标签)。

关键理解: 约束模板只定义规则逻辑,不定义规则作用范围。作用范围由约束(Constraint)注入。

4.1.1 模板结构解剖

一个标准的约束模板 YAML 包含以下核心字段:

字段路径 类型 说明
apiVersion string 固定为 templates.gatekeeper.sh/v1
kind string 固定为 ConstraintTemplate
spec.crd.spec.names.kind string 该模板实例化后对应的约束资源名称(如 K8sRequiredLabels)
spec.targets[0].rego string Rego 策略代码,包含 violation 规则
spec.targets[0].libs []string (可选)引用的 Rego 库代码,用于复用辅助函数

4.1.2 Rego 规则核心:violation 块

Rego 代码中最重要的部分是 violation[{"msg": msg}] 块。当该块的条件为真时,Gatekeeper 判定为违规,并拒绝变更或生成审计告警。

# 示例:检查必须包含 "security-tier" 标签的约束模板片段
violation[{"msg": msg}] {
  # 从 input.review.object 中提取待审查的资源
  obj := input.review.object
  # 检查是否缺少 required-label
  not obj.metadata.labels["security-tier"]
  # 构造违规消息
  msg := sprintf("资源 %v 缺少必需的标签 'security-tier'", [obj.metadata.name])
}
避坑提示: Rego 中的 input.review 是 Gatekeeper 注入的审查上下文,包含待操作资源的完整对象(.object)以及旧对象(.oldObject,用于更新操作)。不要直接使用 input 的顶层字段,必须通过 input.review 访问。

4.2 约束(Constraint)—— 策略的“实例”

约束是约束模板的具体实例,相当于面向对象中的对象(Object)。它通过 spec.match 字段指定该策略作用于哪些资源,并通过 spec.parameters 字段向模板注入具体的参数值(如允许的标签列表、禁止的镜像仓库等)。

4.2.1 约束的匹配机制

spec.match 支持多种匹配维度,形成灵活的策略作用域:

  • kinds:匹配资源类型(如 Pod、Deployment、Service)及其 API 组。
  • namespaces:匹配命名空间列表。
  • labelSelector:匹配资源标签或命名空间标签。
  • namespaceSelector:匹配命名空间的标签选择器。
  • scope:匹配集群作用域(Cluster)或命名空间作用域(Namespaced)资源。
# 约束实例:要求生产命名空间下的 Pod 必须包含 "security-tier" 标签
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels  # 对应模板中 spec.crd.spec.names.kind
metadata:
  name: prod-pod-must-have-security-tier
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "prod-frontend"
      - "prod-backend"
  parameters:
    labels:
      - key: "security-tier"
        allowedRegex: "^critical|high$"  # 注入给模板的参数

4.3 数据注入(data.inventory)—— 策略的“上下文”

很多安全基线策略需要跨资源查询,例如:“检查所有 Pod 是否引用了已存在的 Secret”或“确保 Ingress 引用的 Service 存在于同一命名空间”。此时,仅靠 input.review 中的单个资源对象是不够的。Gatekeeper 提供了 data.inventory 机制,将集群中已有的资源缓存为可查询的数据集。

4.3.1 数据注入的工作原理

  1. 缓存范围:Gatekeeper 通过 --audit-from-cache 和 --cache-used-for 参数控制哪些资源被缓存到 data.inventory 中。
  2. 查询方式:在 Rego 中通过 data.inventory.namespace[ns_name][resource_type][resource_name] 路径访问缓存数据。
  3. 典型场景:验证网络策略是否覆盖了所有 Pod、检查 RBAC 绑定是否引用了有效的 ServiceAccount。
# 使用 data.inventory 检查 Pod 引用的 ConfigMap 是否存在
violation[{"msg": msg}] {
  pod := input.review.object
  volume := pod.spec.volumes[_]
  volume.configMap
  cm_name := volume.configMap.name
  # 从缓存中查询该命名空间下是否存在该 ConfigMap
  not data.inventory.namespace[pod.metadata.namespace]["v1"]["ConfigMap"][cm_name]
  msg := sprintf("Pod %v 引用了不存在的 ConfigMap %v", [pod.metadata.name, cm_name])
}
性能警告: data.inventory 是全量缓存,对于大规模集群(数千个资源),频繁查询可能导致 Gatekeeper 性能下降。建议在 Rego 中使用 data.inventory 时,优先通过命名空间和资源类型缩小查询范围,避免全量遍历。

4.4 三者的协作关系(SVG 架构图)

下图展示了约束模板、约束与数据注入在 Gatekeeper 策略生命周期中的协作流程:

Gatekeeper 核心概念协作关系图 约束模板 · 约束 · 数据注入 协作流程 约束模板 ConstraintTemplate 定义 Rego 规则逻辑 约束 Constraint 注入参数 + 匹配规则 数据注入 data.inventory 集群资源缓存查询 实例化 查询上下文 Kubernetes API 请求(变更事件) AdmissionReview → Gatekeeper Webhook 触发审查 允许/拒绝 协作说明: 1. 约束模板定义“检查逻辑”(如:检查标签是否存在)。 2. 约束实例化模板并注入“匹配范围”和“参数”(如:只检查 prod 命名空间,标签值必须匹配正则)。 3. 审查时,Gatekeeper 将请求对象(input.review)和集群缓存(data.inventory)一并提供给 Rego 引擎执行判定。

4.5 核心概念对比表

维度 约束模板(ConstraintTemplate) 约束(Constraint) 数据注入(data.inventory)
类比 类(Class) 对象(Instance) 数据库视图(View)
职责 定义规则逻辑(Rego) 定义规则作用域与参数 提供跨资源查询能力
是否可复用 是,一个模板可被多个约束引用 是,同一模板可实例化为不同约束 全局缓存,所有模板可查询
变更频率 低(规则逻辑稳定后很少改) 中(随安全基线水位调整) 自动同步集群状态
性能影响 低(仅编译一次) 中(匹配规则影响审查路径) 高(全量缓存,需谨慎查询)

4.6 实战中的常见误区

  • 误区一:在约束模板中硬编码命名空间或标签值。 正确做法:模板只定义逻辑,所有可变参数通过 input.parameters 从约束注入。
  • 误区二:在 Rego 中直接遍历 data.inventory 的所有命名空间。 正确做法:先通过 input.review.object.metadata.namespace 缩小范围,再查询具体资源类型。
  • 误区三:约束的 spec.match 与 spec.parameters 混淆。 记住:match 决定“对谁生效”,parameters 决定“生效时的具体规则值”。
最佳实践: 在编写约束模板时,始终在 Rego 代码开头添加 package your_package_name,并使用 import future.keywords 启用 Rego 未来关键字(如 in),使代码更简洁可读。