Help us understand the problem. What is going on with this article?

AnsibleのDynamic IncludeとStatic Include

More than 3 years have passed since last update.

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になります。実行例は下記の通りです。普通ですね。

playbook.yml
---

- hosts: all
  tasks:
  - include: ping.yml
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になります。

playbook.yml
---

- 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として扱われるようになります。

playbook.yml
---

- 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扱いになります。

playbook.yml
---

- 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を用意しました。

playbook.yml
---

- hosts: all
  tasks:
  - include: hello.yml
  handlers:
  - include: "ping_handler_{{ ansible_os_family }}.yml"
hello.yml
---

- name: hello
  command: echo hello > hello
  notify: ping handler
ping_handler_Debian.yml
---

- 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を使うパターンが考えられます。

playbook.yml
---

- 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して処理を切り分ける場合がよくあります。たとえば、下記のような例です。

playbook.yml
---

- hosts: all
  tasks:

  - include: ping_RedHat.yml
    when: ansible_os_family == 'RedHat'

  - include:  ping_Debian.yml
    when: ansible_os_family == 'Debian'
ping_RedHat.yml
---

- name: ping A
  ping:

- name: ping B
  ping:

- name: ping C
  ping:
ping_Debian.yml
---

- 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にしてみます。

playbook.yml
---

- 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を使うと有効です。

fjct
クラウド・IoT 関連サービスを開発・提供している企業です。(こちらは、富士通クラウドテクノロジーズの有志にて運営しております。)
https://fjct.fujitsu.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした