4. Rego 语言基础(一):Rego 语法入门、规则与表达式、数据查询与迭代
Rego 是 OPA 的专用策略语言,专为在复杂 JSON/YAML 数据上进行声明式查询与策略评估而设计。理解 Rego 的语法核心——规则、表达式与迭代机制——是编写 Gatekeeper 约束模板的前提。本章从零构建 Rego 的认知模型,重点覆盖其声明式逻辑、规则结构、常用表达式以及数据查询的迭代模式。
4.1 Rego 的声明式逻辑模型
Rego 不是命令式语言(如 Python、Go),而是基于 Datalog 的声明式逻辑语言。你只需描述策略的“是什么”,而非“如何计算”。OPA 引擎会自动搜索所有可能的数据绑定,使规则成立。
| 特性 | 命令式语言(Python/Go) | 声明式语言(Rego) |
|---|---|---|
| 核心思维 | 控制流 + 状态变更 | 逻辑断言 + 数据关系 |
| 变量赋值 | 可多次赋值,可变 | 一次赋值,不可变(逻辑绑定) |
| 循环 | for/while 显式迭代 | 隐式迭代(集合推导) |
| 条件分支 | if/else 控制流 | 规则体中的逻辑与/或 |
| 错误处理 | try/except | 规则未定义(undefined) |
关键认知:在 Rego 中,变量一旦被绑定,就不能在同一规则体内重新赋值。这种“不可变性”是避免策略副作用的核心保障。
4.2 规则(Rules)基础
Rego 中的规则是策略的基本单元。每条规则定义了一个逻辑断言:当规则体中的所有条件都为真时,规则头部的变量或文档就被视为“真”或“已定义”。
4.2.1 规则结构
# 完整规则:头部 + 体
allow_request if {
input.method == "GET"
input.path == "/healthz"
}
# 简化规则(单行)
allow_request if input.method == "GET"
# 默认规则(防止未定义)
default allow_request := false
- 规则头:
allow_request是规则名,可带参数。 - 规则体:花括号
{ }内的逻辑表达式,所有表达式必须同时为真(逻辑与)。 if关键字:Rego v0.48+ 推荐使用if提升可读性。default:为规则提供兜底值,避免未定义导致查询失败。
4.2.2 完整规则与部分规则
| 类型 | 语法 | 返回值 | 示例 |
|---|---|---|---|
| 完整规则 | r := value { ... } |
单个值(布尔、字符串、数字) | allowed := true { ... } |
| 部分规则 | r[key] = value { ... } |
键值对集合(对象) | ports[service] = port { ... } |
| 集合规则 | r contains item { ... } |
无序集合(数组去重) | violations contains msg { ... } |
# 完整规则:返回布尔值
is_admin if input.user.role == "admin"
# 部分规则:返回对象 { "nginx": 80, "redis": 6379 }
service_ports[service] := port {
some service in input.services
port := service.port
}
# 集合规则:返回违规消息集合
violations contains msg {
input.containers[_].image == "latest"
msg := "禁止使用 latest 标签"
}
4.3 表达式与操作符
Rego 的表达式覆盖比较、逻辑、算术、集合操作和模式匹配。所有表达式都返回布尔值或绑定变量。
4.3.1 比较与逻辑操作符
| 类别 | 操作符 | 说明 |
|---|---|---|
| 比较 | == != < > <= >= |
严格相等/不等,数值比较 |
| 逻辑 | not and or |
逻辑非、与、或(and 通常用换行隐式表示) |
| 成员 | in _ |
集合/数组/对象成员检查;_ 表示匿名变量 |
| 赋值 | := = |
:= 局部赋值;= 统一(声明式绑定) |
# 比较与逻辑组合
deny if {
input.replicas > 5
not input.namespace == "production"
}
# 成员检查
allowed_regions := {"us-east-1", "eu-west-1"}
valid_region if input.region in allowed_regions
# 匿名变量:忽略具体索引
containers_with_privilege[container_name] {
container := input.containers[_]
container.securityContext.privileged == true
container_name := container.name
}
4.3.2 聚合与内置函数
Rego 提供丰富的内置函数(builtins),用于字符串处理、网络 CIDR 计算、时间比较、JSON 操作等。
# 字符串操作
contains(input.name, "prod")
startswith(input.namespace, "kube-")
endswith(input.image, ":latest")
# 网络 CIDR
cidr_contains("10.0.0.0/8", input.podIP)
# 聚合
count(input.containers) > 1
max([1, 2, 3]) == 3
sum([1, 2, 3]) == 6
# 时间比较
time.now_ns() > time.parse_rfc3339_ns("2025-01-01T00:00:00Z")
注意:Rego 内置函数是纯函数,无副作用。不要期望它们修改外部状态或产生日志输出。
4.4 数据查询与迭代模式
Rego 没有显式的 for 循环,而是通过隐式迭代和集合推导遍历数据。核心机制是 some 关键字和匿名变量 _。
4.4.1 some 关键字:显式绑定迭代变量
# 遍历数组,绑定索引和元素
violations contains msg {
some i, container in input.containers
container.image == "nginx:latest"
msg := sprintf("容器 %s 使用了 latest 标签", [container.name])
}
# 遍历对象键值对
allowed_ports[service_name] := port {
some service_name, port in input.service_ports
port < 1024
}
4.4.2 匿名变量 _:忽略索引
# 只关心元素,不关心索引
privileged_containers[container.name] {
container := input.containers[_]
container.securityContext.privileged == true
}
4.4.3 集合推导与复合迭代
# 嵌套迭代:遍历所有容器的挂载卷
host_path_mounts contains mount {
some container in input.containers
some mount in container.volumeMounts
mount.hostPath != ""
}
# 使用 every 关键字(Rego v0.48+):全称量化
all_containers_have_readiness_probe if {
every container in input.containers {
container.readinessProbe != null
}
}
4.5 Rego 数据查询流程(SVG 图)
下图展示了 Rego 引擎从输入数据到策略决策的完整查询流程,突出规则体中的迭代与绑定机制。
4.6 实战示例:从 Pod 清单中提取违规容器
结合上述语法,编写一个完整的 Rego 规则,检查 Pod 中是否有容器以 root 用户运行(securityContext.runAsUser == 0)。
package example.pod_security
# 默认:无违规
default has_root_container := false
# 完整规则:返回布尔值
has_root_container if {
some container in input.containers
container.securityContext.runAsUser == 0
}
# 集合规则:返回所有 root 容器的名称
root_containers contains container.name {
some container in input.containers
container.securityContext.runAsUser == 0
}
# 带消息的违规集合
violations contains msg {
some container in input.containers
container.securityContext.runAsUser == 0
msg := sprintf("容器 '%s' 以 root 用户运行 (UID=0)", [container.name])
}
执行结果示例:当输入
{"containers": [{"name": "web", "securityContext": {"runAsUser": 0}}, {"name": "sidecar", "securityContext": {"runAsUser": 1000}}]} 时:has_root_container → trueroot_containers → ["web"]violations → ["容器 'web' 以 root 用户运行 (UID=0)"]
4.7 常见陷阱与避坑指南
| 陷阱 | 错误示例 | 正确做法 |
|---|---|---|
| 变量重复赋值 | x := 1; x := 2 |
使用不同变量名或 some 重新绑定 |
误用 = 代替 := |
x = 5 在规则头外 |
局部赋值用 :=,统一用 = |
忘记 some 导致未定义 |
container := input.containers[0] 硬编码 |
使用 some i, container in input.containers |
| 集合规则返回重复项 | 集合规则内多次绑定相同值 | Rego 自动去重,无需手动处理 |
| 未定义规则导致查询失败 | 直接引用未定义规则 | 使用 default 提供兜底值 |
4.8 本章小结
- Rego 是声明式逻辑语言:描述策略约束,而非计算步骤。
- 规则三形态:完整规则(单值)、部分规则(对象)、集合规则(集合)。
- 迭代核心:
some显式绑定、_匿名忽略、every全称量化。 - 表达式:比较、逻辑、成员检查、内置函数构成规则体。
- 不可变性:变量一旦绑定,不可在同一规则体内修改。