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

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

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



    Long story short, we have built a monitoring


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



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

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

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

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

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

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

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



    Первым был установлен 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. Очень много адресов приходилось бы опрашивать и автоматическое обнаружение занимало бы много ресурсов системы, но не решало бы задачи добавления нетехнической информации об объекте.

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


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

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

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

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



    Модуль sap-sync.py


    XML из SAP
    <?xml version="1.0" encoding="ISO-8859-1"?>
     <werks>
          <WERKS>1234</WERKS>
          <NAME1>4321-Пятерочка</NAME1>
          <PLANT_IP>192.168.1.50</PLANT_IP>
          <REGION>31</REGION>
          <PSTLZ>308580</PSTLZ>
          <CITY1>с.Мониторинговое</CITY1>
          <CITY2>Москвоская обл.</CITY2>
          <STREET>Ленина ул.</STREET>
          <HOUSE_NUM1>1</HOUSE_NUM1>
          <TEL_NUMBER>(999)777-77-77</TEL_NUMBER>
          <BRANCH>CH</BRANCH>
          <REGION>CH_MSK</REGION>
          <REGION_NAME>Регион Центральный</REGION_NAME>
          <FORMAT>CH_MSK_D</FORMAT>
          <STATUS>Объект открыт</STATUS>
     </werks>
    


    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 <path>')
            sys.exit(2)
        for opt, arg in opts:
            if opt == '-h':
                print('sync-sap-chg.py -p <path>')
                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
    

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



    Самое главное поле – 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 <name>')
          sys.exit(2)
       for opt, arg in opts:
          if opt == '-h':
             print('existhost.py -n <name>')
             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 для автозапуска и забываем.



    Как заполняли 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 файле.


    Все это возвращается в виде готового для 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 из браузера в письмо все объекты с проблемой, сразу со всеми необходимыми для провайдера данными.


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

    X5RetailGroup
    31,31
    Цифра в X5 Retail Group
    Поделиться публикацией

    Похожие публикации

    Комментарии 23

      +1
      Круто! Очень интересен такой опыт больших российских компаний!
        0
        Спасибо :)
        +1
        Спасибо, прочитал с интересом. Тема мне очень близкая. Тащу примерно такие же задачи, правда в меньшем объеме, но стараюсь по возможности автоматизировать. Для чего использую те же инструменты: zabbix, python, ansible еще.

        Отдельное спасибо за рассказ про компьютер под столом, общие папки и IPAM в xls — это прям откровение.
        А где же ci/cd и скрам с эджайлом?
        Я думал у вас почти Амазон :)

        Пара вопросов:
        1. Как себя ощущает сервер на виртуалке с 14К хостов и 144K Items?
        У меня железный Xeon трудится с 24 ядрами и 32Г памяти, обслуживает около 1К хостов и 60К Items и дышит с трудом

        2. «Недостаточная вложенность действий, т.к. часто необходимо вытянуть значение по SNMP и использовать результат в следующем запросе»
        LLD неплохо подходит для этого. Там как раз два прохода получается: Discovery rules + Items Prototypes

        3. На скринах засветились циски и проприетарный цискин DMVPN. У вас только сеть Заббиксом мониторится? Не пробовали что-нибудь специальное сетевое? LibreNMS например?

        4. В чем храните конфиги тех же цисок?
          0
          Отвечу по своему опыту с забиксом — 1к хостов, 77к итемов — один железный сервер, бд mysql и сервер забикса на нем, по железу i5 3570, 16gb ram. Все крутится относительно шустро, но после апгрейда ssd на nvme 970pro la вырос до 8-9, причем нагрузка от mysql, хотя очереди не больше 30 сек и данные в веб интерфейс прогружаются шустро. Вообще основной «тормоз» забикса — подсистема хранения. Когда перешли с обычных винтов на ссд — разница просто огромная, дальше просто апгрейдились по мере исчерпания места, производительности хватало.
            +2
            Рад что полезно :)
            По поводу стола могу сказать, что это было давно и мы искали подходы. Не все сразу получается сделать идеально, часто находим решение, пробуем и понимаем что нам нужно что-то еще, или что-то другое.

            По вопросам:
            1. Ощущает неплохо, основное на что мы стараемся делаеть упор — не мониторить все сразу, а создавать item'ы только по необходимым метрикам. Как пример — бывают неполадки связанные с кольцами, но мы не стали мониторить STP на всем оборудовании, т.к. их очень мало и они редко возникают, и проще осуществлять починку уже в real-time зайдя на оборудование и проведя диагностику. Если добавлять такой item на все объекты получится +14к айтемов и +14к триггеров. В общем, немного lean, делаем проверки по топ-проблемам.
            У нас сейчас item/trigger 146k/52k. Распеределение item по сервер/прокси_1/прокси_2 — 6k/70k/70k. Машины все виртуальные — 8 CPU, 16 RAM. Но сейчас, чтобы улучшить производительность, растем линейно — добавляем еще 2 прокси, плюс выводим базу на отдельный сервер. Веб всегда жил отдельно. В основном рост связан с увеличением числа объектов, плюс появление новых item, чтобы следить за новыми сервисами.

            2. Да, изначально было так, но исходя из бережливого подхода мониторим только необходимые параметры. Тут тоже пример — нам нужно следить только за внешними интерфейсами на маршрутизаторе, которые связаны с провайдером, аплинки в общем. LLD без фильтра выведет все интерфейсы роутера, коих может быть очень много и начет мониторить все. Если использовать LLD с фильтром, то у всех внешних интерфейсов должен быть один признак, например, description, чтобы по результату LLD мы могли отфильтровать. Но у нас description были разные, да и человеческий фактор может привести к ошибке (инженер зашел и поменял, логика сломалась). Чтобы этого избежать мы этим признаком выбрали наличие BGP сессии на интерфейсе. Т.е. находим через snmpwalk всех соседей (на самом деле смотрим ip source для bgp сессий), а потом находим интерфейсы с этими ip и используем их id. По сути тот же LLD, только запускает он внешний скрипт, а не стандартный SNMP.

            3. Для магазинов используем для сети только заббикс, выходит дешевле, т.к. опенсорс, и очень гибкий, все что угодно можем доделать. Недавно внедрили Splunk. Для корневой сети используем решение посложнее — CA Spectrum. Очень помогает Grafana. Есть желание попробовать описать эти решения в последующих статьях.

            4. Конфиги пока собираем через python-ssh, но сейчас развиваем тему автосбора средствами железок в git. По мере развития постараюсь описать и этот подход, если он будет интересен.
            +1
            Почему, почему не PRTG?
              +1
              Похоже на риторический вопрос. Как IOS vs Android. В целом, изначально выбрали заббикс, а развивать дальше лучше существующее, чем постоянно менять коней на переправе. Тем более, возможностей текущего решения хватает и путей развития и масштабирования тоже масса. PRTG к тому же платный.
              +2
              Лет этак 14 назад, когда я работал инженером в телекоме областного уровня, я написал свой мониторинг на Tcl :) Типа everything is serious, с многопоточным микроядром, модулями, шаблонами для одинакового оборудования, autodiscovery сущностей и связей между объектами, alarm'ами, сетевым API на основе текстовых команд с поддержкой мгновенных асинхронных уведомлений, и так далее. Где-то тысячи полторы железок это все мониторило (а если считать по item'ам — то десятки тысяч) на железе тех времен (на Solaris/SPARC) без особых проблем. Ностальгия :)
                0
                TCL Mon? еще было приложение в котором все это открывалось?
                  +1

                  Точно :) Было и приложение, которое по сети подключалось к нему и показывало карту сети, состояние объектов и alarm'ы, Netstate, его мой коллега разработал.

                    0
                    Я похоже с ним общался, он как раз давал исходники чтоб под линукс это собрать.
                    Самая первая система мониторинга, как же давно это было. Год наверно 2007
                      +1
                      Ну да, где-то середина 2000-х это была :)
                        0
                        сейчас погуглил проект на сайтах отсутствует. Можно порыться, где то у меня были все сорцы.
                          0
                          Они лежат у меня на Гитхабе (вместе с Netstate в src/contrib). Только репозиторий заархивирован, потому что разработка больше не ведется :)
                0
                Спасибо за пост, проделана просто огромная работа…
                Но как, так…
                «Данные по провайдерам, без которых не обойтись инженерам 1-й и 2-й линии, хранятся в excel-файле на общем сетевом диске и актуализируются менеджерами, ведущими договоры по связи.»
                Ребята вы берете инфу о магазинах из SAP, при этом храня данные по провам в excel файле? Что мешает использовать только SAP?
                «У нас в поле с IP- адресом в SAP хранится адрес сервера, а не маршрутизатора, но с помощью модуля ipaddress считаем первый адрес подсети, который в нашем случае всегда роутер»
                А, как вы поступаете с другими устройствами? Коммутаторами, точками например? Есть ли что-то на dhcp?
                Есть ли интеграции с сервисдеск системами? Много ли ложных срабатываний?
                Прошу прощения за такое количество вопросов:)

                  0
                  Ну что тут сказать. Legacy, legacy, legacy. Но честности ради, у нас идет сейчас большая работа по созданию единой CMDB которая учитывает и сетевое оборудование, и провайдеров и создает связи между ними, но это отдельная совсем тема. Тут больше о том, что любую боль можно преодолеть и даже автоматизировать ее. Улучшать можно бесконечно.
                  Другие устройства нас интересуют в меньше степени, поэтому Zabbix для маршрутизаторов. Его задача оперативно отслеживать доступность каналов связи и каналообразующего оборудования. Коммутаторы, как и роутеры, отсылают свои данные в Splunk, это тоже больше отдельная тема. Никто не скажет про коммутатор больше, чем он сам. Но не исключаю, что мы добавим их в zabbix также при наличии необходимости.
                  На DHCP держим только менеджмент точек доступа. Остальной менеджмент весь статикой.
                  Интеграция — созданием заявок в Remedy по выбранным событиям или цепочке событий.
                  Ложные срабатывания это сложный вопрос. Что считать ложным? Ложных не было, бывали случаи когда пропала связь одного Zabbix-Proxy с внешним миром, и он, абсолютно обоснованно, посчитал что все объекты выпали из сети и он забил тревогу. Но это правильное поведение.
                    0
                    Но честности ради, у нас идет сейчас большая работа по созданию единой CMDB которая учитывает и сетевое оборудование, и провайдеров и создает связи между ними, но это отдельная совсем тема.

                    Если не секрет, какое решение для CMDB используете?
                      0
                      Не секрет. HPSM, плюс свои наработки для сетевого оборудования и каналов связи.
                  +1
                  и что важно соблюдать текущую структуру. На практике, конечно, возникали ошибки

                  Всегда держим структуру в первых строках файла. И файлом пользоваться проще, и структуру можно сверять на предмет изменений.
                    0
                    Согласен, такой же подход используем. Ошибки были в основном в нестандартных названиях в ячейках, так что Zabbix не мог их суммаризировать. Как например, скорость, 4mbps или 4096kbps. Все ради красоты)
                    0
                    Планируется ли выложить скрипты в github?
                    Почему это важно. Вы могли бы получить бесплатно новую функциональность от сторонних разработчиков через PR, бесплатное тестирование, бесплатное исправление ошибок через PR и т.д.
                      0
                      Привет. Таких планов не было, большинство скриптов узкоспециализированые под текущую структуру файлов/выгрузок и под инфраструктуру конкретной компании. Если кто-то решит использовать данный подход в своей деятельности — это здорово, но придется это оптимизировать под свою структуру. В текущих реалиях и скорости изменений — нам придется их видоизменить, или полностью переписать в течении года.
                      Если говорить о Zabbix, и скриптах содержащих в своей основе API, то это скорее возможности использования инструмента на практике. И если стремиться к новой функциональности, то скорее через развитие API Zabbix, чем через скрипты использующие его.
                      Есть некоторые скрипты, не относящиеся к теме, которые имеют смысл для PR, в широком смысле. Однако, на мой взгляд их пока недостаточно, чтобы действительно серьезно относиться к этому как к новой функциональности.
                      0
                      Смахивает на РЖД.

                      Короче если бы ВЫ не гнались за гуем ( почему то это люди из мира виндовс… )
                      То использовали бы NAGIOS и все бы упростилось ( добавление хостов ) и ускорилось ( загрука сервера ) в разы.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое