Open17

Raspberry Piが増えてきたのでAnsibleを使ってみる

Ansibleを使ってみたくなったので作業記録を残します。

asa-taka@tailmoon pi-ansible % sw_vers 
ProductName:    macOS
ProductVersion: 11.3.1
BuildVersion:   20E241
asa-taka@tailmoon pi-ansible % ansible --version
ansible [core 2.11.0] 
  config file = /Users/asa-taka/src/local/pi-ansible/ansible.cfg
  configured module search path = ['/Users/asa-taka/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/4.0.0/libexec/lib/python3.9/site-packages/ansible
  ansible collection location = /Users/asa-taka/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.5 (default, May  4 2021, 03:36:27) [Clang 12.0.0 (clang-1200.0.32.29)]
  jinja version = 3.0.1
  libyaml = True

以下のまとめにはinventory(対象ホスト)をファイルで定義している場合とそうでない場合が混じっているので注意。完全にアドホックに実行したい場合はinventoryをオプションで指定し、その中のallという指定の仕方をする(allでなくても可)。

ansible --inventory host1,host2 all -m ping

注意点としてホストが1台の場合は --inventory host1, のように末尾に , をつけなければinventoryの設定ファイルのパスだと解釈されてしまう。

ansible.cfgで設定したhostsでinventoryを設定している場合は

ansible host1 -m ping

で実行できる。

とりあえずAnsibleをインストールして使ってみる。ここではplaybookは書かず、ちょっとした用途で前準備なく実行したい場合に向けて、Ansibleを単なるssh runnerとして使ってみたい。コマンド一斉実行、同時再起動などが行えれば十分に役に立つと思う。

Homebrew でインストールする。

asa-taka@tailmoon ~ % brew install ansible
asa-taka@tailmoon ~ % ansible --version
ansible [core 2.11.0]
  config file = None
  configured module search path = ['/Users/asa-taka/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/4.0.0/libexec/lib/python3.9/site-packages/ansible
  ansible collection location = /Users/asa-taka/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.5 (default, May  4 2021, 03:36:27) [Clang 12.0.0 (clang-1200.0.32.29)]
  jinja version = 3.0.1
  libyaml = True

とりあえず動作確認をする。--inventory でホストのリストを渡し、全てに対して応答確認する。

asa-taka@tailmoon ~ % ansible --inventory 'pi4a,pi4b,pi4c,pi4d' all -m ping
pi4a | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
pi4b | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
...

pi4a~/.ssh/configpi4a.local にパスフレーズ付き鍵でログインするように設定してあり、パスフレーズは事前に ssh-add してある。つまり事前に ssh pi4api4a.local にログインできるようにしてある状態。

単一ホストを指定する場合は --inventory pi4a, のように , を末尾につける。--inventory はファイル指定にも対応しているので , が無いとファイルを探そうとして失敗する。

複数台再起動やシャットダウンをさせてみる。

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/reboot_module.html を利用してみたかったが、playbook を書かずに実行する方法がわからず、今回は意地でもplaybookを使いたくないという謎のモチベーションがあったので諦めた。

代わりに https://www.jeffgeerling.com/blog/2018/reboot-and-wait-reboot-complete-ansible-playbook で紹介されている方法を利用して再起動を実施した。

ansible -i 'pi4a,pi4b,pi4c,pi4d' all --become --background 1 --poll 0 -m shell -a "sleep 3 && reboot"

ansible コマンドを詳しく見る。

ansible [-h] [--version] [-v] [-b] [--become-method BECOME_METHOD]
    [--become-user  BECOME_USER]  [-K] [-i INVENTORY] [--list-hosts] [-l SUBSET] [-P POLL_INTERVAL] [-B SECONDS] [-o] [-t TREE] [-k]
    [--private-key  PRIVATE_KEY_FILE]  [-u  REMOTE_USER]  [-c   CONNECTION]   [-T   TIMEOUT]   [--ssh-common-args   SSH_COMMON_ARGS]
    [--sftp-extra-args  SFTP_EXTRA_ARGS]  [--scp-extra-args  SCP_EXTRA_ARGS] [--ssh-extra-args SSH_EXTRA_ARGS] [-C] [--syntax-check]
    [-D] [-e EXTRA_VARS] [--vault-id VAULT_IDS] [--ask-vault-password | --vault-password-file VAULT_PASSWORD_FILES] [-f  FORKS]
    [-M MODULE_PATH] [--playbook-dir BASEDIR] [--task-timeout TASK_TIMEOUT] [-a MODULE_ARGS] [-m MODULE_NAME]
    pattern

で、pattern には all などが入るので、基本形はこれになる。

ansible --inventory 'pi4a,pi4b' all 

ただこれだと ERROR! No argument passed to command module というメッセージが出力される。デフォルトだと上のコマンドは -m command を指定したのと同じになり、引数が足りないと言われている。なので適当なコマンドを渡してやると正しくそれぞれのホストで実行される。

asa-taka@tailmoon ~ % ansible -i 'pi4a,pi4b' all -a hostname
pi4a | CHANGED | rc=0 >>
pi4a
pi4b | CHANGED | rc=0 >>
pi4b

変数も展開してくれる。

asa-taka@tailmoon ~ % ansible -i 'pi4a,pi4b' all -a 'echo $PWD'
pi4a | CHANGED | rc=0 >>
/home/pi
pi4b | CHANGED | rc=0 >>
/home/pi

ただし && などはそのままただの引数扱いになる

asa-taka@tailmoon ~ % ansible -i 'pi4a,pi4b' all -a 'echo $PWD && echo $PWD'
pi4a | CHANGED | rc=0 >>
/home/pi && echo /home/pi
pi4b | CHANGED | rc=0 >>
/home/pi && echo /home/pi

なのでその場合は -m shell を利用する。

asa-taka@tailmoon ~ % ansible -i 'pi4a,pi4b' all -m shell -a 'echo $PWD && echo $PWD'
pi4b | CHANGED | rc=0 >>
/home/pi
/home/pi
pi4a | CHANGED | rc=0 >>
/home/pi
/home/pi

-m command を利用して再起動することもできるが、途中で接続が切れてコマンドは失敗扱いになる。

asa-taka@tailmoon ~ % ansible -i 'pi4a,pi4b' all -a 'sudo reboot'
pi4a | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: Could not resolve host \"pi4a\"",
    "unreachable": true
}
pi4b | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: Could not resolve host \"pi4b\"",
    "unreachable": true
}

