4. Gatekeeper 策略语言 Rego 入门:Rego 语法基础、规则与表达式、数据查询与迭代

Rego 是 Gatekeeper 的策略决策语言,源于 Open Policy Agent (OPA) 项目。它专为在复杂嵌套结构(如 Kubernetes 资源清单)上表达策略规则而设计。理解 Rego 的核心语法、规则表达式以及数据查询迭代方式,是编写高效、可维护的 Gatekeeper 约束模板的前提。

4.1 Rego 语法基础

Rego 是一种声明式查询语言,其核心思想是“策略即代码”。它不描述“如何做”,而是描述“应该是什么状态”。

4.1.1 基本结构与注释

Rego 文件由 包声明、导入语句 和 规则 组成。注释使用 # 符号。

# 包声明,用于组织策略
package kubernetes.admission

# 导入数据源,例如 Kubernetes 资源
import data.kubernetes as k8s

# 规则定义
violation[msg] {
    # 规则体
}

4.1.2 标量值与复合值

类型 示例 说明
字符串 "production" 双引号包裹
数字 8080、3.14 整数或浮点数
布尔值 true、false 逻辑真/假
对象 {"name": "app", "port": 80} 键值对集合,类似 JSON
数组 [1, 2, 3] 有序列表
集合 {"dev", "staging"} 无序唯一值集合

4.1.3 变量与引用

变量以 _ 或小写字母开头,用于绑定值。引用用于访问嵌套数据。

# 变量绑定
namespace := "default"

# 引用对象字段
input.request.object.metadata.name

# 引用数组元素
input.request.object.spec.containers[0].image
关键点:Rego 中的变量一旦绑定,在同一规则体内不可重新赋值。这保证了策略的确定性。

4.2 规则与表达式

规则是 Rego 的核心,它定义了一个逻辑断言。规则可以生成文档(完整规则)或进行逻辑判断(部分规则)。

4.2.1 完整规则与部分规则

规则类型 语法 用途
完整规则 name := value { body } 生成一个单一值,如布尔值、字符串
部分规则 name[key] := value { body } 生成一个键值对集合
逻辑规则 name { body } 判断条件是否成立,返回 true/false

4.2.2 常用表达式与操作符

  • 比较操作符:==、!=、<、>、<=、>=
  • 逻辑操作符:not、and(隐式,用逗号分隔)、or(使用分号 ; 或显式 or)
  • 成员操作符:some 用于迭代,in 用于检查成员关系
  • 聚合函数:count()、sum()、max()、min()
# 示例:检查容器镜像是否来自允许的仓库
allowed_repos := {"docker.io/library/nginx", "gcr.io/my-project/"}

violation[msg] {
    container := input.request.object.spec.containers[_]
    not startswith(container.image, allowed_repos[_])
    msg := sprintf("容器镜像 '%v' 未在允许的仓库列表中", [container.image])
}

4.2.3 规则体中的隐式 AND 与 OR

规则体中的多个表达式之间是 AND 关系,所有表达式必须同时为真。使用 分号 或 显式 or 实现 OR 逻辑。

# 隐式 AND:两个条件必须同时满足
violation[msg] {
    input.request.operation == "CREATE"
    input.request.object.metadata.namespace == "production"
    msg := "禁止在 production 命名空间创建资源"
}

# 显式 OR:满足任一条件即触发
violation[msg] {
    input.request.object.spec.hostNetwork == true
    ;
    input.request.object.spec.containers[_].securityContext.privileged == true
    msg := "禁止使用主机网络或特权容器"
}
常见误区:初学者常误以为规则体中的逗号是 OR 关系。实际上,逗号分隔的表达式是 AND 关系。OR 关系必须使用分号或 or 关键字。

4.3 数据查询与迭代

Rego 的强大之处在于其内置的查询和迭代能力,能够高效处理 Kubernetes 资源中的嵌套列表和对象。

4.3.1 使用 some 关键字迭代

