2. OPA 规则基础:Rego 语言入门、规则与查询、数据与输入文档

在深入排查 OPA 准入控制延迟之前,必须牢固掌握 Rego 语言的核心概念。本章将系统讲解 Rego 的基本语法、规则与查询的结构,以及 data 和 input 两个关键文档的作用。这些基础是理解后续规则复杂度分析和性能优化的前提。

2.1 Rego 语言入门

Rego 是一种专为策略定义和策略决策而设计的声明式查询语言。它并非通用编程语言,而是专注于“策略即代码”(Policy as Code)场景。Rego 的核心思想是:策略是数据查询。你定义一组规则(Rule),然后通过查询(Query)来获取策略决策结果。

关键认知: Rego 不是命令式语言(如 Python、Go),不关心“如何做”,只关心“要什么”。你描述策略的期望状态,Rego 引擎负责计算是否满足。

2.1.1 基本语法元素

语法元素 说明 示例
标量 字符串、数字、布尔值、null "admin", 8080, true, null
复合值 对象(Object)、数组(Array)、集合(Set) {"name": "app"}, [1,2,3], {"a","b"}
变量 以 _ 或字母开头,不可变绑定 user, _role, namespace
引用 通过点号或方括号访问嵌套数据 input.request.kind, data.roles["admin"]
数组/对象推导 类似 Python 列表推导,用于生成新集合 [x | x = data.users[_]; x.role == "admin"]
内置函数 字符串、正则、时间、加密等标准库 startswith(path, "/api/"), regex.match("^v[0-9]+", version)

2.1.2 表达式与逻辑

Rego 中的表达式是逻辑条件,多个表达式之间用 ; 或换行分隔,表示逻辑“与”(AND)。同一规则内不同子句之间是“或”(OR)关系。

# 逻辑与:所有条件必须同时满足
allow {
    input.user == "admin"
    input.action == "delete"
}

# 逻辑或:使用不同规则体实现
allow {
    input.user == "admin"
}
allow {
    input.user == "superuser"
}

2.2 规则(Rule)与查询(Query)的基本结构

2.2.1 规则结构

规则是 Rego 策略的基本单元。每条规则由一个头部和一个或多个体组成。

# 规则头部:规则名(参数) [= 值]
# 规则体:条件表达式序列

# 布尔规则(默认返回 true/false)
allowed_namespace {
    input.namespace == "production"
}

# 完整规则(返回具体值)
allowed_ports[port] {
    port := input.ports[_]
    port > 1024
    port < 65535
}

# 带默认值的规则
default allow = false
allow {
    input.user == "admin"
}
规则类型 头部形式 返回值 典型用途
布尔规则 rule_name { ... } true(体为真时) 准入控制中的 allow/deny 判断
完整规则 rule_name = value { ... } 指定的值 计算配置、生成报告
集合规则 rule_name[element] { ... } 元素集合 收集满足条件的资源列表
对象规则 rule_name[key] = value { ... } 键值对对象 构建映射关系
默认规则 default rule_name = value 默认值 避免未定义错误

2.2.2 查询结构

查询是向 OPA 引擎发起的策略评估请求。在 Kubernetes 准入控制场景中,查询通常由 API Server 自动构造。

# 简单查询:检查某个规则是否为真
data.kubernetes.admission.allow

# 带变量的查询:获取具体值
x := data.kubernetes.admission.allowed_namespaces[x]

# 复杂查询:组合多个规则
allow := data.kubernetes.admission.allow
violations := data.kubernetes.admission.violations
性能陷阱: 查询中如果使用未绑定的变量(如 data.roles[_]),OPA 会进行全量遍历。在规则复杂度高时,这种查询可能成为延迟瓶颈。后续章节将详细分析。

2.3 数据文档(data)与输入文档(input)的概念

OPA 的评估模型基于两个核心文档:data 和 input。理解它们的区别和用途是编写高效策略的基础。

2.3.1 数据文档(data)

data 是 OPA 的全局持久化数据,包含所有加载的策略规则和外部数据。它由以下部分组成:

  • 策略规则: 所有通过 package 声明的规则自动挂载到 data 下。例如 package kubernetes.admission 中的规则可通过 data.kubernetes.admission 访问。
  • 外部数据: 通过 Bundle、API 推送或文件加载的 JSON/YAML 数据,如 RBAC 配置、命名空间白名单、资源配额等。
  • 内置数据: OPA 内置的元数据,如 data.opametadata。
# 假设加载了以下外部数据(JSON)
{
  "allowed_registries": ["docker.io", "gcr.io"],
  "admin_users": ["alice", "bob"]
}

