Ansible: Playbookの繰り返し制御の色々 (6)

投稿者: | 2017-08-13

今回もAnsiblePlaybookにおける繰り返し制御について、学んでいきます。

引き続き題材は本家のドキュメントから。

シリーズはこちら。

Ansible: Loops

Looping over the inventory

インベントリを使ったループ、ということでこれを実現する方法は複数あるそうです。
一つは、 with_itemsplay_hosts または groups 変数を利用した方法、
もう一つは with_inventory_hostnames を使った方法、と紹介されています。

実際に動かしてみるとわかりやすいので、やってみましょう。

まずは、インベントリファイルとして下記を用意します。

[localnet]
localhost

[www]
www1
www2

これを利用するようにして、Playbookを実行してみます。

まずは、一つ目の with_items を使う方法です。

- hosts: localnet
  connection: local
  tasks:
  # show all the hosts in the inventory
  - debug:
      msg: "{{ item }}"
    with_items:
      - "{{ groups['all'] }}"
  
  # show all the hosts in the current play
  - debug:
      msg: "{{ item }}"
    with_items:
      - "{{ play_hosts }}"

tasksの中は、例文通りです。
こちらを実行すると、下記のようになります。

PLAY [localnet] ******************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************************************************
ok: [localhost] => (item=localhost) => {
    "item": "localhost", 
    "msg": "localhost"
}
ok: [localhost] => (item=www1) => {
    "item": "www1", 
    "msg": "www1"
}
ok: [localhost] => (item=www2) => {
    "item": "www2", 
    "msg": "www2"
}

TASK [debug] *********************************************************************************************************************
ok: [localhost] => (item=localhost) => {
    "item": "localhost", 
    "msg": "localhost"
}

PLAY RECAP ***********************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

一つ目のタスクで「全てのグループのホスト」、二つ目のタスクで「Playbookで指定しているホスト(グループ)」が表示されました。

今度は、二つ目の with_inventory_hostnames を使う方法です。

- hosts: localnet
  connection: local
  tasks:

  # show all the hosts in the inventory
  - debug:
      msg: "{{ item }}"
    with_inventory_hostnames:
      - all
  
  # show all the hosts matching the pattern, ie all but the group www
  - debug:
      msg: "{{ item }}"
    with_inventory_hostnames:
      - all:!www

これも、tasksの中は例文通りですね。
こちらを実行すると、下記のようになります。

PLAY [localnet] ******************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************************************************
ok: [localhost] => (item=www1) => {
    "item": "www1", 
    "msg": "www1"
}
ok: [localhost] => (item=localhost) => {
    "item": "localhost", 
    "msg": "localhost"
}
ok: [localhost] => (item=www2) => {
    "item": "www2", 
    "msg": "www2"
}

TASK [debug] *********************************************************************************************************************
ok: [localhost] => (item=localhost) => {
    "item": "localhost", 
    "msg": "localhost"
}

PLAY RECAP ***********************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

結果は同じ形(実行順がやや違いますが)となっています。
指定した意味としては、一つ目のタスクで「全てのホスト」、二つ目のタスクで「wwwグループ以外で絞り込んだホスト」、ということになります。

このように、自分の必要に応じた形でホストを使ったループ処理が実現できます。
ちなみに、 with_inventory_hostnames で利用している絞り込みのパターンは、こちらの Patterns(本家) に詳細があります。

Loop Control

ここでは、ループの制御のバージョンによる進化が書かれています。

バージョン2.0では、with_loops とタスクのインクルードが使えました。
これにより、一連のタスクを一度にループするできます。
そして Ansible では標準で、ループに item という名前の変数を使います。そして、このために「外側」のループの item の値は上書きされてしまいます。
バージョン2.1では、 loop_control オプションにより、ループに使用する変数名を指定できます。
下記の例を見てみます。

# main.yml
- include: inner.yml
  with_items:
    - 1
    - 2
    - 3
  loop_control:
    loop_var: outer_item

# inner.yml
- debug:
    msg: "outer item={{ outer_item }} inner item={{ item }}"
  with_items:
    - a
    - b
    - c

inner.yml 以下が内側のループに相当するところですね。
ここで、外側である main.ymlloop_control で設定した outer_item 変数が利用されています。

また、注意書きとして記載されていますが、 Ansible がすでに定義されている変数名をループ変数として利用しようとしているのを検知したら、エラーとしてタスク失敗としてしまう、という動作となるようです。

次に、バージョン2.2です。
複雑なデータ構造でループを行なった場合、実行時の表示結果が少しうるさい(busy)ものとなります。
これにラベル(label)ディレクティブをつけることで抑制できます。

- name: create servers
  digital_ocean:
    name: "{{ item.name }}"
    state: present
  with_items:
    - name: server1
      disks: 3gb
      ram: 15Gb
      network:
        nic01: 100Gb
        nic02: 10Gb
        ...
  loop_control:
    label: "{{item.name}}"

上記の例だと、サーバ名を表示させることになるので、何を操作しているかもわかりますし、すっきりとした表示になります。

他のループ制御オプションとして、pauseがあります。
こちらは、繰り返し実行ごとに指定した秒を待たせる、というものになります。

# main.yml
- name: create servers, pause 3s before creating next
  digital_ocean:
    name: "{{ item }}"
    state: present
  with_items:
    - server1
    - server2
  loop_control:
    pause: 3

こちらは name にある説明の通り、次のサーバを作る前に3秒待つ、という動作になります。

Loops and Includes in 2.0

Ansible のバージョン2.0では loop_control を使えません。
そのため、先ほど紹介した「外側」のループの変数名を変更したい場合には、 set_fact を利用して実現することができます。

# main.yml
- include: inner.yml
  with_items:
    - 1
    - 2
    - 3

# inner.yml
- set_fact:
    outer_item: "{{ item }}"

- debug:
    msg: "outer item={{ outer_item }} inner item={{ item }}"
  with_items:
    - a
    - b
    - c

古いバージョンしか使えない環境であれば、このような回避方法があるようです。

Writing Your Own Iterators

最後に、自分でループの記述をしたい場合、 Developing Plugins (本家) にプラグイン開発開始のための情報があります。
今まで見てきたように多くの機能が存在しているので、参照する実装は豊富に存在しています。

まとめ

ということで、ループについて一通り確認してきました。
やり始めてからだいぶ時間かけてしまいましたが、何とかやりきれてよかったです。

もう少し参照しやすい形に整理もしたいですね。 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください