Как мы делали мониторинг сети на 14 000 объектов

У нас было 14 000 объектов, zabbix, api, python и нежелание добавлять объекты руками. Под катом — о том, как сетевиками внедрялся мониторинг с автоматическим добавлением узлов сети, и немного про боль, через которую пришлось пройти.

Статья больше ориентирована на сетевых инженеров с небольшим опытом в python. В помощь при автоматизации мониторинга и улучшения качества жизни и работы, в отсутствии необходимости руками актуализировать весь парк объектов.

mw-uevvuik_o6poo21krw2rt0gy.jpeg

Long story short, we have built a monitoring


Привет! Меня зовут Александр Прохоров, и вместе с командой сетевых инженеров нашего отдела мы занимаемся сетью в #ITX5. Наш отдел развивает сетевую инфраструктуру, мониторинг и автоматизацию сети. Да и все, что связано с передачей данных.

g1mumrtfj5tkbmqs52gl05_d2la.png

Я бы разбил систему мониторинга на 5 подзадач:

  • Загрузка мастер-данных
  • Получение информации о состоянии объектов
  • Триггеры и оповещения
  • Составление отчетности
  • Визуализация


В этой статье хотели бы поделиться, как делали интеграцию мониторинга с мастер-данными в нашей компании.

У нас 14 000 торговых объектов, и первая задача, которую мы решали — это определение недоступных объектов, их количество и географическое распределение.

Мониторинг делали на Zabbix. В двух словах почему — повезло и legacy. Остальное:

Long story.
Все началось с установки мониторинга на компьютер под столом…

Когда я пришел работать в компанию в 2013 году, у нас не было мониторинга сети, хотя сеть даже на тот момент была большой, порядка 4000 объектов. О массовых (и не очень) падениях мы узнавали чаще всего от лавинообразного поступления заявок, пользователей или от других отделов.

ge6lduplk0odlklh6iazcjsw6vk.jpeg

Первым был установлен Zabbix 1.8, как набирающий обороты (модный, современный, молодежный) продукт, легкий и доступный к установке open source с большим сообществом. Нам просто повезло с выбором.

Ресурсов для установки не было, никто и не запрашивал. Было непонятно, будет ли это вообще работать, опыта внедрения ни у кого не было. Но нам было нужно, и мы установили его на компьютер «под стол». Уровень резервирования — ИБП.

Основной вопрос после установки — как влить все объекты (4к!) в мониторинг, и при этом успевать делать заявки, которые уже висят в Remedy. Zabbix уже поддерживал импорт/экспорт xml с данными по узлам сети. Количество объектов большое, создавать отдельную группу под объект смысла не было (да и не появилось), и было решено вгружать в качестве узла сети роутер объекта. Больше из сетевого оборудования ничего не управлялось (уже управляется).

Был сделан парсинг нашего файла с хостами сети (IPAM в excel), он и переформатирован в xml, который «Заббикс» согласен переварить. Не с первого раза, но все хосты были вгружены, актуализацию проводили раз в три месяца, удаляя закрывшиеся объекты и добавляя новые. Со временем получилось так, что Zabbix стал главным и единственным источником информации для нас и горячей линии по доступности объектов и, главное, по массовым падениям. Это позволило горячей линии узнавать о массовых авариях даже ночью, и будить инженеров звонками (к слову, когда никто об этом не знал, жить было проще). Не всегда ночные отключения питания в офисе позволяли ИБП достойно поддерживать мониторинг нашей сети. В какой-то момент мы стали уже делать бекапы. Т.к. мониторинг работал нестабильно и с перебоями, в компании приняли решение организовать группу, занимающуюся только этой задачей. Очень скоро она занялась внедрением централизованного Zabbix, который проверяет не только сеть.

Наш старенький Zabbix продолжал жить под столом. После добавления определенного количества item«ов стала немного тормозить база, web, очередь и задержка опроса вырастала, так что вскоре пришлось обзавестись еще двумя компьютерами в качестве proxy и поставить их все в кроссовую, чтобы уж настоящее резервирование (да и место под столом закончилось). Жизнь в нем поддерживалась, чтобы быстро добавлять какой-то кастомный параметр в мониторинг и наблюдать за ним. В остальном мы переехали на централизованный.

