はじめに

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を作成し、

test.yml
- 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
test.hosts
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ファイルを読み込むようにする、などという手があったが、それよりはこちらの方がずっと簡単に利用できるだろう。

TOP