3. OPA Gatekeeper 核心概念回顾

在开始从 OPA Gatekeeper 向 Kyverno 迁移之前,必须对 Gatekeeper 的核心概念建立清晰、准确的理解。Gatekeeper 是 Kubernetes 的 准入控制器,它利用 OPA(Open Policy Agent) 的 Rego 语言来编写策略,并通过 约束模板(ConstraintTemplate) 和 约束(Constraint) 两个核心资源来管理策略的生命周期。本章将系统回顾这些概念,为后续的迁移适配打下坚实基础。

3.1 核心架构与组件

Gatekeeper 的架构围绕三个核心组件构建:OPA 引擎、约束模板和约束。它们之间的关系如下图所示:

OPA Gatekeeper 核心架构与策略执行流程 Kubernetes API 准入请求(Admission Request) Gatekeeper 准入 Webhook 审计控制器 OPA 引擎 Rego 策略评估 决策缓存 数据注入 集群状态 / 外部数据 约束模板(ConstraintTemplate) 定义 Rego 逻辑与参数 schema 约束(Constraint) 实例化模板,配置参数与匹配规则 审计结果(Audit) violations 列表 / 状态报告
关键理解:Gatekeeper 本身是一个 Kubernetes 扩展,它不直接执行策略,而是将准入请求转发给 OPA 引擎。约束模板是策略的“类”,约束是策略的“实例”。这种分离设计使得策略逻辑可以复用,而参数和匹配范围可以灵活配置。

3.2 约束模板(ConstraintTemplate)

约束模板是 Gatekeeper 策略体系的核心。它是一个 CRD(Custom Resource Definition),用于定义策略的 逻辑(Rego 代码) 和 参数结构(Schema)。一旦创建,Kubernetes 集群中就会生成一个新的 CRD 类型,名称与模板名称一致。

3.2.1 模板结构

一个典型的约束模板包含以下关键字段:

字段路径 类型 说明
spec.crd.spec.names.kind string 生成的 CRD 类型名称,例如 K8sRequiredLabels
spec.targets[].rego string Rego 策略代码,定义 violation 规则
spec.targets[].libs []string 可选的 Rego 库代码,用于代码复用
spec.crd.spec.validation.openAPIV3Schema object 约束实例的参数校验 schema(OpenAPI v3 格式)

3.2.2 Rego 策略逻辑

Rego 代码是约束模板的灵魂。Gatekeeper 要求 Rego 代码中必须定义一个 violation 规则,该规则返回 true 表示违反策略。以下是一个要求资源必须包含 owner 标签的模板示例:

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

        violation[{"msg": msg}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("缺少必需的标签: %v", [missing])
        }
Rego 要点:
  • input.review:准入请求的完整对象,包含 object(新资源)和 oldObject(旧资源,更新时存在)。
  • input.parameters:约束实例中传入的参数,与模板 schema 对应。
  • violation 规则必须返回一个包含 msg 字段的对象,描述违反详情。

3.3 约束(Constraint)

约束是约束模板的实例化资源。它指定了 策略参数 和 匹配范围(哪些资源受此策略约束)。约束的 apiVersion 和 kind 由约束模板动态生成。

3.3.1 约束示例

基于上述 K8sRequiredLabels 模板,创建一个要求所有命名空间必须包含 owner 和 env 标签的约束:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: ns-must-have-owner-env
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels:
      - "owner"
      - "env"

3.3.2 匹配机制

约束的 spec.match 字段支持多种匹配条件,用于精细控制策略作用范围:

匹配字段 说明 示例
kinds 按资源类型匹配(apiGroups + kinds) apiGroups: [""], kinds: ["Pod"]
namespaces 按命名空间白名单匹配 ["production", "staging"]
excludedNamespaces 排除特定命名空间 ["kube-system"]
labelSelector 按资源标签选择器匹配 matchLabels: {app: "web"}
namespaceSelector 按命名空间标签选择器匹配 matchExpressions: [...]
scope 按资源范围匹配(Cluster/Namespaced) Cluster

3.4 数据(Data)

Gatekeeper 中的“数据”是指 OPA 引擎在评估策略时可以访问的 外部信息。这些数据可以来自集群状态,也可以由管理员手动注入。数据机制使得策略可以基于集群的实时状态做出决策。

3.4.1 数据来源

  • 集群数据(Cluster Data):Gatekeeper 自动将集群中的资源(如 Namespace、Node、Pod)同步到 OPA 引擎,供 Rego 代码通过 data 前缀访问。例如 data.kubernetes.namespaces 返回所有命名空间列表。
  • 外部数据(External Data):通过 Config 资源或 Provider 机制注入的自定义数据,例如来自 CMDB 的标签白名单、来自安全团队的禁止镜像列表。
  • 数据注入(Data Injection):管理员可以创建 config 资源,将任意 JSON 数据注入到 OPA 的 data 命名空间中。

3.4.2 数据在 Rego 中的使用

package data_example

# 访问集群中的命名空间列表
violation[{"msg": msg}] {
  ns := input.review.object.metadata.namespace
  all_ns := data.kubernetes.namespaces
  # 检查命名空间是否存在
  not any(all_ns, {x | x.metadata.name == ns})
  msg := sprintf("命名空间 %v 不存在", [ns])
}