Централизованный мониторинг занимался всем IT-оборудованием — роутерами, серверами, кассами, терминалами. Спустя время он оброс огромным количеством item’ов. Для доступа к бесценной информации о доступности необходимо было слегка подождать, возможно, даже выпить кофе. К тому же у нас появлялось все больше требований к более специфичному и кастомному мониторингу сети. Имея на тот момент некоторый опыт по работе с системой, мы решили сделать старое внедрение с нуля, но уже правильно, — под названием zabbix.noc.x5.ru Он разместился в ЦОДе с реализацией необходимых нам функций by ourselves. Об этом то внедрении и основное тело статьи.


Версия Zabbix — 3.0 LTS. Обновляемся только в рамках LTS версий.

Конфигурация — 4 виртуальных машины: Server+Database, Proxy, Proxy, Web

По ресурсам старались следовать рекомендациям на zabbix.com для большого и очень большого внедрения.

Первая проблема, которая перед нами встала — это автоматическое добавление узлов сети в мониторинг. Штатный дискаверинг отпал сразу. Наш диапазон сетей находится на всех просторах супернета 10/8. Очень много адресов приходилось бы опрашивать и автоматическое обнаружение занимало бы много ресурсов системы, но не решало бы задачи добавления нетехнической информации об объекте.

1ronnmq5d1dutzqrzwn-3tfypdm.png

Как добавляли объекты


Решением стало использование внешних скриптов для добавления объектов. Мастер-данные по торговым объектам нашли в используемом в компании SAP’е. Синхронный доступ к web-service для выгрузки данных напрямую из SAP не вышел, запрос по всем объектам выполнялся довольно долго. Сделали асинхронный вызов. Ровно в полночь полная выгрузка из SAP складывается на ftp в виде XML, а в течение дня на него попадают XML«ки с diff’ами от последних версий.

Для загрузки данных в Zabbix использовался Zabbix API, а взаимодействовали с ним из Python.

На первом этапе был определен набор данных, который необходим нам для создания объекта. Все полученные признаки используем для правильной классификации объекта в системе или для дальнейшего удобства. К таким признакам относятся:

  • IP Address — самое главное поле, для создания интерфейса мониторинга
  • SAP ID — у нас уникальный идентификатор объекта
  • Status — открыт/закрыт
  • Name — название или номер объекта, часто используется пользователями при обращении
  • Location — физический адрес
  • Phone — контактный телефон
  • Groups — этот набор групп формируем исходя из типа и расположения объекта


hrhc29q4zq8axnta-ift3gjpcwm.png

Модуль sap-sync.py


XML из SAP

 
      1234
      4321-Пятерочка
      192.168.1.50
      31
      308580
      с.Мониторинговое
      Москвоская обл.
      Ленина ул.
      1
      (999)777-77-77
      CH
      CH_MSK
      Регион Центральный
      CH_MSK_D
      Объект открыт
 



sap-sync.py
#!/usr/bin/python3

import sys, os, getopt, ipaddress
from datetime import datetime as dt
from zabbix.api import ZabbixAPI
import xml.etree.cElementTree as et
from report import report
import existhost

def ping(ip):
    if os.system('ping -c 2 -W 1 %s > /dev/null'%ip) == 0:
        return True
    else:
        return False

def main(argv):
    global opath
    try:
        opts, args = getopt.getopt(argv,"hp:",["path="])
    except getopt.GetoptError:
        print('sync-sap-chg.py -p ')
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print('sync-sap-chg.py -p ')
            sys.exit()
        elif opt in ("-p", "--path"):
            opath = arg

def asynchronization(file, reportdata):
 zapi = ZabbixAPI(url='http://z.noc.x5.ru', user='user', password='pwd')
 
 #Константы, которые потом пригодятся
 left_kidney = '17855'
 right_kidney = '17856'
 f_type={'S':'Super','D':'DK','H':'Giper','A':'DK'}
 format={'D':'13','S':'14','H':'12','A':'13'}
 region={'CT':'21','UR':'19','SZ':'17',~omit~}
 try:
  #Начинаем разбор XML
  tree = et.ElementTree(file=file)
  root = tree.getroot()
  for werks in root.iter('werks'):
        #Создаем структуру словаря для объекта Zabbix
        interfaces=[{
         'main':'1',
         'type':'2',
         'useip':'1',
         'port':'161'
        }]
        shop={
         'inventory':{},
         'interfaces':interfaces,
         'groups':[],
         'templates':[{'templateid':'10194'}],
         'inventory_mode':'1'
        }
        
        #Данные, которые будем искать в XML
        di = {
         'WERKS':'',
             ~omit~,
         'STATUS':'Object Opened'
        }
        
        #Переводим все в словарь
        for item in werks:
          di[item.tag] = item.text
          #Достаем из SAPID только цифры
          if item.tag == 'WERKS':
            n_proxy = ''.join(filter(lambda x: x.isdigit(), item.text))

          #Находим IP роутера по адресу сервера. Подсеть везде /26
          try:
                ipaddress.ip_interface('%s/%s'%(di['PLANT_IP'].strip(),26))
                ip_chk = True
          except:
                ip_chk = False

                #Дальнейшая обработка, только если IP адрес верный
                if (di['PLANT_IP'] != '1.1.1.1') and ip_chk:

                   #Записываем данные в словарь
                   if di['FORMAT'][-1] in ['D','A','S','H']:
                          shop['inventory']['alias']=di['WERKS']
                          shop['inventory']['name']=di['NAME1']
                          shop['inventory']['poc_1_phone_a']=di['TEL_NUMBER']
                          shop['inventory']['location']=di['STREET']

                   #Особенности для каждого формата
                   if (di['FORMAT'][-1] in ['H']):
                          shop['host']=f_type[di['FORMAT'][-1]]+di['WERKS']
                          shop['interfaces'][0]['ip']=str(ipaddress.ip_interface('%s/%s'%(di['PLANT_IP'].strip(),24)).network[1])

                          #Добавляем ID шаблона мониторинга для объекта
                          shop['templates'][0]['templateid']='29529'
                          
                   elif (di['FORMAT'][-1] in ['S']):
                          ~omit~

                   #For D balance in proxies
                   if int(n_proxy[-1]) % 2 == 0:
                          shop['proxy_hostid'] = right_kidney
                   else:
                          shop['proxy_hostid'] = left_kidney

                   #В inventory тоже добавим IP
                   shop['inventory']['oob_ip']=shop['interfaces'][0]['ip']
                        
                   #Назначение групп по словарям format и region
                   shop['groups']=[{'groupid':'9'}]
                   shop['groups'].append({'groupid':format[di['FORMAT'][-1]]})
                   shop['groups'].append({'groupid':region[di['FORMAT'][:2]]})
                          
                   #Проверка статуса магазина
                   if di['STATUS'] == 'Объект открыт':
                          shop['status']='0'
                   else:
                          shop['status']='1'

                   #Записать текущую дату
                   T = dt.date(dt.now()).strftime("%d %B %Y")
                   shop['inventory']['date_hw_decomm'] = T
                   
                   #Проверить есть ли такой хост в Zabbix
                   hostid = existhost.exist(shop['host'])
                   ip = shop['interfaces'][0]['ip']
                   
                   #Если новый - создаем!
                   if hostid == 0 and shop['status'] == '0' and ping(ip):
                          zapi.host.create(shop)
                          reportdata['new'].append(di['WERKS'])
                          
                   #Если уже есть - обновляем!
                   elif hostid != 0 and di['PLANT_IP'] != '1.1.1.1':
                          #Указываем какой HOSTID обновляем
                          shop['hostid']=hostid
                          #При обновлении нужно отрезать интерфейсы
                          shop.pop('interfaces')
                          zapi.host.update(shop)
                          reportdata['update'].append(di['WERKS'])

  #Переместить обработанный файл в архив
  os.system('mv %s /mnt/ftp/old_data/'%(file))
 
 except:
  reportdata['error'].append(di['WERKS'])
  report('Ошибка с файлом %s. Объект с ошибкой:%s'%(file, str(reportdata['error'])), 'SAP Sync Failed!')
  sys.exit()

def checking(path):
        files = os.listdir(path)
        files.sort()
        reportdata ={'new':[], 'update':[], 'error':[]}
        for file in files:
                asynchronization(path+file, reportdata)
        if files != []:
                report('Файлы %s успешно синхронизированы! \n Добавлено %s объектов. \n Их sap:\n %s. \n Обновлено %s объектов. \n Их sap:\n %s'%(str(files), len(reportdata['new']), str(reportdata['new']), len(reportdata['update']), str(reportdata['update'])), 'SAP Sync Succeed!')

