OPA 基础回顾:Rego 语法速览、策略包结构、数据文档与查询
在深入集群故障域策略测试之前,必须牢固掌握 OPA 的策略语言 Rego 及其核心概念。本节将快速梳理 Rego 语法、策略包结构、数据文档与查询机制,为后续的边界条件验证打下基础。
Rego 语法速览
Rego 是一种为策略决策而生的声明式查询语言,其核心思想是 “策略即代码”。与命令式语言不同,Rego 专注于描述策略的期望状态,而非实现步骤。
核心语法元素
| 语法元素 | 说明 | 示例 |
|---|---|---|
| 规则 (Rule) | 策略的基本单元,定义决策逻辑 | allow { input.role == "admin" } |
| 表达式 (Expression) | 条件判断,支持比较、逻辑运算 | input.zone != "dmz" |
| 迭代 (Iteration) | 使用 some 关键字遍历集合 |
some node; input.nodes[node].region == "us-east" |
| 聚合 (Aggregation) | 对集合进行计数、求和等操作 | count(input.nodes) > 3 |
| 内置函数 (Built-in) | 提供字符串、网络、加密等能力 | net.cidr_contains("10.0.0.0/8", input.ip) |
规则类型
- 完整规则 (Complete Rule):返回单个布尔值或标量值,如
allow := true { ... }。 - 部分规则 (Partial Rule):返回一个集合或对象,如
allowed_zones[zone] { ... }。 - 默认规则 (Default Rule):为规则提供默认值,避免未定义错误,如
default allow := false。
💡 关键点: Rego 中所有规则都是 “可选的”。如果规则体中的条件不满足,该规则不会被定义,而非返回
false。使用 default 关键字可以显式设置默认值。
策略包结构
OPA 使用 包 (Package) 来组织策略,类似于编程语言中的命名空间。包结构决定了策略的查询路径和可访问性。
包定义与引用
# 文件: /policies/network.rego
package kubernetes.network
# 规则定义
allow_egress := true {
input.destination == "allowed_service"
}
# 在另一个包中引用
# 使用 data.kubernetes.network.allow_egress 来访问
包结构最佳实践
| 层级 | 用途 | 示例 |
|---|---|---|
kubernetes.admission |
准入控制策略 | 验证 Pod 安全上下文 |
kubernetes.fault_domain |
故障域划分策略 | 检查节点分布与反亲和性 |
kubernetes.resources |
资源配额与限制 | 验证 CPU/内存请求上限 |
data.global |
全局共享数据与函数 | 定义允许的可用区列表 |
📌 实践建议: 将策略按功能域划分到不同包中,避免单个包过于臃肿。使用
data.global 包存放跨策略共享的配置数据,如允许的故障域列表。
数据文档与查询
OPA 将输入数据(input)和外部数据(data)统一视为 文档 (Document)。策略引擎通过查询这些文档来做出决策。
数据文档类型
- 基础文档 (Base Document):通过 JSON 或 YAML 直接加载的静态数据,如集群节点列表、可用区映射。
- 虚拟文档 (Virtual Document):由 Rego 规则计算生成的动态数据,如
allowed_zones规则的结果。
查询机制
OPA 的查询路径直接映射到文档结构。例如,要查询包 kubernetes.fault_domain 中的 valid_distribution 规则,API 请求路径为 /v1/data/kubernetes/fault_domain/valid_distribution。
# 查询示例:检查某个 Pod 的部署是否满足故障域分布
# 输入数据 (input)
{
"pod": {
"name": "web-app-1",
"node_selector": {
"topology.kubernetes.io/zone": "us-east-1a"
}
},
"cluster_nodes": [
{"name": "node-1", "zone": "us-east-1a"},
{"name": "node-2", "zone": "us-east-1b"}
]
}
# 策略规则 (kubernetes.fault_domain)
package kubernetes.fault_domain
default valid_distribution := false
valid_distribution {
# 检查 Pod 选择的可用区是否在集群可用节点中
some node
input.cluster_nodes[node].zone == input.pod.node_selector["topology.kubernetes.io/zone"]
}
⚠️ 常见误区: 不要混淆
input 和 data。input 是每次查询时传入的临时数据,如准入请求;data 是 OPA 中持久化的全局数据,如集群配置。策略应优先使用 input 处理动态请求,使用 data 引用静态基线。
Rego 查询与数据流架构
下图展示了 OPA 中数据文档、查询路径与策略决策的完整交互流程。
Rego 查询与数据流架构(续)
为了进一步理解策略包与数据文档的层级关系,下图展示了典型的 OPA 策略包树状结构,以及查询路径如何映射到包中的规则。
Rego 查询实践:使用 rego 包进行单元测试
在 Go 环境中,可以使用 github.com/open-policy-agent/opa/rego 包直接执行 Rego 查询,无需启动 OPA 服务器。这对于策略的单元测试和集成测试非常有用。
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/open-policy-agent/opa/rego"
)
func main() {
// 定义 Rego 策略
module := `
package kubernetes.fault_domain
default valid_distribution := false
valid_distribution {
input.pod.zone == input.cluster.available_zones[_]
}
`
// 准备输入数据
input := map[string]interface{}{
"pod": map[string]interface{}{
"zone": "us-east-1a",
},
"cluster": map[string]interface{}{
"available_zones": []string{"us-east-1a", "us-east-1b", "us-east-1c"},
},
}
// 创建 Rego 查询
r := rego.New(
rego.Query("data.kubernetes.fault_domain.valid_distribution"),
rego.Module("test.rego", module),
rego.Input(input),
)
// 执行查询
ctx := context.Background()
resultSet, err := r.Eval(ctx)
if err != nil {
panic(err)
}
// 输出结果
if len(resultSet) > 0 {
resultJSON, _ := json.MarshalIndent(resultSet[0].Expressions[0].Value, "", " ")
fmt.Printf("决策结果: %s\n", resultJSON)
}
}
🔧 测试要点: 在编写故障域策略测试时,应覆盖以下边界条件:
- 空输入: 当
input.pod或input.cluster为空时,策略应返回默认值。 - 无效区域: Pod 选择的可用区不在集群可用区列表中。
- 重复区域: 集群可用区列表中存在重复值,策略应能正确处理。
- 大小写敏感: 区域名称的大小写是否影响匹配结果。