hana_shinのLinux技術ブログ

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

Ansibleの使い方(ansible.posixモジュール編)

1 はじめに

ansible.posixは、POSIXLinux/Unix系システム)ベースの操作をサポートするためのモジュールやプラグインが含まれているAnsibleコレクションです。Linuxのシステム管理に役立ちます。詳細は、Ansible.Posix — Ansible Community Documentationを参照してください。

モジュール名 概要
acl ファイルACL情報の設定と取得
at atコマンドを使用してコマンドまたはスクリプトファイルの実行をスケジュール
authorized_key SSHの認証キーの追加または削除
firewalld firewalldを使用して任意のポート/サービスを管理
firewalld_info firewalldに関する情報の収集
mount マウントポイントの制御
patch パッチツールを使用してパッチファイルを適用
rhel_facts RHEL固有のファクトを設定またはオーバーライドするためのファクトモジュール
rhel_rpm_ostree RHEL for Edge rpm-ostreeベースのシステムでパッケージが存在することを確認
rpm_ostree_upgrade rpm-ostreeアップグレードトランザクションの管理
seboolean SELinuxブール値の切り替え
selinux SELinuxのポリシーと状態の変更
synchronize rsyncコマンドを使って、ローカルホストとリモートホスト間でファイルやディレクトリを同期するためのモジュール
sysctl カーネルパラメータを操作するためのモジュール

2 検証環境

動作検証を行うための環境は以下のとおりです。enp1s0はNIC(ネットワークインターフェースカード)の名前です。

                          192.168.122.0/24
control(enp1s0) -------------------------------------(enp1s0) 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.posixがデフォルトでインストールされていました。ansible.posixのバージョンは以下のとおりです

[root@control ~]# ansible-galaxy collection list

# /usr/lib/python3.9/site-packages/ansible_collections
Collection                    Version
----------------------------- -------
amazon.aws                    5.5.1
ansible.netcommon             4.1.0
ansible.posix                 1.5.4
-snip-

3 事前準備

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

4 firewalldモジュールの使い方

firewalldモジュールは、firewalld ルールに対してサービス/ポート(TCPまたはUDPのいずれか)の追加/削除するモジュールです。詳細は、ansible.posix.firewalld module – Manage arbitrary ports/services with firewalld — Ansible Community Documentationを参照してください。

4.1 ポートを解放する方法

playbookを作成します。このplaybookは、nodesグループ内のホストで、publicゾーンにおいてポート80/tcpを許可します。設定は永続的(permanent: yes)かつ即時的(immediate: yes)に行われます。なお、firewalldモジュールに渡すパラメータの意味は以下のとおりです。

パラメータ 意味
zone ファイアウォールのゾーンを指定します。publicはpublicゾーンを指し、そこでポートの変更が行われます。ゾーンは他にdmz,internal等があります
permanent yesに設定することで、ファイアウォールの設定が永続的に保存されます。次回OS再起動後も設定が保持されます
immediate yesに設定することで、即時にファイアウォールの設定を反映します
port 許可またはブロックするポートとプロトコルを指定します。たとえば、80/tcpは80番ポートのTCPプロトコルを示しています
state enabledに設定することで、指定されたポートを有効にします。つまり、ポート80/tcpがpublicゾーンで解放されていることを意味します
[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Allow port 80/tcp in the public zone
      ansible.posix.firewalld:
        zone: public
        permanent: yes
        immediate: yes
        port: 80/tcp
        state: enabled

playbookの実行前に、ターゲットノードで開放されているポート番号を確認します。TCPの80番ポートが開放されていないことが確認できます。なお、firewall-cmdの使い方はfirewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@node1 ~]# firewall-cmd --list-ports

[root@node1 ~]#

playbookを実行します。

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

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

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

TASK [Allow port 80/tcp in the public zone] ****************************************************
changed: [node1]

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

[root@control ~]#

playbookを実行すると、TCPの80番ポートが解放されたことが確認できます。

[root@node1 ~]# firewall-cmd --list-ports
80/tcp
[root@node1 ~]#

4.2 ポートをブロックする方法

playbookを作成します。このplaybookは、nodesグループ内のホストで、publicゾーンにおいてポート80/tcpを無効にします。設定は永続的(permanent: yes)かつ即時的(immediate: yes)に行われます。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Allow port 80/tcp in the public zone
      ansible.posix.firewalld:
        zone: public
        permanent: yes
        immediate: yes
        port: 80/tcp
        state: disabled

playbookの実行前にターゲットノードで解放されているポート番号を確認すると、TCPの80番ポートが開放されていることがわかります。

[root@node1 ~]# firewall-cmd --list-ports
80/tcp

playbookを実行します。

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

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

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

TASK [Allow port 80/tcp in the public zone] ****************************************************
changed: [node1]

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

[root@control ~]#

playbookを実行すると、TCPの80番ポートがブロックされていることが確認できます。

[root@node1 ~]# firewall-cmd --list-ports

[root@node1 ~]#

5 firewalld_infoモジュールの使い方

firewalld_infoモジュールは、firewalldのルールに関する情報を取得するモジュールです。詳細は、ansible.posix.firewalld_info module – Gather information about firewalld — Ansible Community Documentationを参照してください。

5.1 全ての情報を取得する方法

playbookを作成します。このplaybookは、nodesグループ内のホストについて、firewalldのアクティブなゾーンに関する情報を取得し、その結果をresultに格納します。そして、取得した情報をdebugモジュールを使って表示します。なお、debugモジュールについては、Ansibleの使い方(デバッグ編) - hana_shinのLinux技術ブログを参照してください。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Gather information about active zones
      ansible.posix.firewalld_info:
        active_zones: true
      register: result
    - name: Display the result
      ansible.builtin.debug:
        var: result

playbookを実行すると、firewalldの各種情報が表示されることがわかります。

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

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

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

TASK [Gather information about active zones] **************************************************
ok: [node1]

TASK [Display the result] *********************************************************************
ok: [node1] => {
    "result": {
        "active_zones": true,
        "changed": false,
        "collected_zones": [
            "public"
        ],
        "failed": false,
        "firewalld_info": {
            "default_zone": "public",
            "version": "1.2.1",
            "zones": {
                "public": {
                    "forward": true,
                    "forward_ports": [],
                    "icmp_block_inversion": false,
                    "icmp_blocks": [],
                    "interfaces": [
                        "enp1s0"
                    ],
                    "masquerade": false,
                    "ports": [],
                    "protocols": [],
                    "rich_rules": [],
                    "services": [
                        "ssh",
                        "dhcpv6-client",
                        "cockpit"
                    ],
                    "source_ports": [],
                    "sources": [],
                    "target": "default"
                }
            }
        },
        "undefined_zones": []
    }
}

PLAY RECAP ************************************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

