ansibleで変数に入れたyes/noの扱いに苦しんだ


タイトルをどうつければ伝わるか悩んだ結果こうなった・・・

背景

Ansibleを使っていると、ある1つの環境だけ特別に実行したいとかあると思います。
(雲行きが怪しいぞ・・・という反応がうかがえます。)
例えば3台立っているアプリケーションサーバに、cronを仕込みたいけど、3台で動かしたくないなーといった場合を考えてみます。
(ちゃんとグループを分けなさい、ケチらずバッチサーバを用意して分けなさいという意見が聞こえてきます。)

こんな感じにインベントリ内に変数is_mainを持たせました。

hosts
[app]
foo-app-01 is_main=yes
foo-app-02 is_main=no
foo-app-03 is_main=no

このyes/noはansibleを書いているとよく出てくる値です。
serviceなら自動実行を開始させるためにenabled: yesみたいに使います。今回はcronのdisabledの値として使うイメージです。

次にタスクの内容です。is_main=yesなところだけが有効になってほしかったのでタスクはこんな感じにした。

tasks/main.yml
cron:
  # ...
  disabled: "{{ is_main == False }}" # NG!! 

今回の条件では disabledに入る値は常にFalse になるため、全部の環境でcronが有効になります。ちなみに、{{ not is_main }} でも同じです。

やりたい事と違う!

原因の推測

何度かdebugタスクで表示して確認した上での推測です。全然裏が取れてないです。

  • まずinventoryで定義した変数is_mainは文字列として扱っている
  • ファイル自体はYAMLなので、まず最初にYAMLとして処理される。YAMLではYes,No等の値はBool値として解釈される。なのでdisabled: no等の記載なら、期待通り動く。
  • 今回の場合、disabled: "{{ is_main == False }}"はYAML的にはただの文字列。
  • しかし、テンプレート構文なのでJinja2で解釈した結果になる
  • Jinja2的には変数is_mainの値は文字列と解釈して「1文字以上の文字列だからTrue」と判断している
  • なのでこの式は、disabled: "{{ is_main == False }}"disabled: "{{ True == False }}"disabled: False となる

あっているでしょうか・・・?

対応

自然な感じで書きたいのだけど、どうしたら良いか調べたところ bool フィルターを使うと良さそうとのことだった。boolフィルターは良くあるポジティブな値はTrueにしてくれます。今回求めてたやつですね!

tasks/main.yml
cron:
  # ...
  disabled: "{{ is_main | bool == False }}"

しかしこの見た目は不吉な匂いを感じます。
これなら文字列が入ってくる前提で考えて {{ is_main == 'no' }} とかの方がまだ良い気がしないでもない・・・。

やはりグループを分けるべきだったか・・・。

Ansibleはこういうミスは忘れたころに何度も踏んでる気がする・・・頭の悪さを感じてとても辛い。

追記: インベントリファイル(INI形式)の変数の扱い

コメントにて、インベントリファイルの変数で対処する方法等を教えていただきました。
また、インベントリファイルのbooleanの扱いについてBlogでまとめておられます。ありがたし。