世界を動かす技術を、日本語で。

地獄のYAMLドキュメント (2023)

概要

  • YAML は人間に優しいデータ形式を目指すが、複雑さが逆効果になる場合が多い
  • バージョンやパーサ依存の挙動、危険な落とし穴が多数存在
  • JSONTOML など、よりシンプルな代替案も複数存在
  • YAMLの微妙な仕様がテンプレート化や構成管理を難しくする
  • 最終的には 安全なサブセット利用JSON生成 が現実的な回避策

YAMLの複雑さと落とし穴

  • YAML は人間に優しいフォーマットを目指すが、仕様が極めて複雑
    • 仕様書は10章以上、4階層の節番号、専用のエラッタページまで存在
    • バージョンごとに挙動が異なり、同じ文書でもパース結果が変化
  • JSON と比較すると、YAMLは仕様も実装も遥かに難解
    • JSONは仕様が6つの図で終わるシンプルさ
    • YAMLは頻繁にバージョンアップされ、1.2.2(2021年10月)でも大きく仕様変更
  • 代表的な落とし穴
    • セクサジェシマル(60進数)表記 :22:22がバージョンによって数値や文字列に
    • タグとエイリアス :!や*記法で予期せぬ解釈やエラー発生
    • ノルウェー問題 :"no"や"on"がバージョンやパーサによってboolや文字列に
    • 非文字列キー :on: などがboolキーとなり、言語によって型が変化
    • 意図しない数値化 :9.6.24のようなバージョンが数値として解釈される危険

YAMLのパーサ依存性と安全性問題

  • パーサごとに バージョン対応状況や解釈が異なる ため、同じYAMLでも動作がバラバラ
    • PythonのPyYAMLは1.1仕様、Goのyamlパッケージは独自実装
  • タグ機能により、 任意コード実行のリスク が存在
    • yaml.safe_loadのような安全な読み込み関数利用が必須
  • シンタックスハイライトに頼っても、各エディタやサービスごとに強調箇所が異なる
    • Vim、GitHub、Codebergなどで一貫性がない

テンプレート化とYAMLの危険性

  • YAMLのテンプレート化は 極めて危険
    • ホワイトスペースや構文の微妙な違いで壊れやすい
    • 断片的なテキストの結合・エスケープが困難
  • KubernetesやGitHub Actionsなどでテンプレート化が多用されるが、 バグや予期せぬ挙動の温床

代替となる構成フォーマット

  • TOML
    • 文字列は常にクォートされ、YAML特有の落とし穴が少ない
    • Python標準ライブラリでサポート、広い言語対応
    • 深いネストにはやや不向き
  • JSON with comments
    • コメント可能な拡張JSON(VS Codeなどで採用)
    • 普及度は限定的だが、シンプルな仕様
  • YAMLの安全なサブセット
    • 全ての文字列をクォート、yes/no等のbool表記を避ける
    • 明示的なルール運用が必要だが、逸脱を自動検出するツールは未発達

JSON生成によるYAML回避

  • 多くのアプリはYAMLしか受け付けないが、 YAMLはJSONのスーパーセット
    • JSONを生成すればYAMLとしても利用可能
  • 構成ファイルが肥大化し、抽象化やパーツ共有が必要になった場合
    • テンプレートよりも NixPython 等のプログラミング言語による生成が安全

まとめ:YAMLの利用と今後

  • YAMLは多機能だが、 仕様の複雑さと落とし穴 が多く、慎重な運用が必須
  • 可能なら TOMLや拡張JSON の利用、YAMLが必須なら 安全なサブセット運用JSON生成 が推奨
  • 長期的には、 より安全な構成管理手法や言語 の選択が望ましい

Hackerたちの意見

ノルウェーの問題、ちょっとイライラするよね。Ansibleのドキュメントでは、yes/noがtrue/falseの代わりに使われてることが多いんだ。公式ドキュメントでこれを見た時、Ansibleの推奨スタイルだと思って使ってたんだけど、最近は警告やリントエラーが出るようになって、見つけたら全部修正してる。でも、Ansibleのドキュメントではまだよく使われてるんだよね。

それは、ファイルをどう解析/デコード/アンマーシャルするかによるね。「一般的な」YAMLパーサーを使うと、noはfalseに変換されるけど、パーサーがデータ構造の型を知ってたり、特定の文字列を置き換えないように指示できたり、フックがあればnoを文字列として扱えるんだ。だから、リンターがパーサーとは違う動作をすることもあるかもね。

