Всем привет! На днях захотелось сделать графики по всем нашим точкам доступа, у нас их много, часть на базе 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
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 делаем чтобы входящий трафик отобразился вниз, да, я знаю, что есть трансформации, но у нас это отработало как-то криво, график пропал совсем, так и не поняли в чём дело
и на выходе получаем возможность просматривать график по любой из точек доступа:

ссылка на оригинал статьи https://habr.com/ru/post/658863/
Добавить комментарий