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
实践提示:在 Kubernetes 准入控制场景中,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 准入控制请求数据模型,以及标量和复合类型如何协同工作。

Rego 数据模型:Kubernetes 准入请求结构 input (Object) request.kind (Object) request.uid (String) request.object (Object) request.userInfo (Object) spec (Object) metadata (Object) replicas (Number) template (Object) containers (Array) 说明:整个请求是一个嵌套的对象(Object),其中包含字符串、数字等标量,以及数组(如 containers)和子对象(如 spec)。

4.7 复合类型操作实战

下面的 SVG 图展示了 Rego 中三种复合类型的核心操作模式,包括对象属性访问、数组迭代和集合成员检查。

Rego 复合类型操作模式 对象 (Object) 定义:{"name": "app", "ver": 2} 访问:obj.name → "app" 访问:obj["ver"] → 2 检查:obj.other != null 数组 (Array) 定义:[80, 443, 8080] 访问:arr[0] → 80 迭代:some i; arr[i] == 443 长度:count(arr) → 3 集合 (Set) 定义:{80, 443, 8080} 检查:s[443] → true 交集:s & {80, 22} → {80} 差集:s - {8080} → {80, 443} 策略编写建议 1. 使用对象(Object)表示结构化配置,如 Deployment 的 spec。 2. 使用数组(Array)表示有序列表,如容器端口、环境变量列表。 3. 使用集合(Set)进行高效的成员检查和去重,如白名单端口、允许的镜像仓库。 4. 集合的成员检查(s[element])比数组的线性迭代(some i; arr[i] == x)性能更好。

4.8 常见陷阱与最佳实践

陷阱 1:混淆空对象与空集合
在 Rego 中,x := {} 定义了一个空对象,而不是空集合。要定义空集合,必须使用 x := set()。在策略中错误地使用空对象进行成员检查会导致逻辑错误。
最佳实践:使用集合进行白名单校验
当需要检查某个值是否在允许列表中时,优先使用集合而非数组。例如:
allowed_registries := {"docker.io", "gcr.io", "quay.io"}
allowed_registries[image_registry] 比遍历数组更简洁、性能更好。
关键要点回顾:
  • Rego 是声明式语言,规则定义条件而非步骤。
  • 标量类型(字符串、数字、布尔、null)是构建块。
  • 对象用于表示键值结构,数组用于有序列表,集合用于唯一元素集合。
  • 集合的成员检查操作是 O(1) 复杂度,适合大规模白名单场景。
  • 理解类型差异是编写高效、正确策略的基础。