hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

Ansibleの使い方(ループ処理編)



1 はじめに

loopは、タスクを繰り返すために使用するキーワードです。従来はwith_list,with_items等を使用していましたが、Ansible version 2.5でloopキーワードが追加され、今後はloopキーワードの使用が推奨されるようです。Loops — Ansible Community Documentation

2 検証環境

動作検証をするための環境は以下のとおりです。

                 192.168.122.0/24
control ------------------------------------- node1
      .220                               .87
ホスト名 役割
control コントロールノードとして動作します。Ansibleをインストールします。
node1 ターゲットノードとして動作します。モジュールが実行されるホストです

コントロールノード、ターゲットノードのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

コントロールノード、ターゲットノードのカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

ansibleの版数は 2.14.9 です。

[root@control ~]# ansible --version
ansible [core 2.14.9]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.16 (main, Dec  8 2022, 00:00:00) [GCC 11.3.1 20221121 (Red Hat 11.3.1-4)] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

3 事前準備

ansibleのインストールや公開鍵認証の設定方法などは、Ansibleの使い方(モジュール編) - hana_shinのLinux技術ブログを参照してください。

4 動作確認

4.1 単純なループ処理

playbookを作成します。このPlaybookでは、loopキーワードを使用して、リスト内のファイル名に基づいて3つのファイルを作成します。具体的には、fileモジュールを使用して、/tmp/ディレクトリ直下にfile1.txt、 file2.txt、およびfile3.txtを作成します。各ループでは、item変数にファイル名が自動的に設定されます。なお、itemはAnsibleで予約された変数で、以下の例では、各ループでファイル名が自動的に設定されます。なお、モジュールについては、Ansibleの使い方(モジュール編) - hana_shinのLinux技術ブログを参照してください。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Create file
      ansible.builtin.file:
        path: "/tmp/{{ item }}"
        state: touch
      loop:
        - file1.txt
        - file2.txt
        - file3.txt

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

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

TASK [Create file] ******************************************************************************************************
changed: [192.168.122.220] => (item=file1.txt)
changed: [192.168.122.220] => (item=file2.txt)
changed: [192.168.122.220] => (item=file3.txt)

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行すると、/tmp直下にfile1.txt、file2.txt、file3.txtが作成されていることがわかります。

[root@node1 ~]# ls -l /tmp/file*
-rw-r--r--. 1 root root 0  3月  1 22:27 /tmp/file1.txt
-rw-r--r--. 1 root root 0  3月  1 22:27 /tmp/file2.txt
-rw-r--r--. 1 root root 0  3月  1 22:27 /tmp/file3.txt

4.2 辞書を使ったループ処理

playbookを作成します。このPlaybookでは、ファイル名とファイルの実行権を指定してファイルを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Create files with owners using loop
      ansible.builtin.file:
        path: "/tmp/{{ item.key }}.txt"
        mode: "{{ item.value }}"
        state: touch
      loop:
        - { key: 'file1', value: '0777' }
        - { key: 'file2', value: '0666' }
        - { key: 'file3', value: '0444' }

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Create files with owners using loop] ******************************************************************************
changed: [192.168.122.220] => (item={'key': 'file1', 'value': '0777'})
changed: [192.168.122.220] => (item={'key': 'file2', 'value': '0666'})
changed: [192.168.122.220] => (item={'key': 'file3', 'value': '0444'})

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行すると、/tmp直下にfile1.txt、file2.txt、file3.txtが作成され、ファイルの実行権が777, 666, 444であることがわかります。

[root@node1 ~]# ls -l /tmp/file*
-rwxrwxrwx. 1 root root 0  3月  1 22:28 /tmp/file1.txt
-rw-rw-rw-. 1 root root 0  3月  1 22:28 /tmp/file2.txt
-r--r--r--. 1 root root 0  3月  1 22:28 /tmp/file3.txt

あと始末をします。

[root@node1 ~]# rm -f /tmp/file*

4.3 ループ変数を変更する方法(loop_var)

デフォルトのループ変数は item です。 loop_varを使用するとループ変数を item 以外のものに変更できます。ここでは、ループ変数をxxxに変更してみます。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Create file
      ansible.builtin.file:
        path: /tmp/{{ xxx }}
        state: touch
      loop:
        - file1.txt
        - file2.txt
        - file3.txt
      loop_control:
        loop_var: xxx

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

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

TASK [Create file] ******************************************************************************************************
changed: [192.168.122.220] => (item=file1.txt)
changed: [192.168.122.220] => (item=file2.txt)
changed: [192.168.122.220] => (item=file3.txt)

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行すると、/tmp直下にfile1.txt、file2.txt、file3.txtが作成されていることがわかります。

[root@node1 ~]# ls -l /tmp/file*
-rw-r--r--. 1 root root 0  3月  1 22:32 /tmp/file1.txt
-rw-r--r--. 1 root root 0  3月  1 22:32 /tmp/file2.txt
-rw-r--r--. 1 root root 0  3月  1 22:32 /tmp/file3.txt

あと始末をします。

[root@node1 ~]# rm -f /tmp/file*

4.4 ループの進捗状況を表示する方法(index_var)

