Ansible Tips: rawモジュールのススメ

レッドハットのAnsibleサポートチームのひよこ大佐こと八木澤です。クリスマス目前で、気温も寒くなってきましたが皆様いかがお過ごしでしょうか。 今回は、「Ansible Tips」として、Ansibleで知っておくと役立つちょっとした小ネタや知識をご紹介します。

この記事は「Ansible Advent Calendar 2024」の23日目の記事になります。

rawモジュールとは?

Ansibleには多種多様なモジュールがありますが、一部他のモジュールと挙動が大きく異なるモジュールも存在します。そのうちの一つが、「raw」モジュールです。

ansible.builtin.raw module – Executes a low-down and dirty command — Ansible Community Documentation

rawとは「生の、未加工の」という意味の英語です。このモジュールはその名の通り「実行先のノードで指定されたコマンドを実行する」という最小限の機能のみを有しており、同様の目的で使用される「shell」や「command」モジュールとは、大きく挙動が異なります。

百聞は一見にしかずということで、まずは実際にrawモジュールを実行してみましょう。

---
- hosts: all
  tasks:
    - name: Execute whoami command
      raw: whoami

上記のPlaybookは、rawモジュールで「whoami」コマンドを実行するという至ってシンプルなものです。実際に実行すると、以下のようにコマンドが実行されます(実行結果をわかりやすくするため、-vvvを指定しています)

TASK [Execute whoami command] *********************************************************************************************************************************************************************************************************************************************************************************************************
task path: /home/hiyoko/Documents/raw.yml:4
<aap25-ctr1> ESTABLISH SSH CONNECTION FOR USER: hiyoko
<aap25-ctr1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' -tt aap25-ctr1 whoami
<aap25-ctr1> (0, b'hiyoko\r\n', b'Shared connection to aap25-ctr1 closed.\r\n')
changed: [aap25-ctr1] => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to aap25-ctr1 closed.\r\n",
    "stderr_lines": [
        "Shared connection to aap25-ctr1 closed."
    ],
    "stdout": "hiyoko\r\n",
    "stdout_lines": [
        "hiyoko"
    ]
}

rawモジュールの最大の特徴は、「ターゲットノード上のPython実行環境を介さず、直接コマンドを実行する」ことにあります。つまり、コネクションプラグイン(sshなど)の接続が確立さえしていれば機能するモジュールとなります。この点で、他のPythonを利用するモジュールとは大きく性格が異なります。上記のタスクの実行結果を見ると、SSHコネクションが確立された直後にコマンドが実行され、実行結果が返却されていることがわかります。実際に、同じくwhoamiコマンドを実行するだけのshellモジュールと、-vvvの出力を比較してみましょう。shellモジュールでは、SSHのコネクションが確立された後、AnsiballZ_command.py を転送していることがわかります。

TASK [Execute whoami command] *********************************************************************************************************************************************************************************************************************************************************************************************************
task path: /home/hiyoko/Documents/raw.yml:4
<aap25-ctr1> ESTABLISH SSH CONNECTION FOR USER: hiyoko
<aap25-ctr1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' aap25-ctr1 '/bin/sh -c '"'"'echo ~hiyoko && sleep 0'"'"''
<aap25-ctr1> (0, b'/home/hiyoko\n', b'')
<aap25-ctr1> ESTABLISH SSH CONNECTION FOR USER: hiyoko
<aap25-ctr1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' aap25-ctr1 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/hiyoko/.ansible/tmp `"&& mkdir "` echo /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332 `" && echo ansible-tmp-1734916880.8232417-13918-57931879742332="` echo /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332 `" ) && sleep 0'"'"''
<aap25-ctr1> (0, b'ansible-tmp-1734916880.8232417-13918-57931879742332=/home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332\n', b'')
Using module file /usr/lib/python3.13/site-packages/ansible/modules/command.py
<aap25-ctr1> PUT /home/hiyoko/.ansible/tmp/ansible-local-13901wvkcruny/tmp2872f93f TO /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332/AnsiballZ_command.py
<aap25-ctr1> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' '[aap25-ctr1]'
<aap25-ctr1> (0, b'sftp> put /home/hiyoko/.ansible/tmp/ansible-local-13901wvkcruny/tmp2872f93f /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332/AnsiballZ_command.py\n', b'')
<aap25-ctr1> ESTABLISH SSH CONNECTION FOR USER: hiyoko
<aap25-ctr1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' aap25-ctr1 '/bin/sh -c '"'"'chmod u+x /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332/ /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332/AnsiballZ_command.py && sleep 0'"'"''
<aap25-ctr1> (0, b'', b'')
<aap25-ctr1> ESTABLISH SSH CONNECTION FOR USER: hiyoko
<aap25-ctr1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' -tt aap25-ctr1 '/bin/sh -c '"'"'/usr/bin/python3 /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332/AnsiballZ_command.py && sleep 0'"'"''
<aap25-ctr1> (0, b'\r\n{"changed": true, "stdout": "hiyoko", "stderr": "", "rc": 0, "cmd": "whoami", "start": "2024-12-22 20:21:20.973026", "end": "2024-12-22 20:21:20.974877", "delta": "0:00:00.001851", "msg": "", "invocation": {"module_args": {"_raw_params": "whoami", "_uses_shell": true, "expand_argument_vars": true, "stdin_add_newline": true, "strip_empty_ends": true, "argv": null, "chdir": null, "executable": null, "creates": null, "removes": null, "stdin": null}}}\r\n', b'Shared connection to aap25-ctr1 closed.\r\n')
<aap25-ctr1> ESTABLISH SSH CONNECTION FOR USER: hiyoko
<aap25-ctr1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="hiyoko"' -o ConnectTimeout=10 -o 'ControlPath="/home/hiyoko/.ansible/cp/031c196615"' aap25-ctr1 '/bin/sh -c '"'"'rm -f -r /home/hiyoko/.ansible/tmp/ansible-tmp-1734916880.8232417-13918-57931879742332/ > /dev/null 2>&1 && sleep 0'"'"''
<aap25-ctr1> (0, b'', b'')
changed: [aap25-ctr1] => {
    "changed": true,
    "cmd": "whoami",
    "delta": "0:00:00.001851",
    "end": "2024-12-22 20:21:20.974877",
    "invocation": {
        "module_args": {
            "_raw_params": "whoami",
            "_uses_shell": true,
            "argv": null,
            "chdir": null,
            "creates": null,
            "executable": null,
            "expand_argument_vars": true,
            "removes": null,
            "stdin": null,
            "stdin_add_newline": true,
            "strip_empty_ends": true
        }
    },
    "msg": "",
    "rc": 0,
    "start": "2024-12-22 20:21:20.973026",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "hiyoko",
    "stdout_lines": [
        "hiyoko"
    ]
}

