Unifi prometheus exporter

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! На днях захотелось сделать графики по всем нашим точкам доступа, у нас их много, часть на базе Mikrotik и с ними нет проблем, он легко опрашивается по SNMP и отдаёт статистику сразу по всем точкам, а вот с Unifi всё сложней, нужно опрашивать каждую точку доступа отдельно, а они у нас иногда меняют, соответственно, нужно какое-то решение, которое будет отслеживать эти изменения автоматически. На просторах Интернет не нашлось ничего готового, поэтому решили написать очередной "велосипед", но хотелось сделать максимально совместимо, чтобы этим могли пользоваться другие.

Наш подход

Мы решили, что напишем приложение/демон, которое может авторизоваться в контроллере Unifi, получить список точек доступа, а дальше по запросу в ручку /metrics будет обращаться ко всем точкам доступа по snmp и отдавать результат наружу в формате prometheus

Реализация

Подробно останавливаться на деталях реализации приложения не буду, всё залил на github и подробно задокументировал. Есть также docker-образ.

Опишу некоторые подходы, которые были использованы:

  • язык разработки Golang

  • для обработки cli-аргументов и переменных окружения использован фреймворк urfave/cli/v2

  • в качестве роутера http-запросов использовали gorilla/mux

  • для ограничения одновременного опроса точек доступа использован примитив синхронизации "семафор"

  • использовали mutex для синхронизации списка точек доступа между горутинами

  • для опроса точек доступа приложение обращается к сторонней реализации snmp-exporter

Настройка окружения

Нам нужно настроить две приложеньки:

  • snmp-exporter - демон, в которого мы будем обращаться из нашей приложеньки

  • unifi-prometheus-exporter - наша приложенька

Я сразу скажу, что я честно закопался в код snmp-exporter, в надежде, что можно будет импортировать код и использовать его функции нативно, но там всё не очень хорошо, код не модульный, его нельзя импортировать, поэтому придётся использовать две приложеньки.

Snmp-exporter

Для начала надо запустить snmp-exporter, к которому мы будем обращаться для опроса наших точек доступа, покажу как это можно запустить в docker-compose и в kubernetes. Установку docker-compose и kubernetes тут рассматривать не буду, это не является целью данного поста.

Экспортер слушает на порту 9116, чтобы опросить удалённый узел по snmp, достаточно послать в экспортер http-запрос в ручку /snmp c параметрами module (по умолчанию if_mib) и target (что опрашиваем), например:

curl http://127.0.0.1:9116/snmp?target=10.0.0.1

docker-compose

docker-compose.yaml

version: "2"

services:
  nexus:
    image: prom/snmp-exporter
    ports:
      - "9116:9116"

kubernetes

snmp-exporter.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: snmp-exporter
  labels:
    app: snmp-exporter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: snmp-exporter
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: snmp-exporter
    spec:
      containers:
      - image: prom/snmp-exporter
        imagePullPolicy: IfNotPresent
        name: exporter
        ports:
        - containerPort: 9116
---
apiVersion: v1
kind: Service
metadata:
  name: snmp-exporter
spec:
  ports:
  - port: 9116
    protocol: TCP
    targetPort: 9116
    name: snmp-exporter
  selector:
    app: snmp-exporter

unifi-prometheus-exporter

теперь запускаем нашу приложеньку, у неё есть ряд cli-аргументов, продублированных переменными окружения:

NAME:
   exporter - экспортер snmp-метрик от точек доступа unifi

USAGE:
   exporter [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --controller-login value               логин от exporter-контроллера [$CONTROLLER_LOGIN]
   --controller-password value            пароль от exporter-контроллера [$CONTROLLER_PASSWORD]
   --controller-address value             адрес exporter-контроллера (default: "https://127.0.0.1:8443") [$CONTROLLER_ADDRESS]
   --snmp-exporter-address value          адрес snmp-экспортера (default: "http://snmp-exporter:9116") [$SNMP_EXPORTER_ADDRESS]
   --access-points-update-interval value  интервал обновления списка точек (default: 1h0m0s) [$ACCESS_POINTS_UPDATE_INTERVAL]
   --listen-port value                    порт прослушки http-сервера (default: 8080) [$LISTEN_PORT]
   --parallel value                       количество потоков для опроса точек-доступа (default: 10) [$PARALLEL]
   --poll-timeout value                   таймаут для опроса точек доступа (default: 15s) [$POLL_TIMEOUT]
   --help, -h                             show help (default: false)

в принципе, тут, на мой взгляд, всё понятно. запускаем в docker-compose/kubernetes

docker-compose

version: "2"

services:
  nexus:
    image: maetx777/unifi-prometheus-exporter
    environment:
    - CONTROLLLER_LOGIN=admin
    - CONTROLLER_PASSWORD=123456
    ports:
      - "9116:9116"

kubernetes

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: unifi-snmp-exporter
  labels:
    app: unifi-snmp-exporter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: unifi-snmp-exporter
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: unifi-snmp-exporter
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '8080'
        prometheus.io/path: '/metrics'
    spec:
      containers:
      - image: maetx777/unifi-prometheus-exporter
        name: exporter
        ports:
        - containerPort: 8080
        env:
        - name: CONTROLLER_LOGIN
          value: admin
        - name: CONTROLLER_PASSWORD
          value: 123456
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: unifi-prometheus-exporter
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
    name: unifi-prometheus-exporter
  selector:
    app: unifi-prometheus-exporter      

остановлюсь на нюансах

  • CONTROLLER_LOGIN - при необходимости меняем

  • CONTROLLER_PASSWORD - при необходимости меняем :)

  • CONTROLLER_ADDRESS - меняем на адрес unifi-контроллера с указанием протокола и порта, например https://1.2.3.4:8443

  • SNMP_EXPORTER_ADDRESS - меняем на адрес snmp-экспортера, что запустили ранее, можно использовать dns-имя

  • передавать секреты в kubernetes указанным способом плохо, для этого есть специальный ресурс Secret, но здесь это не рассматривается, каждый сам под себя допилит