if __name__ == "__main__":
        main(sys.argv[1:])
        checking(opath)



Основное назначение этого модуля — парсинг XML и перевод в JSON для API Zabbix. Очень удобно в этом случае использовать словари Python, т.к. не нужно их дополнительно форматировать, — с использованием модуля zabbix.api можно просто скормить ему словарь с правильной структурой. JSON со структурой выглядит так:

{
      'host': 'Hostname'
      'groups': [...]
      'interfaces': [{},{},{}]
      'inventory': {}
      'templates': [{},{},{}]
      'inventory_mode': '1'
      'proxy_hostid': 'INT'
      'status': '0'
}

[] - массив
{} - словарь


У нас в поле с IP- адресом в SAP хранится адрес сервера, а не маршрутизатора, но с помощью модуля ipaddress считаем первый адрес подсети, который в нашем случае всегда роутер.

str(ipaddress.ip_interface('%s/%s'%(di['PLANT_IP'].strip(),24)).network[1])


Дату последнего успешного обновления записываем в Inventory, в спорных ситуациях помогает понять, насколько актуальна информация в системе.

#Записать текущую дату
T = dt.date(dt.now()).strftime("%d %B %Y")
shop['inventory']['date_hw_decomm'] = T


Потом в инвентарных данных очень удобно смотреть статистику по дате обновления:

ja7jkpml-hjexniqyu-_5e2vrau.png

Самое главное поле — STATUS, «открыт» — добавляется и начинает мониториться, любой другой статус — деактивирует узел сети. Объекты не удаляем, чтобы сохранить исторические данные и статистику.

После тестов пришлось дописать функцию пинга, чтобы перед первичным добавлением проверять доступен ли хост, т.к. на практике стали попадаться статусы «Открыт», которые не совсем еще открыты, но уже почти.

def ping(ip):
    if os.system('ping -c 2 -W 1 %s > /dev/null'%ip) == 0:
        return True
    else:
        return False


Ранее в Zabbix API была функция host.exist, но в новых версиях ее совместили с host.get. Если узел существует, то запрос возвращает hostid в базе zabbix«а. Если не найден — возвращает 0. Для проверки теперь пришлось дописать existhost, но по факту это host.get.

existhost.py
#!/usr/bin/python3
import sys, getopt
from zabbix.api import ZabbixAPI

def main(argv):
   global aname
   try:
      opts, args = getopt.getopt(argv,"hn:",["name="])
   except getopt.GetoptError:
      print('existhost.py -n ')
      sys.exit(2)
   for opt, arg in opts:
      if opt == '-h':
         print('existhost.py -n ')
         sys.exit()
      elif opt in ("-n", "--name"):
         aname = arg

def exist(name):
   zapi = ZabbixAPI(url='http://z.noc.x5.ru/', user='user', password='pwd')
   hostget = zapi.host.get(search={'name':'%s'%name}, output='hostid')
   if hostget == []:
       return 0
   else:
       return int(hostget[0]['hostid'])

if __name__ == "__main__":
        main(sys.argv[1:])
        print(exist(aname))



Напоследок собираем информацию о проведенной работе, добавляем в логи, репорты и перемещаем обработанный файл в хранилище в OLD.

report.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import smtplib, sys
from email.mime.text import MIMEText    
def report(message, subject):
        me = 'zbx-scripts@x5.ru'
        you = 'mail@x5.ru'
        smtp_server = 'smtp.ru'
        msg = MIMEText(message)
        msg['Subject'] = subject
        msg['From'] = me
        msg['To'] = you
        s = smtplib.SMTP(smtp_server)
        s.sendmail(me, [you], msg.as_string())
        s.quit()
if __name__ == '__main__':
        report(sys.argv[2], sys.argv[1])



В целом база для мониторинга готова, пускаем скрипт в cron для автозапуска и забываем.

ilhk0rph3lcm1azoquz00y_zck0.jpeg

Как заполняли inventory


Мы уже не мы, мы — сетевики.

