Dynamic Inventoryで独自書式を定義し、inventoryファイルを簡易化する

Dynamic Inventoryとは

Dynamic Inventoryの使いどころ

ansible-playbookを実行するときに、-iでinventoryファイルだけでなく、JSONを標準出力するプログラムも渡せる。Dynamic Inventoryという機能だ。
本来は、ホストがAnsibleとは別で管理されている場合などに、ホストの情報をAPIやDBアクセスなどで取得・加工してAnsibleに渡すのが主目的の機能だが、これを利用してinventoryファイルに独自の書式を導入し、それをプログラミングで解読して標準のAnsibleのinventoryに加工し、JSON出力してみる。

Ansibleのinventoryファイルはグループが階層構造を作るとき読みづらくなるため、独自書式を導入することにより、可読性を向上させたい。

Dynamic Inventoryの仕様

端的にいうと実行可能な(つまり実行権限が付与されている)ファイルで、実行するとJSONを出力すればいいのだが、以下のような仕様があり、Ansible実行時にこれらに則ってプログラムファイルが実行される。

  • --listをつけて呼び出すと全group情報(group、groupのchildren、vars、hosts)をJSONで標準出力する
  • --host <HOSTNAME>をつけて呼び出すと指定したHOSTNAMEのhostvarsをJSONで標準出力する
  • _metaがkeyでhostvarsがvalueで定義されて、--list時にgroup情報と共にJSON出力されると、Ansible実行時に内部で--hostつきでhost数分呼び出されることがなくなり、パフォーマンス向上するため、推奨されている

つまり、--listつきでAnsibleから呼び出され、_meta.hostvarsの有無によって--host <HOSTNAME>がさらに呼び出される。

Inventoryの書式は少し複雑

Ansibleを作っていくとき、汎用性を高めるためにplaybookをミドルウェアごとに作成し、inventoryで各サーバにどのミドルウェアを構築するかを指定できるようにするとする。この場合、複雑なinventoryファイルを書かなくてはいけなくなる。

例えば、開発用サーバを1台作成するため、開発環境用inventoryを作るとする。このサーバにはapache, mysql, redisを入れる。inventoryのYAML(hosts.yml)は以下のようになる。

開発用サーバ(svr0001)を一台作成するだけなのに、ミドルウェアごとに設定を書くことになり、svr0001についての記述箇所が分散してしまう。書きづらいだけでなくsvr0001の全体像が掴みづらい。

このような記述になってしまう原因は、apache,mysql,redisのplaybookでhosts: apache, hosts: mysql, hosts: redisというように実行対象グループを限定していることにある。各ミドルウェアのplaybookをすべてincludeしている最上位playbookがあり、ansible-playbook実行時に指定するplaybookは常にその最上位playbookにしているのだが、あるサーバに対してどのミドルウェアのplaybookを実行するか判定するには、ミドルウェアのplaybookのhostsで判定するしかない。そのためsvr0001はapacheグループにも属さなければいけなし、mysqlグループとredisグループにも属さなければいけない。
開発環境では今後svr0001だけでなく、FQDN等が違うsvr0002も増やすことになるだろうし、グループ変数を別に定義しなければいけないことを考えると、apacheグループの下に子グループapache00, apache01, ...とさらに小分けしたグループを作らなければならない。
この実装方式では、YAMLが最上位のdevelopmentグループは別として、hostにたどり着くまでに3階層くだらなければいけないし、さらに重要なこととして、一サーバについての設定が分散してしまう。

Inventoryの書式に独自書式を追加する

先に見たような実装方式であってもinventoryを簡潔に書くために、独自の書式を追加する。
もともとグループ情報を定義するときに指定できる要素は以下となる。

  • vars: グループ変数
  • children: 子グループのキー
  • hosts: ホスト

これにsetupsという要素を独自に追加する。

setupsにはapache, mysql, redisなどを設定することができる。setupsにはあるサーバに対して実行したいミドルウェアplaybookの名称を書く。setupsがミドルウェアplaybook実行時のhosts:によるグループ制限に対して効くようになる。

なぜ独自に定義したsetupsに書いた値がホストグループに変わってhosts:によるグループ制限に効くのかというと、Dynamic Inventoryで実行されるプログラムでsetups要素を見つけたらYAMLのデータ構造を加工し、先に見たようなYAMLの構造に書き換えるからだ。つまり、setups要素に列挙したものが、ホストグループになるようにデータを加工する。

プログラムを見る前に、YAML(hosts.yml)がどれだけシンプルになるのか以下に記載する。

これだけシンプルになった。

  • apache00のような子グループがなくなった
  • svr0001が属するグループがapache, mysql, redisではなく唯一のグループとなった
  • svr0001が属するグループに意味がわかる名前をつけられる
  • svr0001が属するグループの変数が一箇所にまとまった

独自書式を可能にするプログラム

独自要素であるsetupsをどのように標準のinventoryに書き換えているか、実際のプログラム(d_inventory)を書く。

コメントを詳細に書いたが、setups要素をchildrenに変換したりして、YAMLのデータ構造を変えている。

このプログラムが--listつきで呼び出されると(実際にはコメントで記載した通りオプションは見ていないが)、次のJSONを出力する。

正しくapache, mysql, redisのグループにsvr0001が属していることになる。
一番初めに書いたYAMLよりは階層が深くなってしまっているが、これは人が読み書きするのではなく、Ansibleが読むだけなので気にしないでいいだろう。
また変数がすべてのグループに書かれているが、apacheのplaybookを実行しているときにmysqlに関する変数が定義されていても影響はないため問題ないし、同上の理由により可読性も気にしないでいいだろう。

Dynamic Inventoryでどこまで書式を拡張してもいいか

このようにDynamic Inventoryを使うことで、独自の書式をinventoryに取り入れることができ、チームの運用にあったYAMLを書くことができるようになる。
ただしあまりに拡張しすぎると返って複雑になったり柔軟性を失ったりしかねないので、拡張のメリットの大きさや実装の簡易さを十分考慮した方がいい。
ちなみに、今回の実装では、filter(lambda (k, v): "setups" in v, data.items())を冒頭で実行しているため、setupsという独自要素が一切なければ、そのままYAMLのデータ構造を変えずにJSON出力できるようになっており、独自書式でも受け付けるし、従来通りのAnsible標準の書式でも受け付けられるようになっている。このような後方互換性も考慮に入れた方がいいだろう。

-Linux, Python
-