各ループでループカウンタとファイル名を表示するplaybookを作成します。なお、リモートホストのファクトは取得の必要がないのでfalseとしています。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  gather_facts: false
  tasks:
    - name: Counting
      loop:
        - file1.txt
        - file2.txt
        - file3.txt
      loop_control:
        index_var: loop_counter
      ansible.builtin.debug:
        msg: "{{ loop_counter }} -> {{ item }}"

playbookを実行すると、ループカウンタ(0,1,2)とファイル名が表示されていることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Counting] *********************************************************************************************************
ok: [192.168.122.220] => (item=file1.txt) => {
    "msg": "0 -> file1.txt"
}
ok: [192.168.122.220] => (item=file2.txt) => {
    "msg": "1 -> file2.txt"
}
ok: [192.168.122.220] => (item=file3.txt) => {
    "msg": "2 -> file3.txt"
}

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

4.5 ループを一定時間停止する方法(pause)

3秒間隔でファイル名を表示するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  gather_facts: false
  tasks:
    - name: Counting
      loop:
        - file1.txt
        - file2.txt
        - file3.txt
      loop_control:
        index_var: loop_counter
        pause: 3
      ansible.builtin.debug:
        msg: "{{ loop_counter }} -> {{ item }}"

playbookを実行します。3秒間隔で実行されていることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Counting] *********************************************************************************************************
ok: [192.168.122.220] => (item=file1.txt) => {
    "msg": "0 -> file1.txt"
}
ok: [192.168.122.220] => (item=file2.txt) => {
    "msg": "1 -> file2.txt"
}
ok: [192.168.122.220] => (item=file3.txt) => {
    "msg": "2 -> file3.txt"
}

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

4.6 指定した条件にマッチするまでタスクを繰り返し実行する方法(until)

playbookを作成します。lsコマンドの実行結果はregisterキーワードによって、変数command_result.rcに格納されます。そして、untilキーワードでcommand_result.rcが0になるまで、lsコマンドを3秒間隔(delay)で5回実行(retries)します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Wait until a condition is met
      command: ls /tmp/test.txt
      register: command_result
      until: command_result.rc == 0
      delay: 3
      retries: 5

playbook中のcommand_result.rcは、以下のようになります。debugモジュールを使ってcommand_resultの中身を確認することができます。debugモジュールの使い方は、Ansibleの使い方(デバッグ編) - hana_shinのLinux技術ブログを参照してください。

    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "attempts": 1,
        "changed": true,
        "cmd": [
            "ls",
            "/tmp/test.txt"
        ],
        "delta": "0:00:00.011117",
        "end": "2024-03-02 21:11:50.231665",
        "failed": false,
        "msg": "",
        "rc": 0,
        "start": "2024-03-02 21:11:50.220548",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "/tmp/test.txt",
        "stdout_lines": [
            "/tmp/test.txt"
        ]
    }
}

事前準備として、/tmp/test.txtが存在しないことを確認します。

[root@node1 ~]# rm /tmp/test.txt

playbookを実行します。/tmp/test.txtがないため、lsコマンドが失敗するため、5回実行されていることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Wait until a condition is met] ************************************************************************************
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (5 retries left).
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (4 retries left).
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (3 retries left).
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (2 retries left).
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (1 retries left).
fatal: [192.168.122.220]: FAILED! => {"attempts": 5, "changed": true, "cmd": ["ls", "/tmp/test.txt"], "delta": "0:00:00.008907", "end": "2024-03-01 22:43:38.125986", "msg": "non-zero return code", "rc": 2, "start": "2024-03-01 22:43:38.117079", "stderr": "ls: '/tmp/test.txt' にアクセスできません: そのようなファイルやディレクトリはありません", "stderr_lines": ["ls: '/tmp/test.txt' にアクセスできません: そのようなファイルやディレクトリはありません"], "stdout": "", "stdout_lines": []}

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

[root@control ~]#

次はplatbook実行中に、ターゲットノードで/tmp/test.txtを作成してみます。
playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

ターゲットノードで/tmp/test.txtを作成します。

[root@node1 ~]# touch /tmp/test.txt

/tmp/test.txtが作成されたので、3回目でlsコマンドが正常終了しているとがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Wait until a condition is met] ************************************************************************************
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (5 retries left).
FAILED - RETRYING: [192.168.122.220]: Wait until a condition is met (4 retries left).
changed: [192.168.122.220]

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

4.7 フィルタを使った繰り返し

playbookを作成します。loop は反復処理を指示するためのキーワードであり、同じタスクを複数回繰り返す際に使用します。range は整数の範囲を生成するための関数です。以下の例では、rangeは1から3までの整数を出力します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  gather_facts: false
  tasks:
    - name: Create files with loop and filter
      ansible.builtin.file:
        path: "/tmp/file{{ item }}.txt"
        state: touch
      loop: "{{ range(1, 4) }}"

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Create files with loop and filter] ********************************************************************************
changed: [192.168.122.220] => (item=1)
changed: [192.168.122.220] => (item=2)
changed: [192.168.122.220] => (item=3)

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行すると、/tmp直下にfile1.txt、file2.txt、file3.txtが作成されていることがわかります。

[root@node1 ~]# ls -l /tmp/file*
-rw-r--r--. 1 root root 0  3月  1 22:45 /tmp/file1.txt
-rw-r--r--. 1 root root 0  3月  1 22:45 /tmp/file2.txt
-rw-r--r--. 1 root root 0  3月  1 22:45 /tmp/file3.txt

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ
Ansible の使い方