Второй этап — наполнение объектов данными, которые необходимы инженерам для работы. Необходимо видеть все данные для решения инцидента в одной системе, чтобы не бегать по разным системам, собирая информацию по кусочкам из разных источников. И так как основные технические данные в мониторинге, то остальные мы также втягиваем в мониторинг.
Для сбора и передачи необходимой информации в Zabbix был написан inventory.py, который в большой степени занимается сбором данных по SNMP с оборудования, и в меньшей парсингом Excel-файла.

Напрашивается вопрос — почему бы не использовать встроенные item«ы и заносить их результат в inventory средствами самого Zabbix? Ответа три:

  1. Недостаточная вложенность действий, т.к. часто необходимо вытянуть значение по SNMP и использовать результат в следующем запросе.
  2. Запускать один раз в день сбор данных по всем узлам внешним скриптом не нагружает основную деятельность мониторинга и не образуется очередь по item«ам
  3. Есть данные, которые не собрать по SNMP


Данные по провайдерам, без которых не обойтись инженерам 1-й и 2-й линии, хранятся в excel-файле на общем сетевом диске и актуализируются менеджерами, ведущими договоры по связи. Интеграция с файлом вызывала большие сомнения — парсинг excel, заполняемого вручную, который может поменять структуру, название, расположение и т.д., скорее всего будет постоянно вываливать ошибки. Но из-за отсутствия другого актуального источника таких данных пришлось использовать его. Чтобы как-то обезопасить себя от постоянных правок скрипта, договорились с менеджерами о структуре и корректном заполнении, объяснили, как будет выполняться автоматическая выгрузка и что важно соблюдать текущую структуру. На практике, конечно, возникали ошибки, но мы довольно быстро их отслеживали, ругались, но исправляли.

inventory.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys, json, pysnmp, ipaddress, xlrd
from datetime import datetime as dt
from pysnmp.entity.rfc3413.oneliner import cmdgen
def snmp(host, operation, *oid):
  generator = cmdgen.CommandGenerator()
  auth_data = cmdgen.UsmUserData('user', 'pwd', 'hash')
  transport = cmdgen.UdpTransportTarget((host, 161))
  getAtt = getattr(generator, '%sCmd'%operation)
  rst = (errorIndication, errorStatus, errorIndex, varBinds) = getAtt(auth_data, transport, *oid)
  if not errorIndication is None or errorStatus is True:
     return "Error: %s %s %s %s" % rst
  else:
     if operation=='get':
        return varBinds
     elif operation=='next':
        result=[]
        for var in varBinds:
           result.append(var)
        return result
def xlsdata(file, sap):
  rb = xlrd.open_workbook(file)
  sheet = rb.sheet_by_index(0)
  base = {}
  for i in range(0, sheet.nrows-1):
     sapnum = str(round(sheet.cell(i,4).value)) if isinstance(sheet.cell(i,4).value,float) else sheet.cell(i,4).value
     name = str(round(sheet.cell(i,8).value)) if isinstance(sheet.cell(i,8).value,float) else sheet.cell(i,8).value
     if sap.upper() == sapnum.upper():
        base = {
          'type'                : (sheet.cell(i,2).value),
          'serialno_a'  : (sheet.cell(i,13).value),
          'serialno_b'  : (sheet.cell(i,20).value),
          'tag'         : ('2') if sheet.cell(i, 20).value != '' else ('1'),
          'macaddress_a'        : (sheet.cell(i, 15).value),
          'macaddress_b'        : (sheet.cell(i, 22).value)
        }
        base['date_hw_purchase'] = dt.date(dt.now()).strftime("%d %B %Y")
  return (base)

