zaki work log

作業ログやら生活ログやらなんやら

Ansibleのアドホックコマンドで複数ホストに対してコマンドをまとめて実行やファイル転送をワンショット実行

最近チームで障害調査の割合が増えだしたのでざっとまとめてみた。

ansibleコマンドを使って複数ホストに対して同じ処理を同時に行う。
メンテや障害調査で何十台もあるホストに同じコマンド実行したりログ収集したり。
普段はansible-playbookで作成したplaybookの内容を処理させてるけど、ワンショットでちょっとした操作をしたい場合に使う。

コマンドの基本は以下の通り。
ansible all -i /path/to/inventory [-b] -m <module name> -a <module arguments>

  • allはinventoryファイルに記述してるグループ名。allだと全ノードで、その他はinventoryの内容に拠る
  • -i inventoryはinventoryファイルの指定。どのホストにアクセスするかを記述している
  • -bは付けるとroot権限での実行
  • -m <module name>は使うモジュール名。省略時はcommandになる。だいたいshellfetchcopyを使うことになる気がする
  • -a <module argument>はモジュールに対する引数。普段playbookにyamlで書く内容をここに書き足す。shellならリモートで実行するコマンド名とか

そのほかはansible --help見てね。

Ansibleはすでに使える状態とする。
(Ansible使って構築したシステムが対象で、inventoryファイルも見れる状態、みたいな)

環境 (CentOS 7)

[vagrant@ansible-controller ~]$ ansible --version
ansible 2.6.14
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

※ 注意: あくまで調査のためなどのワンショット用。定型のコマンド実行がいくつもある場合は「ansibleコマンド実行を並べてシェルスクリプトにすればいいんじゃね?」ではなく、playbook書こうね

ping

まずは疎通確認。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini 
-m ping
ansible-node01 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
ansible-node02 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
ansible-controller | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

-m pingping moduleを使用する。

全台でコマンド実行

例としてdf -hを全ホストで実行。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini -a "df -h"
ansible-node02 | SUCCESS | rc=0 >>
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        40G  3.3G   37G   9% /
devtmpfs        489M     0  489M   0% /dev
tmpfs           496M     0  496M   0% /dev/shm
tmpfs           496M  6.7M  489M   2% /run
tmpfs           496M     0  496M   0% /sys/fs/cgroup
ansible         476G  261G  215G  55% /ansible
tmpfs           100M     0  100M   0% /run/user/1000

ansible-node01 | SUCCESS | rc=0 >>
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        40G  3.3G   37G   9% /
devtmpfs        489M     0  489M   0% /dev
tmpfs           496M     0  496M   0% /dev/shm
tmpfs           496M  6.7M  489M   2% /run
tmpfs           496M     0  496M   0% /sys/fs/cgroup
ansible         476G  261G  215G  55% /ansible
tmpfs           100M     0  100M   0% /run/user/1000

ansible-controller | SUCCESS | rc=0 >>
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        40G  3.4G   37G   9% /
devtmpfs        489M     0  489M   0% /dev
tmpfs           496M  160K  496M   1% /dev/shm
tmpfs           496M  6.7M  489M   2% /run
tmpfs           496M     0  496M   0% /sys/fs/cgroup
ansible         476G  261G  215G  55% /ansible
tmpfs           100M     0  100M   0% /run/user/1000

[vagrant@ansible-controller ~]$ 

-m <module name>を省略すると、commandモジュールが使われる。
モジュールへの引数(この場合df -h)は-aオプションで指定する。

shellモジュールじゃないのでdf -h > /tmp/df.logみたいなことはできない。
リダイレクトとかが必要なら-m shellを指定するか、-a "bash -c 'df -h > /tmp/df.log'"みたいに書く。

ホスト名を変数として参照する

inventoryに記述してる「Ansible実行ノードから見たホスト名」は{{ inventory_hostname }}で参照できる。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini -a "echo {{ inventory_hostname }}"
ansible-node02 | SUCCESS | rc=0 >>
ansible-node02

ansible-node01 | SUCCESS | rc=0 >>
ansible-node01

ansible-controller | SUCCESS | rc=0 >>
ansible-controller

[vagrant@ansible-controller ~]$ 

ファイルの取得(リモートからget)

fetchモジュールを使う。

