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()
4. AWX WEB UI 에서 [Inventories] 메뉴를 선택 후 새 Inventory 를 생성해 줍니다. Name 과 Organization 만 입력하고 SAVE 하면 됩니다. SAVE 를 클릭하면 Source 를 수정할 수 있게 됩니다. Source 를 클릭 후 Create 버튼을 클릭 합니다.
5. Name 에 원하는 이름을 적고, Source 는 Custom Script 를 선택하면 Source Details 부분이 생겨납니다. Custom Inventory Script 의 돋보기 버튼을 누르면 Inventory Scripts 에서 생성한 스크립트가 나옵니다. 선택 후 저장합니다.
6. 다시 AWX WEB UI 에서 [Inventories] 메뉴를 선택 후 추가한 인벤토리를 클릭하고 Source 를 클릭하여 Custom Script 가 추가된 것을 확인 할 수 있습니다.
좌측 Sources 이름 옆에 구름 아이콘이 회색으로 있습니다. 우측 Actions 를 보시면 연필, 화살표, 휴지통이 보입니다. 화살표 버튼이 싱크 버튼입니다. 싱크 버튼을 클릭하면 구름 아이콘이 반짝입니다. 구름 아이콘을 클릭 하면 싱크가 되는 상황을 자세히 볼 수 있습니다. 싱크가 정상이면 구름 아이콘이 녹색으로 변경 됩니다.
7. 성공적으로 싱크가 완료 되었다면 다시 AWX WEB UI 에서 [Inventories] 메뉴를 선택 후 추가한 인벤토리를 선택 후 Hosts 및 Groups 를 보시면 추가된 호스트와 그룹을 확인할 수 있습니다.
# 추가한 인벤토리 스크립트는 인벤토리 메뉴내에서 스케쥴을 이용하여 자동으로 가져오도록 설정할수도 있습니다.
# 특정 그룹 또는 호스트별로 job 을 실행 시키기 위해서는 템플릿에서 LIMIT 부분을 활용하시면 됩니다. LIMIT 패턴 설정은 이곳을 확인하시면 됩니다.