def inventory(host, sap):
  BGPASBASE={}
  for line in open('/path/a.prokhorov/integration/BGP-AS-BASE.cfg'):
     if ':' in line:
        line = line.split(':')
        BGPASBASE[line[0]]='%s(%s)'%(line[1].rstrip(), line[0])
  ### Get Data from Operator
  shop = xlsdata('/mnt/oprf/providers_base.xlsx', sap)
  shop['date_hw_expiry'] = 'Failed'
  ### Get SNMP data
  shop['host_router'] = 'None'
  shop['host_netmask'] = 'None'
  shop['host_networks'] = 'None'
  try:
     ### Get Networks from router
     networks = ''
     for ip,mask in snmp(host, 'next', 'iso.3.6.1.2.1.4.20.1.1', 'iso.3.6.1.2.1.4.20.1.3'):
        networks = networks+str(ipaddress.ip_interface(u'%s/%s'%(ip[1].prettyPrint(), mask[1].prettyPrint())))+'\n'
     shop['host_networks']=networks
     ### Get BGP information
     bgppeers, ispnames = '',''
     for peer,asbgp in snmp(host, 'next', 'iso.3.6.1.2.1.15.3.1.7', 'iso.3.6.1.2.1.15.3.1.9'):
        asbgp = asbgp[1].prettyPrint()
        bgppeers = bgppeers+peer[1].prettyPrint()+'\n'
        ispnames = ispnames+(BGPASBASE.get(asbgp) if BGPASBASE.get(asbgp)!=None else asbgp)+'\n'
     shop['host_router'] = bgppeers.strip()[:38]
     shop['host_netmask'] = ispnames.strip()[:38]
     ### Get Vendor name and Model type
     hardware = snmp(host, 'get', 'iso.3.6.1.2.1.47.1.1.1.1.13.1', 'iso.3.6.1.2.1.47.1.1.1.1.10.1', 'iso.3.6.1.2.1.47.1.1.1.1.12.1', 'iso.3.6.1.2.1.1.1.0', 'iso.3.6.1.2.1.47.1.1.1.1.7.1')
     if str(hardware[0][1]) == '0235A325':
        shop['model'] = hardware[4][1].prettyPrint()
     else:
        shop['model'] = hardware[0][1].prettyPrint()
     shop['os_short'] = hardware[1][1].prettyPrint()
     shop['vendor'] = hardware[2][1].prettyPrint()
     version = hardware[3][1].prettyPrint()
     os = version.split('\n')[0]
     shop['os_full'] = version[:250]
     shop['os'] = ''.join(os.split(',')[:2])[:60]
     ### Make indicators
     shop['date_hw_expiry'] = 'Success'
     shop['date_hw_install'] = dt.date(dt.now()).strftime("%d %B %Y")
  except:
     shop.pop('host_router')
     shop.pop('host_netmask')
     shop.pop('host_networks')
  return shop
  #return json.dumps(dict([('inventory',shop)]), sort_keys=True, indent=4)
if __name__ == "__main__":
  print(inventory(sys.argv[1], sys.argv[2]))


BGP-AS-BASE.cfg

3216:Beeline
9002:Retn
2854:Orange
~omit~
8359:MTS


Файл BGP-AS-BASE.cfg представляет собой соответствие номера AS и названия провайдера. Нужен, чтобы определять провайдера, с которым установлено BGP (вдруг в файлике с договорами ошибка). Не использовалась внешняя база, т.к. есть много приватных номеров AS.

В части SNMP:

  • запрашиваем у роутера подсети по OID 1.3.6.1.2.1.4.20.1.1 и маски подсетей по OID 1.3.6.1.2.1.4.20.1.3 в одном запросе. Обрабатываем, переводим в вид x.x.x.x/xx и записываем в ячейку host_networks.
  • запрашиваем данные о ip адресах BGP пиров, а также их ASN, находим в созданной нами базе имя провайдера по номеру. Записываем их в поля host_router и host_netmask. Важно сразу сделать ограничение на 38 символов, т.к. эти поля не поддерживают большее количество. У нас названия полей в БД не всегда совпадают с данными, которые они хранят, т.к. использовали уже существующие поля в БД Zabbix, чтобы не возится с созданием новых полей в таблице. Правильные названия полей правили в WEB’е, путаницы не было.
  • выгружаем данные по вендору, модели и софту оборудования. Парсим, пишем в переменные. Конструкция, связанная с записью модели железки связана с тем, что у Cisco в некоторых моделях название пишется в другой OID(чаще всего для шасси), поэтому пришлось сделать дополнительную проверку данных.


Весь блок связанный с SNMP заключен в try-except, чтобы в случае отсутствия на оборудовании SNMP скрипт не вываливался, и мы хотя бы получили данные из Excel по провайдерам. Для понимания какая часть скрипта выполнилась, а какая нет — успешность SNMP блока записываем в поле date_hw_expiry, плюс записываем дату, когда последний раз смогли снять все данные по SNMP, и дату когда последний раз смогли найти данные в Excel файле.
ccheenqcce-qkrwbxcerarkji2e.png

