2. OPA 与 Rego 基础:OPA 架构、Rego 语言核心概念、策略即代码的理念

本章是整门课程的基石。在深入 Kubernetes 容量规划与巡检自动化之前,必须理解 OPA(Open Policy Agent)的架构定位、Rego 语言的表达方式,以及“策略即代码”(Policy as Code)这一核心理念如何改变云原生环境下的治理模式。

2.1 策略即代码(Policy as Code)的理念

传统策略管理依赖人工审核、静态配置文件或硬编码在业务逻辑中的条件判断。这种方式在微服务和 Kubernetes 动态环境中暴露出严重问题:策略变更周期长、版本管理缺失、审计困难、与基础设施耦合过紧。

策略即代码将策略从应用代码和基础设施配置中解耦,以声明式语言(如 Rego)编写,纳入 GitOps 工作流,实现版本控制、自动化测试和持续交付。其核心优势包括:

维度 传统策略管理 策略即代码(OPA)
存储方式 配置文件、数据库、硬编码 代码仓库(Git)
变更流程 手动修改、重启服务 PR 审查、CI/CD 自动化部署
可审计性 日志分散、难以追溯 完整变更历史、每次决策可审计
耦合度 与业务代码或基础设施绑定 完全解耦、独立于应用
测试能力 依赖集成测试、覆盖不全 单元测试、模拟输入、快速反馈
关键认知: 策略即代码不是简单的“把策略写成 YAML”,而是将策略视为一等公民——可测试、可版本化、可复用、可组合。OPA 是这一理念在云原生生态中最成熟的实现。

2.2 OPA 架构:解耦策略决策

OPA 是一个通用的、轻量级的策略引擎,其核心架构遵循 决策请求-决策响应 模式。OPA 本身不执行策略,而是根据输入数据和策略规则做出决策,将决策结果返回给调用方。

OPA 架构:策略决策解耦模型 Kubernetes API Server 微服务 A 业务逻辑 CI/CD 流水线 策略检查 查询 查询 查询 OPA 策略引擎 Rego 策略 规则集合 数据文档 缓存输入 决策引擎 评估 & 输出 决策日志 允许/拒绝 允许/拒绝 允许/拒绝 外部数据 集群状态 / 配置 策略变更 Git Push / API 数据加载 架构要点 1. OPA 不主动拦截请求,而是被外部系统调用(查询模式)。 2. 策略与数据分离:Rego 规则定义逻辑,外部数据(如集群状态)动态注入。 3. 决策结果始终是“允许/拒绝”或结构化数据,不产生副作用。
部署模式: OPA 可以以 Sidecar、DaemonSet 或独立服务的形式运行。在 Kubernetes 环境中,最常用的集成方式是 OPA Gatekeeper(基于 Admission Webhook),它会在资源创建/更新时自动调用 OPA 进行策略检查。

2.3 Rego 语言核心概念

Rego 是 OPA 专用的声明式查询语言,灵感来源于 Datalog。它不描述“如何做”,而是描述“策略是什么”。理解 Rego 的关键在于掌握其数据模型和基本语法结构。

2.3.1 数据模型:文档与规则

在 Rego 中,所有数据都被视为文档(Document)。输入数据(input)、外部数据(data)以及策略规则本身都是文档。规则定义实际上是在生成新的文档。

概念 说明 示例
input 每次策略查询时传入的请求数据 input.request.operation
data OPA 加载的外部数据(集群状态、配置) data.namespaces
规则 定义策略逻辑,生成布尔值或结构化结果 deny[msg] { ... }
包 组织规则的命名空间 package kubernetes.admission

2.3.2 基本语法结构

一个 Rego 策略文件通常包含包声明、导入语句和规则定义。以下是一个完整的策略示例:

package kubernetes.admission

import future.keywords.in

# 规则:禁止使用 latest 标签
deny[msg] {
    input.request.kind.kind == "Pod"
    some container in input.request.object.spec.containers
    container.image == "nginx:latest"
    msg := sprintf("容器 %v 使用了 latest 标签,禁止部署", [container.name])
}

# 规则:必须包含资源限制
deny[msg] {
    input.request.kind.kind == "Pod"
    some container in input.request.object.spec.containers
    not container.resources.limits
    msg := sprintf("容器 %v 未设置资源限制", [container.name])
}
语法要点:
  • deny[msg] 是部分规则,当条件满足时生成一条拒绝消息。
  • some ... in 用于迭代数组或集合。
  • not 表示逻辑否定,检查某个条件不成立。
  • sprintf 用于格式化字符串,构建可读的错误信息。

2.3.3 规则类型

Rego 支持三种规则类型,理解它们的区别是编写正确策略的前提:

类型 返回值 典型用途 示例
完整规则 单个值(布尔、字符串、对象) 全局开关、配置项 allowed := true
部分规则 集合(一组值) 收集所有违规消息 deny[msg] { ... }
函数规则 带参数的计算结果 复用逻辑、复杂计算 valid_port(p) { p > 0 }

2.4 Rego 查询与决策流程

理解 OPA 如何处理一个策略查询请求,有助于调试和优化策略性能。下图展示了从输入到决策的完整路径:

Rego 查询决策流程 1. 接收查询 input + 查询路径 2. 加载策略 Rego 规则编译 3. 数据绑定 input + data 合并 4. 规则评估 模式匹配 & 迭代 允许(Allow) 无 deny 规则匹配 拒绝(Deny) 至少一条 deny 匹配 默认允许:OPA 采用“默认允许”原则,只有当显式规则匹配时才拒绝。

2.5 常见模式与最佳实践

在编写 Rego 策略时,以下模式会频繁出现在容量规划和巡检场景中:

  • 迭代检查: 使用 some 关键字遍历 Pod 中的容器列表,检查每个容器的资源请求。
  • 聚合统计: 使用 count() 函数统计满足条件的资源数量,例如统计所有未设置 requests 的容器。
  • 数据引用: 通过 data.inventory 访问集群中已有的资源清单,实现跨资源策略(如检查某个 Namespace 是否已存在特定标签)。
  • 错误消息构建: 使用 sprintf 和 concat 生成结构化的审计信息,便于后续告警和日志分析。
避坑指南:
  • 避免在规则中使用 == true 或 == false,直接使用布尔值即可。
  • 注意 not 的优先级:not p and q 会被解析为 not (p and q),应使用括号明确意图。
  • 部分规则(如 deny[msg])的求值是惰性的,只有被查询时才会执行。

2.6 本章小结