2、OPA基础回顾:Rego语言核心概念、策略包(Package)与规则(Rule)、数据文档与查询
在将OPA集成到Operator生命周期之前,必须牢固掌握Rego语言的基础。Rego是OPA的原生声明式查询语言,专门用于定义策略规则。本章将系统梳理Rego的核心概念、包与规则的组织方式,以及数据文档与查询机制,为后续的Operator策略编写打下坚实基础。
2.1 Rego语言核心概念
Rego是一种基于逻辑编程(受Datalog启发)的声明式语言。与传统的命令式语言不同,Rego不描述“如何做”,而是描述“应该是什么”。策略编写者只需声明规则,OPA引擎负责计算规则是否被满足。
2.1.1 基本语法要素
| 概念 | 语法示例 | 说明 |
|---|---|---|
| 标量 | "hello"、42、true |
字符串、数字、布尔值 |
| 变量 | x、user、_ |
以字母或下划线开头;_为匿名变量 |
| 引用 | data.deployments、input.request.kind |
访问嵌套文档的路径 |
| 数组 | [1, 2, 3]、["a", "b"] |
有序集合,索引从0开始 |
| 对象 | {"key": "value"} |
键值对集合,键通常为字符串 |
| 集合 | {"a", "b", "c"} |
无序唯一值集合 |
| 规则 | allow { ... } |
策略的核心逻辑单元 |
2.1.2 迭代与推导
Rego通过规则推导实现迭代,而非使用循环。当规则体中的变量存在多个可能绑定时,Rego引擎会自动进行回溯搜索,找出所有满足条件的解。
关键理解:Rego中的每条规则本质上是一个逻辑蕴含——如果规则体(body)中的所有条件都为真,则规则头(head)为真。这种声明式特性使得策略代码简洁且易于验证。
2.2 策略包(Package)与规则(Rule)
Rego使用包(package)来组织策略代码,类似于编程语言中的命名空间。包名决定了规则在OPA数据文档树中的路径。
2.2.1 包的定义与作用
package kubernetes.admission
# 该包下的所有规则都会挂载到 data.kubernetes.admission 路径下
包名使用点号分隔的层级结构,每个层级对应数据文档树中的一个节点。包名通常反映策略的职责域,例如:
package operator.health—— Operator健康检查策略package operator.reconcile—— 调谐逻辑策略package operator.security—— 安全准入策略
2.2.2 规则的分类
| 规则类型 | 语法 | 返回值 | 典型用途 |
|---|---|---|---|
| 完整规则 | r = v { body } |
单个值(标量、对象、数组) | 计算配置值、默认策略 |
| 部分规则 | r[k] = v { body } |
键值对集合 | 生成允许列表、资源映射 |
| 布尔规则 | r { body } |
true/false(默认false) | 准入判断、权限检查 |
| 函数规则 | f(x) = y { body } |
根据输入参数计算 | 复用逻辑、数据转换 |
2.2.3 规则体与逻辑运算符
规则体由多个表达式组成,表达式之间默认是逻辑与(AND)关系。同一规则内可以使用分号(;)或换行分隔。逻辑或(OR)通过定义多条同名规则实现。
package operator.reconcile
# 布尔规则:判断是否允许调谐
allow_reconcile {
input.resource.namespace == "production"
input.resource.labels.managed == "true"
}
# 逻辑或:两条同名规则,任一满足即可
allow_reconcile {
input.resource.namespace == "staging"
input.resource.labels.managed == "true"
input.resource.annotations["emergency"] == "true"
}
# 完整规则:返回调谐间隔
reconcile_interval = "30s" {
input.resource.kind == "Deployment"
}
reconcile_interval = "60s" {
input.resource.kind == "StatefulSet"
}
最佳实践:在Operator策略中,优先使用布尔规则做准入判断,使用完整规则做配置计算。避免在规则体内嵌套过多逻辑,保持每条规则的职责单一。
2.3 数据文档与查询
OPA将所有的输入数据、外部数据和策略规则统一组织成分层文档(hierarchical document)。理解数据文档模型是正确编写查询的前提。
2.3.1 数据文档模型
OPA内部维护一个全局的文档树,主要包含以下数据源:
| 文档路径 | 来源 | 说明 |
|---|---|---|
input |
每次查询时传入 | 当前请求的上下文(如AdmissionReview、调谐事件) |
data |
策略包 + 外部数据 | 包含所有已加载的规则和外部数据(如集群状态) |
data.<package> |
Rego策略文件 | 每个package下的规则自动挂载到对应路径 |
data.<external> |
外部数据绑定 | 通过OPA API注入的JSON数据(如白名单、配置) |
2.3.2 查询机制
OPA的查询分为两种模式:
- 完整查询(Complete Query):直接查询某个规则或数据路径,返回最终结果。例如:
data.operator.reconcile.allow_reconcile返回true或false。 - 部分查询(Partial Query):使用变量绑定,返回所有满足条件的解。例如:
data.operator.reconcile.reconcile_interval[x]返回所有可能的调谐间隔值。
2.3.3 内置函数与数据操作
Rego提供了丰富的内置函数用于数据操作,在Operator策略中高频使用的包括:
| 函数 | 用途 | Operator场景示例 |
|---|---|---|
count(arr) |
计算数组/集合元素个数 | 检查Pod副本数是否达标 |
object.get(obj, key, default) |
安全访问对象属性 | 获取可选标签,避免空指针 |
regex.match(pattern, str) |
正则匹配 | 验证资源名称是否符合命名规范 |
time.now_ns() |
获取当前时间戳 | 判断调谐是否超时 |
io.jwt.decode() |
解码JWT令牌 | 验证Webhook请求的认证信息 |
2.4 策略编写示例:Operator调谐准入
以下是一个完整的Rego策略示例,展示了包、规则、数据查询和内置函数的综合运用:
package operator.admission
# 外部数据:允许执行调谐的命名空间白名单
allowed_namespaces := data.allowed_namespaces
# 布尔规则:判断调谐请求是否被允许
allow_reconcile {
# 检查命名空间是否在白名单中
input.resource.namespace == allowed_namespaces[_]
# 检查资源是否被标记为托管
input.resource.labels.managed == "true"
# 检查调谐间隔是否合规(至少30秒)
reconcile_interval_seconds >= 30
}
# 完整规则:从资源注解中提取调谐间隔
reconcile_interval_seconds := seconds {
interval_str := object.get(input.resource.annotations, "reconcile-interval", "30s")
# 解析字符串,提取数字部分
seconds := parse_interval(interval_str)
}
# 辅助函数:解析时间间隔字符串
parse_interval(str) = sec {
regex.match(`^[0-9]+s$`, str)
sec := to_number(trim(str, "s"))
}
parse_interval(str) = sec {
regex.match(`^[0-9]+m$`, str)
sec := to_number(trim(str, "m")) * 60
}
常见陷阱:在Rego中,未定义的变量或规则默认值为 undefined,而非 false。在布尔规则中,如果规则体中的某个引用不存在,整个规则会失败(即不产生结果),而不是返回 false。务必使用 object.get() 或默认值处理可选字段。