takashi kono's blog

コーヒーとキーボードと共に何かを記録していくブログ

Ansible のCommand Modules を触ってみた

目的

Ansible にあるたくさんのモジュールの中からよく使われそうなモジュールを使ってみて知見を得る

環境

Microsoft Azure Cloud Shell
Ubuntu 16.04.4 LTS

Ansible のバージョン

kono@Azure:~/ansible_pj$ ansible --version
ansible 2.5.2
  config file = None
  configured module search path = [u'/home/kono/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/ansible/local/lib/python2.7/site-packages/ansible
  executable location = /opt/ansible/bin/ansible
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
kono@Azure:~/ansible_pj$

予めしておくこと

Ansible による構成対象の VMssh ログイン出来るように Cloud Shell の公開鍵を登録することです。おそらく VM を作るときに聞いてくると思いますが。。。すみません確認して追記します。

Commands modules

Commands modules — Ansible Documentation

まずは Commands modules から見ていく

command

command_module.yaml を作成

$ cat command_modules.yaml
# Command modules test
- hosts: any
  user: takashi
  tasks:
    - name: return distribution
      command: cat /etc/lsb-release
    - name: return df
      command: df -h
    - name: return memory free
      command: free
    - name: return inodes info
      command: df -i
    - name: ip address
      command: ip a
    - name: list of listened ports
      command: netstat -nlt

実行してみる

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts command_modules.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [return distribution] ******************************************************************************************************************************************************************************************************************
changed: [40.74.70.39]

TASK [return df] ****************************************************************************************************************************************************************************************************************************
changed: [40.74.70.39]

TASK [return memory free] *******************************************************************************************************************************************************************************************************************
changed: [40.74.70.39]

TASK [return inodes info] *******************************************************************************************************************************************************************************************************************
changed: [40.74.70.39]

TASK [ip address] ***************************************************************************************************************************************************************************************************************************
changed: [40.74.70.39]

TASK [list of listened ports] ***************************************************************************************************************************************************************************************************************
changed: [40.74.70.39]

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.70.39                : ok=7    changed=6    unreachable=0    failed=0

kono@Azure:~/ansible_pj$

実行されているような気がしますね。では、デバッグメッセージのオプションを使ってみましょう

$ ansible-playbook -i hosts command_modules.yaml -vvv
上記のように、 -vvv オプションをつけることで詳細なメッセージを確認できます。 v の数が何個かで出力レベルがわかるので、自分の欲しい情報が何か整理してから使っても良いかも。基本とりあえず -vvv で。という感じなんだと妄想してみる。

ちなみに、ソースコードを確認してみる

https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/commands/command.py

https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/commands/command.py#L137-L145

このあたりで色々定義しているのがわかる

expect

https://docs.ansible.com/ansible/latest/modules/expect_module.html#expect-module

先に書きますが、私の環境では上手く行きませんでした。

以下は参考までにどうぞ。
まず、適当に playbook を書いてみた。
expect.yaml

kono@Azure:~/ansible_pj$ cat expect.yaml
- hosts: any
  user: takashi
  tasks:
    - name: expect module test
      expect:
        command: /usr/bin/python3 /home/takashi/input_test.py
        responses:
          'please input something: ' : 'This is expect module test'
kono@Azure:~/ansible_pj$

expect というくらいなので、ターミナルに何かコメントがあって、入力待ち状態になる時にこれを使うはず。
というわけで、ssh 先の VM に python3 で適当にコードを書いてみた。

takashi@test1:~$ cat input_test.py 
#!/usr/bin/python3
# coding: utf-8

tmp = input("please input something: ")
print(tmp)

takashi@test1:~$ 

で、実行

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts expect.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [expect module test] *******************************************************************************************************************************************************************************************************************
fatal: [40.74.95.98]: FAILED! => {"changed": false, "msg": "The pexpect python module is required"}
        to retry, use: --limit @/home/kono/ansible_pj/expect.retry

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.95.98                : ok=1    changed=0    unreachable=0    failed=1

kono@Azure:~/ansible_pj$ 

失敗した。
メッセージには、 The expected python module is required とある。
このモジュールを使うためには、 pexpect が必須なんですね。
公式のドキュメントにも書いてありました。

expect - Executes a command and responds to prompts. — Ansible Documentation

ということで、 pexpect が入っているか確認します。

kono@Azure:~/ansible_pj$ ansible --version
ansible 2.5.2
...
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
kono@Azure:~/ansible_pj$ pip2
pip2    pip2.7
kono@Azure:~/ansible_pj$ pip2.7 list | grep pexpect
kono@Azure:~/ansible_pj$

ない!

pexpect のインストールですね。 やってみましょう。

kono@Azure:~/ansible_pj$ pip2.7 install pexpect
Collecting pexpect
  Using cached https://files.pythonhosted.org/packages/89/e6/b5a1de8b0cc4e07ca1b305a4fcc3f9806025c1b651ea302646341222f88b/pexpect-4.6.0-py2.py3-none-any.whl
Collecting ptyprocess>=0.5 (from pexpect)
  Using cached https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl
Installing collected packages: ptyprocess, pexpect
Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/ptyprocess-0.6.0.dist-info'
Consider using the `--user` option or check the permissions.

kono@Azure:~/ansible_pj$ 

??? 何かおかしい。 Permissiono denied ですと!?
--user オプションを使ってみましょう。書いてあるとおり。

kono@Azure:~/ansible_pj$ pip2.7 install pexpect --user
Collecting pexpect
  Using cached https://files.pythonhosted.org/packages/89/e6/b5a1de8b0cc4e07ca1b305a4fcc3f9806025c1b651ea302646341222f88b/pexpect-4.6.0-py2.py3-none-any.whl
Collecting ptyprocess>=0.5 (from pexpect)
  Using cached https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl
Installing collected packages: ptyprocess, pexpect
Successfully installed pexpect-4.6.0 ptyprocess-0.6.0
kono@Azure:~/ansible_pj$
kono@Azure:~/ansible_pj$ pip2.7 list | grep pexpect
pexpect    4.6.0
kono@Azure:~/ansible_pj$

あっさり解決。。。ちゃんと入った。
ということで、リトライします。

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts expect.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [expect module test] *******************************************************************************************************************************************************************************************************************
fatal: [40.74.95.98]: FAILED! => {"changed": false, "msg": "The pexpect python module is required"}
        to retry, use: --limit @/home/kono/ansible_pj/expect.retry

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.95.98                : ok=1    changed=0    unreachable=0    failed=1

kono@Azure:~/ansible_pj$

??? 同じエラーが出てしましました。 モジュールが必要だったのは、もしかすると、VM の方だったのかな?
本当は pip のインストール及び、 pexpect のインストールも Ansible で実施したかったけど、本質じゃないので普通に apt-get install python3-pip で実施することにした。

takashi@test1:~$ pip3 -V
pip 1.5.4 from /usr/lib/python3/dist-packages (python 3.4)
takashi@test1:~$

しかし、 pip3 がインストールできたので、 pexpext のインストールを Ansible でやってみる。

kono@Azure:~/ansible_pj$ cat python_modules.yaml
# Command modules test
- hosts: any
  user: takashi
  become: true
  tasks:
    - name: install pexpect
      command: /usr/bin/pip3 install pexpect
kono@Azure:~/ansible_pj$

プレイブックが出来たので、実行してみる

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts python_modules.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [install pexpect] **********************************************************************************************************************************************************************************************************************
changed: [40.74.95.98]

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.95.98                : ok=2    changed=1    unreachable=0    failed=0

kono@Azure:~/ansible_pj$

お。出来てそう。VM で確認してみる。

takashi@test1:~$ pip3 list | grep pexpect
pexpect (4.6.0)
takashi@test1:~$ 

入りました。
ようやく準備が出来たので、 expect をもう一度試してみる。

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts expect.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [expect module test] *******************************************************************************************************************************************************************************************************************
fatal: [40.74.95.98]: FAILED! => {"changed": false, "msg": "The pexpect python module is required"}
        to retry, use: --limit @/home/kono/ansible_pj/expect.retry

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.95.98                : ok=1    changed=0    unreachable=0    failed=1

kono@Azure:~/ansible_pj$

それでもダメだった。。。んー、何がいけなかったのか。。。
テスト
AzureCloud Shell 環境での動作について

https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/commands/expect.py#L95-L99

ここでやっていることと同じことをしてみる。

python2.7 の場合の import pexpect のテスト

kono@Azure:~/ansible_pj$ python2.7
Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     import pexpect
...     print(True)
... except ImportError:
...     print(False)
...
True
>>>

OK 問題ない。

kono@Azure:~/ansible_pj$ python
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     import pexpect
...     print(True)
... except ImportError:
...     print(False)
...
False
>>>

pexpect がインポート出来ていない。
もう一度、確認。

kono@Azure:~/ansible_pj$ ansible --version
ansible 2.5.2
  config file = None
  configured module search path = [u'/home/kono/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/ansible/local/lib/python2.7/site-packages/ansible
  executable location = /opt/ansible/bin/ansible
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
kono@Azure:~/ansible_pj$

executable location に実行ファイルがありそうだ。

kono@Azure:~/ansible_pj$ ls -l /opt/ansible/bin/ | grep python
...
lrwxrwxrwx  1 root root       7 Jun 15 19:04 python -> python2
-rwxr-xr-x 11 root root 3492656 Jun 15 19:04 python2
lrwxrwxrwx  1 root root       7 Jun 15 19:04 python2.7 -> python2
...
kono@Azure:~/ansible_pj$

おお。 python2 に全て集約されている!!
pip 系はどうだろう?

kono@Azure:~/ansible_pj$ ls -l /opt/ansible/bin/ | grep pip
-rwxr-xr-x 11 root root     224 Jun 15 19:04 pip
-rwxr-xr-x 11 root root     224 Jun 15 19:04 pip2
-rwxr-xr-x 11 root root     224 Jun 15 19:04 pip2.7
kono@Azure:~/ansible_pj$

pip 系はそのままなんだろうか。。。それぞれのバージョンを見てみよう。

kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip -V
pip 10.0.1 from /opt/ansible/local/lib/python2.7/site-packages/pip (python 2.7)
kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip2 -V
pip 10.0.1 from /opt/ansible/local/lib/python2.7/site-packages/pip (python 2.7)
kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip2.7 -V
pip 10.0.1 from /opt/ansible/local/lib/python2.7/site-packages/pip (python 2.7)
kono@Azure:~/ansible_pj$

全部同じ出力である。同じ所を参照しているので問題なさそう。
pip に関して問題ないことがわかったので、 python2import pexpect テストを実施してみよう

kono@Azure:~/ansible_pj$ /opt/ansible/bin/python2
Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...   import pexpect
...   print True
... except ImportError:
...   print False
...
False
>>>

なんだと。。。 python2 の起動 path が違うから別物を使っていたということか???

kono@Azure:~/ansible_pj$ whereis python2.7
python2: /usr/bin/python2.7 /usr/bin/python2 /usr/bin/python2.7-config /usr/lib/python2.7 /etc/python2.7 /usr/local/lib/python2.7 /usr/include/python2.7 /opt/ansible/bin/python2.7 /opt/ansible/bin/python2 /usr/share/man/man1/python2.1.gz
kono@Azure:~/ansible_pj$

なるほど。PATH は通っているけれど、$ python2.7 で呼び出した時は、 /usr/bin/python2.7 が呼び出され、$ /opt/ansible/bin/python2.7 では、そのとおりの pathPython2.7 が起動したわけですね。
結果、同じように import pexpect をしているけど、挙動が違うと。

では、 Ansible が参照する pippexpect をインストールしよう。

kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip install pexpect --user virtualenv
Can not perform a '--user' install. User site-packages are not visible in this virtualenv.
kono@Azure:~/ansible_pj$

ダメなようですね。
ちなみに、本当に参照している先が違うのでしょうか。

kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip show wheel
...
Location: /opt/ansible/lib/python2.7/site-packages
...
kono@Azure:~/ansible_pj$

これと

kono@Azure:~/ansible_pj$ pip2.7 show wheel
...
Location: /usr/lib/python2.7/dist-packages
...
kono@Azure:~/ansible_pj$

同じパッケージで目に入ったのが wheel なので指定させて頂いた。
まるで使う場所が違いますね。

別のアプローチを考えよう。
Ansible が利用するデフォルトの Python を指定するとか。

kono@Azure:~/ansible_pj$ pip2.7 list | wc -l
9
kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip list | wc -l
73
kono@Azure:~/ansible_pj$

この結果を見るとアプローチを変えるのは厳しそう。
/opt/ansible/lib/python2.7/site-packages 内に無理やり入れる方法を考えてみよう。

Azure にて、 /opt/ansible/lib/python2.7/site-package に何かを入れられるとは思いがたい。。。

なにせ、Azure Cloud Shell の制限事項として、 sudo がつかえないのである。

どこか詰んだ感じがしますね。
Ansible が使うパッケージの場所

kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip show wheel | grep Location
Location: /opt/ansible/lib/python2.7/site-packages
kono@Azure:~/ansible_pj$

ロケーションのディレクトリのパーミッション及び、所有者:グループ

kono@Azure:~/ansible_pj$ ls -l /opt/ansible/lib/python2.7/ | grep site-packages
-rw-r--r--  11 root root     0 Jun 15 19:04 no-global-site-packages.txt
drwxr-xr-x 120 root root  4096 Jun 18 20:26 site-packages
kono@Azure:~/ansible_pj$

root:root でないとインストールできないことがわかります。

試しに、 /opt/ansible/bin/pip install pexpect を実行

kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip install pexpect
Collecting pexpect
  Using cached https://files.pythonhosted.org/packages/89/e6/b5a1de8b0cc4e07ca1b305a4fcc3f9806025c1b651ea302646341222f88b/pexpect-4.6.0-py2.py3-none-any.whl
Collecting ptyprocess>=0.5 (from pexpect)
  Using cached https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl
Installing collected packages: ptyprocess, pexpect
Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/opt/ansible/lib/python2.7/site-packages/ptyprocess-0.6.0.dist-info'
Consider using the `--user` option or check the permissions.

kono@Azure:~/ansible_pj$

--user をつけると

kono@Azure:~/ansible_pj$ /opt/ansible/bin/pip install pexpect --user
Can not perform a '--user' install. User site-packages are not visible in this virtualenv.
kono@Azure:~/ansible_pj$

Permission denied 権限がない。 sudo をつけると、、、

kono@Azure:~/ansible_pj$ sudo /opt/ansible/bin/pip install pexpect --user
bash: sudo: command not found
kono@Azure:~/ansible_pj$

コマンドがない。 orz

ということで、 expect モジュールですが、これは上手く出来た時に追記したいと思います。
非常に残念ですが、しょうが無いですね。

raw

raw - Executes a low-down and dirty SSH command — Ansible Documentation

純粋に ssh コマンドを実行して何かを手で実施する時と同じことを実施してくれるモジュール。

playbook

kono@Azure:~/ansible_pj$ cat raw.yaml
# Command modules test
- hosts: any
  user: takashi
  #become: true
  tasks:
    - name: tail syslog
      raw: tail /var/log/syslog
kono@Azure:~/ansible_pj$

ansible 実行

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts raw.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [tail syslog] **************************************************************************************************************************************************************************************************************************
changed: [40.74.67.49]

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.67.49                : ok=2    changed=1    unreachable=0    failed=0

kono@Azure:~/ansible_pj$

これではよくわからないので、 -vvv オプションをつけてみましょう。

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts raw.yaml -vvv
...json
TASK [tail syslog] **************************************************************************************************************************************************************************************************************************
task path: /home/kono/ansible_pj/raw.yaml:6
<40.74.67.49> ESTABLISH SSH CONNECTION FOR USER: takashi
<40.74.67.49> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'IdentityFile="/home/kono/.ssh/id_rsa"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=takashi -o ConnectTimeout=10 -o ControlPath=/home/kono/.ansible/cp/8aa58877c0 -tt 40.74.67.49 'tail /var/log/syslog'
<40.74.67.49> (0, "Jun 24 01:36:11 test1 kernel: [   62.830553] hv_balloon: Data Size is 8\r\nJun 24 02:05:11 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10\r\nJun 24 02:05:14 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/uname -a removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:16 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -h removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:19 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -i removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:21 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/usr/bin/free removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:24 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/sbin/ip a removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:26 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/netstat -nlt removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:29 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=which python3 removes=None creates=None chdir=None stdin=None\r\nJun 24 02:12:17 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10\r\n", 'Shared connection to 40.74.67.49 closed.\r\n')
changed: [40.74.67.49] => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 40.74.67.49 closed.\r\n",
    "stdout": "Jun 24 01:36:11 test1 kernel: [   62.830553] hv_balloon: Data Size is 8\r\nJun 24 02:05:11 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10\r\nJun 24 02:05:14 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/uname -a removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:16 test1 ansible-command: Invoked with warn=True executable=None_uses_shell=False _raw_params=/bin/df -h removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:19 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -i removes=None creates=Nonechdir=None stdin=None\r\nJun 24 02:05:21 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/usr/bin/free removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:24 test1 ansible-command:Invoked with warn=True executable=None _uses_shell=False _raw_params=/sbin/ip a removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:26 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/netstat -nlt removes=None creates=None chdir=None stdin=None\r\nJun 24 02:05:29 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=which python3 removes=None creates=None chdir=None stdin=None\r\nJun 24 02:12:17 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10\r\n",
    "stdout_lines": [
        "Jun 24 01:36:11 test1 kernel: [   62.830553] hv_balloon: Data Size is 8",
        "Jun 24 02:05:11 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10",
        "Jun 24 02:05:14 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/uname -a removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:05:16 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -h removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:05:19 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -i removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:05:21 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/usr/bin/free removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:05:24 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/sbin/ip a removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:05:26 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/netstat -nlt removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:05:29 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=which python3 removes=None creates=None chdir=None stdin=None",
        "Jun 24 02:12:17 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10"
    ]
}
META: ran handlers
META: ran handlers

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.67.49                : ok=2    changed=1    unreachable=0    failed=0

kono@Azure:~/ansible_pj$

コマンドでは以下と同じことを行っています。

kono@Azure:~/ansible_pj$ ssh takashi@40.74.67.49 tail /var/log/syslog
Jun 24 02:05:14 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/uname -a removes=None creates=None chdir=None stdin=None
Jun 24 02:05:16 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -h removes=None creates=None chdir=None stdin=None
Jun 24 02:05:19 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/df -i removes=None creates=None chdir=None stdin=None
Jun 24 02:05:21 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/usr/bin/free removes=None creates=None chdir=None stdin=None
Jun 24 02:05:24 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/sbin/ip a removes=None creates=None chdir=None stdin=None
Jun 24 02:05:26 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=/bin/netstat -nlt removes=None creates=None chdir=None stdin=None
Jun 24 02:05:29 test1 ansible-command: Invoked with warn=True executable=None _uses_shell=False _raw_params=which python3 removes=None creates=None chdir=None stdin=None
Jun 24 02:12:17 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10
Jun 24 02:13:10 test1 ansible-setup: Invoked with filter=* gather_subset=['all'] fact_path=/etc/ansible/facts.d gather_timeout=10
Jun 24 02:17:01 test1 CRON[3938]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)
kono@Azure:~/ansible_pj$