ログファイルを収集するときとか。
flat=yesは付けないとフルパスで取得するので、ファイル1個持ってくるような場合は付けたほうが良いかも。

$ ansible all -i /ansible/practice/inventory.ini -m fetch -a "src=/tmp/df.log dest=/tmp/df.log flat=yes"

ちなみに↑こうすると、全ホストのファイルをローカルの/tmp/df.logに転送してしまって最後に実行したホストのファイルしか残らないので、転送後ファイル名には{{ inventory_hostname }}を使うと良い。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini -m fetch -a "src=/tmp/df.log dest=/tmp/df-{{ inventory_hostname }}.log flat=yes"
ansible-node02 | SUCCESS => {
    "changed": true, 
    "checksum": "01d36a6de187e0f927f4125396d48437ed78dd58", 
    "dest": "/tmp/df-ansible-node02.log", 
    "md5sum": "5d0209756abf2aff520810a025f6f8ee", 
    "remote_checksum": "01d36a6de187e0f927f4125396d48437ed78dd58", 
    "remote_md5sum": null
}
ansible-node01 | SUCCESS => {
    "changed": true, 
    "checksum": "01d36a6de187e0f927f4125396d48437ed78dd58", 
    "dest": "/tmp/df-ansible-node01.log", 
    "md5sum": "5d0209756abf2aff520810a025f6f8ee", 
    "remote_checksum": "01d36a6de187e0f927f4125396d48437ed78dd58", 
    "remote_md5sum": null
}
ansible-controller | SUCCESS => {
    "changed": true, 
    "checksum": "01d36a6de187e0f927f4125396d48437ed78dd58", 
    "dest": "/tmp/df-ansible-controller.log", 
    "md5sum": "5d0209756abf2aff520810a025f6f8ee", 
    "remote_checksum": "01d36a6de187e0f927f4125396d48437ed78dd58", 
    "remote_md5sum": null
}
[vagrant@ansible-controller ~]$ 
[vagrant@ansible-controller ~]$ ll /tmp/df*
-rw-rw-r--. 1 vagrant vagrant 375 Oct  8 21:59 /tmp/df-ansible-controller.log
-rw-rw-r--. 1 vagrant vagrant 375 Oct  8 21:59 /tmp/df-ansible-node01.log
-rw-rw-r--. 1 vagrant vagrant 375 Oct  8 21:59 /tmp/df-ansible-node02.log
[vagrant@ansible-controller ~]$ 

ファイルの送信(リモートへpush)

copyモジュールを使う。
調査用スクリプトを各ホストに配置して実行、みたいなケースでファイルを送信する。