丁寧に行いたい場合は、先に示した方法を利用した方がいい。

asa-taka@tailmoon ~ % ansible -i 'pi4a,' all --background 1 --poll 0 -m shell -a "sleep 3 && sudo reboot"
pi4a | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_job_id": "314420265886.5008",
    "changed": true,
    "finished": 0,
    "results_file": "/home/pi/.ansible_async/314420265886.5008",
    "started": 1
}

ただやはりオプションが多く面倒なので -m command -a 'sudo reboot' で済ませてしまっている。他のスクリプトに組み込まない限りはこちらでもそう問題はないと思う。

DEPRECATION WARNINGが邪魔な場合は、ユーザホームの設定ファイルで無効化しておくことができる。

cat << EOF >> ~/.ansible.cfg
[defaults]
deprecation_warnings=False
EOF

https://docs.ansible.com/ansible/latest/user_guide/intro_adhoc.html#intro-adhoc に書かれている方法でrebootモジュールも動かせそうなのでやってみた。

asa-taka@tailmoon pi-ansible % ansible pi4b.local -m ansible.builtin.reboot --become
pi4b.local | CHANGED => {
    "changed": true,
    "elapsed": 34,
    "rebooted": true
}

綺麗に成功した。ただここまでして、やっていることは ssh pi4b.local sudo reboot と同じ。ホストが多くなれば多少恩恵はありそうだけれど。ただ、再起動の完了を同期的に実行できるのは他のコマンドと組み合わせるときにメリットになるかもしれない。例えば

ansible pi4b.local -m ansible.builtin.reboot --become
ssh pi4b.local <command after reboot>

みたいなスクリプトを組む時など。実験してみたところsshが問題なく動くタイミングでansibleコマンドが終了することが確認できた。

[DEPRECATION WARNING]: Distribution debian 10.9 on host pi4d.local should use /usr/bin/python3, but is using /usr/bin/python for backward compatibility with prior Ansible releases. A future Ansible release will default to using the discovered platform python
for this host. See https://docs.ansible.com/ansible/2.11/reference_appendices/interpreter_discovery.html for more information. This feature will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

これをどうにかしたい。

  • リモートホスト上で python3 を使うべきだけど広報互換性のために python を使っている
  • 今後のバージョンではこの挙動はなくなり自動で選択されるようになる

という意味かな。そこまで使い込んでいないので多少挙動が変わっても問題なさそうと判断して warning を disable することにする。

ansible の設定ファイルの場所は man ansible に書かれている。

       /etc/ansible/ansible.cfg -- Config file, used if present
       ~/.ansible.cfg -- User config file, overrides the default config if present
       ./ansible.cfg -- Local config file (in current working directory) assumed to be 'project specific' and overrides
       the rest if present.

今回はプロジェクトルートの ./ansible.cfg に書くことにした。セクションが未指定だと怒られたので https://docs.ansible.com/ansible/latest/reference_appendices/config.html で確認。deprecation_warningsdefaults セクションらしい。

cat << EOF > ./ansible.cfg
[defaults]
deprecation_warnings=False
EOF

無事WARNINGが消えた。

Ansibleの設定について調べる。

ansible の設定ファイルの場所は man ansible に書かれている。

       /etc/ansible/ansible.cfg -- Config file, used if present
       ~/.ansible.cfg -- User config file, overrides the default config if present
       ./ansible.cfg -- Local config file (in current working directory) assumed to be 'project specific' and overrides
       the rest if present.

エイリアスを指定してあるプロジェクトの設定をローカル上の場所を選ばず実行できるようにする。例えばこんな感じで設定を行なったディレクトリを用意する。

asa-taka@tailmoon ~ % tree ~/src/local/pi-ansible
/Users/asa-taka/src/local/pi-ansible
├── ansible.cfg
├── hosts
└── playbook
    └── reboot.yaml
asa-taka@tailmoon ~ % cat ~/src/local/pi-ansible/ansible.cfg 
[defaults]
inventory=./hosts
deprecation_warnings=False

inventory を相対パスにしているのがひとつの検証どころ。これに対して環境変数で ANSIBLE_CONFIG で設定ファイルの場所を指定した ansible のエイリアスを指定して実行してみる。

alias pi-ansible="ANSIBLE_CONFIG=~/src/local/pi-ansible/ansible.cfg ansible"

関係ないディレクトリから実行しても問題なく動いた。

asa-taka@tailmoon ~ % pi-ansible kubemaster -m ping
pi4a.local | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

ただしホストグループの補完は効かなかったのでどうにかできるならしてみたい。一応ホスト一覧は

pi-ansible --list-hosts all

で表示できたが、グループ一覧の出し方はわからなかった。

ログインするとコメントできます