Ansibleはドキュメントの金字塔ってわけじゃないよ。ドキュメントは更新されてるし維持されてるけど、基盤となるインターフェースが一貫してないから、それがドキュメントにも影響してる。なぜそうなってるのか不思議だよね、たぶんスタイルガイドなしで異なるアイデアを持った開発者がいるからだろうけど。とはいえ、Ansibleは素晴らしいツールだよ、これらの特異性を許せるならね。

これって本当に過去10年間の問題だったの?確か仕様のバージョン1.2が2009年に修正したはずなんだけど。

これのほとんどは、基本的に文字列を引用符で囲むことで解決できるよ。YAMLには、再帰やアンカー/エイリアス/タグみたいに、JSONではできないことがあるからね。まあ、もしかしたらcue/dhall/hclの方がうまく解決できるかも。Jsonnetもそうだね。どれだけ良いのか、あんまり試したことがないからわからないけど。

ほとんどの問題は、基本的に文字列を引用符で囲むことで解決されるよね。そうそう、私も最初にそう思った。個人的にはYAMLは気にしないけど、文字列を引用する習慣はつけてる。だって、JSONではキーと文字列の両方を引用してるから、YAMLではキー/値ペアごとに約2つのダブルクォートを節約できるってことだし、これが重要な指標ならね。

JSONでは仕様の一部としてやってないけど、ポストプロセッシングとしてやるのを止めるものはないよ。例えば、OpenAPIでは特別な$refキーを使って、ポストプロセッサがそこに参照されている値を入れ替えるんだ。これはjsonnetやcue、hclがやってることと実質的に同じだけど、ポストプロセッサじゃなくてプリプロセッサとしてね。

そうだね、これがデフォルトでyamllintによって強制されてる。 "なんでこんなトリビアルな設定ファイルフォーマットにリンターが必要なんだよ"って叫ぶのはとても公平だし、こういう足元をすくうような問題はYAMLを避ける正当な理由だよ。でも全体的に、YAMLの不安定さは解決しやすい問題だし、YAMLを使う理由があって、リンターを追加するのが可能なコンテキストがあれば、そんなに大したことじゃないと思う。投稿でも示唆されてるけど、実際に確立された普遍的な代替手段はないしね。TOMLは良いデフォルトだけど、かなり単純なものにしか使えない。個人的には「Nixを使えばいい」ってアプローチが好きだけど、どこにでもNixインタープリターを置けるわけじゃないし。Cueはほとんどのユースケースにはオーバースペックだと思う。要するに、結論は「YAMLを使うな」じゃなくて、「YAMLの足元をすくう問題に気をつけて、代替手段を知っておけ」ってことだね。

記事から: > YAMLの問題の多くは、引用されていない文字列のように見えるものが異なる動作をすることから生じている。これは簡単に避けられる: すべての文字列を常に引用すること。

22:22が文字列として引用される必要があるって、俺にはすごく直感に反するな。機能的にはK-Vペアなのに。YAML自体も辞書の構文で:を使ってるし!

この二つの原則、(1) YAMLは引用を必要とすべきで、(2) YAMLの値は再帰/アンカーにあるっていうのが、YAMLの存在理由や人々が使う理由と根本的に逆だと思う。YAMLの特徴は、明示的な開始や - もっと重要なのは - 終了の区切りがない「簡単さ」にあるんだよね。これは、構造のためのホワイトスペースの区切りと、値のためのヒューリスティック解析を組み合わせて実現されてる。後者は根本的に欠陥があるけど、YAMLファンはその欠陥を価値あるトレードオフだと思ってる。もし区切りを必須にするなら、YAMLは存在理由を失うと思う。再帰やアンカーは、ほとんど使われないオプションの追加機能で、一部のパーサーはサポートすらしてない。もしそれがYAMLの主な価値だったら、もっと普及してるはず。ちなみに、俺はYAMLが嫌いで、存在しなければいいのにと思ってるけど、なぜ存在するのかは理解してるし、そのニーズを満たす代替案についてはあまり良い提案がないんだ。TOMLも欠陥があるし。

Jsonnetは結構いいけど、ライブラリのサポートがちょっとイマイチだね。例えば、yaml用の良いライブラリがあって、ラウンドトリップ処理ができるから、プログラム的にyamlを修正してもコメントを保持できるんだ。yamlには確かに欠点もあるし(正直言ってバカみたいなものもあるけど)、いろんな面でうまくいってるから、評価されるべきだと思う。

Hacker Newsで議論の続きを見る