はじめに
Ansible Vaultはとても遅い。
インベントリにたくさんのホストが書かれていて、かつhost_vars以下にそのたくさんのホストの分のAnsible Vaultで暗号化されたファイルが置かれている状況だとそれが良く分かる。
実際にどれだけ遅いか見てみよう。
以下、VirtualBox上に立てた1CPU, 1GBの仮想マシンのCentOS 7.1にAnsible 1.9.2をインストールして確認している。grep aes /proc/cpuinfo
で見る限りAES-NIは利用できるが、どれくらい意義があるのかは調べていない。
host_varsディレクトリにhost00.ymlからhost99.ymlまでの計100個、Ansible Vaultで暗号化されたファイルを作る。
$ mkdir host_vars
$ ansible-vault create host_vars/host00.yml # 中身を「key1: val1」にする
$ for n in {01..99}; do cp host_vars/host00.yml host_vars/host${n}.yml; done
$ vi vault # ansible-vault実行時に設定したパスワードを記述する
インベントリファイルを以下の内容で作成する。つまりhost00からhost99まで100個のホストを記述したことになる。
[test]
host[00:99]
playbookを以下の内容で作成する。
- hosts: host00
connection: local
gather_facts: no
tasks:
- debug: var=key1
見ての通り、対象としているホストはインベントリファイルに書いた全部ではなくhost00だけである。
なお、debugモジュールはlocalhostで実行されるので「connection: local」はわかりやすいように書いているだけで特に意味はない。何にせよインベントリに記述したホストは実際に存在していなくても良い。
さて、実行してみよう。
$ time ansible-playbook -i hosts --vault-password-file vault read1.yml
PLAY [host00] *****************************************************************
TASK: [debug var=key1] ********************************************************
ok: [host00] => {
"var": {
"key1": "val1"
}
}
PLAY RECAP ********************************************************************
host00 : ok=1 changed=0 unreachable=0 failed=0
real 1m30.620s
user 1m30.475s
sys 0m0.052s
たったこれだけで90秒も掛かるのである。
インベントリを以下のようにhost00だけにしたとすると、1秒ちょっとで終了する。
[test]
host00
$ time ansible-playbook -i hosts.1 --vault-password-file vault read1.yml
real 0m1.314s
user 0m1.150s
sys 0m0.030s
つまり対象のホストが1つしかなかったとしても、インベントリにあるだけhost_vars以下のファイルを全部開く、という動きをするようだ。
それではどうすれば良いか
というわけでまず、1つのホスト分しかAnsible Vaultから取得しないで良い場合にどのように高速化するか。
playbookごとにインベントリファイルを別々にして、インベントリファイルに載せるホスト数を減らすのも1つの手かもしれないが、その後の手間の点でちょっといまいちである。
ここは頑張ってplaybook内で1つのホスト分しか取得しないようにしてみる。
まず、先に作ったhost_varsをリネームして、Ansible Vaultで作ったファイルはhost_varsに置かないようにする。
$ mv host_vars/ _host_vars
lookupプラグインの1つ、pipeを使ってansible-vault viewを実行し、必要なホストの分だけ取り出してset_factモジュール経由で渡すようにする。
- hosts: host00
connection: local
gather_facts: no
tasks:
- set_fact: "key1={{ lookup('pipe', 'ansible-vault view _host_vars/' + inventory_hostname + '.yml --vault-password-file vault | grep "key1: " | sed "s/key1: //"') }}"
- debug: var=key1
このplaybookを実行してみる。
$ time ansible-playbook -i hosts read1a.yml
PLAY [host00] *****************************************************************
TASK: [set_fact key1={{ lookup('pipe', 'ansible-vault view _host_vars/' + inventory_hostname + '.yml --vault-password-file vault | grep "key1: " | sed "s/key1: //"') }}] ***
ok: [host00]
TASK: [debug var=key1] ********************************************************
ok: [host00] => {
"var": {
"key1": "val1"
}
}
PLAY RECAP ********************************************************************
host00 : ok=2 changed=0 unreachable=0 failed=0
real 0m0.819s
user 0m0.732s
sys 0m0.066s
想定通り速くなった。
ちなみに、ansible_ssh_passに値をセットすれば期待通りにSSHのパスワードログインもできるようになる。
ただし、gather_factsが動くよりも先にセットしないとならないため、playbookは以下のような感じになるだろう。
- hosts: host00
gather_facts: no
tasks:
- set_fact: "ansible_ssh_pass={{ lookup('pipe', 'ansible-vault view _host_vars/' + inventory_hostname + '.yml --vault-password-file vault | grep "ansible_ssh_pass: " | sed "s/ansible_ssh_pass: //"') }}"
- hosts: host00
gather_facts: yes
tasks:
- file: dest=/tmp/1 state=touch
そもそもAnsible Vaultなど要らないのでは
では、1つのホスト分だけでなく100個のホスト分全部必要な場合はどうだろうか。
- hosts: test
gather_facts: no
serial: 1
tasks:
- set_fact: "key1={{ lookup('pipe', 'ansible-vault view _host_vars/' + inventory_hostname + '.yml --vault-password-file vault | grep "key1: " | sed "s/key1: //"') }}"
- hosts: test
gather_facts: no
tasks:
- debug: var=key1
「serial: 1」にしているのは増やしても速くならなかった上に増やし過ぎると読み込みに失敗してエラーになったからである。
実行してみる。
$ time ansible-playbook -i hosts read100a.yml -f 100
real 1m5.342s
user 0m58.082s
sys 0m4.108s
当然時間が掛かる。
しかしどうせコマンド叩いて値を取得してるのだったらAnsible Vaultなんて使わずに別のもっと速そうなの使えば良いのではなかろうか、というわけでopenssl encを使ってみる。一応256bitsのAESを使ってAnsible Vaultと同条件にする。
$ mkdir openssl
$ echo "key1: val1" | openssl enc -aes256 -e -kfile vault -out openssl/host00
$ for n in {01..99}; do cp openssl/host00 openssl/host${n}; done
playbookの変更はset_factの箇所だけ。
- hosts: test
gather_facts: no
serial: 1
tasks:
- set_fact: "key1={{ lookup('pipe', 'openssl enc -aes256 -d -kfile vault -in openssl/' + inventory_hostname + ' | grep "key1: " | sed "s/key1: //"') }}"
- hosts: test
gather_facts: no
tasks:
- debug: var=key1
実行してみる。
$ time ansible-playbook -i hosts read100b.yml -f 100
real 0m5.489s
user 0m1.956s
sys 0m1.427s
十分である。インベントリのホスト数が多い場合はAnsible Vaultなんて使わずにもうこれで良いのではないだろうか。
まあ安全性を高める目的でopenssl encの復号パスワードの取得のためにAnsible Vaultを使っても良いかもしれない。それだとAnsible Vaultが使われるのは1回だけなのでパフォーマンス上の影響はほとんどなく、playbook実行時にパスワードを入力して実行させられる。
この場合、openssl encでは-kfileオプションの代わりに-kオプションなどでパスワードをセットすることになる。
playbookで他のホストの変数を取得したい場合はhostvarsが使用できる。
debug: var=hostvars['host99'].key1
などとすれば良い。
ちなみにhost_vars以下にAnsible Vaultで作ったファイルを戻して以下のread100.yml(普通に100個のホスト分読むもの)を実行したら98秒だった。
- hosts: test
connection: local
gather_facts: no
tasks:
- debug: var=key1
参考:他のバージョンのAnsibleでAnsible Vaultを使う
開発版を使ってみる
開発版だとこんなことをしなくてもAnsible Vaultが普通に使えたりはしないだろうか。
$ sudo yum install git gcc python-devel python-pip
$ sudo pip install paramiko PyYAML Jinja2 httplib2 six
$ git clone git://github.com/ansible/ansible.git --recursive
$ cd ansible/
$ source hacking/env-setup
$ ansible-playbook --version
ansible-playbook 2.0.0 (devel 6154ed1dda) last updated 2015/08/30 20:17:52 (GMT +900)
lib/ansible/modules/core: (detached HEAD 66288d48a2) last updated 2015/08/30 20:18:02 (GMT +900)
lib/ansible/modules/extras: (detached HEAD 8d8f1c510d) last updated 2015/08/30 20:18:16 (GMT +900)
config file =
configured module search path = None
最初に作ったホスト1つだけ取得するplaybook(read1.yml)を実行してみる。
$ time ansible-playbook -i hosts --vault-password-file vault read1.yml
real 0m47.208s
user 0m46.656s
sys 0m0.180s
実行時間が半分くらいになったが、まだ遅い。
しかしpipでcryptographyをインストールしておくともっと速くなるらしい。
$ sudo yum install libffi-devel openssl-devel
$ sudo pip install cryptography
read1.ymlを実行。
$ time ansible-playbook -i hosts --vault-password-file vault read1.yml
real 0m7.779s
user 0m7.022s
sys 0m0.156s
まあ悪くはない。
ホスト100個とも取得するplaybook(read100.yml)はどうだろうか。
$ time ansible-playbook -i hosts --vault-password-file vault read100.yml -f 10
real 0m31.422s
user 0m27.608s
sys 0m0.660s
これ、時間が掛かっているのはAnsible Vaultが原因ではなくてdebugモジュールの出力に時間が掛かるせい。
「-f 100」にしたら出力できなくてエラーになったし。
debugモジュールの箇所をコメントアウトすると7秒程度になる。
ついでにpipeプラグインもエラーになって動かなかった。
Ansible 1.6.10
実はインベントリに定義したホスト全部読むようになったのは1.7からなのだ。
$ sudo yum install gcc python-devel python-pip
$ sudo pip install ansible==1.6.10
read1.ymlは当然速い。
$ time ansible-playbook -i hosts --vault-password-file vault read1.yml
real 0m1.008s
user 0m0.669s
sys 0m0.091s
read100.ymlは時間が掛かるが1.9.2よりは速い。
$ time ansible-playbook -i hosts --vault-password-file vault read100.yml -f 100
real 0m48.760s
user 0m46.572s
sys 0m0.753s