3. Rego 语言入门:语法基础、规则定义、查询与数据绑定
Rego 是 OPA 的策略描述语言,专为在复杂嵌套数据上表达策略约束而设计。与通用编程语言不同,Rego 基于逻辑推理和模式匹配,其核心思想是“将策略视为数据查询”。本章从最基础的语法结构开始,逐步构建对规则定义、查询执行和数据绑定机制的理解。
3.1 Rego 语法基础
Rego 文件由 包声明、导入语句 和 规则定义 组成。其语法风格受 Prolog 和 JSON 的启发,强调声明式而非命令式。
3.1.1 包与导入
- 包(package):每个 Rego 文件以
package <path>开头,用于组织规则命名空间,避免冲突。 - 导入(import):使用
import data.<path>引入其他包的数据或规则;使用import input引入 OPA 的输入数据。
package kubernetes.admission
import data.kubernetes.namespaces
import input.request.object as pod
3.1.2 字面量与注释
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "production" |
双引号包裹 |
| 数字 | 42, 3.14 |
整数和浮点数 |
| 布尔 | true, false |
小写 |
| 数组 | [1, 2, 3] |
有序集合 |
| 对象 | {"key": "value"} |
键值对,键可为字符串或变量 |
| 集合 | {"a", "b"} |
无序唯一值 |
| 注释 | # 这是一条注释 |
单行,以 # 开头 |
3.2 规则定义:从简单到完整
规则是 Rego 的基本构建块。每条规则定义一个布尔值、一个标量值或一个复合数据结构。规则可以是有条件的,也可以是无条件的。
3.2.1 布尔规则
最简单的规则返回 true 或 false。当规则体中的所有表达式都满足时,规则为真。
# 如果命名空间标签包含 "env: production",则视为生产环境
package example
production_ns {
input.metadata.labels.env == "production"
}
3.2.2 完整规则 vs 部分规则
| 类型 | 语法 | 返回值 | 示例 |
|---|---|---|---|
| 完整规则 | r = <value> { ... } |
单个标量或对象 | max_replicas = 10 { true } |
| 部分规则 | r[<key>] = <value> { ... } |
生成键值对集合 | allowed_ports[port] { ... } |
| 布尔规则 | r { ... } |
隐式 true |
is_admin { input.role == "admin" } |
# 完整规则:返回一个对象
allowed_images = {"nginx": "1.21", "redis": "6.2"} { true }
# 部分规则:动态生成集合
allowed_containers[container] {
container := input.containers[_]
container.image != "latest"
}
3.2.3 默认值
使用 default 关键字为规则提供回退值,避免规则未定义导致的查询失败。
default allow = false
allow {
input.user == "admin"
}
3.3 查询与数据绑定
OPA 通过查询引擎执行 Rego 规则。查询可以针对 input(请求数据)和 data(内置数据或外部加载的数据)进行。数据绑定是 Rego 将变量与具体值匹配的过程。
3.3.1 查询执行流程
关键概念: OPA 的查询引擎使用回溯算法遍历规则体中的表达式。当遇到变量时,引擎尝试从
input 或 data 中寻找匹配的值;如果找不到,则变量绑定失败,该规则路径被剪枝。
3.3.2 变量绑定示例
以下规则展示了变量 ns 如何从 input 中绑定值,并用于后续条件判断。
package example
# 变量 ns 绑定到 input.metadata.namespace
allow {
ns := input.metadata.namespace
ns != "kube-system"
input.metadata.labels.owner == "team-a"
}
3.3.3 迭代与集合绑定
使用 some 关键字或下划线 _ 进行迭代,将数组或集合中的元素绑定到变量。
# 检查 Pod 是否使用了任何不被允许的镜像
violation[msg] {
container := input.spec.containers[_]
container.image == "nginx:latest"
msg := sprintf("容器 %v 使用了 latest 标签", [container.name])
}
性能提示: 在 K8s 准入控制场景中,
input.spec.containers[_] 会遍历所有容器。如果集群中大量 Pod 包含多个容器,建议在规则中尽早过滤(例如先检查命名空间),减少不必要的迭代。
3.4 内置函数与聚合
Rego 提供丰富的内置函数,用于字符串处理、集合操作、时间解析等。在 K8s 策略中,最常用的包括:
count():计算集合或数组长度split():字符串分割sprintf():格式化字符串regex.match():正则匹配time.now_ns():获取当前时间戳
# 检查 Pod 的 hostAliases 数量是否超过限制
too_many_aliases {
count(input.spec.hostAliases) > 5
}
# 验证镜像仓库地址
valid_registry {
regex.match(`^registry\.example\.com/`, input.spec.containers[_].image)
}
3.5 常见陷阱与避坑指南
| 陷阱 | 错误示例 | 正确做法 |
|---|---|---|
| 变量未定义 | input.x == y(y 未绑定) |
使用 y := input.x 先绑定 |
| 规则覆盖 | 两个同名规则同时为真 | 使用 else 或 not 明确分支 |
误用 = |
x = 1 用于赋值 |
使用 := 进行局部赋值 |
| 忽略默认值 | 规则未定义时查询报错 | 添加 default allow = false |
3.6 本章小结
Rego 的语法虽然简洁,但其声明式逻辑和变量绑定机制需要一定的思维转换。掌握包结构、规则类型(完整/部分/布尔)、以及通过迭代进行数据绑定,是编写高效 K8s 策略的基础。下一阶段将把这些基础规则组合成完整的准入控制策略,并开始关注规则复杂度对 API 延迟的影响。