5.2 特定の情報を取得する方法

playbookを作成します。このplaybookは、nodesグループ内のホストで、firewalldのアクティブなゾーンに関する情報を取得し、その結果をresultに格納します。その後、firewalldのバージョン情報をdebugモジュールを使って表示します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Gather information about active zones
      ansible.posix.firewalld_info:
        active_zones: true
      register: result
    - name: Display the result
      ansible.builtin.debug:
        var: result.firewalld_info.version

playbookを実行すると、firewalldのバージョンが1.2.1であることがわかります。

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

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

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

TASK [Gather information about active zones] **************************************************
ok: [node1]

TASK [Display the result] *********************************************************************
ok: [node1] => {
    "result.firewalld_info.version": "1.2.1"
}

PLAY RECAP ************************************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

6 sysctlモジュールの使い方

sysctlモジュールは、カーネルパラメータを操作するためのモジュールです。カーネルパラメータは/proc/sys/配下のファイルに保存されます。たとえば、IPフォワーディングの有効化または無効化を制御するカーネルパラメータは、/proc/sys/net/ipv4/ip_forwardファイルに保存されています。また、sysctlモジュールのsysctl_fileパラメータを使用すると、指定したファイルにカーネルパラメータを設定できます。詳細は、ansible.posix.sysctl module – Manage entries in sysctl.conf. — Ansible Community Documentationを参照してください。

6.1 ルーティングを有効にする方法

playbookを作成します。このplaybookは、nodesグループ内のホストで、カーネルパラメータのnet.ipv4.ip_forwardを1に設定します。これにより、IPフォワーディングが有効になります。なお、sysctlモジュールに渡すパラメータの意味は以下のとおりです。