ソースコードは以下。ドキュメントしか無いことがわかります。

ansible/raw.py at devel · ansible/ansible · GitHub

ssh したあとのコマンドをしっかり記述することで、何でも出来そうですね。その反面べき等性は自分たちで実装する必要はあるのだと思います。

script

script - Runs a local script on a remote node after transferring it — Ansible Documentation

気になった特徴は以下の内容です。

This module does not require python on the remote system, much like the raw module.

つまり、 python の入っていないようなネットワークデバイスでもスクリプトが使えるということなんだと思います。

とはいえ、サンプルに Shell Script しか書かれていないので、心配になったため、Pythonスクリプトも準備する。

こんな感じで playbook を書いてみました。

kono@Azure:~/ansible_pj$ cat script.yaml
# Command modules test
- hosts: any
  user: takashi
  #become: true
  tasks:
    - name: execute local shell script
      script: ./scripts/get_cwd.sh
      register: result
    - debug:
        var: result
    - name: execute local python script
      script: ./scripts/get_cwd.py
      register: result
    - debug:
        var: result
kono@Azure:~/ansible_pj$

それぞれのスクリプトは以下の通り。

kono@Azure:~/ansible_pj$ cat ./scripts/get_cwd.sh
echo `pwd`
kono@Azure:~/ansible_pj$ cat ./scripts/get_cwd.py
#!/usr/bin/python3
# coding: utf-8
import os
print(os.getcwd())


