4. Rego语言入门(一):基本语法、标量类型、复合类型(对象、数组、集合)
Rego 是 OPA 的策略语言,专为在复杂嵌套结构上表达策略逻辑而设计。它脱胎于 Datalog,是一种声明式查询语言。理解 Rego 的基本语法和类型系统,是编写合规校验规则的第一步。本章将聚焦于 Rego 的标量类型与三种核心复合类型:对象、数组和集合。
4.1 Rego 基本语法与表达式
Rego 文件以 .rego 为后缀。一个策略文件通常包含 包声明(Package) 和 规则(Rules)。规则是 Rego 的核心,它定义了一组条件,当条件满足时,规则会生成一个值(通常是 true 或一个数据集合)。
- 包声明:
package kubernetes.admission用于组织策略,避免命名冲突。 - 注释:使用
#进行单行注释。 - 赋值:使用
:=进行局部变量赋值;使用=进行统一(unification)或比较。 - 引用:使用点号
.访问嵌套字段,如input.request.object.spec.replicas。 - 迭代:使用
some关键字引入循环变量,如some i。
一个最简单的 Rego 规则示例如下:
package example.hello
# 定义一个布尔规则
default allow = false
allow {
input.user == "admin"
}
上述规则中,allow 的值默认为 false。只有当 input.user 等于 "admin" 时,allow 才会被求值为 true。
4.2 标量类型
Rego 支持四种基本的标量类型,它们是构建复杂数据结构的基石。
| 类型 | 描述 | 示例 |
|---|---|---|
| 字符串 | 双引号包裹的文本序列。 | "nginx", "1.24.0" |
| 数字 | 整数或浮点数。 | 3, 8080, 3.14 |
| 布尔值 | 逻辑真或假。 | true, false |
| 空值 | 表示缺失或未定义的值。 | null |
null 常用于检查某个字段是否被显式设置。例如,input.request.object.spec.replicas != null 确保副本数字段存在。
4.3 复合类型:对象(Object)
对象是键值对的无序集合,类似于其他语言中的字典或哈希表。键必须是字符串,值可以是任意类型(包括嵌套的对象、数组等)。
对象定义语法:
# 定义一个对象
deployment_config := {
"name": "my-app",
"replicas": 3,
"labels": {"app": "frontend", "tier": "web"},
"ports": [80, 443]
}
访问对象属性:
- 点号访问:
deployment_config.name返回"my-app"。 - 方括号访问:
deployment_config["labels"]返回{"app": "frontend", "tier": "web"}。
对象在策略中常用于表示 Kubernetes 资源的完整配置,例如 input.request.object 就是一个包含 Pod、Deployment 等资源全部字段的对象。
4.4 复合类型:数组(Array)
数组是元素的有序序列,索引从 0 开始。数组中的元素可以是任意类型,包括混合类型。
数组定义语法:
# 定义一个数组
container_ports := [80, 443, 8080]
# 混合类型数组
mixed := ["text", 42, true, {"key": "value"}]
访问数组元素:
- 索引访问:
container_ports[0]返回80。 - 迭代:常与
some关键字配合使用。
# 检查数组中是否包含特定端口
contains_port(port) {
some i
container_ports[i] == port
}
4.5 复合类型:集合(Set)
集合是唯一元素的无序集合。集合中的元素必须是可哈希的(即标量类型或对象/数组的引用,但实践中多为标量)。集合是 Rego 中非常强大的特性,常用于去重和成员关系检查。
集合定义语法:
# 定义一个集合(使用花括号,但元素之间没有冒号)
allowed_ports := {80, 443, 8080}
# 空集合
empty_set := set()
{} 和空集合 set() 是不同的。Rego 中 {} 默认被解释为空对象。要创建空集合,必须显式使用 set() 内置函数。
集合操作:
- 成员检查:
allowed_ports[443]返回true。 - 交集:
allowed_ports & {80, 22}返回{80}。 - 并集:
allowed_ports | {22}返回{80, 443, 8080, 22}。 - 差集:
allowed_ports - {8080}返回{80, 443}。
4.6 类型对比与使用场景
下表总结了三种复合类型的关键差异,帮助你在编写策略时做出正确选择。
| 特性 | 对象 (Object) | 数组 (Array) | 集合 (Set) |
|---|---|---|---|
| 结构 | 键值对,无序 | 索引序列,有序 | 唯一元素,无序 |
| 键/索引 | 字符串键 | 整数索引 (0, 1, 2...) | 无键,元素本身 |
| 重复元素 | 键唯一,值可重复 | 允许重复 | 不允许重复 |
| 典型用途 | 表示资源清单、配置映射 | 表示端口列表、容器列表、环境变量 | 表示白名单、黑名单、已授权角色 |
| 成员检查 | obj["key"] 或 obj.key |
arr[i] 配合迭代 |
set[element] 直接检查 |
4.7 类型关系与数据模型
在 OPA 策略中,这三种类型经常嵌套使用,形成复杂的树状数据模型。下面的 SVG 图展示了 Rego 中一个典型的 Kubernetes 准入控制请求数据模型,以及标量和复合类型如何协同工作。
4.7 复合类型操作实战
下面的 SVG 图展示了 Rego 中三种复合类型的核心操作模式,包括对象属性访问、数组迭代和集合成员检查。
4.8 常见陷阱与最佳实践
在 Rego 中,
x := {} 定义了一个空对象,而不是空集合。要定义空集合,必须使用 x := set()。在策略中错误地使用空对象进行成员检查会导致逻辑错误。
当需要检查某个值是否在允许列表中时,优先使用集合而非数组。例如:
allowed_registries := {"docker.io", "gcr.io", "quay.io"}allowed_registries[image_registry] 比遍历数组更简洁、性能更好。
- Rego 是声明式语言,规则定义条件而非步骤。
- 标量类型(字符串、数字、布尔、null)是构建块。
- 对象用于表示键值结构,数组用于有序列表,集合用于唯一元素集合。
- 集合的成员检查操作是 O(1) 复杂度,适合大规模白名单场景。
- 理解类型差异是编写高效、正确策略的基础。