OPA 策略实战:用 `units.parse` 统一解析 Mi/Gi 等内存单位,实现跨单位资源限额比较
OPA 策略实战用units.parse统一解析 Mi/Gi 等内存单位实现跨单位资源限额比较【免费下载链接】opaOpen Policy Agent (OPA) is an open source, general-purpose policy engine.项目地址: https://gitcode.com/gh_mirrors/op/opa容器规格中的内存限制往往以多种后缀形式出现Mi、Gi乃至裸数字而 Kubernetes 命名空间级配额又是另一个单位体系直接做字符串比较毫无意义。本文以 Open Policy Agent (OPA) 仓库内完整的可运行示例 memory-limits 为主线讲解 Rego 内置函数units.parse的语法、单位表与大小写规则并深入其 Go 实现让你能够在一条策略中把不同单位的资源值统一为数字后安全比较。读完本文你将掌握units.parse的完整单位清单与错误行为、一个容器内存超限即拒绝的端到端 Rego 策略、以及该内置函数在 v1/topdown/parse_units.go 中的底层解析原理。问题背景为什么需要units.parse在 Kubernetes 等容器编排环境中资源规格的书写习惯并不统一有的字段使用二进制后缀512Mi、1Gi有的使用十进制后缀2G、500M还有的直接给裸数字1073741824。而命名空间配额namespace cap通常又是一个单独配置的值例如1Gi。如果策略代码里直接写input.container.memory_limit input.namespace_memory_limit比较的将是字符串2Gi与1Gi按字典序的结果不可靠若手工写2Gi 2048Mi之类的等价表又会随着单位组合爆炸而难以维护。OPA 提供的units.parse内置函数正是为解决这个问题而生它把带可选单位后缀的字符串转换成数字让策略可以用纯数值进行比较。官方文档 Unit Built-ins 明确说明其常见用途就是在同一条策略检查中比较带不同后缀到达的资源限额。units.parse语法与单位表units.parse(string) number接收一个字符串参数返回一个数字。它支持三类写法类别后缀说明十进制 SI 后缀K、M、G、T、P、E以及小写k、g、t、p、e按 1000 进位1K 1000二进制后缀Ki、Mi、Gi、Ti、Pi、Ei以及ki、mi等小写首字母变体按 1024 进位1Ki 1024毫milli小写m1m 0.001关键的大小写规则来自 units.mdx 与源码注释m与M严格区分大小写小写m表示毫milli0.001大写M表示兆mega1000000其余后缀如K/k、G/g大小写均可接受首字母大小写不敏感裸数字无后缀同样合法直接原样解析为数值。从 parse_units.go 的常量定义可以看到十进制进位的实现siK 1000、siM siK * 1000、siG siM * 1000依次递推到siE二进制常量ki、mi、gi等则按源码注释说明借用自topdown/parse_bytes以 1024 为底。完整示例按命名空间配额拒绝超限容器仓库中的示例目录 memory-limits 包含策略、输入、输出与 playground 配置五个文件下面逐一展开。策略文件 policy.regopackage play # Cap for the whole namespace, normalized to a number. max_memory : units.parse(input.namespace_memory_limit) # Containers whose limit is above the namespace cap. deny contains msg if { some c in input.containers units.parse(c.memory_limit) max_memory msg : sprintf( container %q memory limit %s exceeds namespace cap %s, [c.name, c.memory_limit, input.namespace_memory_limit], ) }策略共两个部分max_memory把命名空间的配额字符串1Gi通过units.parse归一化为数字1073741824作为全命名空间统一的上限基准deny规则遍历input.containers中的每个容器把各自的memory_limit如512Mi、2Gi也解析为数字与max_memory做数值比较一旦超限就用sprintf生成一条包含容器名、原字符串限制值和命名空间配额的可读消息。注意这里刻意保留了c.memory_limit与input.namespace_memory_limit的原始字符串用于生成报错消息——解析后的数字只用于比较展示给用户时仍使用人类友好的原始表述。输入 input.json{ namespace_memory_limit: 1Gi, containers: [ { name: app, memory_limit: 512Mi }, { name: batch, memory_limit: 2Gi } ] }输入模拟了一次真实的准入审查场景命名空间配额为1Gi有两个容器——app申请512Mi低于配额batch申请2Gi明显超限。输出 output.json{ deny: [ container \batch\ memory limit 2Gi exceeds namespace cap 1Gi ], max_memory: 1073741824 }结果验证了策略逻辑max_memory被正确解析为1073741824即1Gi 2^30二进制进位的直接体现deny只包含batch容器的拒绝消息app的512Mi换算为536870912未超过配额因此不产生拒绝记录。该示例在 playground 中的展示配置 config.jsonshowInput: true、showData: false表明示例默认展示输入而不展示 data便于读者直接对照输入与输出理解单位换算关系。源码级原理units.parse的 Go 实现units.parse的实现在 v1/topdown/parse_units.go注册入口为func init() { RegisterBuiltinFunc(ast.UnitsParse.Name, builtinUnits) }整个builtinUnits函数的处理流程可以拆成四步取字符串并清理通过builtins.StringOperand取出第一个操作数并移除字符串中的转义引号strings.ReplaceAll(s, \, )与units.parse_bytes保持行为一致空格校验只要字符串中包含空格就直接返回errIncludesSpacesspaces not allowed in resource strings因此形如1 Gi的写法是非法输入拆分数字与单位调用extractNumAndUnit拆出数值部分与后缀数值为空时报errNoAmount指数过大时报errUnitsExponentTooLarge单位换算与精度处理核心细节在大小写归一化上——只把第一个字母之后的部分转为小写unit[:1] strings.ToLower(unit[1:])这样M与m得以保留区别m命中毫分支siMilli 0.001M命中十进制兆分支Mi命中二进制兆分支。未知后缀则返回errUnitNotRecognized。数值计算采用math/big的big.Rat有理数而非浮点数。源码注释给出了明确理由像0.001这样的量在二进制浮点中无法精确表示big.Rat不会出现浮点精度问题。最终输出时若结果为纯整数如1073741824直接输出整数否则用FloatString(10)保留 10 位小数输出例如1m会得到0.001。常见的报错信息一览触发条件错误信息字符串含空格spaces not allowed in resource strings无数值部分如只传Gino amount provided数值无法解析为数字could not parse amount to a number后缀无法识别unit xxx not recognized指数过大exponent too large这些错误行为都体现在 parse_units.go 的预定义错误变量中可供你在调试策略时对照排查。实战建议与扩展思考1. 统一在数据侧做归一化像示例中的max_memory : units.parse(input.namespace_memory_limit)这样先在策略开头把所有参照值归一化为数字后续所有规则都基于纯数值比较可显著降低维护成本。若归一化逻辑需要复用也可以把它封装为自定义规则或函数供多个deny规则共享。2. 保留原始字符串用于报错units.parse只负责换算不负责展示。示例中把原始2Gi、1Gi拼进sprintf消息的做法值得推广比较用数字报告用原文既准确又易读。3. 警惕大小写陷阱这是最常见的踩坑点500M是五亿十进制兆500m是 0.5毫500Mi是约 5.24 亿二进制兆。在写策略或生成输入数据时务必确认字段的单位约定否则一次M/m之差就会导致配额放宽 10 亿倍。4. 与units.parse_bytes的关系units.parse_bytes是面向字节场景的兄弟函数二者共享单位常量设计二进制常量即借用自parse_bytes。区别在于units.parse额外支持毫m且对大小写更敏感适用于内存、CPU 等更细粒度的资源换算场景。参考资源示例入口memory-limits/intro.md完整策略policy.rego示例输入 / 期望输出input.json / output.json内置函数文档builtins/units.mdxGo 实现v1/topdown/parse_units.go至此你已经能够像示例一样用一行units.parse把千差万别的内存单位统一成可比较的数字让容器限额审查策略既严谨又易读。【免费下载链接】opaOpen Policy Agent (OPA) is an open source, general-purpose policy engine.项目地址: https://gitcode.com/gh_mirrors/op/opa创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考