4. Rego 数据模型:文档模型、完整文档与部分文档、默认值规则
Rego 的数据模型是策略即代码的基石。理解其“文档模型”的核心理念,以及“完整文档”与“部分文档”的差异,是编写高效、可维护策略的前提。本章将深入剖析这些概念,并重点讲解默认值规则的最佳实践。
4.1 文档模型:一切皆文档
在 Rego 中,所有数据——无论是输入(input)、系统状态(data)还是你定义的规则——都被视为一个巨大的、嵌套的 JSON 文档。这个文档模型是 Rego 查询和策略评估的基础。
- 统一视图:策略引擎将外部数据(如 Kubernetes 资源清单)和内部规则输出合并到一个单一的、可查询的文档树中。
- 分层结构:文档通过点号(
.)访问其嵌套属性,例如input.namespace或data.resources.cpu。 - 不可变性:在单次策略评估中,输入文档是只读的。规则通过定义新文档(即规则输出)来产生结果。
核心理解:将 Rego 视为一个文档生成器,而不是一个函数执行器。你编写的每条规则都在为最终的策略文档贡献一个片段。
4.2 完整文档与部分文档
Rego 规则根据其定义方式,可以生成两种类型的文档:完整文档和部分文档。这是 Rego 数据模型中最重要的区分之一。
| 特性 | 完整文档 (Complete Document) | 部分文档 (Partial Document) |
|---|---|---|
| 定义方式 | 使用 := 或 = 赋值,且规则体只有一个表达式。 |
使用 := 或 = 赋值,但规则体包含多个表达式或使用 else 关键字。 |
| 输出结构 | 一个单一的、确定的值(标量、对象、数组)。 | 一个函数或一个带有变量的对象集合。 |
| 查询方式 | 直接通过规则名称查询,如 data.allowed_namespaces。 |
需要提供参数进行查询,如 data.namespace_allowed("default")。 |
| 典型用例 | 定义常量、全局配置、聚合结果。 | 定义查找表、条件判断、数据过滤。 |
4.2.1 完整文档示例
完整文档在评估后产生一个固定的值。它不依赖于任何输入变量(除了全局的 input 或 data)。
# 完整文档:返回一个静态对象
allowed_regions := ["us-east-1", "eu-west-1", "ap-southeast-1"]
# 完整文档:基于 input 计算出一个单一值
max_cpu_cores := 4 if {
input.tier == "production"
} else := 2
4.2.2 部分文档示例
部分文档类似于一个函数。它根据提供的参数返回不同的结果。这在实现查找表或动态策略时非常有用。
# 部分文档:根据命名空间返回 CPU 配额
namespace_cpu_quota(namespace) := quota if {
quota := data.quotas[namespace].cpu
}
# 部分文档:检查某个资源类型是否被允许
allowed_resource_type(kind) := true if {
kind in ["Deployment", "StatefulSet", "DaemonSet"]
}
关键区别:完整文档在查询时不需要参数,而部分文档需要。部分文档的规则头包含变量(如
namespace),这些变量在规则体中被使用。
4.3 默认值规则
默认值规则是 Rego 中处理“未定义”或“缺失数据”的优雅方式。当一条规则在所有条件下都无法被满足时,它不会返回 false,而是返回 undefined。默认值规则允许你为这种情况提供一个安全的回退值。
- 语法:使用
default关键字,后跟规则名称和默认值。 - 优先级:默认值规则的优先级最低。只有当其他所有同名规则都未定义时,默认值才会生效。
- 应用场景:为配额设置默认限制、为缺失的标签提供默认行为、防止因数据不完整导致的策略评估错误。
4.3.1 默认值规则示例
# 默认值规则:如果未定义,则 CPU 配额默认为 2
default cpu_quota := 2
# 常规规则:为特定命名空间定义配额
cpu_quota := 8 if {
input.namespace == "production"
}
cpu_quota := 4 if {
input.namespace == "staging"
}
# 查询:如果 input.namespace 是 "development",则 cpu_quota 返回 2
4.3.2 默认值规则与部分文档结合
默认值规则也可以与部分文档结合,为函数提供默认行为。
# 为部分文档设置默认值
default namespace_allowed(_) := false
# 常规规则:允许特定命名空间
namespace_allowed(namespace) := true if {
namespace in ["default", "kube-system", "production"]
}
# 查询:namespace_allowed("random-ns") 返回 false
4.4 最佳实践与避坑指南
避免过度使用完整文档:虽然完整文档简单,但过度使用会导致策略僵化。优先使用部分文档来构建可组合、可扩展的策略逻辑。
- 为所有可能未定义的规则提供默认值:这可以防止因数据缺失导致的意外策略拒绝。
- 使用有意义的默认值:默认值应反映最安全或最保守的策略(例如,默认拒绝访问,而不是默认允许)。
- 文档命名规范:使用清晰、描述性的名称来区分完整文档和部分文档。例如,
max_cpu是完整文档,而namespace_max_cpu是部分文档。 - 利用部分文档进行数据抽象:将复杂的
data查询封装在部分文档中,使策略规则更简洁、更易读。
进阶技巧:结合使用部分文档和
walk 关键字,可以高效地遍历和查询复杂的嵌套数据结构,这在处理 Kubernetes 资源清单时非常有用。