kono@Azure:~/ansible_pj$

動かしてみましょう。

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts script.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [execute local shell script] ***********************************************************************************************************************************************************************************************************
changed: [40.74.67.49]

TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [40.74.67.49] => {
    "result": {
        "changed": true,
        "failed": false,
        "rc": 0,
        "stderr": "Shared connection to 40.74.67.49 closed.\r\n",
        "stdout": "/home/takashi\r\n",
        "stdout_lines": [
            "/home/takashi"
        ]
    }
}

TASK [execute local python script] **********************************************************************************************************************************************************************************************************
changed: [40.74.67.49]

TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [40.74.67.49] => {
    "result": {
        "changed": true,
        "failed": false,
        "rc": 0,
        "stderr": "Shared connection to 40.74.67.49 closed.\r\n",
        "stdout": "/home/takashi\r\n",
        "stdout_lines": [
            "/home/takashi"
        ]
    }
}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.67.49                : ok=5    changed=2    unreachable=0    failed=0

kono@Azure:~/ansible_pj$

どちらでも実行できましたね。
しかも、ローカルホストで作成しておいた Script ファイルをリモートで実行してくれている。
raw に似ていますね。

どうやって実現しているのか謎ですが、以外に便利な反面、冪等性の担保が難しそうです。
show run 取るくらいならこんな感じでいいかもしれませんけどね。