После запуска

После запуска мы увидим в логах unifi-prometheus-controller нечто подобное

INFO[0000] Daemon start
INFO[0000] Start http server
INFO[0000] Start fatals catcher
INFO[0000] Start signals catcher
INFO[0000] Start access points updater
INFO[0001] Http client authorized
INFO[0001] Update access points list
INFO[0001] Access point name Room1, ip 10.0.0.10
INFO[0001] Access point name Room2, ip 10.0.0.20

теперь можно обратиться к нашему контроллеру чтобы получить метрики найденных точек

# curl -s http://127.0.0.1:8080/metrics|grep ifOutOctets
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="ath0",ifIndex="6",ifName="ath0"} 0
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="ath1",ifIndex="7",ifName="ath1"} 3.319545249e+09
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="br0",ifIndex="9",ifName="br0"} 2.7572029e+07
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="eth0",ifIndex="2",ifName="eth0"} 4.93001573e+08
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="eth1",ifIndex="3",ifName="eth1"} 0
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="lo",ifIndex="1",ifName="lo"} 3572
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="teql0",ifIndex="5",ifName="teql0"} 0
ifOutOctets{ap_name="Room1",ap_ip="10.0.0.10",ifAlias="",ifDescr="vwire2",ifIndex="8",ifName="vwire2"} 0
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="ath0",ifIndex="6",ifName="ath0"} 0
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="ath1",ifIndex="7",ifName="ath1"} 6.28150693e+08
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="br0",ifIndex="9",ifName="br0"} 2.7178302e+07
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="eth0",ifIndex="2",ifName="eth0"} 4.95262026e+08
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="eth1",ifIndex="3",ifName="eth1"} 0
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="lo",ifIndex="1",ifName="lo"} 8180
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="teql0",ifIndex="5",ifName="teql0"} 0
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="vwire2",ifIndex="8",ifName="vwire2"} 0
ifOutOctets{ap_name="Room2",ap_ip="10.0.0.20",ifAlias="",ifDescr="wifi0",ifIndex="4",ifName="wifi0"} 0

запрос занимает какое-то время, чем больше точек - тем дольше будет работать запрос, но опрос происходит асинхронно в многопоточном режиме, так что обычно он укладывается в приемлемое время (у нас 10 точек опрашивается за 10 секунд)

также метрики "обогащаются" тегами с именем (ap_name) и ip-адресом (ap_ip) опрошенных точек

Prometheus

Как устанавливать prometheus я тут писать не буду, это не является целью данной статьи, у нас это дело работает на базе kubernetes_sd_config, в представленном конфиге kubernetes задаётся аннотация, которая сообщает системе prometheus порт и ручку для опроса

Grafana

Наконец, покажу простенький дашборд grafana для просмотра графиков

После создания дашборда сразу идём в Dashboard settings, создаём переменную

Query: ifOutOctets{ap_name=~".+"}
Regex: /ap_name="([^"]+)"/

смысл этой переменной в том, что мы выбираем все уникальные значения ap_name, чтобы дальше это можно было выбирать из списка:

теперь создаём график и пишем там формулы

A: irate(ifOutOctets{ap_name=~"[[ap_name]]"}[5m])*8
A.Legend: {{ap_name}} {{ap_ip}} {{ifDescr}} out

B: irate(ifInOctets{ap_name=~"[[ap_name]]"}[5m])*-8
B.Legend: {{ap_name}} {{ap_ip}} {{ifDescr}} in

умножение на 8 необходимо по той причине, что ifOutOctets - значение в байтах, а нам нужен график в мегабит/сек

умножение на -8 делаем чтобы входящий трафик отобразился вниз, да, я знаю, что есть трансформации, но у нас это отработало как-то криво, график пропал совсем, так и не поняли в чём дело

и на выходе получаем возможность просматривать график по любой из точек доступа:

Источник: https://habr.com/ru/post/658863/


Интересные статьи

Интересные статьи

Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
Всем привет. Ниже представлена расшифровка доклада с Big Monitoring Meetup 4. Prometheus – система мониторинга различных систем и сервисов, с помощью которой системные администраторы могут собир...
Если в вашей компании хотя бы два сотрудника, отвечающих за работу со сделками в Битрикс24, рано или поздно возникает вопрос распределения лидов между ними.
На сегодняшний день у сервиса «Битрикс24» нет сотен гигабит трафика, нет огромного парка серверов (хотя и существующих, конечно, немало). Но для многих клиентов он является основным инструментом ...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...