Automatically import AWX inventory from zabbix inventories

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

 

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

참고 URL : 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 의 내용을 그대로 복사해 줍니다.

저는 경로를 명확히 해주기 위해 44번 라인의 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 패턴 설정은 이곳을 확인하시면 됩니다.

Subscribe
Notify of
guest

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.

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