# 访问外部注入的数据
violation[{"msg": msg}] {
  image := input.review.object.spec.containers[_].image
  blocked := data.external.blocked_images
  image == blocked[_]
  msg := sprintf("镜像 %v 被禁止使用", [image])
}
性能注意:频繁访问 data.kubernetes 中的大型集合(如所有 Pod)可能导致 OPA 评估延迟。建议在 Rego 中使用 count() 和集合操作来优化查询,或通过缓存机制减少重复计算。

3.5 审计(Audit)

审计是 Gatekeeper 的 被动合规检查 机制。与准入控制(实时拦截)不同,审计会定期扫描集群中已有的资源,检查它们是否违反当前生效的约束,并将结果记录在约束资源的 status 字段中。

3.5.1 审计工作流程

Gatekeeper 审计流程 1. 定时触发 审计间隔(默认 60s) 2. 列举约束 获取所有约束实例 3. 匹配资源 按 match 规则筛选 4. Rego 评估 执行 violation 规则 5. 记录结果 写入约束 status.byPod 6. 合规报告 violations 列表 / 审计日志

3.5.2 审计结果查看

审计结果存储在约束资源的 status 字段中,可以通过 kubectl describe 查看:

kubectl describe k8srequiredlabels ns-must-have-owner-env

# 输出示例(简化)
Status:
  By Pod:
    - Constraint UID:  ...
      Enforced:         true
      Id:               gatekeeper-audit-xxxxx
      Observed Generation: 1
      Operations:
      - audit
      Violations:
        - Enforcement Action: deny
          Kind:                 Namespace
          Message:              缺少必需的标签: {"owner"}
          Name:                 default
        - Enforcement Action: deny
          Kind:                 Namespace
          Message:              缺少必需的标签: {"env"}
          Name:                 kube-public
  Total Violations:  2
审计 vs 准入:准入控制在资源创建/更新时实时拦截违规请求;审计则是对已有资源进行周期性合规检查。两者结合可以实现“事前拦截 + 事后发现”的完整策略覆盖。迁移到 Kyverno 时,需要理解 Kyverno 的 backgroundScan 机制对应 Gatekeeper 的审计功能。

3.6 Rego 语法基础

Rego 是 OPA 的声明式查询语言,受 Datalog 启发。理解 Rego 的核心语法是编写和迁移 Gatekeeper 策略的前提。以下列出迁移过程中最常遇到的 Rego 语法要素:

3.6.1 核心语法对照

Rego 语法 说明 迁移到 Kyverno 的对应概念
package <name> 定义策略包,用于命名空间隔离 Kyverno 使用 YAML 规则,无包概念
violation[{"msg": msg}] { ... } 定义违反规则,返回消息 Kyverno 使用 validate.deny.conditions 或 validate.pattern
input.review.object 访问被审查的 Kubernetes 资源 Kyverno 使用 {{ request.object }} 或 @ 引用
input.parameters 访问约束实例传入的参数 Kyverno 使用 params 对象或直接写在规则中
data.kubernetes 访问集群状态数据 Kyverno 使用 context 和 APICall 获取外部数据
some ... in ... 迭代集合,类似 for 循环 Kyverno 使用 foreach 或 JMESPath 表达式
count() 计算集合元素数量 Kyverno 使用 length() 函数
set / object / array 集合、对象、数组类型 Kyverno 使用 YAML 结构,类型由 JMESPath 处理

3.6.2 常见 Rego 模式示例

以下展示几个在 Gatekeeper 策略中高频出现的 Rego 模式,以及它们在 Kyverno 中的对应写法(预览):

// Rego: 检查容器镜像是否来自允许的仓库
package allowed_registry

violation[{"msg": msg}] {
  container := input.review.object.spec.containers[_]
  not startswith(container.image, "gcr.io/my-company/")
  msg := sprintf("容器镜像 %v 必须来自 gcr.io/my-company/", [container.image])
}

// Rego: 检查资源是否设置了 resource limits
package require_limits

violation[{"msg": msg}] {
  container := input.review.object.spec.containers[_]
  not container.resources.limits
  msg := sprintf("容器 %v 必须设置 resource limits", [container.name])
}

// Rego: 使用外部数据检查禁止的端口
package block_ports

violation[{"msg": msg}] {
  port := input.review.object.spec.ports[_]
  blocked_ports := data.external.blocked_ports
  port.containerPort == blocked_ports[_]
  msg := sprintf("端口 %v 被禁止使用", [port.containerPort])
}
迁移关键洞察:
  • Rego 的 violation 规则本质是一个 集合推导:当条件满足时,向集合中添加一条违规消息。Kyverno 的 deny 规则则是 条件判断:当条件为真时拒绝请求。
  • Rego 中的 not 和 some 是逻辑核心,迁移时需要转换为 Kyverno 的 anyPattern 或 foreach + deny 组合。
  • Rego 的 data 查询在 Kyverno 中通过 context 和 apiCall 实现,需要显式定义数据源。

3.7 本章小结