Automatically import AWX inventory from zabbix inventories

AWX 인벤토리를 zabbix 인벤토리에서 가져 오기

 

참고 URL

https://docs.ansible.com/ansible-tower/latest/html/administration/custom_inventory_script.html

https://github.com/ansible/ansible/tree/stable-2.9/contrib/inventory

 

AWX (Ansible Tower) 혹은 Ansible 에서 인벤토리에 호스트를 추가하는 일은 어렵지 않습니다. 하지만 추가할 호스트가 많아지면 엄청난 시간을 할애해야 할 것입니다.

이 같은 단순반복 작업을 편하게 할 수 있게 inventory Scripts 라는 메뉴가 존재 합니다. 위의 github 에서 제공되고 있는 참고 링크를 보시면 다양한 커스텀 스크립트들이 지원되고 있습니다.

현재 회사에는 자빅스의 디스커버리룰을 이용하여 호스트 및 그룹을 자동 등록하고 있기 때문에 자빅스 API 를 이용하여 AWX 의 인벤토리도 자동으로 가져오는 방법을 이용해 보았습니다.

 

1. AWX 서버에 zabbix.ini github 를 참고해서 /etc/ansible/zabbix.ini 파일을 생성 후 자신의 환경에 맞게 작성합니다.
: vi /etc/ansible/zabbix.ini

# Ansible Zabbix external inventory script settings
#

[zabbix]

# Server location
server = https://zabbix.umount.net

# Login
username = zbxapiadmin
password = zbxapiadminpassword

# Verify the server's SSL certificate
validate_certs = True

# Read zabbix inventory per host
read_host_inventory = True

# Set ansible_ssh_host based on first interface settings
use_host_interface = True

 

2. AWX 서버에 pip 을 이용하여 zabbix-api 를 설치해줍니다.

pip-3 install zabbix-api

 

3. AWX WEB UI 에서 [Inventory Scripts] 메뉴를 선택 후 Create 버튼을 누릅니다. Name 에는 Inventory Script 의 이름을 원하시는대로 작성해 주시고 Organization 을 선택합니다.

Custom Script 부분에 zabbix.py github 의 내용을 그대로 복사해 줍니다.

저는 경로를 명확히 해주기 위해 54번 라인의 conf_path 경로를 절대경로로 수정하였습니다.

#!/usr/bin/env python

# (c) 2013, Greg Buehler
# (c) 2018, Filippo Ferrazini
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

######################################################################

"""
Zabbix Server external inventory script.
========================================
Returns hosts and hostgroups from Zabbix Server.
If you want to run with --limit against a host group with space in the
name, use asterisk. For example --limit="Linux*servers".
Configuration is read from `zabbix.ini`.
Tested with Zabbix Server 2.0.6, 3.2.3 and 3.4.
"""

from __future__ import print_function

import os
import sys
import argparse
from ansible.module_utils.six.moves import configparser

try:
    from zabbix_api import ZabbixAPI
except Exception:
    print("Error: Zabbix API library must be installed: pip install zabbix-api.",
          file=sys.stderr)
    sys.exit(1)

import json


