Ansibleの構成情報をグラフDBで管理する


これは NIFTY Advent Calendar 13日目の記事です。

ニフティ(新卒2年目)の @ntoofu です。

昨日は @umiiiiins さんの botocoreとpythonでニフティクラウドAPIを簡単に使う という記事でした。

私は、自社サービスを利用する話ではなく、業務中に行っていた取り組みに関する話をしたいと思います。

要約

Ansibleのインベントリファイルとして管理している構成情報を流用性のためにグラフDB(Neo4j)に格納し、Dynamic Inventoryとして扱えるようにしました。

経緯

  • サーバやネットワークの情報が分散・重複して管理されている
  • 情報を統合するために、構成情報をまとめてグラフDBで管理してみようと考えた
    • サーバとサーバが接続するネットワークのように、管理情報間で関係を持つことが多いから
    • 登録する項目が増えたりすることも考え、柔軟性のためグラフDBを使ってみることにした
  • 自チームではプログラムから扱いやすい形で構成情報を最も多く持っているのがAnsibleのインベントリだった
  • 手始めに、Ansibleの情報をグラフDBに入れ、利用はDynamic Inventoryを作成することにした
    • Dynamic InventoryはAnsibleにおいて、本来テキストファイルとして記述する管理ホストの情報をまとめたファイルを、プログラムによって動的生成するもの

方法

作成したコードはこちらにあります。
Ansibleはバージョン2.1.0を前提にしています。

概要

  • Ansibleが内部で使っているモジュールを利用してインベントリファイルのパースを実施
  • パースして得たデータを一部加工してNeo4jに投入
  • Neo4jの内容を取得し、Dynamic Inventoryとして扱える形式で返す
  • インベントリファイルをパースしたデータと、作成したDynamic Inventoryを元にパースしたデータが一致するかテスト

Ansibleインベントリのパース

Ansibleのインベントリファイル は通常、リンク先に例のあるようにINIファイル風の形式をしており、自力でパースするのは少し大変です。そこで、以下のような形で、Ansible自体がパースするときに利用しているモジュールを使うことにしました。

    from ansible.inventory import Inventory
    from ansible.vars import VariableManager
    from ansible.parsing.dataloader import DataLoader

    variable_manager = VariableManager()
    loader = DataLoader()
    if vault_pass:
        loader.set_vault_password(vault_pass)
    inventory = Inventory(loader=loader,
                          variable_manager=variable_manager,
                          host_list=inventory_file)
    inventory.set_playbook_basedir(base_dir)
    variable_manager.set_inventory(inventory)

Neo4jへの読み書き

こちらのドライバを利用しました。ドライバの使い方は公式ドキュメントに任せます。

気をつけたところは、辞書型データの扱いです。
Neo4jではノードに対しキーと値の組をプロパティとして割り当てることができますが、辞書型データは値として受け取ることが出来ません。

Bulbsを用いることで、辞書型をJSON形式の文字列型に変換して扱えるようにする方法もあるらしいのですが、辞書型で扱いたい情報はネットワークの情報など複数のホストで共通に使うことのある値が多いので、辞書型データの内容をそのままノードとして作成し、リレーションとして持つことにしました。

テスト

通常のファイルベースのインベントリからDynamic Inventoryに移行する場合に共通の問題だと思いますが、既存のものと差異がないかを簡単に確認する方法を用意する必要があります。
今回は、Ansible内部のモジュールを利用して2つのインベントリをパースした後、それらを比較して差分がないか確認するスクリプトを作成し、テストしています。

実践

# 必要なライブラリを用意(適宜環境にあわせて下さい)
git clone https://github.com/ntoofu/neo4j-ansible-inventory
cd neo4j-ansible-inventory
git submodule init && git submodule update
pip install -r requirements.txt

# テスト用インベントリ準備(既にあれば不要)
git clone https://github.com/ansible/ansible-examples
ansible_basedir=./ansible-examples/lamp_haproxy
ansible_inventory_path=./ansible-examples/lamp_haproxy/hosts

# Neo4jをlocalhostに作成
docker run -d -p 7474:7474 -p 7687:7687 -e NEO4J_AUTH='none' -v /tmp/neo4j/data:/data -v /tmp/neo4j/logs:/logs --name neo4j neo4j:3.0

# 適当なインベントリの情報をNeo4jへ
python store.py -n localhost -i ${ansible_inventory_path} -b ${ansible_basedir}

# Neo4jからDynamic Inventory
python inventory.py -n localhost --list

ブラウザからNeo4jのWeb UIに入ると以下のようにデータが入っている様子を閲覧可能です。(画像は こちら のデータを投入した例)

今後の課題

今回は、Ansibleのインベントリで持っている情報をそのままNeo4jに投入しているため、構造はインベントリの構造そのままで、変数名なども全て踏襲している状況です。

Neo4jにはAnsible以外からも利用可能な形で構成情報を持たせたいため、Ansibleの都合で付けている変数名(意図せぬ上書きを回避するためのプレフィックスなど)を変換したいこともあります。

また、Neo4jはAnsibleのインベントリ以外がソースのデータも投入し、Ansible以外からも利用したいと考えており、Ansibleに特化しない汎用の構造でデータを入れたいとも思っています。

他にも、Ansibleで利用する変数のために複雑なクエリを用いたいこともあるかと思います。(例えば、同じプロパティを持つグループが階層構造になっている場合に、最も近い親のプロパティを参照したい、など)

したがって、単純なマッピングではなく、やや複雑な対応付けをする仕組みがほしいと思っており、この部分をどうするかは考える必要がありそうです。


明日は、 @plan0213 さんの "ネットワーク機器へのコマンド入力自動化" です。