Ansible dynamic inventory + Zabbix APIで監視対象を動的に構成管理

More than 1 year has passed since last update.

Overview

普段Ansibleを使っているがinventory管理が思いのほか大変で、ホスト追加の度にinventoryファイルを編集とかしてた。
が、 dynamic inventory というものがあるのを最近知ったので使ってみる。
簡単にいうと、動的にAnsibleのinventoryホストを生成することが出来る機能で、一々マシンの追加の度にinventoryファイルを編集とかしなくていい。人間に優しい。
あと既にサーバ監視にZabbixを使っていたのでZabbixのホスト一覧からinventoryを生成し、Zabbix管理下のホストを構成管理ということもしてみる。

Requirements

  • Ansible (Tested on 1.8.1)
  • Zabbix (Tested on 2.0.8)
  • CentOS (Teted on 6.5)

Installation

dynamic inventoryを使うには-iオプションにansibleが認識可能なJSON形式の結果を吐く実行可能なスクリプトを渡してやればいい。
形式さえ合ってればいいのでシェルスクリプトでもrubyでも何でも好きな言語で書ける。

例えばローカルの/etc/hostsに存在するホスト名をinventoryとして使いたければ

inventory.sh
#!/bin/bash
HOSTS=$(cat /etc/hosts | grep -v ^# | awk '{print $2}')
HOSTS=$(echo "$HOSTS" | sed -e 's/^\(.*\)$/"\1"/g')
HOSTS=$(echo $HOSTS | sed -e 's/\s/,/g')
echo "{\"all\": {\"hosts\": [$HOSTS]}}"

のようなスクリプトを書いて渡してやるだけでいい。

ansibleで使うには

$ ansible -i inventory.sh all -m ping

のようにする。

さらに/etc/ansible/hostsにスクリプトを設置すればいちいち-iオプションを使う必要もなくなる。

$ cp inventory.sh /etc/ansible/hosts && chmod +x /etc/ansible/hosts

$ ansible all -m ping

web1 | success >> {
    "changed": false, 
    "ping": "pong"
}

web2 | success >> {
    "changed": false, 
    "ping": "pong"
}

...(中略)...

素晴らしい。

JSONの形式は以下のような形式ならなんでもいい。

{
    "databases"   : {
        "hosts"   : [ "host1.example.com", "host2.example.com" ],
        "vars"    : {
            "a"   : true
        }
    },
    "webservers"  : [ "host2.example.com", "host3.example.com" ],
    "atlanta"     : {
        "hosts"   : [ "host1.example.com", "host4.example.com", "host5.example.com" ],
        "vars"    : {
            "b"   : false
        },
        "children": [ "marietta", "5points" ]
    },
    "marietta"    : [ "host6.example.com" ],
    "5points"     : [ "host7.example.com" ],
    "_meta" : {
       "hostvars" : {
          "host1.example.com"     : { 
             "asdf" : 1234,
             "ansible_ssh_host": "host1.example.com" 
           },
          "host2.example.com"     : { 
             "asdf" : 5678,
             "ansible_ssh_host": "host2.example.com" 
           }
       }
    }
}

_meta内にはhostvarsを入れられるのでhostごとに異なる情報があればここに入れておく。(ansible_ssh_port, ansible_ssh_user, ansible_ssh_host等)

公式ドキュメント: Developing Dynamic Inventory Sources — Ansible Documentation

Zabbix連携

Ansibleが既に公式で配布しているZabbix用のdynamic inventoryスクリプトがあるのでこれを使います。

使ってみたがそのままでは動作しなかったのと、デフォルトではホストグループ単位でのグループ化しか出来なかったので少し修正して、
Zabbixのテンプレート単位でのinventory化も出来るようにします。
修正後のファイルはGistに公開してあります

@@ -71,19 +71,41 @@ class ZabbixInventory(object):
             'hosts': []
         }

+    def templatestub(self):
+        return {
+            'hosts': []
+        }
+
     def get_host(self, api, name):
         data = {}
         return data

     def get_list(self, api):
-        hostsData = api.host.get({'output': 'extend', 'selectGroups': 'extend'})
-
+        hostsData = api.host.get(
+          {
+            'output': 'extend', 
+            'selectGroups': 'extend', 
+            'selectInterfaces': 'extend',
+            'selectInventory': 'extend'
+          }
+        )
+        templateData = api.template.get(
+          {
+            'output': 'extend', 
+            'selectGroups': 'extend', 
+            'selectHosts': 'extend',
+          }
+        ) 
         data = {}
         data[self.defaultgroup] = self.hoststub()
+        data['_meta'] = {}
+        data['_meta']['hostvars'] = {}

         for host in hostsData:
             hostname = host['name']
             data[self.defaultgroup]['hosts'].append(hostname)
+            data['_meta']['hostvars'][hostname] = host
+            data['_meta']['hostvars'][hostname]['ansible_ssh_host'] = hostname

             for group in host['groups']:
                 groupname = group['name']
@@ -93,6 +115,18 @@ class ZabbixInventory(object):

                 data[groupname]['hosts'].append(hostname)

+        for template in templateData:
+            templatename = template['name']
+            
+            for host in template['hosts']:
+                hostname = host['name']
+
+                if not templatename in data:
+                    data[templatename] = self.templatestub()
+

  • 修正したものをansibleのconfディレクトリにコピー
cp zabbix.py /etc/ansible/hosts
chmod +x /etc/ansible/hosts
cp zabbix.ini /etc/ansible/
  • zabbixのpythonクライアントをインストール
pip install zabbix-api
  • 確認
# ホストグループベースのansible実行
$ ansible webservers -m ping

web1 | success >> {
    "changed": false, 
    "ping": "pong"
}

web2 | success >> {
    "changed": false, 
    "ping": "pong"
}

...(中略)...

# テンプレートベースのansible実行
$ ansible "Template OS Linux" -m ping

web1 | success >> {
    "changed": false, 
    "ping": "pong"
}

web2 | success >> {
    "changed": false, 
    "ping": "pong"
}

...(中略)...

これでZabbix上のホストグループベースでのinventoryの生成と、テンプレートベースでのinventoryの生成が出来ました。
テンプレートベースでの構成管理が出来るので、テンプレートと密接に関わるUserParameterやLow Level Discoveryの構成管理は楽になると思います。

またスクリプトのZabbix APIのhost.getを叩いてる箇所に

'selectInventory': 'extend'

をパラメータに追加してホストインベントリも取得しているので
Zabbixでラック管理やシリアル管理、タグ管理等の資産管理をしている場合は
hostvarsを見て特定のラックのみ更新、特定のタグのマシンのみ更新とか出来ます。
他にもZabbix API次第でinventoryの生成方法を色々工夫出来ると思います。

あとはZabbixのディスカバリチェックで監視対象のホストをホストグループに追加したり
テンプレートにリンクさせたりすれば動的にinventoryを増やしたり減らしたり出来ます。

dynamic inventory用のスクリプト自体は公式が配布しているものを少し修正すれば
十分実用的になるので各々の環境に合わせたdynamic inventoryプラグインを作るのがよいかと思います。