Все это возвращается в виде готового для Zabbix«а JSON.

Обновление всех инвентарных данных запускается раз в день, выполняя выгрузку всех хостов и запуская inventory.py для каждого объекта.

mp-update.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from multiprocessing import Pool
import time
from zabbix.api import ZabbixAPI
from inventory import inventory
from report import report

def updating(shop):
  try:
    shop['inventory'] = inventory(shop['interfaces'][0]['ip'], shop['host'][-4:])
    shop.pop('interfaces')
    shop['inventory_mode'] = '1'
    shop.pop('host')
    print (shop['hostid'])
    return zapi.host.update(shop)
  except:
    print(">>>",shop['hostid'])
    with open ('/home/local/integration/error.txt', 'a') as err:
       err.write(shop['hostid'])
       err.write("\n")

if __name__ =='__main__':
  t = time.time()
  zapi = ZabbixAPI(url='http://z.noc.x5.ru/', user='user', password='pwd')      
  shopbase = zapi.host.get(output=['host', 'hostid'], groupids= ['12', '13', '14'], monitored="1", selectInterfaces=['ip'])
  pool = Pool(processes=10)
  p=[0 for x in range(0,len(shopbase))]

  for i in range(0, len(shopbase), 10):
     print ("Index:", i,"\n",shopbase[i],"\n")
     pool.map(updating,shopbase[i:i+10])
  pool.close()
  pool.join()
  print(time.time()-t)

  report('Инвентарные данные обновлены в Zabbix.', 'Inventory updating succeed')        



Используется multiprocessing (пример взят с бескрайних просторов интернета). Для поиска используем SAP ID, который у нас присутствует в названии хоста. Полученный вывод записываем update«ом в Zabbix.

Summary


Для осуществления всех интеграций, автообновления и актуализации встроенных механизмов API Zabbix более чем достаточно. Основные используемые функции — это host.get, host.create и host.update, которые вместе позволяют полностью управлять созданием и обновлением базы объектов мониторинга. Данные на вход этих функций можно подавать из любых систем и источников, которые есть в наличии.

Основные модули python, которые помогли нам справится с этой задачей: pysnmp, xlrd, zabbix.api, xml, ipaddress, json.
xlrd — парсинг excel.
xml — парсинг XML.
pysnmp — вытаскивание данных по SNMP с оборудования.

Взаимодействие по SNMP легче чем по SSH, как минимум потому что на практике железка более быстро отзывается на SNMP, чем на соединение по SSH, парсинг ответов SNMP практически не нужен, хотя CLI различных вендоров часто сильно разнится друг от друга, а выводы одной и той же команды могут отличаться даже в разных моделях одного вендора.

Основные применяемые OID'ы:
iso.3.6.1.2.1.4.20.1.1 — адреса всех интерфейсов роутера
iso.3.6.1.2.1.4.20.1.3 — маски подсетей всех интерфейсов роутера
iso.3.6.1.2.1.15.3.1.7 — все BGP пиры роутера
iso.3.6.1.2.1.15.3.1.9 — AS всех BGP пиров роутера
iso.3.6.1.2.1.47.1.1.1.1.13.1 — модель
iso.3.6.1.2.1.47.1.1.1.1.10.1 — краткая версия софта
iso.3.6.1.2.1.47.1.1.1.1.12.1 — вендор
iso.3.6.1.2.1.1.1.0 — подробная версия софта
iso.3.6.1.2.1.47.1.1.1.1.7.1 — модель, тип шасси

Dashboard пришлось немного дописать, чтобы разделить проблемы по торговым сетям и не мешать их в кучу. Также добавить несколько полей, например ip, имя провайдера и адрес, чтобы в случае массовой аварии достаточно было copy-paste из браузера в письмо все объекты с проблемой, сразу со всеми необходимыми для провайдера данными.
eam3zojwyhottbk5offhtkltg3o.png

Отдельно был написан «Workview», в котором мы можем найти всю собранную информацию, плюс графики, которые собираются для данного объекта.

m1m-jpml-eypxjlgqlsoltn7in4.png

© Habrahabr.ru