はじめに
Ansible 1.8で追加された機能であるFact Cachingは、playbook実行時に行われるgather factsの際に集めたホストの情報を一定の期間保存し、他のホストでの別のplaybookの実行中に利用できるようにする。
私はFact Cachingという機能の概要を最初に聞いた際に、任意の値を記録できることを期待していた。
具体的には以下のような用途で利用したいと思っていた。
各ホストは適切な設定ファイルに共通のキーフレーズを指定する必要があるとする。最初のplaybook実行時にはキーフレーズをランダムに生成し、それを使用する。
後にシステムの負荷が高まったなどでホストが追加されたとする。同じplaybookを再度使用して追加したホストを構築する際、最初に生成したキーフレーズを取り出し、設定ファイルに指定する。
ところが最初に書いたように、Fact Cachingに保存されるのはホストの情報だけであった。”Fact” Cachingなので当たり前と言えば当たり前だ。
そこを何とかしてやろうとソースを読んだ結果、Fact Cachingに任意の値を書き込むことができたので以下に記述する。
事前準備
CentOSだと2014-12-12時点でepelのAnsibleはまだ1.7.2なのでepel-testingなどから1.8以上をインストールする。
# yum install epel-release
# yum install --enablerepo=epel-testing ansible
とりあえずここではFact Cachingの保存先にredisを使用することにする。
CentOS 7ではAnsibleの実行ホストにredisとpython-redisをインストールする。どちらもepelにある。
インストール後、redisを起動する。
# yum install redis python-redis
# systemctl start redis
CentOS 6ではpython-redisは古すぎるため、epelにあるものはFact Cachingに使用できない。pipでインストールするのが良いだろう。
# yum install redis python-pip
# pip install redis
# service redis start
ansible.cfgには以下を設定している。
[defaults]
fact_caching = redis
fact_caching_timeout = 315360000 # 10年くらい
Fact Cachingにはどんな場合に値が書き込まれるのか
ソースより、おおよそ以下のようなことがわかった。
読んだソースは主に libansibleplaybook__init__.py である。
- Ansibleが持つcache(playbook実行時の値の保管場所)にはsetup cacheとvars cacheという2つがある
- ただし、利用者がアクセスできるのはsetup cacheとvars cacheを混ぜた後なので、値を利用するという意味では特に区別する必要はない
- Fact Cachingはsetup cacheの保存に関する機能であり、vars cacheには影響しない。vars cacheは設定によらずメモリ上に保存され、playbook実行終了とともに消滅する
- モジュールが返した実行結果に”ansible_facts”というキーが含まれていた場合、”ansible_facts”の値がcacheに書き込まれる
- ここで”ansible_facts”を返したモジュールがset_factかinclude_varsの場合はvars cacheに書き込まれる。他のモジュールだった場合はsetup cacheに書き込まれる
- gather fact実行時には内部でsetupモジュールが呼ばれている
- setupモジュールは”ansible_facts”を返すset_fact、include_vars以外のモジュールなのでsetup cacheに書き込まれる
- registerを使った場合はvars cacheに書き込まれる
これらからわかることは、”ansible_facts”というキーを含む実行結果を返すモジュールを自分で作ればFact Caching(setup cache)に任意の値を保存させることができる、ということだ。
で、結局set_factモジュールをコピーして別の名前にすればいいだけじゃないか、と思いついた。
action pluginについて
しかしset_factモジュールのソースである modules/core/utilities/logic/set_fact.py には処理の実体はない。
これはset_factモジュールの実体がaction pluginとして実装されているからである。
action pluginはモジュールとしてほぼ振る舞うが、通常のモジュールとの違いは、action pluginとして実装された処理はリモートホストではなくAnsibleを実行しているホストで実行されることである。
set_fact以外にaction pluginとして実体が実装されているモジュールの一例を挙げるとrawがある。操作対象にpythonがインストールされてなくてもrawモジュールが動く(かもしれない)のは、rawモジュールの実行主体がリモートホストではなくAnsibleを実行しているホストだからである。
set_factモジュールの実体が実装されたaction pluginは runner/action_plugins/set_fact.py がソースファイルである。
またaction pluginが実行されるためには、実体がなかったとしても同名のモジュールのソースファイルを配置する必要がある。これが modules/core/utilities/logic/set_fact.py である。
というわけでset_factをコピーしたset_cacheというモジュールを作ってやるには2ファイルをコピーしてやれば良い。
ここではコピーではなくシンボリックリンクを作ってやることにする。以下はCentOS 7での例。CentOS 6ならpython2.7のところがpython2.6になる。
# ln -s /usr/lib/python2.7/site-packages/ansible/modules/core/utilities/logic/set_{fact,cache}.py
# ln -s /usr/lib/python2.7/site-packages/ansible/runner/action_plugins/set_{fact,cache}.py
シンボリックリンクを作る場所は別のディレクトリでも良いが、その場合はansible.cfgの[defaults]セクションのaction_plugins項にaction pluginを置いたディレクトリを指定してやる。またモジュールを置いたディレクトリを指定する方法は複数あるが、ansible.cfgを使うなら[defaults]セクションのlibrary項に指定する。
ちなみに、ansible.cfgにはコメントアウトされたlibrary_path項が見えるがこれは違う、というかlibrary_pathなんて設定項目は存在しないはずだ。
簡単な実行例
以下のようなplaybookとinventoryを作成し、
- hosts: host1
gather_facts: false
tasks:
- set_cache:
aaa: 111
tags: test1
- hosts: host2
gather_facts: false
tasks:
- debug: var=hostvars['host1']['aaa']
tags: test2
host1
host2
まず以下を実行してから、
# ansible-playbook test.yml -i test.hosts -t test1
以下を実行する。
# ansible-playbook test.yml -i test.hosts -t test2
実行結果に以下があれば上手く行っている。
ok: [host1] => {
"hostvars['host1']['aaa']": "111"
}
おわりに
これまでも値の保存を行いたければansible実行ホスト自身に対してtemplateモジュールなどでキーフレーズの入ったyamlファイルを作成して、後のplaybook実行ではinclude_varsモジュールなどでそのyamlファイルを読み込むようにする、などという手があったが、それよりはこちらの方がずっと簡単に利用できるだろう。