変数の優先順位 — 表を覚える前に測ってみる
一言でいうと
Ansibleで同じ名前の変数を定義できる場所は22か所あり、その順序を覚えるよりも、場所を減らす設計のほうが、事故を確実に防ぎます。
なぜ必要なのか
「確かにgroup_varsにポートを8080と書いたのに、9090と表示される」。こうした報告は、ほとんどいつも同じ形をしています。誰かが急いでいるときに、コマンドラインに-eを付けて実行し、その事実がどこにも残らなかったのです。あるいは、ロールの中にvars/main.ymlがあり、それがプレイのvars_filesに勝っていました。
この問題が難しい理由は、間違った値がエラーとして現れないからです。プレイブックは成功し、ファイルは作られ、ただ内容が違っています。構文チェックもリントも、これを捕まえられません。捕まえられるのは、「今この場所で、この名前が何に解決されるか」を実際に測ることだけです。
そのため、このモジュールは、公式ドキュメントの一覧を書き写す代わりに、同じ名前を1か所ずつ増やして仕込みながら、勝者がどう変わるかを測定します。一度測ったことのある人は、表を覚えなくても、次に同じ状況になったとき、何を最初に疑うべきかがわかります。
どう動くのか
公式ドキュメントは、場所を低いものから高いものまで22個並べています。ラボで測定する12か所を、低いものから書くと次のとおりです。
| 順位 | 場所 | 性格 |
|---|---|---|
| 1 | ロールのdefaults/main.yml |
「外から上書きしてください」と差し出す値 |
| 2 | インベントリのgroup_vars/all |
すべてのホストのベースの値 |
| 3 | インベントリのgroup_vars/<그룹> |
グループごとの値 |
| 4 | インベントリのhost_vars/<호스트> |
ホストごとの値 |
| 5 | プレイのvars: |
このプレイだけ |
| 6 | プレイのvars_files: |
このプレイが読み込んだファイル |
| 7 | ロールのvars/main.yml |
「外から触らないでください」というロール内部の値 |
| 8 | ブロックのvars: |
ブロックの中だけ |
| 9 | タスクのvars: |
タスク1つだけ |
| 10 | set_fact |
実行中に立てた値 |
| 11 | ロール呼び出しの引数 | ロールを呼び出すときに渡した値 |
| 12 | コマンドラインの-e |
何にでも勝つ |
表の山括弧の中の韓国語はプレースホルダーで、順にグループ名とホスト名です。
ここで驚く場所が3つあります。
1つ目は、vars_filesがプレイのvars:より高いことです。同じプレイの中で、すぐ上に書いたvars:が、下で読み込んだファイルに負けます。順序を目で読む感覚とは逆です。
2つ目は、ロールのdefaultsとvarsが正反対だということです。同じロールのディレクトリの中に並んでいますが、defaultsは一番下、varsはずっと上です。この違いが、そのままロール作者の意図です。外から変更してよい値はdefaultsに、変更してはいけない値はvarsに置きます。他人のロールを使っていて、「いくら上書きしても変わらない」なら、その値はvars/main.ymlにあります。
3つ目は、範囲が狭いほど高いということです。プレイ < ブロック < タスクの順序は、覚えるものではなくルールです。狭い場所に書いた人のほうが、より具体的な意図を持っていると見るのです。
測定に使うツールも知っておく価値があります。インベントリの中での勝負は、ansible-inventory --listが、すでにマージされた結果をJSONで見せてくれます。プレイの中での勝負は、debugタスクを1つ挟んで実行してみるのが最も速いです。
ansible-inventory -i inventory --list # 인벤토리 세 자리가 합쳐진 결과
ansible-inventory -i inventory --graph # 그룹 구조
インベントリをディレクトリとして渡すときにはまる落とし穴が1つあります。ディレクトリの中の.ini拡張子のファイルは、デフォルトの設定では無視されます(INVENTORY_IGNORE_EXTS)。ファイル1つを-iで渡したときにはうまく動いたhosts.iniが、ディレクトリの中に入った瞬間に消えるのです。ホストが1つも見えなければ、まずこれを疑います。
現場での姿
1つ目は、ディクショナリはマージされないことです。svc_limits: {cpu: "1", memory: 1Gi}を低い場所に置き、高い場所でsvc_limits: {memory: 2Gi}を渡すと、結果は{memory: 2Gi}です。cpuは消えます。マージしたければ、combineフィルターで明示する必要があります。全体設定のhash_behaviour = mergeに変える方法もありますが、その設定はリポジトリ全体の動作を変えるので、公式ドキュメントも推奨していません。
2つ目は、-eは元に戻せないことです。コマンドラインの値はどの場所よりも高く、プレイブックの中で上書きする方法がありません。便利で使い始めると、結局「誰がいつ何を渡したのか」が記録から消えます。インシデント対応用にだけ使い、使った事実を残すチームのルールが必要です。
3つ目は、場所を減らす設計です。表を知っていることよりも強い防御は、同じ名前を仕込む場所を減らすことです。よくあるルールは3つです。環境ごとの値は、インベントリのgroup_varsの1か所だけに置く、ロールは、外から変更してもよい値だけをdefaultsに置く、-eは例外的な状況にだけ使う、というものです。この3つを守れば、優先度の表を知らなくても、事故はほとんど起きません。
4つ目は、vars_promptです。実行するときに人に尋ねて受け取る場所もあります(プレイのvars:とvars_files:の間)。自動化パイプラインでは、実行が止まってしまうので、CIに入るプレイブックには使いません。このラボも自動採点なので、扱いません。
次のラボですること
svc_tierという名前1つを12か所に1か所ずつ増やして仕込みながら、勝者がどう変わるかを自分で測ります。インベントリの3か所はansible-inventory --listで、残りはプレイブックを実行して出力物で確認します。最後に、測定した順序を人が読める表にして残し、ディクショナリがマージされないことと、combineでマージすることを並べて確認します。