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数据(如白名单、配置)
OPA数据文档模型与查询路径 OPA数据文档模型与查询路径 OPA Engine input data 外部数据绑定 input.request.kind input.request.object data.operator.health data.operator.reconcile data.operator.security data.allowed_namespaces data.resource_quotas 查询示例:data.operator.reconcile.allow_reconcile → 返回 true/false

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() 或默认值处理可选字段。

2.5 本章小结