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

Наш подход

Мы решили, что напишем приложение/демон, которое может авторизоваться в контроллере 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

version: "2"

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

kubernetes

---
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               логин от unifi-контроллера [$CONTROLLER_LOGIN]
   --controller-password value            пароль от unifi-контроллера [$CONTROLLER_PASSWORD]
   --controller-address value             адрес unifi-контроллера (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 делаем чтобы входящий трафик отобразился вниз, да, я знаю, что есть трансформации, но у нас это отработало как-то криво, график пропал совсем, так и не поняли в чём дело.

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

Комментарии (0)