ソースコードは以下のリンクです。こちらも raw 同様、何もしていませんね。。。

ansible/script.py at devel · ansible/ansible · GitHub

shell

shell - Execute commands in nodes. — Ansible Documentation

次は、 shell モジュールですね。
ドキュメントの中にもあるように、 command モジュールライクらしいです。

The shell module takes the command name followed by a list of space-delimited arguments. It is almost exactly like the command module but runs the command through a shell (/bin/sh) on the remote node.

このドキュメントだけではちょっと良くわからなかったのでググってみた結果

shell モジュールは、 &, |, > 等のシェルの機能が使えるが、 command モジュールでは使えないというのが最も大きな差であるようだ。

ということで、 Playbook を書いてみた。

kono@Azure:~/ansible_pj$ cat shell.yaml
# Command modules test
- hosts: any
  user: takashi
  #become: true
  tasks:
    - name: Execute the command in remote shell.
      shell: ls /var/log/ | wc -l
      register: result
    - debug:
        var: result
kono@Azure:~/ansible_pj$

パイプでつなげているが、使えるだろうか。

kono@Azure:~/ansible_pj$ ansible-playbook -i hosts shell.yaml

PLAY [any] **********************************************************************************************************************************************************************************************************************************

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

TASK [Execute the command in remote shell.] *************************************************************************************************************************************************************************************************
changed: [40.74.67.49]

TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [40.74.67.49] => {
    "result": {
        "changed": true,
        "cmd": "ls /var/log/ | wc -l",
        "delta": "0:00:00.004307",
        "end": "2018-06-24 03:57:31.229757",
        "failed": false,
        "rc": 0,
        "start": "2018-06-24 03:57:31.225450",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "29",
        "stdout_lines": [
            "29"
        ]
    }
}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
40.74.67.49                : ok=3    changed=1    unreachable=0    failed=0

kono@Azure:~/ansible_pj$ 

きちんと出来ましたね。さすがです。 raw モジュールとも似ているのでしょうかね。。。

ansible/shell.py at devel · ansible/ansible · GitHub

ソースコードを見ても、ドキュメントとサンプルしか無いような気が。。。

telnet

telnet - Executes a low-down and dirty telnet command — Ansible Documentation

rawssh ではなく telnet で実行するという理解で合っているはず。

1時間くらい ubuntutelnet 出来るように調べてみたけど、あまり進展がなかったので、このモジュールについては今後機会があれば追記するようにします。

最後に

今回はコマンドモジュールにフォーカスして使いかたを自分用の備忘録として書き留めました。
ここまで読んで力になれ無かったとしても。。。ごめんなさい。
というか、未来の自分にごめんなさいをしたい。。。意外と expect を一番試してみたかったんですよね。そのため、結構試行錯誤したのですが、残念でした。

今度時間と環境を作ってリトライしようと思います。

以上です。