Ansibleモジュールのchangedの判断基準
Ansible で playbook を実行する際に、事前に check mode で一度変更点を確認することがよくあると思います。
具体的には check mode の出力結果で、 playbook 内のどの task が changed になり、どの task が changed にならないかを見ることで、今回の実行での変更点が何であるかを把握するといった形になります。
ここで重要になるのが、"その module の changed: True
は何を表すのか"という部分です。
例えば、"対象のホスト(AWS の EC2)を、登録されているロードバランサー(ELB)から切り離す"というカスタムモジュール(my_ec2_lb
とする)を作成する場合、 changed: True/False
は何を表すでしょうか。
カスタムモジュールでなくとも、すでに公式で elb_instance
というモジュールがあり、それによると
- ELB に対して操作を行い、対象の EC2 が ELB から切り離された場合は
changed: True
- ELB に対して操作を行い、対象の EC2 はすでに ELB から切り離されていた場合は
changed: False
というような判断を行っています。ざっくり言うと変更があったらそのまま True
なので、特におかしな点はないように思います。
ですが、自分たちでカスタムモジュールを作成する場合、どんな時に changed: True
にすれば良いのかを深く考えなければいけない場合があります。
話を my_ec2_lb
に戻して、そのカスタムモジュールは次のような機能であるとします。
- 対象のホスト(inventory に定義されている host)がどのロードバランサーに登録されているかを、ホスト上で取得する
- 取得したロードバランサーに対して、対象のホスト(自分自身)を切り離すよう指示する
この場合、 my_ec2_lb
も公式の elb_instance
と同じように、単純に変更があったら changed: True
になるように実装するでしょうか?
実は"変更があったら"に加えてもう一つの視点がありまして、それは"対象は何なのか"という点です。
my_ec2_lb
という架空のカスタムモジュールにおける対象ホストは inventory に定義されている host であり、パラメータで instance_id を指定するような形式ではなく、 playbook の実行対象そのものになります。
ざっくり言うと elb_instance
は対象が ELB 、 my_ec2_lb
は対象が EC2 ということになります。
あらためて、 my_ec2_lb
によってそのホストをロードバランサーから切り離した時、それは対象であるホストを変更したことになる(= changed: True
)のでしょうか。なかなか判断が難しいところです。
ここまでがとっても長い前置きの課題の共有ですw
そんなわけで、カスタムモジュールを作成するなら公式のモジュールの実装を参考にすれば良いので、公式のモジュールの check mode の出力結果を見ることで changed の判断基準が少しでも明確になればなと思い試してみました。 使った playbook などは GitHub にあげております。
前置きが長すぎたのでさくっといきますか。 Ansible のバージョンは 2.4.1.0
を使用しました。
commands 系モジュール
- name: 1.1.1 shell module shell: date check_mode: no register: shell_result # changed => True
changed の判断基準に関しておそらく shell
モジュールや command
モジュールはよく取り上げられていると思います。
基本的には check mode では実行が skip されるので changed: True/False
どうこうの話ではありません。余談ですが check mode でもコマンドを実行したい場合は check_mode: no
として task を定義してあげる必要があります。
どちらのモジュールでも、変更を行うようなコマンド実行であろうが無かろうが、全て結果は changed: True
になります。
自分で changed: True/False
を操作したい場合は、 chenged_when:
を task に定義してあげる必要があります。
この動きからわかることは、"変更される可能性がゼロではない場合、念のため changed: True
にすべきである"というような判断がなされていると推測できます。
files 系モジュール
- name: 2.2.1 copy module - same content copy: content: | 127.0.0.1 localhost ::1 localhost dest: /etc/hosts register: copy_same_result # changed => False - name: 2.2.2 copy module - different content copy: content: | 127.0.0.1 localhost dest: /etc/hosts register: copy_different_result # changed => True
続いて一番定番であると思われる files 系モジュールについてですが、こちらは予想通り単純に変更があるか無いかという点がそのまま changed の値となります。
ファイルの名前、中身、 owner/group 、 mode など、一つでも変更があればちゃんと check mode 時も changed: True
としてくれます。
この動きからわかることは、"対象のホストのファイルが変更される場合、 changed: True
にすべきである"という判断がなされていると推測できます。
packaging 系モジュール
- name: 3.1.1 yum module - already installed yum: name: python state: present register: yum_already_result # changed => False - name: 3.1.2 yum module - update yum: name: util-linux state: latest register: yum_update_result # changed => True
続いて packaging 系モジュール。面倒なので yum
と pip
しか試しませんでしたが、こちらも単純な結果になります。
すでに求めるパッケージがインストールされている場合は False
、 新規インストールやアップデートが必要になる場合は True
という出力になりました。
この動きからわかることは、"対象のホストのパッケージが変更される場合、 changed: True
にすべきである"という判断がなされていると推測できます。
他のモジュール
- name: 99.2.1 uri module uri: url: "http://localhost:8080/" method: POST return_content: yes check_mode: no # NOTE: uri module does not support check mode. This task sends a actual request even if check mode register: uri_result # changed => False
最後に他に気になったモジュールの結果についてです。
一応 set_fact
モジュールと git
モジュールも試しましたが前述と矛盾しない結果になり、 set_fact
はただ fact をセットするだけなので changed: False
に、 git
は clone してホスト上のファイルを変更するので changed: True
になりました。 get_url
モジュールでファイルのダウンロードを行う場合も同じで changed: True
になります。
一番注目すべきは uri
モジュールです。 HTTP のリクエストを送信するモジュールですが、 method: POST
であっても changed: False
になります。
余談ですが uri
モジュールも check mode をサポートしてないので、今回の例では check_mode: no
として skip されないようにしました。普段は真似しないでください。
この動きからわかることは、"あくまでも対象のホストのファイルなどが変更される場合、 changed: True
にすべき"であり、"HTTP の POST のような更新系の操作であっても対象が別であれば changed: True
にする必要はない"というような判断がなされていると推測できます。
なぜなら、もし"変更されるか否かがわからないので changed: False
としている"というのであれば、前述の commands 系モジュールの判断と矛盾してしまうからです。
check mode 時の changed の判断基準
結論として、カスタムモジュールを作成する場合の changed の判断基準は以下のようになりそうです。
- 対象のホストの何らかが変更される可能性がゼロではない場合
changed: True
にしておく - 対象のホストのファイル/パッケージなどが変更される場合、
changed: True
にする - 何らかの変更があるかもしれないが、それは対象のホストに関するものでない場合、
changed: False
にしてよい
これらを元にして、この記事の最初で問いかけた
例えば、"対象のホスト(AWS の EC2)を、登録されているロードバランサー(ELB)から切り離す"というカスタムモジュール(my_ec2_lb とする)を作成する場合、 changed: True/False は何を表すでしょうか
に関しての答えはどうなるかというと、ロードバランサーの切り離しが成功した場合でも、元々ロードバランサーから切り離されていた場合でも、常に changed: False
として changed に意味を持たせない、というのが正しいような気がします。
ただ実際には、"ロードバランサーで In Service になっている"ことを"対象のホストの状態"と見なすという視点もあるので、その場合はロードバランサーを切り離したら changed: True
になるとも言えますし、こればっかりは好みや開発チームの中でのルール決めに依存しそうです。
バシッと結論が出ない感じで少しモヤモヤしますが、カスタムモジュールを作成することがあれば参考にしていただけたら幸いです。