[vagrant@ansible-controller ~]$ ll ~/script.sh 
-rw-rw-r--. 1 vagrant vagrant 5 Oct  8 22:02 /home/vagrant/script.sh
[vagrant@ansible-controller ~]$ 
[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini 
-m copy -a "src=~/script.sh dest=/tmp/"
ansible-node01 | SUCCESS => {
    "changed": true, 
    "checksum": "eefd5bc2c547bf82b177b6259c13f7723dc876d9", 
    "dest": "/tmp/script.sh", 
    "gid": 1000, 
    "group": "vagrant", 
    "md5sum": "c59548c3c576228486a1f0037eb16a1b", 
    "mode": "0664", 
    "owner": "vagrant", 
    "secontext": "unconfined_u:object_r:user_home_t:s0", 
    "size": 5, 
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1570572200.39-212610671017175/source", 
    "state": "file", 
    "uid": 1000
}
ansible-node02 | SUCCESS => {
    "changed": true, 
    "checksum": "eefd5bc2c547bf82b177b6259c13f7723dc876d9", 
    "dest": "/tmp/script.sh", 
    "gid": 1000, 
    "group": "vagrant", 
    "md5sum": "c59548c3c576228486a1f0037eb16a1b", 
    "mode": "0664", 
    "owner": "vagrant", 
    "secontext": "unconfined_u:object_r:user_home_t:s0", 
    "size": 5, 
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1570572200.14-254346991562955/source", 
    "state": "file", 
    "uid": 1000
}
ansible-controller | SUCCESS => {
    "changed": true, 
    "checksum": "eefd5bc2c547bf82b177b6259c13f7723dc876d9", 
    "dest": "/tmp/script.sh", 
    "gid": 1000, 
    "group": "vagrant", 
    "md5sum": "c59548c3c576228486a1f0037eb16a1b", 
    "mode": "0664", 
    "owner": "vagrant", 
    "secontext": "unconfined_u:object_r:user_home_t:s0", 
    "size": 5, 
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1570572200.23-184499488704481/source", 
    "state": "file", 
    "uid": 1000
}
[vagrant@ansible-controller ~]$ 

特権(root権限)での実行

/var/log/messagesを転送したりする場合。-bを使う。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini 
-b -a "head -1 /var/log/messages"
ansible-node01 | SUCCESS | rc=0 >>
Aug 25 03:27:01 ansible-node01 rsyslogd: [origin software="rsyslogd" swVersion="8.24.0-34.el7" x-pid="2553" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

ansible-node02 | SUCCESS | rc=0 >>
Aug 25 03:15:02 ansible-node02 rsyslogd: [origin software="rsyslogd" swVersion="8.24.0-34.el7" x-pid="2560" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

ansible-controller | SUCCESS | rc=0 >>
Aug 25 03:39:01 ansible-controller rsyslogd: [origin software="rsyslogd" swVersion="8.24.0-34.el7" x-pid="2553" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

[vagrant@ansible-controller ~]$ 

-b(--become)がないと、実行しているユーザ権限になるのでroot権限が必要な操作はエラーになる。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini 
-a "head -1 /var/log/messages"
ansible-node02 | FAILED | rc=1 >>
head: cannot open ‘/var/log/messages’ for reading: Permission deniednon-zero return code

ansible-node01 | FAILED | rc=1 >>
head: cannot open ‘/var/log/messages’ for reading: Permission deniednon-zero return code

ansible-controller | FAILED | rc=1 >>
head: cannot open ‘/var/log/messages’ for reading: Permission deniednon-zero return code

[vagrant@ansible-controller ~]$ 

shellfetchcopyなど、ほかの操作も同様。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini -m fetch -b -a "src=/var/log/messages dest=/tmp/messages-{{ inventory_hostname }}.log flat=yes"
ansible-node02 | SUCCESS => {
    "changed": true, 
    "checksum": "bcbfea8c6f6c2fbb2ab029f6768c4fce68cc956e", 
    "dest": "/tmp/messages-ansible-node02.log", 
    "md5sum": "fe0451caa2a48f85eded78602e8d26c7", 
    "remote_checksum": "bcbfea8c6f6c2fbb2ab029f6768c4fce68cc956e", 
    "remote_md5sum": null
}
ansible-node01 | SUCCESS => {
    "changed": true, 
    "checksum": "6039559ed28f2f8dcb4868b7cbcdc847ade7c411", 
    "dest": "/tmp/messages-ansible-node01.log", 
    "md5sum": "0b5fb83405738892c5cdc5361541370a", 
    "remote_checksum": "6039559ed28f2f8dcb4868b7cbcdc847ade7c411", 
    "remote_md5sum": null
}
ansible-controller | SUCCESS => {
    "changed": true, 
    "checksum": "ea9db0b38da4672e42bb16b0685730b1b3a5cd75", 
    "dest": "/tmp/messages-ansible-controller.log", 
    "md5sum": "99981f36e54dbdb954d8ecf32e1f6a11", 
    "remote_checksum": "ea9db0b38da4672e42bb16b0685730b1b3a5cd75", 
    "remote_md5sum": null
}
[vagrant@ansible-controller ~]$ 

鍵認証設定してない場合

-kを使う。

[vagrant@ansible-controller ~]$ ansible all -i /ansible/practice/inventory.ini -m ping -k
SSH password: 
ansible-node02 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
ansible-node01 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
ansible-controller | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
[vagrant@ansible-controller ~]$ 

-kを付けると公開鍵認証でなくパスワード認証になる。


アドホック コマンド」というのはAnsibleユーザ会のもくもく会に参加して初めて聞いたんだけど、helpメッセージの最後の行に出てましたわ…

[vagrant@ansible-controller ~]$ ansible --help
Usage: ansible <host-pattern> [options]

Define and run a single task 'playbook' against a set of hosts

Options:
  -a MODULE_ARGS, --args=MODULE_ARGS
                        module arguments
  --ask-vault-pass      ask for vault password
  -B SECONDS, --background=SECONDS
                        run asynchronously, failing after X seconds
                        (default=N/A)
  -C, --check           don't make any changes; instead, try to predict some
                        of the changes that may occur
  -D, --diff            when changing (small) files and templates, show the
                        differences in those files; works great with --check
  -e EXTRA_VARS, --extra-vars=EXTRA_VARS
                        set additional variables as key=value or YAML/JSON, if
                        filename prepend with @
  -f FORKS, --forks=FORKS
                        specify number of parallel processes to use
                        (default=5)
  -h, --help            show this help message and exit
  -i INVENTORY, --inventory=INVENTORY, --inventory-file=INVENTORY
                        specify inventory host path or comma separated host
                        list. --inventory-file is deprecated
  -l SUBSET, --limit=SUBSET
                        further limit selected hosts to an additional pattern
  --list-hosts          outputs a list of matching hosts; does not execute
                        anything else
  -m MODULE_NAME, --module-name=MODULE_NAME
                        module name to execute (default=command)
  -M MODULE_PATH, --module-path=MODULE_PATH
                        prepend colon-separated path(s) to module library
                        (default=[u'/home/vagrant/.ansible/plugins/modules',
                        u'/usr/share/ansible/plugins/modules'])
  -o, --one-line        condense output
  --playbook-dir=BASEDIR
                        Since this tool does not use playbooks, use this as a
                        subsitute playbook directory.This sets the relative
                        path for many features including roles/ group_vars/
                        etc.
  -P POLL_INTERVAL, --poll=POLL_INTERVAL
                        set the poll interval if using -B (default=15)
  --syntax-check        perform a syntax check on the playbook, but do not
                        execute it
  -t TREE, --tree=TREE  log output to this directory
  --vault-id=VAULT_IDS  the vault identity to use
  --vault-password-file=VAULT_PASSWORD_FILES
                        vault password file
  -v, --verbose         verbose mode (-vvv for more, -vvvv to enable
                        connection debugging)
  --version             show program's version number and exit

  Connection Options:
    control as whom and how to connect to hosts

    -k, --ask-pass      ask for connection password
    --private-key=PRIVATE_KEY_FILE, --key-file=PRIVATE_KEY_FILE
                        use this file to authenticate the connection
    -u REMOTE_USER, --user=REMOTE_USER
                        connect as this user (default=None)
    -c CONNECTION, --connection=CONNECTION
                        connection type to use (default=smart)
    -T TIMEOUT, --timeout=TIMEOUT
                        override the connection timeout in seconds
                        (default=10)
    --ssh-common-args=SSH_COMMON_ARGS
                        specify common arguments to pass to sftp/scp/ssh (e.g.
                        ProxyCommand)
    --sftp-extra-args=SFTP_EXTRA_ARGS
                        specify extra arguments to pass to sftp only (e.g. -f,
                        -l)
    --scp-extra-args=SCP_EXTRA_ARGS
                        specify extra arguments to pass to scp only (e.g. -l)
    --ssh-extra-args=SSH_EXTRA_ARGS
                        specify extra arguments to pass to ssh only (e.g. -R)

  Privilege Escalation Options:
    control how and which user you become as on target hosts

    -s, --sudo          run operations with sudo (nopasswd) (deprecated, use
                        become)
    -U SUDO_USER, --sudo-user=SUDO_USER
                        desired sudo user (default=root) (deprecated, use
                        become)
    -S, --su            run operations with su (deprecated, use become)
    -R SU_USER, --su-user=SU_USER
                        run operations with su as this user (default=None)
                        (deprecated, use become)
    -b, --become        run operations with become (does not imply password
                        prompting)
    --become-method=BECOME_METHOD
                        privilege escalation method to use (default=sudo),
                        valid choices: [ sudo | su | pbrun | pfexec | doas |
                        dzdo | ksu | runas | pmrun | enable | machinectl ]
    --become-user=BECOME_USER
                        run operations as this user (default=root)
    --ask-sudo-pass     ask for sudo password (deprecated, use become)
    --ask-su-pass       ask for su password (deprecated, use become)
    -K, --ask-become-pass
                        ask for privilege escalation password

Some modules do not make sense in Ad-Hoc (include, meta, etc)