パラメータ 意味
name カーネルパラメータの名前を指定します
value カーネルパラメータに設定する値を指定します。'1'はIPフォワーディングを有効にすることを意味します
sysctl_set trueに設定することで、システムファイルにカーネルパラメータの変更を永続的に反映します。OS再起動後も設定を保持します
state このタスクで扱うリソースの状態を指定します。presentは、指定されたカーネルパラメータが存在していること、または期待値に設定されていることを確認します
reload trueに設定することで、変更が適用された後に設定ファイルをリロードし、システムに変更を反映します
sysctl_file カーネルパラメータを保存するファイルのパスを指定します
[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Set ip forwarding on in /proc and in the sysctl file and reload if necessary
      ansible.posix.sysctl:
        name: net.ipv4.ip_forward
        value: '1'
        sysctl_set: true
        state: present
        reload: true

playbookを実行する前に、ターゲットノードのip_forwardの値を確認すると、0であることがわかります。

[root@node1 ~]# sysctl -n net.ipv4.ip_forward
0

playbookを実行します。

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

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

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

TASK [Set ip forwarding on in /proc and in the sysctl file and reload if necessary] ***********
changed: [node1]

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

[root@control ~]#

playbookを実行すると、ターゲットノードのip_forwardの値が1に変更されたことが確認できます。

[root@node1 ~]# sysctl -n net.ipv4.ip_forward
1

6.2 カーネルパニックを有効にする方法

playbookを作成します。このplaybookは、nodesグループ内のホストで、/etc/sysctl.d/10-test.confファイルのkernel.panicを1に設定します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Set kernel.panic to 1 in /etc/sysctl.d/10-test.conf
      ansible.posix.sysctl:
        name: kernel.panic
        value: '1'
        sysctl_file: /etc/sysctl.d/10-test.conf

playbookを実行します。

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

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

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

TASK [Set ip forwarding on in /proc and in the sysctl file and reload if necessary] ***********
changed: [node1]

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

[root@control ~]#

/etc/sysctl.d/10-test.confを確認すると、kernel.panicに1が設定されていることがわかります。

[root@node1 ~]# cat /etc/sysctl.d/10-test.conf
kernel.panic=1

7 atモジュールの使い方

atモジュールは、atコマンドを使ってコマンドやスクリプトファイルの実行をスケジュールするモジュールです。詳細は、ansible.posix.at module – Schedule the execution of a command or script file via the at command — Ansible Community Documentationを参照してください。

atモジュールを実行するためには、ターゲットノードにatコマンドがインストールされている必要があります。そのため、atパッケージを事前にインストールします。

[root@node1 ~]# dnf install at

playbookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Get target node's time
      ansible.builtin.command:
        cmd: date
      register: node_time

    - name: Output target node's time to control node's stdout
      ansible.builtin.debug:
        msg: "Target node's time: {{ node_time.stdout }}"

    - name: Schedule a command using at module
      ansible.posix.at:
        command: echo "$(date)" > /tmp/time.txt
        count: 1
        units: minutes
      register: result

    - name:
      ansible.builtin.debug:
        msg: "Job scheduled with command: {{ result }}"

playbookを実行します。atモジュールを実行する直前のターゲットノードの時刻が21時38分13秒であることがわかります。

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

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

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

TASK [Get target node's time] ****************************************************************
changed: [node1]

TASK [Output target node's time to control node's stdout] ************************************
ok: [node1] => {
    "msg": "Target node's time: 2024年  4月 17日 水曜日 21:38:13 JST"
}

TASK [Schedule a command using at module] ****************************************************
changed: [node1]

TASK [ansible.builtin.debug] *****************************************************************
ok: [node1] => {
    "msg": "Job scheduled with command: {'changed': True, 'state': 'present', 'script_file': '/tmp/atyq_8mt9r', 'count': 1, 'units': 'minutes', 'failed': False}"
}

PLAY RECAP ***********************************************************************************
node1                      : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでatqコマンドを実行します。echoコマンドが21時39分00秒に実行されることがわかります。

[root@node1 ~]# atq
22      Wed Apr 17 21:39:00 2024 a root

ファイルの中身を確認すると、echoコマンドが21時39分00秒に実行されたことがわかります。

[root@node1 ~]# cat /tmp/time.txt
2024年  4月 17日 水曜日 21:39:00 JST

8 selinuxモジュールの使い方

selinuxモジュールは、SELinuxのモードを変更するモジュールです。

ターゲットノードのSELinuxのモードをEnforcingに変更するPlaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Enable SELinux
      ansible.posix.selinux:
        policy: targeted
        state: enforcing

Playbookを実行する前にターゲットノードでSELinuxのモードを確認すると、Permissiveであることがわかります。

[root@node1 ~]# getenforce
Permissive

playbookを実行します。

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

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

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

TASK [Enable SELinux] ************************************************************************
changed: [node1]

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

[root@control ~]#

Playbookを実行した後にターゲットノードでSELinuxのモードを確認すると、Enforcingに変更されたことがわかります。

[root@node1 ~]# getenforce
Enforcing

Ansibleの使い方(Handlersセクション編)

1 はじめに

1つのプレイブックは、1つ以上のプレイから構成されます。プレイは、以下のセクションから構成されます。本記事では、Handlersセクションについて動作確認をしてみます。

セクション名 概要
Targetsセクション ターゲットノードへの接続に関する情報を定義します
Tasksセクション 実行する処理を定義します
Handlersセクション notifyディレクティブで指定したタスクを実行するセクションです。サービスの再起動や設定の更新などに使用します
Varsセクション 変数を定義します

2 検証環境

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

                          192.168.122.0/24
control(enp1s0) -------------------------------------(enp1s0) 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

3 事前準備

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

4 インベントリファイルの作成

インベントリファイルを作成します。

[root@control ~]# cat hosts.ini
[nodes]
node1 ansible_host=192.168.122.220

5 notifyディレクティブの動作確認

notifyディレクティブあり/なしの場合の動作確認をしてみます。

5.1 notifyディレクティブありの場合

Playbookを作成します。このPlaybookは、Tasksセクションでfileモジュールを実行してファイルを作成します。ファイルが作成されると、notifyディレクティブが実行され、それによりHandlersセクション内のdebugモジュールが呼び出されます。debugモジュールが実行されると、指定されたメッセージが表示されます。

[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Create file
      ansible.builtin.file:
        path: /tmp/test.txt
        state: touch
      notify: Display

  handlers:
    - name: Display
      ansible.builtin.debug:
        msg: The handlers section is called.

Playbookを実行すると、指定したメッセージが表示されることがわかります。

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

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

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

TASK [Create file] ******************************************************************************************
changed: [node1]

RUNNING HANDLER [Display] ***********************************************************************************
ok: [node1] => {
    "msg": "The handlers section is called."
}

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

[root@control ~]#

5.2 notifyディレクティブなしの場合

Playbookを作成します。このPlaybookでは、リモートノードに対してファイルを作成するタスクを実行します。ただし、notifyディレクティブが定義されていないため、Tasksセクションでファイルを作成しても、Handlersセクションのdebugモジュールは実行されません。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Create file
      ansible.builtin.file:
        path: /tmp/test.txt
        state: touch

  handlers:
    - name: Display
      ansible.builtin.debug:
        msg: The handlers section is called.

Playbookを実行します。notifyディレクティブが定義されていないため、メッセージが表示されないことがわかります。

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

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

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

TASK [Create file] ******************************************************************************************
changed: [node1]

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

[root@control ~]#

6 実践編(httpdの起動)

httpdパッケージをインストールしたら、httpdサービスを起動するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Install Apache
      ansible.builtin.dnf:
        name: httpd
        state: present
      notify:
        - Start Apache

  handlers:
    - name: Start Apache
      ansible.builtin.service:
        name: httpd
        state: started

playbookを実行します。

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

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

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

TASK [Install Apache] ********************************************************************
changed: [node1]

RUNNING HANDLER [Start Apache] ***********************************************************
changed: [node1]

PLAY RECAP *******************************************************************************
node1                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

httpdサービスの状態を確認するとactiveになっていることがわかります。

[root@node1 ~]# systemctl is-active httpd
active

Ansibleの使い方(配列、連想配列編)

1 はじめに

本記事では、配列、連想配列を使った簡単なplaybookを作成して、その動作確認をしてみます。

(1) 配列(シーケンス)
配列は、先頭に「-」(ハイフン)を付け、その後にスペースを空けて値を記述します。以下は、配列名がtest_arrayの例です。

test_array:
  - user1
  - user2
  - user3

(2) 連想配列マッピング
連想配列とは、配列の添え字にスカラー以外のデータ型(例:文字列)を使用できる配列のことです。Key(キー)のあとに「:」(コロン)を付け、その後スペースを空けてValue(バリュー)を記述します。以下は、配列名がtest_associative_arrayの例です。

test_associative_array:
  key1: value1
  key2: value2
  key3: value3

2 検証環境

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

                          192.168.122.0/24
control(enp1s0) -------------------------------------(enp1s0) 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

3 事前準備

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

4 インベントリファイルの作成

管理対象となるサーバの一覧を記述したインベントリファイルを作成します。この後の動作確認でも、このインベントリファイルを使用します。node1 はターゲットノードのホスト名であり、ansible_host はターゲットノードのIPアドレスを定義しています。

[root@control ~]# cat hosts.ini
[nodes]
node1 ansible_host=192.168.122.220

5 配列の使い方

5.1 その1

Varsセクションで配列usersを定義します。Tasksセクションでは、loopディレクティブを使って配列に対して繰り返し処理を実行します。この繰り返し処理の中で、配列usersの要素であるuser1とuser2をuserモジュールのパラメータとして渡すことで、user1とuser2を作成します。なお、モジュールの使い方は、Ansibleの使い方(モジュール編) - hana_shinのLinux技術ブログを参照してください。

[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  vars:
    users:
      - user1
      - user2
  tasks:
    - name: Add users
      ansible.builtin.user:
        name: "{{ item }}"
        state: present
      loop: "{{ users }}"

playbookを実行します。

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

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

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

TASK [Add users] **************************************************************************************
changed: [node1] => (item=user1)
changed: [node1] => (item=user2)

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

[root@control ~]#

ターゲットノードで作成したユーザを確認すると、user1とuser2が作成されたことがわかります。

[root@node1 ~]# id user1
uid=1000(user1) gid=1000(user1) groups=1000(user1)
[root@node1 ~]# id user2
uid=1001(user2) gid=1001(user2) groups=1001(user2)

あと始末をします。

[root@node1 ~]# userdel user1
[root@node1 ~]# userdel user2

5.2 その2

Varsセクションで配列usersを定義します。Tasksセクションでは、loopディレクティブを使って配列に対して繰り返し処理を実行します。この繰り返し処理の中で、配列usersの要素であるユーザ名(user1,user2)とUID(5000,5001)をuserモジュールのパラメータとして渡すことで、user1(UID=5000)とuser2(UID=5001)を作成します。

[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  vars:
    users:
      - name: user1
        uid: 5000
      - name: user2
        uid: 5001
  tasks:
    - name: Add users with specific UID.
      ansible.builtin.user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        state: present
      loop: "{{ users }}"

playbookを実行します。

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

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

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

TASK [Add users with specific UID.] *******************************************************************
changed: [node1] => (item={'name': 'user1', 'uid': 5000})
changed: [node1] => (item={'name': 'user2', 'uid': 5001})

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

[root@control ~]#

idコマンドを使って作成したユーザを確認すると、user1(uid=5000)とuser2(uid=5001)が作成されたことがわかります。

[root@node1 ~]# id user1
uid=5000(user1) gid=5000(user1) groups=5000(user1)
[root@node1 ~]# id user2
uid=5001(user2) gid=5001(user2) groups=5001(user2)

あと始末をします。

[root@node1 ~]# userdel user1
[root@node1 ~]# userdel user2

6 連想配列の使い方

6.1 その1

連想配列の使い方を確認するplaybookを作成します。Varsセクションで連想配列を定義します。Tasksセクションでは、loopディレクティブを使って連想配列に対して繰り返し処理を実行します。この繰り返し処理の中で、連想配列のキーとバリューをdebugモジュールのパラメータとして渡します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  vars:
    test_associative_array:
      key1: value1
      key2: value2
      key3: value3
  tasks:
    - name: Print values from the associative array
      ansible.builtin.debug:
        msg: "{{ item.key }}: {{ item.value }}"
      loop: "{{ test_associative_array | dict2items }}"

playbookを実行すると、連想配列で定義してキーとバリューが出力されていることが確認できます。

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

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

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

TASK [Print values from the associative array] **********************************************************************************
ok: [node1] => (item={'key': 'key1', 'value': 'value1'}) => {
    "msg": "key1: value1"
}
ok: [node1] => (item={'key': 'key2', 'value': 'value2'}) => {
    "msg": "key2: value2"
}
ok: [node1] => (item={'key': 'key3', 'value': 'value3'}) => {
    "msg": "key3: value3"
}

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

[root@control ~]#

6.2 その2

連想配列の使い方を確認するplaybookを作成します。Varsセクションで連想配列を定義します。Tasksセクションで、指定したキーに対するバリューを表示します。バリューをdebugモジュールのパラメータとして渡します。

[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  vars:
    test_associative_array:
      key1: value1
      key2: value2
      key3: value3
  tasks:
    - name: Print value of key2 from the associative array
      ansible.builtin.debug:
        msg: "{{ test_associative_array['key2'] }}"

playbookを実行すると、キー (key2) に対応するバリュー (value2) が出力されていることが確認できます。

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

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

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

TASK [Print value of key2 from the associative array] ********************************************************************
ok: [node1] => {
    "msg": "value2"
}

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

[root@control ~]#

Ansibleの使い方(マジック変数、ファクト変数編)

1 はじめに

本記事では、以下のドキュメントに記載されているマジック変数とファクト変数について動作確認をしてみます。
特別な変数 — Ansible Documentation

種類 概要
ファクト変数 ターゲットノード(リモートホスト)から収集したシステムに関する情報を保持する変数です。たとえば、ターゲットノードのホスト名やIPアドレスなどの情報を取得できます。ファクトはsetupというモジュールが実行されることにより、対象のターゲットノードでPythonスクリプトが実行され収集されます。
マジック変数 Ansible があらかじめ定義している変数です。マジック変数の中で、ターゲットノードに関する情報を設定した変数をファクト変数と呼びます。ユーザーはマジック変数に値を設定できません。

2 検証環境

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

                          192.168.122.0/24
control(enp1s0) -------------------------------------(enp1s0) 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

3 事前準備

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

4 インベントリファイルの作成

管理対象となるサーバの一覧を記述したインベントリファイルを作成します。この後のモジュールの動作確認でも、このインベントリファイルを使用します。node1 はターゲットノードのホスト名であり、ansible_host はターゲットノードのIPアドレスを定義しています。

[root@control ~]# vi hosts.ini
[root@control ~]# cat hosts.ini
[nodes]
node1 ansible_host=192.168.122.220

5 ファクト変数を表示する方法

5.1 全てのファクト変数を表示する方法

全てのファクトを表示するplaybookを作成します。
playbook中のgather_facts,ansible_factsの意味は以下のとおりです。
・gather_facts:trueはファクトを収集する。falseはファクトを収集しない。
・ansible_facts:Ansibleが収集したファクトを格納する変数です。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  gather_facts: true
  tasks:
    - name: Display Gathered Facts
      debug:
        var: ansible_facts

playbookを実行すると、「Gathering Facts」と表示されるタイミングで、ターゲットノードからファクトが収集されていることがわかります。

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

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

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

TASK [Display Gathered Facts] *****************************************************************************************
ok: [node1] => {
    "ansible_facts": {
        "all_ipv4_addresses": [
            "192.168.122.220"
        ],
        "all_ipv6_addresses": [
            "fe80::5054:ff:fe2a:749c"
        ],
        "ansible_local": {},
        "apparmor": {
            "status": "disabled"
        },
-snip-

5.2 特定のファクトを表示する方法

(1) カーネル版数を表示する方法

カーネル版数はansible_facts内で以下のように定義されています。したがって、カーネル版数は {{ ansible_facts.kernel }} で参照することができます。

    "ansible_facts": {

        -snip-

        "kernel": "5.14.0-284.11.1.el9_2.x86_64",
    }

カーネル版数を表示するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  gather_facts: true
  tasks:
    - name: Display Gathered Facts
      debug:
        msg: "The kernel version of the remote host is {{ ansible_facts.kernel }}"

playbookを実行すると、カーネル版数(5.14.0-284.11.1.el9_2.x86_64)が表示されていることがわかります。

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

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

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

TASK [Display Gathered Facts] *****************************************************************************************
ok: [node1] => {
    "msg": "The kernel version of the remote host is 5.14.0-284.11.1.el9_2.x86_64"
}

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

[root@control ~]#

(2) ホスト名を表示する方法
ホスト名はansible_facts内で以下のように定義されています。したがって、ホスト名は {{ ansible_facts.nodename }} で参照することができます。

    "ansible_facts": {

        -snip-

        "nodename": "node1",
    }

ターゲットノードのホスト名を表示するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  gather_facts: true
  tasks:
    - name: Display Gathered Facts
      debug:
        msg: "The name of the remote host is {{ ansible_facts.nodename }}"

playbookを実行すると、ターゲットノードのホスト名(node1)が表示されていることがわかります。

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

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

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

TASK [Display Gathered Facts] *****************************************************************************************
ok: [node1] => {
    "msg": "The name of the remote host is node1"
}

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

[root@control ~]#

(3) インタフェース名を表示する方法
インタフェースはansible_facts内で以下のように定義されています。したがって、インタフェースは {{ ansible_facts.interfaces }} で参照することができます。

    "ansible_facts": {

        -snip-

        "interfaces": [
            "enp1s0",
            "lo"
        ],
    }

ターゲットノードが持っているインタフェース名を表示するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Display Gathered Facts
      debug:
        msg: "The host has the interface {{ ansible_facts.interfaces }}"

playbookを実行すると、ターゲットノードは、2つのインタフェース(lo,enp1s0)を持っていることがわかります。

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

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

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

TASK [Display Gathered Facts] *****************************************************************************************
ok: [node1] => {
    "msg": "The host has the interface ['enp1s0', 'lo']"
}

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

[root@control ~]#

(4) IPアドレスを表示する方法

IPアドレスはansible_facts内で以下のように定義されています。したがって、IPアドレスは {{ ansible_facts.enp1s0.ipv4.address }}で参照することができます。

    "ansible_facts": {
        "enp1s0": {

            -snip-

            "ipv4": {
                "address": "192.168.122.220",
                "broadcast": "192.168.122.255",
                "netmask": "255.255.255.0",
                "network": "192.168.122.0",
                "prefix": "24"
            },

        },
    }

ターゲットノードのNIC(enp1s0)に設定されているIPアドレスを表示するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  gather_facts: true
  tasks:
    - name: Display Gathered Facts
      debug:
        msg: "IP adress is {{ ansible_facts.enp1s0.ipv4.address }}"

playbookを実行すると、ターゲットノードのNIC(enp1s0)に設定されているIPアドレスが 192.168.122.220 であることがわかります。

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

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

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

TASK [Display Gathered Facts] *****************************************************************************************
ok: [node1] => {
    "msg": "IP adress is 192.168.122.220"
}

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

[root@control ~]#

6 マジック変数を表示する方法

マジック変数は、Ansible がシステム内の状態を反映する変数です。ユーザーは、マジック変数に値を設定することはできません。マジック変数の一覧は、特別な変数 — Ansible Documentationを参照してください。
https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_variables.html

6.1 プレイブック名を表示する方法(ansible_play_name)

ansible_play_nameは、プレイブック名を表示するマジック変数です。ansible_play_nameを使ってplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Display Ansible Playbook Name
      debug:
        msg: "{{ ansible_play_name }}"

playbookを実行すると、プレイブックの名前(Sample Playbook)が表示されていることがわかります。

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

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

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

TASK [Display Ansible Playbook Name] ******************************************************
ok: [node1] => {
    "msg": "Sample Playbook"
}

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

[root@control ~]#

6.2 パッケージ管理ソフト名を表示する方法(ansible_pkg_mgr)

パッケージ管理ソフトウェアは、コンピュータにインストールされたソフトウェアの記録を管理し、新しいソフトウェアのインストール、既存ソフトウェアのアップデート、そして不要なソフトウェアの削除を容易に行えるソフトウェアです。具体例としては、yum(RHEL7など)、dnf(RHEL 8、9など)、およびyast(SLES)などがあります。ansible_pkg_mgrを使ってplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: debug ansible_hostname
      debug:
        msg: "{{ansible_pkg_mgr}}"

playbookを実行すると、ターゲットノードのパッケージ管理ソフトがdnfであることがわかります。

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

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

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

TASK [debug ansible_hostname] *****************************************************************************************
ok: [node1] => {
    "msg": "dnf"
}

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

[root@control ~]#

6.3 インベントリファイル内のホスト名を表示する方法(inventory_hostname)

inventory_hostnameは、インベントリファイル内のホスト名を示すマジック変数です。この変数を使用してPlaybookを作成することができ、これにより特定のホストに対してロールを動的に適用することができます。なお、inventory_hostname_shortは、FQDNのホスト名の部分だけを示すマジック変数です。たとえば、インベントリファイル内でnode1.example.comと定義されていた場合、inventory_hostname_shortはnode1を示すことになります。

[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: debug ansible_hostname
      debug:
        msg: "{{inventory_hostname}}"

playbookを実行すると、インベントリファイル内のホスト名であるnode1が出力されていることがわかります。

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

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

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

TASK [debug ansible_hostname] *****************************************************************************************
ok: [node1] => {
    "msg": "node1"
}

PLAY RECAP ************************************************************************************************************
node1

CPUQuotaの使い方

1 CPUQuotaとは?

CPUQuotaは、プロセスに割り当てられるCPUリソースの割合を制限するための機能です。CPUQuotaを設定すると、特定のプロセスがシステム全体のCPUリソースのうち、一定の割合しか利用できなくなります。例えば、CPUQuotaに20%を設定すると、そのユニットファイルで指定されたプロセスは、システム上のCPUリソースのうち20%まで利用できるようになります。これにより、他の重要なプロセスが十分なCPUリソースを利用できるようになり、システム全体の安定性やパフォーマンスを向上させることができます。

本記事では、CPUQuotaにCPU使用率を設定して、その効果を検証してみます。

2 検証環境

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

仮想マシンに搭載しているCPU数は4個です。lscpuの使い方は、lscpuコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lscpu -ae
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 1:1:1:1          yes
  2    0      2    2 2:2:2:2          yes
  3    0      3    3 3:3:3:3          yes

3 事前準備(stress-ngパッケージのインストール)

CPU負荷をかけるため、stress-ngパッケージをインストールします。stress-ngパッケージはepelリポジトリにあるので、まず、epel-releaseパッケージをインストールします。

[root@server ~]# yum -y install epel-release

次に、stress-ngパッケージをインストールします。

[root@server ~]# yum -y install stress-ng

stress-ngコマンドの版数を確認します。

[root@server ~]# stress-ng -V
stress-ng, version 0.15.00 (gcc 11.3, x86_64 Linux 5.14.0-284.11.1.el9_2.x86_64) ?諮

なお、stress-ngコマンドの使い方は、stress-ngコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

4 動作確認(CPUQuotaを設定しない場合)

4.1 実験1(1つのCPUに負荷をかけた場合)

1つのCPUにCPU使用率40%の負荷をかけるユニットファイルを作成します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 1 -l 40

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使ってstress-ngプロセスのCPU使用率を確認すると、CPU1の使用率が約40%であることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          2328       1   3  0.1 do_wait
stress-ng          2329    2328   1 39.7 -

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

4.2 実験2(2つのCPUに負荷をかけた場合)

2つのCPUにCPU使用率40%の負荷をかけるユニットファイルを作成します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 2 -l 40

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使ってstress-ngプロセスのCPU使用率を確認すると、CPU2,CPU3の使用率が約40%であることがわかります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          1378       1   0  0.1 do_wait
stress-ng          1379    1378   2 39.2 -
stress-ng          1380    1378   3 39.1 -

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

4.3 実験3(3つのCPUに負荷をかけた場合)

3つのCPUにCPU使用率40%の負荷をかけるユニットファイルを作成します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 3 -l 40

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使ってstress-ngプロセスのCPU使用率を確認すると、CPU1,CPU2,CPU3の使用率が約40%であることがわかります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          1416       1   0  0.0 do_wait
stress-ng          1417    1416   1 39.2 do_select
stress-ng          1418    1416   2 39.0 do_select
stress-ng          1419    1416   3 39.2 do_select

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

4.4 実験4(4つのCPUに負荷をかけた場合)

4つのCPUにCPU使用率40%の負荷をかけるユニットファイルを作成します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 4 -l 40

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使ってstress-ngプロセスのCPU使用率を確認すると、CPU0,CPU1,CPU2,CPU3の使用率が約40%であることがわかります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          2365       1   1  0.0 do_wait
stress-ng          2366    2365   0 39.6 do_select
stress-ng          2367    2365   3 39.6 do_select
stress-ng          2368    2365   2 39.7 do_select
stress-ng          2369    2365   1 39.7 do_select

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

5 動作確認(CPUQuota=10%を設定した場合)

5.1 実験5(1つのCPUに負荷をかけた場合)

1つのCPUでCPU使用率が40%になるようにユニットファイルを作成します。このとき、CPUQuotaに10%を設定します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 1 -l 40
CPUQuota=10%

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使用して、stress-ngプロセスのCPU使用率を確認しました。CPU0のCPU使用率が8.8%であるため、stress-ngプロセスはシステム全体でCPUを8.8%使用しています。CPUQuotaとして10%を指定しているため、ほぼ期待どおりのCPU使用率であることが分かります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          2105       1   3  0.0 do_wait
stress-ng          2106    2105   0  8.8 -

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

5.2 実験6(2つのCPUに負荷をかけた場合)

2つのCPUでCPU使用率が40%になるようにユニットファイルを作成します。このとき、CPUQuotaに10%を設定します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 2 -l 40
CPUQuota=10%

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使用して、stress-ngプロセスのCPU使用率を確認しました。各CPUの使用率は以下の通りです:CPU1=5.0%、CPU3=4.6%。したがって、stress-ngプロセスはシステム全体でCPUを合計で9.6%使用しています。CPUQuotaとして10%を設定しているため、ほぼ期待どおりのCPU使用率であることがわかります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          2163       1   0  0.2 do_wait
stress-ng          2164    2163   3  4.6 -
stress-ng          2165    2163   1  5.0 -

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

5.3 実験7(3つのCPUに負荷をかけた場合)

3つのCPUでCPU使用率が40%になるようにユニットファイルを作成します。このとき、CPUQuotaに10%を設定します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 3 -l 40
CPUQuota=10%

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使用して、stress-ngプロセスのCPU使用率を確認しました。各CPUの使用率は以下の通りです:CPU0=3.2%、CPU2=3.3%、CPU3=3.3%。したがって、stress-ngプロセスはシステム全体でCPUを合計で9.8%使用しています。CPUQuotaとして10%を設定しているため、ほぼ期待どおりのCPU使用率であることが分かります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          2198       1   2  0.0 do_wait
stress-ng          2199    2198   0  3.2 -
stress-ng          2200    2198   3  3.3 -
stress-ng          2201    2198   2  3.3 -

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

5.4 実験8(4つのCPUに負荷をかけた場合)

4つのCPUでCPU使用率が40%になるようにユニットファイルを作成します。このとき、CPUQuotaに10%を設定します。

[root@server ~]# vi /etc/systemd/system/stress-ng.service
[root@server ~]# cat /etc/systemd/system/stress-ng.service
[Unit]
Description=CPUQuotaTest

[Service]
ExecStart=stress-ng -k -c 4 -l 40
CPUQuota=10%

[Install]
WantedBy=multi-user.target

ユニットファイルを変更したので、Systemdに設定ファイルの再読み込みを指示します。

[root@server ~]# systemctl daemon-reload

stress-ngサービスを起動します。

[root@server ~]# systemctl start stress-ng.service

psコマンドを使用して、stress-ngプロセスのCPU使用率を確認しました。各CPUの使用率は以下の通りです:CPU0=2.4%、CPU1=2.4%、CPU2=2.1%、CPU3=2.2%。したがって、stress-ngプロセスはシステム全体でCPUを合計で9.1%使用しています。CPUQuotaとして10%を設定しているため、ほぼ期待どおりのCPU使用率であることが分かります。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          2234       1   2  0.1 do_wait
stress-ng          2235    2234   3  2.2 -
stress-ng          2236    2234   2  2.1 -
stress-ng          2237    2234   1  2.4 -
stress-ng          2238    2234   0  2.4 -

stress-ngサービスを停止します。

[root@server ~]# systemctl stop stress-ng.service

6 まとめ

(1) CPUQuota未使用時のCPU使用率
CPUQuotaを指定していないため、stress-ngコマンドの引数で指定したCPU使用率(40%)までCPUが使用されていることが分かります。

CPU0 CPU1 CPU2 CPU3
実験1 - 39.7 - -
実験2 - - 39.2 39.1
実験3 - 39.2 39.0 39.2
実験4 39.6 39.7 39.7 39.6

(2) CPUQuota=10%を指定した時のCPU使用率
CPU使用率の合計がCPUQuotaで指定したCPU使用率(10%)未満になっていることがわかります。

CPU0 CPU1 CPU2 CPU3 CPU使用率の合計
実験5 8.8 - - - 8.8
実験6 - 5.0 - 4.6 9.6
実験7 3.2 - 3.3 3.3 9.8
実験8 2.4 2.4 2.1 2.2 9.1

Z 参考情報

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

Ansibleの使い方(デバッグ編)

1 はじめに

本記事では、Ansibleのデバッグ方法について説明します。

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

3 事前準備

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

4 playbookの構文をチェックする方法(–syntax-check)

–syntax-checkは、playbookの構文をチェックするオプションです。構文をチェックするだけで、playbookの実行はしません。

テスト用のplaybookを作成します。このplaybookに誤りはありません。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: The output of magic variables
      ansible.builtin.debug:
        msg: The name of playbook is {{ ansible_play_name }}

--syntax-checkオプションを付けて、playbookを実行します。

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

playbook: test.ym

意図的にmsgの開始位置を左にずらして、debugモジュールと同じ位置にしてみます。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: The output of magic variables
      ansible.builtin.debug:
      msg: The name of playbook is {{ ansible_play_name }}

--syntax-checkオプションを付けて、playbookを実行すると、構文エラーが発生していることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml --syntax-check
ERROR! conflicting action statements: debug, msg

The error appears to be in '/root/test.yml': line 4, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  tasks:
    - name: The output of magic variables
      ^ here

5 デバッグメッセージを表示する方法(-v,-vv...)

-vはデバッグメッセージを出力するオプションです。vの個数は最大6個(-vvvvvv)まで指定できます。-vがデバックメッセージを最も簡潔に出力します。vの数が多くなると、デバッグメッセージをより詳細に出力します。-vvvからネットーワークに関するデバッグメッセージが出力されます。

-vを指定してplaybookを実行すると、デバッグメッセージが出力されていることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml -v
Using /etc/ansible/ansible.cfg as config file

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

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

TASK [Execute the hostname command] *****************************************************************************************************
changed: [192.168.122.220] => {"changed": true, "cmd": ["/usr/bin/hostname"], "delta": "0:00:00.007782", "end": "2024-02-24 22:11:47.767717", "msg": "", "rc": 0, "start": "2024-02-24 22:11:47.759935", "stderr": "", "stderr_lines": [], "stdout": "node1", "stdout_lines": ["node1"]}

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

[root@control ~]#

6 タスク毎に実行を確認する方法(--step)

--stepは、Ansible Playbookを実行する際に、各タスクごとにユーザーに対して処理を続けるか中断するかを確認するオプションです。このオプションを使用すると、Playbookの実行中に各タスクが開始される前に、ユーザーに確認メッセージが表示されます。

テスト用のplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Display nodename
      ansible.builtin.debug:
        msg: "The name of the remote host is {{ ansible_facts.nodename }}"
    - name: Display kernel version
      ansible.builtin.debug:
        msg: "The kernel version of the remote host is {{ ansible_facts.kernel }}"

--stepオプションを指定してplaybookを実行します。各タスクで以下のいずれかを押下します。
・n:タスクを実行しません。
・y:タスクを実行します。
・c:最後のタスクまで自動で実行します。
以下の例では、最初にyを押下して、リモートホストからファクトを収集します。その後、2回ともyを押下してタスクを実行しています。ちなみに、ファクトの収集に対してNを押下すると、リモートホストからホスト名等のファクトを入手できないので、その後のタスクの実行は失敗します。

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

PLAY [Sample Playbook] ***********************************************************************************
Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: y

Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: ***********************************************

TASK [Gathering Facts] ***********************************************************************************
ok: [192.168.122.220]
Perform task: TASK: Display nodename (N)o/(y)es/(c)ontinue: y

Perform task: TASK: Display nodename (N)o/(y)es/(c)ontinue: **********************************************

TASK [Display nodename] **********************************************************************************
ok: [192.168.122.220] => {
    "msg": "The name of the remote host is node1"
}
Perform task: TASK: Display kernel version (N)o/(y)es/(c)ontinue: y

Perform task: TASK: Display kernel version (N)o/(y)es/(c)ontinue: ****************************************

TASK [Display kernel version] ****************************************************************************
ok: [192.168.122.220] => {
    "msg": "The kernel version of the remote host is 5.14.0-284.11.1.el9_2.x86_64"
}

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

[root@control ~]#

7 debugモジュールを使う方法

debugは、playbookの実行中にメッセージを表示し、変数や式のデバッグするためのモジュールです。詳細は、ansible.builtin.debug module -- Print statements during execution — Ansible Core Documentationを参照してください。

7.1 msgパラメータを使う方法

マジック変数を標準出力に出力するplaybookファイルを作成します。マジック変数とは、Ansible がシステム内の状態を反映する変数です。ユーザーはマジック変数に値を設定することはできません。ここでは、playbookの名前を保持するマジック変数(ansible_play_name)の値を出力してみます。マジック変数は、特別な変数 — Ansible Documentationを参照してください。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: The output of magic variables
      ansible.builtin.debug:
        msg: The name of playbook is {{ ansible_play_name }}

playbookを実行すると、プレイブックの名前(Sample Playbook)が表示れていることがわかります。

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

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

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

TASK [The output of magic variables] ***************************************************************************************
ok: [192.168.122.220] => {
    "msg": "The name of playbook is Sample Playbook"
}

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

[root@control ~]#

7.2 varパラメータを使う方法

次の内容のplaybookファイルを作成します。commandモジュールに対してhostnameコマンドをパラメータとして渡し、registerディレクティブを使ってhostnameコマンドの実行結果をresult変数に格納します。そして、その後のタスクでdebugモジュールを実行してhostnameコマンドの実行結果を表示します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Execute the hostname command
      shell: /usr/bin/hostname
      register: result
    - name: Print return information from the previous task
      ansible.builtin.debug:
        var: result

playbookを実行すると、hostnameコマンドの実行結果が表示れていることがわかります。

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

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

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

TASK [Execute the hostname command] *****************************************************************************************************
changed: [192.168.122.220]

TASK [Print return information from the previous task] **********************************************************************************
ok: [192.168.122.220] => {
    "result": {
        "changed": true,
        "cmd": "/usr/bin/hostname",
        "delta": "0:00:00.011434",
        "end": "2024-02-24 20:57:19.969301",
        "failed": false,
        "msg": "",
        "rc": 0,
        "start": "2024-02-24 20:57:19.957867",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "node1",
        "stdout_lines": [
            "node1"
        ]
    }
}

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

[root@control ~]#

実行結果のうち、ホスト名だけを表示する場合、以下のようにvar変数にresult.stdoutと指定します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Execute the hostname command
      shell: /usr/bin/hostname
      register: result
    - name: Print return information from the previous task
      debug:
        var: result.stdout

playbookを実行すると、ターゲットノードのホスト名(node1)が表示れていることがわかります。

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

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

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

TASK [Execute the hostname command] *****************************************************************************************************
changed: [192.168.122.220]

TASK [Print return information from the previous task] **********************************************************************************
ok: [192.168.122.220] => {
    "result.stdout": "node1"
}

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

[root@control ~]#

7.3 verbosityパラメータを使う方法

verbosityは、出力メッセージを詳細に表示するパラメータです。しかし、以下のようなplaybookを作成して実行してみましたが、私の環境では、デバッグメッセージが表示されませんでした。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Execute the hostname command
      ansible.builtin.command:
        cmd: /usr/bin/hostname
      register: var_hostname

    - name: Display hostname command result
      ansible.builtin.debug:
        var: var_hostname
        verbosity: 1

playbookを実行します。デバッグメッセージは表示されませんでした。

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

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

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

TASK [Execute the hostname command] *****************************************************************************************************
changed: [192.168.122.220]

TASK [Display hostname command result] **************************************************************************************************
skipping: [192.168.122.220]

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

[root@control ~]#

8 failモジュールを使う方法

failは、特定の条件が満たされた場合に、Playbookの実行を中止し、エラーメッセージを表示するモジュールです。

8.1 ファイルの状態を使った例

以下の処理を実行するplaybookを作成します。
・/tmp/test.txtが存在する場合:エラーメッセージ(File does't exist)を表示しない
・/tmp/test.txtが存在しない場合:エラーメッセージ(File does't exist)を表示する
なお、statモジュールについては、Ansibleの使い方(モジュール編) - hana_shinのLinux技術ブログを参照してください。

(1) その1(条件に一致しない場合)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Check the status of /tmp/test.txt
      ansible.builtin.stat:
        path: /tmp/test.txt
      register: result

    - name: Fail if /tmp/test.txt does not exist
      ansible.builtin.fail:
        msg: "File doesn't exist"
      when: not result.stat.exists

ターゲットノードでテスト用のファイルを作成します。

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

playbookを実行します。/tmp/test.txtが存在するので、エラーメッセージが表示されないことがわかります。

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

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

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

TASK [Check the status of /tmp/test.txt] *******************************************************************************************
ok: [192.168.122.220]

TASK [if /tmp/test.txt does not exist] *********************************************************************************************
skipping: [192.168.122.220]

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

[root@control ~]#

(2) その2(条件に一致する場合)
ターゲットノードでテスト用ファイルを削除します。

[root@node1 ~]# rm /tmp/test.txt
rm: 通常の空ファイル '/tmp/test.txt' を削除しますか? y

playbookを実行します。/tmp/test.txtが存在しないので、エラーメッセージが表示されていることがわかります。

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

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

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

TASK [Check the status of /tmp/test.txt] *******************************************************************************************
ok: [192.168.122.220]

TASK [if /tmp/test.txt does not exist] *********************************************************************************************
fatal: [192.168.122.220]: FAILED! => {"changed": false, "msg": "File does't exist"}

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

[root@control ~]#

8.2 ファイルサイズを使った例

以下の処理を実行するplaybookを作成します。
・ファイルサイズが3バイト未満の場合:ファイルサイズを表示してplaybookを中断する
・ファイルサイズが3バイト以上の場合:正常終了する

(1) その1(条件に一致する場合)
以下の処理を実行するplaybookを作成します。
・ファイルサイズが3バイト未満の場合:ファイルサイズを表示してplaybookを中断する

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Check the status of /tmp/test.txt
      ansible.builtin.stat:
        path: /tmp/test.txt
      register: result

    - name: Fail if /tmp/test.txt size is less than 3 bytes
      ansible.builtin.fail:
        msg: "File size is {{ result.stat.size }} bytes"
      when: result.stat.size < 3

ターゲットノードでテスト用のファイル(2byte)を作成します。

[root@node1 ~]# echo 0 > /tmp/test.txt
[root@node1 ~]# ls -l /tmp/test.txt
-rw-r--r--. 1 root root 2  2月 25 22:22 /tmp/test.txt

playbookを実行します。ファイルサイズが2バイトである旨を表示して、playbookを終了していることがわかります。

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

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

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

TASK [Check the status of /tmp/test.txt] *******************************************************************************************
ok: [192.168.122.220]

TASK [if /tmp/test.txt does not exist] *********************************************************************************************
fatal: [192.168.122.220]: FAILED! => {"changed": false, "msg": "File size is 2 byte "}

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

[root@control ~]#

(2) その2(条件に一致しない場合)
ターゲットノードでテスト用のファイル(4byte)を作成します。

[root@node1 ~]# echo 012 > /tmp/test.txt
[root@node1 ~]# ls -l /tmp/test.txt
-rw-r--r--. 1 root root 4  2月 25 22:25 /tmp/test.txt

playbookを実行します。playbookが正常終了していることがわかります。

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

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

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

TASK [Check the status of /tmp/test.txt] *******************************************************************************************
ok: [192.168.122.220]

TASK [if /tmp/test.txt does not exist] *********************************************************************************************
skipping: [192.168.122.220]

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

[root@control ~]#

9 assertモジュールを使う方法

assertは、条件に一致しない場合、Playbookの実行を中止しエラーメッセージを表示するモジュールです。詳細は、ansible.builtin.assert module -- Asserts given expressions are true — Ansible Core Documentationを参照してください。

以下の処理を実行するplaybookを作成します。
・ファイルサイズが3バイト未満の場合:ファイルサイズを表示してplaybookを中断する
・ファイルサイズが3バイト以上の場合:正常終了する

(1) その1(条件に一致する場合)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Check the status of /tmp/test.txt
      ansible.builtin.stat:
        path: /tmp/test.txt
      register: result

    - name: Fail if /tmp/test.txt size is less than 3 bytes
      ansible.builtin.assert:
        that:
          - result.stat.size >= 3
        fail_msg: "The file size is less than 3 bytes. The process will be aborted"

ターゲットノードでテスト用のファイル(4byte)を作成します。

[root@node1 ~]# echo 012 > /tmp/test.txt
[root@node1 ~]# ls -l /tmp/test.txt
-rw-r--r--. 1 root root 4  2月 25 22:25 /tmp/test.txt

playbookを実行します。

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

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

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

TASK [Check the status of /tmp/test.txt] *******************************************************************************************
ok: [192.168.122.220]

TASK [if /tmp/test.txt does not exist] *********************************************************************************************
ok: [192.168.122.220] => {
    "changed": false,
    "msg": "All assertions passed"
}

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

[root@control ~]#

(2) その2(条件に一致しない場合)
ターゲットノードでテスト用のファイル(2byte)を作成します。

[root@node1 ~]# echo 0 > /tmp/test.txt
[root@node1 ~]# ls -l /tmp/test.txt
-rw-r--r--. 1 root root 2  2月 25 23:14 /tmp/test.txt

playbookを実行します。

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

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

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

TASK [Check the status of /tmp/test.txt] ********************************************************************************
ok: [192.168.122.220]

TASK [Fail if /tmp/test.txt size is less than 3 bytes] ******************************************************************
fatal: [192.168.122.220]: FAILED! => {
    "assertion": "result.stat.size >= 3",
    "changed": false,
    "evaluated_to": false,
    "msg": "The file size is less than 3 bytes. The process will be aborted"
}

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

[root@control ~]#

10 debuggerオプションを使う方法

debuggerオプションは、playbook実行中、デバッガを起動するオプションです。gdbやcrash等のデバッガと同じように、playbookのステップ実行や変数の値を確認したりすることができます。

playbookを作成します。このとき、"debugger: always"という1行を記載します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  debugger: always
  tasks:
    - name: Install the latest version of Apache
      ansible.builtin.dnf:
        name: httpd
        state: latest

playbookを実行します。以下は、hコマンドを押下して、デバッガのヘルプを表示してるところです。

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

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

TASK [Gathering Facts] *****************************************************************************************************************
ok: [192.168.122.220]
[192.168.122.220] TASK: Gathering Facts (debug)> h

Documented commands (type help <topic>):
========================================
EOF  c  continue  h  help  p  pprint  q  quit  r  redo  u  update_task

[192.168.122.220] TASK: Gathering Facts (debug)>

ここでは、cを押下してplaybookの処理を先に進めてみます。最初のc押下でファクトの収集、2つ目のc押下でhttpdパッケージをインストールしています。

[192.168.122.220] TASK: Gathering Facts (debug)> c

TASK [Install the latest version of Apache] ********************************************************************************************
ok: [192.168.122.220]
[192.168.122.220] TASK: Install the latest version of Apache (debug)> c

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

[root@control ~]#