4. Rego 策略缓存机制:OPA 内部缓存类型与命中场景

在准入控制场景中,OPA 每次收到 Kubernetes API Server 的准入请求时,都需要执行 Rego 策略。如果每次执行都重新解析、编译策略并重新查询外部数据,延迟将不可接受。OPA 通过三层缓存机制来降低重复计算开销:AST 缓存、规则缓存 和 HTTP 缓存。理解这三层缓存的命中与未命中场景,是定位策略缓存延迟故障的基础。

4.1 三层缓存架构总览

下图展示了 OPA 内部三层缓存的协作关系,以及请求数据在缓存层之间的流动路径。

OPA 三层缓存架构与数据流 第一层:AST 缓存 缓存已解析的 Rego 模块 键:模块路径 + 内容哈希 值:AST 语法树 命中:跳过解析 → 直接编译 第二层:规则缓存 缓存已编译的规则集 键:策略包名 + 数据依赖 值:编译后的 IR 命中:跳过编译 → 直接执行 第三层:HTTP 缓存 缓存外部数据查询结果 键:URL + 请求参数 值:HTTP 响应体 命中:跳过网络请求 准入请求到达 Rego 执行引擎 未命中:重新解析 Rego 文件 未命中:重新编译规则 未命中:发起 HTTP 请求 说明: 三层缓存按“解析 → 编译 → 数据获取”顺序逐层检查。上层缓存命中可跳过下层开销,但下层缓存未命中不会影响上层。

4.2 AST 缓存(模块解析缓存)

作用对象:Rego 源文件(.rego 模块)。
缓存粒度:每个模块文件独立缓存。
缓存键:模块文件路径 + 文件内容 SHA256 哈希。

当 OPA 启动或通过 Bundle API 加载策略时,会先检查 AST 缓存。如果模块内容未变化,则直接使用缓存的 AST 语法树,跳过词法分析和语法解析阶段。

场景 缓存状态 对延迟的影响
策略文件未修改,OPA 重启 未命中(缓存为空) 首次加载需完整解析,延迟增加 10-50ms
策略文件未修改,Bundle 热更新 命中(内容哈希一致) 跳过解析,延迟降低至接近 0
策略文件内容变更 未命中(哈希变化) 重新解析,延迟增加 10-50ms
大量策略模块同时加载 逐模块检查,部分命中 未命中模块累积延迟
故障排查要点: 如果 OPA 频繁重启或 Bundle 推送频率过高,AST 缓存会反复失效。检查 OPA 日志中的 bundle_loading 事件,确认模块是否被重复解析。

4.3 规则缓存(编译缓存)

作用对象:编译后的规则集(Intermediate Representation, IR)。
缓存粒度:按策略包(package)粒度缓存。
缓存键:包名 + 所有依赖模块的 AST 版本戳。

规则缓存是三层缓存中最关键的一层。在准入控制场景中,同一个策略包可能被数千个请求反复调用。如果规则缓存命中,OPA 可以直接执行编译后的 IR,无需重新编译。

场景 缓存状态 对延迟的影响
同一策略包连续处理多个准入请求 命中 执行延迟仅 0.1-0.5ms
策略包依赖的模块发生变更 未命中(版本戳变化) 重新编译,延迟增加 20-100ms
策略包使用 data 引用外部包 部分命中(依赖链检查) 依赖包未命中时级联重新编译
OPA 内存压力触发缓存逐出 未命中(LRU 淘汰) 下次请求需重新编译
常见故障: 当策略包之间存在复杂的交叉引用(例如包 A 调用包 B 的规则,包 B 又调用包 C),任何一个底层模块变更都会导致上层所有依赖包的规则缓存失效。这种“级联失效”是准入控制场景中缓存延迟突增的常见原因。

4.4 HTTP 缓存(外部数据缓存)

作用对象:通过 http.send 获取的外部数据。
缓存粒度:每个 HTTP 请求独立缓存。
缓存键:HTTP 方法 + URL + 请求头 + 请求体哈希。
缓存控制:遵循 HTTP 响应头中的 Cache-Control 和 ETag。

在准入控制场景中,策略经常需要查询外部服务(如镜像仓库、RBAC 系统)。HTTP 缓存可以避免重复的网络往返,但缓存策略配置不当会导致数据陈旧或缓存穿透。

场景 缓存状态 对延迟的影响
外部数据未变化,请求参数相同 命中 延迟降低至 0.1-1ms(无网络开销)
外部数据变化,但 Cache-Control 未过期 命中(陈旧数据) 延迟低但策略决策可能错误
外部服务返回 Cache-Control: no-cache 未命中(强制回源) 每次请求都发起 HTTP 调用,延迟 10-200ms
大量并发请求查询同一外部端点 缓存雪崩(同时失效) 瞬时大量 HTTP 请求,延迟飙升

4.5 缓存命中与未命中的完整决策流程

以下 SVG 图展示了 OPA 在处理一个准入请求时,三层缓存的逐级检查顺序以及未命中时的回退路径。

准入请求缓存命中/未命中决策流程 准入请求到达 AST 缓存命中? 是 使用缓存的 AST 否 重新解析 Rego 文件 规则缓存命中? 是 使用编译后的 IR 否 重新编译规则 HTTP 缓存命中? 是 使用缓存的外部数据 否 发起 HTTP 请求获取数据 执行策略并返回决策 返回准入响应 关键结论: 三层缓存逐级检查,任何一层未命中都会导致该层对应的开销(解析/编译/网络请求)。缓存延迟故障通常表现为“某层缓存频繁未命中”或“级联失效”。

4.6 缓存延迟故障的典型模式

  • AST 缓存频繁未命中:通常由 Bundle 推送过于频繁或 OPA 实例频繁重启导致。检查 opa_bundle_loading_seconds 指标。
  • 规则缓存级联失效:当策略包依赖链较深时,底层模块变更会引发上层所有包重新编译。使用 opa inspect 命令分析依赖树。
  • HTTP 缓存穿透:外部服务未返回有效的 Cache-Control 头,或请求参数(如时间戳)每次都不同,导致缓存无法命中。检查 opa_http_cache_hit_ratio 指标。
  • 缓存逐出导致抖动:OPA 内存不足时,LRU 缓存会逐出最近最少使用的条目。如果准入请求的流量模式突然变化,冷启动延迟会显著增加。
排查命令示例: 使用 OPA 的 opa eval --metrics 命令可以查看每次策略评估的缓存命中情况。
# 查看策略评估的缓存指标
opa eval --metrics --format pretty 'data.kubernetes.admission.deny' \
  --input input.json \
  --data policy.rego

# 输出示例(关注 cache_hit 相关指标)
# ast_cache_hit: 1
# rule_cache_hit: 1
# http_cache_hit: 0
# eval_time: 0.002ms

4.7 缓存配置参数速查表

缓存类型 相关配置参数 默认值 建议调整场景
AST 缓存 --ast-cache-max-size 1000 个模块 策略模块数量超过 500 时适当增大
规则缓存 --rule-cache-max-size 500 个包 准入策略包数量多时增大,注意内存开销
HTTP 缓存 --http-cache-max-size 1000 个条目 外部数据查询量大时增大,配合 TTL 使用
全局缓存逐出 --cache-eviction-interval 5 分钟 流量抖动大时缩短间隔,减少冷启动