# 在 Rego 中访问
allowed_registry {
    input.image.startswith(data.allowed_registries[_])
}

is_admin {
    input.user == data.admin_users[_]
}

2.3.2 输入文档(input)

input 是每次策略评估时传入的临时请求数据。在 Kubernetes 准入控制中,input 包含 AdmissionReview 请求的全部内容。

# 典型的 Kubernetes 准入控制 input 结构(简化)
{
  "kind": "AdmissionReview",
  "request": {
    "uid": "12345",
    "operation": "CREATE",
    "object": {
      "kind": "Pod",
      "metadata": {
        "name": "nginx-pod",
        "namespace": "default"
      },
      "spec": {
        "containers": [
          {"name": "nginx", "image": "nginx:latest"}
        ]
      }
    },
    "userInfo": {
      "username": "kubernetes-admin",
      "groups": ["system:masters"]
    }
  }
}

# 在 Rego 中访问 input
deny[msg] {
    input.request.operation == "CREATE"
    input.request.object.kind == "Pod"
    not startswith(input.request.object.spec.containers[_].image, "allowed-registry.com/")
    msg := sprintf("容器镜像 %v 不在允许的仓库中", [input.request.object.spec.containers[_].image])
}

2.3.3 data 与 input 的对比

维度 data input
生命周期 持久化,跨多次评估共享 每次评估独立传入,评估后丢弃
数据来源 Bundle、API、文件加载 调用方(如 API Server)传入
更新方式 通过 Bundle 更新或 API 推送 每次请求重新构造
典型内容 策略规则、白名单、配置、RBAC AdmissionReview、HTTP 请求、事件
访问性能 可被 OPA 缓存优化 每次评估需重新解析
安全影响 需保护 data 不被篡改 需验证 input 的合法性

2.4 规则评估流程与文档关系

下面的 SVG 图展示了 OPA 在 Kubernetes 准入控制场景中的规则评估流程,以及 data 和 input 如何协同工作。

OPA 规则评估流程:data 与 input 协同 Kubernetes API Server 构造 AdmissionReview input 文档 OPA 策略引擎 加载 Rego 规则 评估 input 与 data 返回决策结果 data 文档 策略规则 外部配置数据 Bundle / API 推送 加载 data 决策结果 allow / deny / violations 返回决策 关键理解: 1. input 是每次请求的“快照”,包含当前被评估的资源对象和操作信息。 2. data 是持久化的“知识库”,包含策略规则和外部参考数据。 3. OPA 引擎将 input 与 data 结合评估,输出决策结果。 4. data 的更新频率远低于 input,因此 data 的缓存优化对性能至关重要。

2.5 实战示例:一个完整的准入控制规则

下面是一个完整的 Rego 规则示例,展示了 data 和 input 的协同使用。该规则用于检查 Pod 是否使用了允许的镜像仓库。

package kubernetes.admission

# 默认拒绝
default deny = false

# 从 data 中读取允许的镜像仓库列表
allowed_registries := data.config.allowed_registries

# 拒绝规则:如果镜像不在允许列表中,则拒绝
deny[msg] {
    input.request.operation == "CREATE"
    input.request.object.kind == "Pod"
    container := input.request.object.spec.containers[_]
    not startswith(container.image, allowed_registries[_])
    msg := sprintf("容器 %v 的镜像 %v 不在允许的仓库列表中", [container.name, container.image])
}

# 审计规则:记录所有创建 Pod 的操作
audit_log[entry] {
    input.request.operation == "CREATE"
    input.request.object.kind == "Pod"
    entry := {
        "pod_name": input.request.object.metadata.name,
        "namespace": input.request.object.metadata.namespace,
        "user": input.request.userInfo.username,
        "time": time.now_ns()
    }
}
性能观察: 上述规则中 allowed_registries[_] 会遍历整个列表。如果 data.config.allowed_registries 包含数千个条目,每次评估都会产生 O(n) 的复杂度。后续章节将介绍如何通过索引、集合和缓存来优化这类查询。

2.6 常见误区与最佳实践

误区 说明 正确做法
混淆 data 与 input 将请求相关的数据硬编码到 data 中,或将配置数据放在 input 中 data 存持久化配置,input 存请求数据
过度使用全量遍历 在规则中频繁使用 data.list[_] 遍历大数组 使用集合(Set)或索引优化查询
忽略默认值 未定义 default 规则,导致查询返回 undefined 始终为布尔规则定义 default allow = false
规则体过于复杂 单个规则体包含过多条件,难以调试和优化 拆分为多个子规则,使用中间变量
忽略 input 验证 直接使用 input 字段而不检查是否存在 使用 input.request.object.metadata.name != "" 等前置检查