some 关键字用于声明局部变量,并隐式地遍历集合或数组。

# 迭代所有容器,检查是否设置了资源限制
violation[msg] {
    some i
    container := input.request.object.spec.containers[i]
    not container.resources.limits
    msg := sprintf("容器 %v 未设置资源限制", [container.name])
}

4.3.2 使用 _ 通配符迭代

当不需要索引时,可以使用 _ 通配符,它表示“任意一个”。

# 检查任意一个容器是否使用了 latest 标签
violation[msg] {
    container := input.request.object.spec.containers[_]
    endswith(container.image, ":latest")
    msg := sprintf("容器 %v 使用了 latest 镜像标签", [container.name])
}

4.3.3 嵌套迭代与数据关联

Rego 支持多层嵌套迭代,可以轻松关联不同层级的数据。

# 检查所有挂载的卷是否来自允许的存储类
allowed_storage_classes := {"fast-ssd", "standard-hdd"}

violation[msg] {
    volume := input.request.object.spec.volumes[_]
    volume.persistentVolumeClaim
    claim_name := volume.persistentVolumeClaim.claimName
    # 假设 PVC 信息在 input 的其他部分或 data 中
    pvc := data.kubernetes.persistentvolumeclaims[claim_name]
    not pvc.spec.storageClassName in allowed_storage_classes
    msg := sprintf("卷 %v 使用了不允许的存储类 %v", [volume.name, pvc.spec.storageClassName])
}
Rego 数据查询与迭代流程 K8s 准入请求 迭代容器列表 条件表达式 外部数据 (data) 关联查询 PVC 聚合与消息生成 violation 规则输出 Rego 通过隐式迭代(some/_)遍历容器列表,结合外部数据(data)进行关联查询,最终聚合生成违规消息。 所有条件满足时,violation 规则才会输出结果。

4.4 内置函数与常用模式

Rego 提供了丰富的内置函数,用于字符串处理、正则匹配、时间操作等。在 Gatekeeper 策略中,以下函数最为常用:

函数 签名 用途示例
startswith startswith(string, prefix) 检查镜像仓库前缀
endswith endswith(string, suffix) 检查镜像标签后缀
contains contains(string, search) 检查字符串是否包含子串
regex.match regex.match(pattern, value) 正则匹配命名规则
sprintf sprintf(format, values) 格式化违规消息
count count(collection) 统计容器数量
# 综合示例:使用正则匹配检查命名空间标签
package kubernetes.admission

import data.kubernetes as k8s

required_labels := {"environment", "team", "cost-center"}

violation[msg] {
    namespace := input.request.object
    required_labels[_] != label
    label := namespace.metadata.labels[_]
    # 使用正则检查标签值格式
    not regex.match("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", label)
    msg := sprintf("命名空间 %v 的标签值 '%v' 不符合命名规范", [namespace.metadata.name, label])
}
性能注意:在 Gatekeeper 中,Rego 规则会对每个准入请求执行。避免在规则中使用高复杂度的正则表达式或对大量外部数据进行全量迭代。优先使用 startswith、contains 等简单函数,并利用 Gatekeeper 的缓存机制(如 data.inventory)减少重复查询。

4.5 调试与测试 Rego 策略

编写 Rego 策略后,推荐使用 opa eval 命令或 Gatekeeper 的 gator 工具进行本地测试。

  • opa eval:直接评估 Rego 表达式,适合快速验证语法和逻辑。
  • gator test:Gatekeeper 提供的测试框架,支持加载约束模板和断言文件,适合集成测试。
  • 打印调试:使用 trace() 函数输出调试信息,在 Gatekeeper 日志中查看。
# 使用 opa eval 测试规则
opa eval --data policy.rego --input input.json "data.kubernetes.admission.violation"

# 使用 trace 函数调试
violation[msg] {
    container := input.request.object.spec.containers[_]
    trace(sprintf("检查容器: %v", [container.name]))
    # ... 其他逻辑
}