どんな時に使えばいいの?

rawモジュールが、shellやcommandモジュールと異なることはわかりました。では、実際にrawモジュールをどのような時につかえばよいのでしょうか?

・シンプルなコマンドを実行する

ご紹介したように、マネージドノード上であるコマンドや特定のシェルスクリプトを実行したいという場合に、rawモジュールであればPythonスクリプトを介さず直接SSH接続したような感覚でコマンドを実行できます。上記の例はRHELに対して実行していますが、実はrawモジュールはWindows環境やネットワークデバイスに対しても動作しますので、環境を選ばずにコマンドを実行させることができます。

さらに、rawモジュールによるコマンド実行は、オーバーヘッドが非常に小さいです。plofile_tasksのcallbackプラグインで計測した結果を比較してみましょう。

shellモジュールを利用した場合:

TASKS RECAP ********************************************************************
月曜日 23 12月 2024  10:35:31 +0900 (0:00:00.220)       0:00:00.224 *************** 
=============================================================================== 
Execute whoami command -------------------------------------------------- 0.22s

rawモジュールを利用した場合:

TASKS RECAP ********************************************************************
月曜日 23 12月 2024  10:36:15 +0900 (0:00:00.020)       0:00:00.024 *************** 
=============================================================================== 
Execute whoami command -------------------------------------------------- 0.02s

rawモジュールを利用するだけで、シンプルなコマンド1つの実行が0.2秒の差があります。当然、より多くのコマンドを実行したり、スクリプトを実行するようなシチュエーションでは、この差は大きくなるでしょう。このように、ノードに対してシンプルなコマンドを実行したり、スクリプトを起動するような用途では、rawは非常に優れた選択肢になります。

・接続先にPythonが存在しない場合の回避策

通常、RHELノードなどであればPythonはデフォルトで利用可能ですが、ネットワーク機器など接続先によってはPythonが利用できない場合があります。そういったデバイスの自動化において対応するモジュールがない場、rawモジュールであれば直接コマンドを実行できるため、多少ダーティーなPlaybookにはなりますが、自動化を実装することができます。

・トラブルシューティングの切り分け

トラブルシューティングの際は、なるべく問題の原因となるレイヤーを少なくしたいものです。rawモジュールであれば、間にPythonスクリプトの実行という余計なプロセスを挟むことなくコマンドやスクリプトを実行できるため、例えばshellモジュールで実行したスクリプトが正常に動作しない場合に、どこに原因があるのか切り分ける時などに非常に役立ちます。

rawモジュールのデメリットは?

では、すべてのコマンドを実行するモジュールはrawモジュールひとつでよいように思えますが、実際にはshellモジュールなどを利用することが多いのはなぜでしょうか?

理由のひとつに、「利用できる機能が最小限である」ということが挙げられます。rawモジュールのドキュメントを見ていただければわかる通り、指定できるオプションはほぼ存在しません。また、最初のrawモジュールとshellモジュールの実行結果を比較していただくと、実行後に利用できる戻り値も大きく異なることがわかります。そのため、コマンドの実行結果を元に条件分岐したりするようなケースでは不便なケースもあります。

また、シェルの機能に極度に依存するようなもの(長大なパイプやリダイレクトなど)が機能しないケースもあるため、必ずしもrawモジュールだけを使えばよいというものではなく、適材適所でその時の要件にあったモジュールを選択していただければ幸いです。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。