一个叫noyaml.com的吐槽网站最近又被翻出来传播,作者ghuntley用一整页布满讽刺注释的YAML文件,列了一串配置文件坑过工程师的经典案例:挪威的国家代码NO会被解析成布尔值false,看起来差不多的07和08,一个变成整数7,一个原样保留成字符串。这类段子在DevOps圈流传多年,几乎成了黑话。
但把这份吐槽合集当段子看,会漏掉一件更硬核的事:这些坑不是YAML写得烂,而是YAML 1.1和1.2两个规范版本对同一段文本的隐式类型判断规则完全不同。更关键的是,Kubernetes官方文档自己写的措辞是“大部分兼容YAML 1.1”——不是严格遵循1.1,也没有升级到1.2。这句模糊表态,才是这些坑十几年反复出现的真正原因。
NO变false不是段子,是规范条款
YAML 1.1里,八进制数字的判定要求前导零后面只能跟0到7,08里的8不满足这个格式,所以被当成字符串留着;07满足格式,被解析成八进制整数7。裸日期在1.1里会被隐式识别成时间戳类型,按UTC处理。到了YAML 1.2的Core Schema,规则收紧:数字序列统一按十进制解析,八进制必须显式写0o前缀,隐式布尔只认true/false,裸日期也不再自动转成时间戳。
同一段文本,1.1和1.2给出的答案不一样。这不是哪个解析器写bug了,是两份规范本身在设计上就不同。
Kubernetes“大部分兼容1.1”,模糊在哪
如果Kubernetes明说自己完全遵循1.1,工程师至少能照着1.1规范去猜行为。麻烦在于官方文档用的是“大部分兼容”这种留有余地的表述,意味着具体到某个边缘写法,实际行为可能既不是纯1.1也不是1.2,取决于底层解析库怎么实现。同一份manifest,换一个解析器、换一个CI工具链去读,结果可能不一样。这才是原文那份吐槽合集自己也没讲清楚的部分——它只说“Kubernetes uses YAML 1.1”,听起来是个确定答案,实际官方留的是活口。
Kubernetes官方写的是“大部分兼容”,不是“完全遵循”。
这类解析歧义历史上不止带来配置误判,还带来过安全问题。早期Ruby、PHP、Python的PyYAML都出现过反序列化任意对象、甚至执行代码的漏洞——因为默认的加载函数会把YAML里声明的自定义类型直接实例化,配置文件变成了攻击入口。这些库后来都做了修复,默认改成安全加载,但历史教训说明YAML的风险从来不止“解析出来的值不对”,更严重时是“解析这一步本身就不安全”。
- 风险.老式YAML加载函数一旦允许反序列化任意对象,配置文件就能变成可执行代码的入口。
社区在补,但补丁刚起步
这些坑不是没人管。Kubernetes社区已经有一个叫kyaml的方案在推进,思路很直接:输出YAML时把字符串强制加引号,布尔值统一渲染成true/false,从源头上减少隐式类型带来的歧义。这算是官方对“大部分兼容1.1”这个历史包袱给出的一个回应,但目前还处在治理和落地阶段,不是已经切换完成的既定行为。
另一条路线更彻底:不修补YAML,直接换掉。StrictYAML去掉隐式类型推断,CUE、Dhall、Nickel走的是用更严格的类型系统生成配置。社区里流传过一句话,说某个项目把一千多行YAML换成几十个结构体后,贡献者反而变多了——配置的复杂度会直接影响别人愿不愿意提PR。
- 结论.真正该被追问的不是YAML该不该背锅,而是Kubernetes什么时候把“大部分兼容”改成一个确定答案。
对写Kubernetes manifest和CI配置的工程师来说,眼下能做的现实动作很具体:涉及NO、YES、ON、OFF这类词一律加引号;版本号一律用字符串写;CI流程里跑一遍yamllint当强制检查。指望上游一次性解决历史包袱,短期内不现实。
