Ansible Advent Calendar 2016の7日目だけ空いていたので、遅ればせながら参加させていただきます。
ちなみに、InventoryじゃなくてIncludeのはなしです。
はじめに
Ansible 2.0以降、includeにはDynamic IncludeとStatic Includeの2種類が存在するようになりました。実は、意識せずとも、playbookの書き方次第で、dynamicになっていたり、staticになっていたりします。また、Ansibleの実行バージョンによっても、同じplaybookに対するincludeの解釈が異なるため、注意が必要な場合があります。
ここでは、下記公式のドキュメントを参考に、dynamicとstaticの違いを噛み砕いてみたいと思います。
Dynamic IncludeとStatic Include
簡単な例
Dynamic IncludeとStatic Includeの違いを一言で言うと、実行時にincludeが評価される(dynamic)か、実行前にincludeが評価される(static)という違いです。
まずは簡単な例で、それぞれの動作を見てみたいと思います。なお、下記はAnsible 2.2.0.0 にて動作を確認しています。
Static Includeの例
Ansible 2.1以降、普通にincludeした場合、Static Includeになります。実行例は下記の通りです。普通ですね。
---
- hosts: all
tasks:
- include: ping.yml
---
- name: ping
ping:
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
ping TAGS: []
Ansible 1.9以前は、常にStatic Includeとして処理されていました。ただし、Ansible 2.0のバージョンのみ、常にDynamic Includeになっていたので、注意が必要です。
Dynamic Includeの例
Dynamic Includeになるケースは3通りあります。(Ansible 2.1以降)
- static:noを宣言している場合
- includeのファイル名に変数を含む場合
- ループを使用したincludeを実行する場合
ただし、ansible.cfgでstaticを強制するオプションを設定している場合、Dynamic Include自体が無かったAnsible 1.9以前と同様、Dynamic Includeを実施しようとすると、エラーになります。
static:noを宣言している場合
include時、static: no
を付けると、Dynamic Includeになります。
---
- hosts: all
tasks:
- include: ping.yml
static: no
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 : ok=2 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
include TAGS: []
実行してみると、Dynamic Includeでは ansible-playbook
実行時、 [include] のTASKが表示されることがわかります。
また、 ansible-playbook --list-tasks
の出力結果をみると、Static Includeのときはpingが表示されていますが、Dynamicの場合はincludeになっていることがわかります。
includeのファイル名に変数を含む場合
Ansible 2.0以降、include時に変数を利用することができるようになりました。変数を利用してincludeした場合、明示的に static: no
を付与していなくても、Dynamic Includeとして扱われるようになります。
---
- hosts: all
tasks:
- include: "{{ include_file }}.yml"
$ ansible-playbook -c local --extra-vars="include_file=ping" playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
include TAGS: []
ループを使用したincludeを実行する場合
ループを使用したincludeも、Ansible 2.0以降でできるようになりました。下記のようなwith_itemsなどを使った例でも同様にDynamic Include扱いになります。
---
- hosts: all
tasks:
- include: ping.yml
with_items:
- host1
- host2
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
TASK [ping] ********************************************************************
ok: [localhost]
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
include TAGS: []
ansible.cfgでstaticを強制する
Ansible 2.1 以降 ansible.cfgでstaticを強制するオプションを設定することもできます。
-
task_includes_static
すべてのタスクが静的であることを強制する -
handler_includes_static
すべてのハンドラが静的であることを強制する
ためしに、task_includes_static=True
に設定してループ内のincludeを実施してみると
$ ansible-playbook -c local playbook.yml
ERROR! You cannot use 'static' on an include with a loop
The error appears to have been in '/home/heriet/work/ansible/example_include/example_dynamic/playbook.yml': line 5, column 5, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
tasks:
- include: ping.yml
^ here
ERROR! You cannot use 'static' on an include with a loop
と怒られてしまいました。
基本的にはデフォルトのまま、このオプションは無効化して使うことのほうが多いと思われます。
Dynamic Include と Static Includeの違い
上で挙げていたとおり、Dynamic Include と Static Includeの根本的な違いは実行時に評価されるか、実行前に評価されるかというところですが、それによって下記のような影響があります
- Dynamic Includeされるhandlerにはnotifyできない
- Dynamic Include内にしか存在しないタグは –list-tags で出力されない
- Dynamic Include内のタスクは –list-tasks で出力されない
Dynamic Includeされるhandlerにはnotifyできない
例として、たとえば、OSによってhandlerを切り替えたい場合があるとします。下記のようなplaybookを用意しました。
---
- hosts: all
tasks:
- include: hello.yml
handlers:
- include: "ping_handler_{{ ansible_os_family }}.yml"
---
- name: hello
command: echo hello > hello
notify: ping handler
---
- name: ping handler
ping:
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [hello] *******************************************************************
ERROR! The requested handler 'ping handler' was not found in either the main handlers list nor in the listening handlers list
上記を実行すると。'ping handler' was not found
と言われてエラーになってしまいます。ping_handler_Debian.ymlのなかに ping handler
は居るのに何故でしょうか?
そう、handlersのincludeに変数を使用しているので、この部分はDynamic Includeになっています。当然、Dynamic Includeでは処理されるまで評価されないので、taskの実行時点ではping handler
は無い状態ということになります。
もし、上記のようなことがしたい場合は、whenを使うパターンが考えられます。
---
- hosts: all
tasks:
- include: hello.yml
handlers:
- include: ping_handler_Debian.yml
when: ansible_os_family == 'Debian'
この場合、Static Includeになるため、実行前に ping handler
を認識してくれます
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [hello] *******************************************************************
changed: [localhost]
RUNNING HANDLER [ping handler] *************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0
実際には、handlerだけOSごとにincludeすることはあまりないかもしれませんが、handlerを上手く認識していないときの可能性の1つとして頭に入れておいてもよいかもしれません。
Dynamic Include内にしか存在しないタグは –list-tags で出力されない
Dynamic Include内のタスクは –list-tasks で出力されない
上記の簡単な例で示した通り、Dynamic Includeされたものはその中のタスクは認識されず、includeされる旨だけが出力されます。--list-tagsも同様です。ちなみに、--start-at-taskや--tagsを指定した場合は意図通りDynamic Includeでも内部的なtaskやtagを都度認識して実行されます。
意図的にDynamic Includeにする例
汎用的なplaybook/roleの場合、OSやそのバージョンごとにincludeして処理を切り分ける場合がよくあります。たとえば、下記のような例です。
---
- hosts: all
tasks:
- include: ping_RedHat.yml
when: ansible_os_family == 'RedHat'
- include: ping_Debian.yml
when: ansible_os_family == 'Debian'
---
- name: ping A
ping:
- name: ping B
ping:
- name: ping C
ping:
---
- name: ping D
ping:
- name: ping E
ping:
- name: ping F
ping:
上記の例ではping A〜Fと名前をつけただけのものですが、実際にはなんらかのOSごとの処理がたくさん並んでいるものと思ってください。Dynamic Includeになる条件を満たしていないので、上記はStatic Includeになります。これを実行すると、下記のようになります。
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [ping A] ******************************************************************
skipping: [localhost]
TASK [ping B] ******************************************************************
skipping: [localhost]
TASK [ping C] ******************************************************************
skipping: [localhost]
TASK [ping D] ******************************************************************
ok: [localhost]
TASK [ping E] ******************************************************************
ok: [localhost]
TASK [ping F] ******************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
Debian環境で実行したので、A〜Cはそれぞれスキップされ、D〜Fは実行されたことがわかります。しかし、ping_RedHat.yml内にping A〜Cだけではなく、実際にはたくさんのタスクが並んでいたら、実行結果にskipが大量に並び、わずらわしいことになるでしょう。
そこで、明示的なDynamic Includeにしてみます。
---
- hosts: all
tasks:
- include: ping_RedHat.yml
when: ansible_os_family == 'RedHat'
static: no
- include: ping_Debian.yml
when: ansible_os_family == 'Debian'
static: no
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
skipping: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_os/ping_Debian.yml for localhost
TASK [ping D] ******************************************************************
ok: [localhost]
TASK [ping E] ******************************************************************
ok: [localhost]
TASK [ping F] ******************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
初回のinclude(ping_RedHat.ymlのぶん)がスキップされた結果がでるだけで、見通しがよくなりました。大量のtaskのskipが煩わしく、Static Includeにする必然性がなければ、Dynamic Includeにしてしまうのが手です。
まとめ
Playbookの書き方によって、Dynamic IncludeになったりStatic Includeになる例を示しました。普段はその差を意識せずとも問題ないケースが多いですが、notifyやtask、tagなどの取扱いが微妙に異なるので、思わぬ落とし穴に落ちる場合があるかもしれません。また、内部taskのskipを表示させたくない場合に、意図的にDynamic Includeを使うと有効です。