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 中数据文档、查询路径与策略决策的完整交互流程。

OPA 数据文档与查询流 OPA 数据文档与查询流 输入数据 (input) 动态请求 / 准入 Webhook 基础数据 (data) 静态 JSON / YAML 配置 策略包 (Package) kubernetes.fault_domain 虚拟文档 valid_distribution 规则 决策输出 true / false / 集合 📋 查询路径示例: GET /v1/data/kubernetes/fault_domain/valid_distribution?input={...}

Rego 查询与数据流架构(续)

为了进一步理解策略包与数据文档的层级关系,下图展示了典型的 OPA 策略包树状结构,以及查询路径如何映射到包中的规则。

OPA 策略包树状结构 OPA 策略包树状结构与查询路径 data kubernetes global admission fault_domain resources validate_pod valid_distribution check_limits 📌 查询路径映射 /v1/data/kubernetes/fault_domain/ valid_distribution 路径 = 包名 + 规则名

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 选择的可用区不在集群可用区列表中。
  • 重复区域: 集群可用区列表中存在重复值,策略应能正确处理。
  • 大小写敏感: 区域名称的大小写是否影响匹配结果。