class ZabbixInventory(object):

    def read_settings(self):
        config = configparser.SafeConfigParser()
        conf_path = '/etc/ansible/zabbix.ini'
        if not os.path.exists(conf_path):
            conf_path = os.path.dirname(os.path.realpath(__file__)) + '/zabbix.ini'
        if os.path.exists(conf_path):
            config.read(conf_path)
        # server
        if config.has_option('zabbix', 'server'):
            self.zabbix_server = config.get('zabbix', 'server')

        # login
        if config.has_option('zabbix', 'username'):
            self.zabbix_username = config.get('zabbix', 'username')
        if config.has_option('zabbix', 'password'):
            self.zabbix_password = config.get('zabbix', 'password')
        # ssl certs
        if config.has_option('zabbix', 'validate_certs'):
            if config.get('zabbix', 'validate_certs') in ['false', 'False', False]:
                self.validate_certs = False
        # host inventory
        if config.has_option('zabbix', 'read_host_inventory'):
            if config.get('zabbix', 'read_host_inventory') in ['true', 'True', True]:
                self.read_host_inventory = True
        # host interface
        if config.has_option('zabbix', 'use_host_interface'):
            if config.get('zabbix', 'use_host_interface') in ['false', 'False', False]:
                self.use_host_interface = False

    def read_cli(self):
        parser = argparse.ArgumentParser()
        parser.add_argument('--host')
        parser.add_argument('--list', action='store_true')
        self.options = parser.parse_args()

    def hoststub(self):
        return {
            'hosts': []
        }

    def get_host(self, api, name):
        api_query = {'output': 'extend', 'selectGroups': 'extend', "filter": {"host": [name]}}
        if self.use_host_interface:
            api_query['selectInterfaces'] = ['useip', 'ip', 'dns']
        if self.read_host_inventory:
            api_query['selectInventory'] = "extend"

        data = {'ansible_ssh_host': name}
        if self.use_host_interface or self.read_host_inventory:
            try:
                hosts_data = api.host.get(api_query)[0]
                if 'interfaces' in hosts_data:
                    # use first interface only
                    if hosts_data['interfaces'][0]['useip'] == 0:
                        data['ansible_ssh_host'] = hosts_data['interfaces'][0]['dns']
                    else:
                        data['ansible_ssh_host'] = hosts_data['interfaces'][0]['ip']
                if ('inventory' in hosts_data) and (hosts_data['inventory']):
                    data.update(hosts_data['inventory'])
            except IndexError:
                # Host not found in zabbix
                pass
        return data

    def get_list(self, api):
        api_query = {'output': 'extend', 'selectGroups': 'extend'}
        if self.use_host_interface:
            api_query['selectInterfaces'] = ['useip', 'ip', 'dns']
        if self.read_host_inventory:
            api_query['selectInventory'] = "extend"

        hosts_data = api.host.get(api_query)
        data = {'_meta': {'hostvars': {}}}

        data[self.defaultgroup] = self.hoststub()
        for host in hosts_data:
            hostname = host['name']
            hostvars = dict()
            data[self.defaultgroup]['hosts'].append(hostname)

            for group in host['groups']:
                groupname = group['name']

                if groupname not in data:
                    data[groupname] = self.hoststub()

                data[groupname]['hosts'].append(hostname)
            if 'interfaces' in host:
                # use first interface only
                if host['interfaces'][0]['useip'] == 0:
                    hostvars['ansible_ssh_host'] = host['interfaces'][0]['dns']
                else:
                    hostvars['ansible_ssh_host'] = host['interfaces'][0]['ip']
            if ('inventory' in host) and (host['inventory']):
                hostvars.update(host['inventory'])
            data['_meta']['hostvars'][hostname] = hostvars

        return data

    def __init__(self):

        self.defaultgroup = 'group_all'
        self.zabbix_server = None
        self.zabbix_username = None
        self.zabbix_password = None
        self.validate_certs = True
        self.read_host_inventory = False
        self.use_host_interface = True

        self.meta = {}

        self.read_settings()
        self.read_cli()

        if self.zabbix_server and self.zabbix_username:
            try:
                api = ZabbixAPI(server=self.zabbix_server, validate_certs=self.validate_certs)
                api.login(user=self.zabbix_username, password=self.zabbix_password)
            # zabbix_api tries to exit if it cannot parse what the zabbix server returned
            # so we have to use SystemExit here
            except (Exception, SystemExit) as e:
                print("Error: Could not login to Zabbix server. Check your zabbix.ini.", file=sys.stderr)
                sys.exit(1)

            if self.options.host:
                data = self.get_host(api, self.options.host)
                print(json.dumps(data, indent=2))

            elif self.options.list:
                data = self.get_list(api)
                print(json.dumps(data, indent=2))

            else:
                print("usage: --list  ..OR.. --host <hostname>", file=sys.stderr)
                sys.exit(1)

        else:
            print("Error: Configuration of server and credentials are required. See zabbix.ini.", file=sys.stderr)
            sys.exit(1)

ZabbixInventory()

awx-inventory-from-zabbix-01

 

4. AWX WEB UI 에서 [Inventories] 메뉴를 선택 후 새 Inventory 를 생성해 줍니다. Name 과 Organization 만 입력하고 SAVE 하면 됩니다. SAVE 를 클릭하면 Source 를 수정할 수 있게 됩니다. Source 를 클릭 후 Create 버튼을 클릭 합니다.

awx-inventory-from-zabbix-02

 

5. Name 에 원하는 이름을 적고, Source 는 Custom Script 를 선택하면 Source Details 부분이 생겨납니다. Custom Inventory Script 의 돋보기 버튼을 누르면 Inventory Scripts 에서 생성한 스크립트가 나옵니다. 선택 후 저장합니다.

awx-inventory-from-zabbix-03

 

6. 다시 AWX WEB UI 에서 [Inventories] 메뉴를 선택 후 추가한 인벤토리를 클릭하고 Source 를 클릭하여 Custom Script 가 추가된 것을 확인 할 수 있습니다.

좌측 Sources 이름 옆에 구름 아이콘이 회색으로 있습니다. 우측 Actions 를 보시면 연필, 화살표, 휴지통이 보입니다. 화살표 버튼이 싱크 버튼입니다. 싱크 버튼을 클릭하면 구름 아이콘이 반짝입니다. 구름 아이콘을 클릭 하면 싱크가 되는 상황을 자세히 볼 수 있습니다. 싱크가 정상이면 구름 아이콘이 녹색으로 변경 됩니다.

awx-inventory-from-zabbix-04

awx-inventory-from-zabbix-05

 

7. 성공적으로 싱크가 완료 되었다면 다시 AWX WEB UI 에서 [Inventories] 메뉴를 선택 후 추가한 인벤토리를 선택 후 Hosts 및 Groups 를 보시면 추가된 호스트와 그룹을 확인할 수 있습니다.

awx-inventory-from-zabbix-06

awx-inventory-from-zabbix-07

 

# 추가한 인벤토리 스크립트는 인벤토리 메뉴내에서 스케쥴을 이용하여 자동으로 가져오도록 설정할수도 있습니다.

# 특정 그룹 또는 호스트별로 job 을 실행 시키기 위해서는 템플릿에서 LIMIT 부분을 활용하시면 됩니다. LIMIT 패턴 설정은 이곳을 확인하시면 됩니다.

You may also like...

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x