- PVSM.RU - https://www.pvsm.ru -
Всем привет.
С момента публикации статьи про «В меру Универсальное Устройство Управления» [1] прошло немало времени (а если быть точным, больше года). Немало, но недостаточно много, чтобы я таки написал нормальную программную начинку для этого устройства. Ведь не для красоты ж оно есть — оно должно собирать данные с датчиков и делать так, чтобы эти данные оказывались в системе мониторинга (в моём случае Zabbix)
За прошедшее время из программной начинки было реализовано следующее:
Были попытки написать отдельные мониторилки для ntpd и для gpsd. Много времени было потрачено на супер-мониторилку, которая должна была уметь читать конфиг, запускать процессы сбора данных из различных источников согласно конфигу, собирать данные из этих процессов и выводить на экранчик показания, одновременно давая возможность заббиксу читать эти данные. По факту получилось реализовать диспетчер процессов, который читал конфиг и плодил нужные процессы, и рисовалку на экране, которая получилась весьма крутой — даже умеет читать layout из конфига и менять содержимое экрана по таймеру, при этом собирая данные от процессов в тот момент, когда они нужны. Нет в этой супер-мониторилке только одного — собственно процессов, которые бы собирали данные. Ну и плюс были идеи сделать систему сигналов, чтобы функции кнопкам назначать, учитывать приоритеты разных источников данных, ну и так далее, но всё упёрлось в свободное время и в то, что эта супер-мониторилка получалась уж очень раздутой и монструозной.
На какое-то время я забил на разработку полноценной программной начинки. Ненуачо, скрипт же работает, а правило «работает — не трожь», как говорят, святое правило администратора. Но вот ведь незадача — чем больше хочется мониторить, тем больше надо скриптов писать и тем больше надо добавлять исключений в SELinux для заббикса (я ж не только raspi мониторю) — в дефолтной политике заббиксу (как и rsyslog, например) запрещено вызывать произвольные программы, и это понятно. Отключать SELinux для заббикса совсем или писать свою политику под каждый бинарник, который будет дёргаться, очень не хотелось. Поэтому пришлось думать.
А давайте вообще разберёмся, как можно собирать данные в систему мониторинга:
Я использую pull-мониторинг, не по религиозным причинам, а просто так сложилось. На самом деле разницы между push и pull немного, особенно на малых нагрузках (на одной из предыдущих работ делал Nagios+NSCA, большой разницы не заметил, элементы всё равно руками создавать). Можно было бы использовать zabbix_sender, если бы у меня уже был push-мониторинг, но его нет, а на нет и суда нет, а мешать одно с другим как-то неаккуратненько. А вот в вопросе, по какому протоколу мониторить, вроде бы выбор большой, да не очень — discovery поддерживается только через агента или через SNMP, что оставляет нас уже только с двумя вариантами. Агент отпадает из-за описанной проблемы с SELinux. Вуаля, у нас остаётся pull-мониторинг через SNMP.
Урраа! А чего ура-то? В линуксе вроде как есть snmpd, но как заставить его отдавать то, что нам нужно, но о чём snmpd не имеет ни малейшего понятия? Оказывается, у snmpd есть целых 3 (принципиально различных) способа отдавать произвольные данные по произвольным OIDам:
Я пишу на питоне, поэтому пошёл искать, а не реализовал ли кто уже протокол agentx. И ведь нашлись такие хорошие люди — https://github.com/rayed/pyagentx [2] и https://github.com/pief/python-netsnmpagent [3]. Второй проект вроде поживее, но первый показался проще. Я начал с первого (pyagentx), он работает и делает всё, что надо. Но вот когда я стал думать, а как в эту библиотеку передавать данные, захотелось таки разобраться со вторым пакетом (python-netsnmpagent). Проблема с pyagentx заключается в том, что так, как оно написано, оно не может получать данные от вызывающих функций, а следовательно, запрос свежих данных должен происходить прямо в функции, которая посылает обновления в snmpd, что не всегда удобно и не всегда возможно. Можно было, конечно, отпочковать что-то своё и переопределить функции, но по сути пришлось бы переписать класс почти целиком, чего делать также не хотелось — на коленке ж разрабатываем, всё должно быть просто и быстро. Однако нежелание разбираться с python-netsnmpagent таки победило и я нашёл способ передать данные в updater из pyagentx, но об этом ниже.
Следующий вопрос был такой — а как должна выглядеть архитетура? Попытка написать диспетчер, форкающий источники данных и читающий данные из них, уже была и закончилась не очень хорошо (см. выше), так что было решено отказаться от реализации диспетчера. И так удачно сложилось, что то ли я где-то увидел статью про systemd, то ли просто в очередной раз пощекотало давнее желание разобраться с ним поближе, и я решил, что диспетчером в моём случае будет systemd. Haters gonna hate, а мы будем разбираться, коли оно уже даже на raspi из коробки есть.
Какие полезные возможности systemd для себя я обнаружил:
С учётом этих находок в голове нарисовалась архитектура:
Одно очень нехорошее место — это разделяемое внутреннее состояние между потоком-коллектором и потоком-agentx. Но я себе это простил, потому что в питоне есть волшебный GIL, который решает вопрос синхронизации между двумя потоками. Хотя это, конечно, очень плохо и не по книге. Была мысль вынести разделяемое состояние в отдельный процесс и заставить процесс-agentx и процесс-коллектор работать с процессом-состоянием через сокет, но заломало меня делать ещё один сокет, писать ещё один юнит и так далее.
Почему мне не понравилось IPC в питоне применительно к данной задаче:
int random() {return 4;}
dbus выглядел решением всех бед, да и есть он везде, где есть systemd, но вот беда — pydbus требует GLib >=2.46, чтобы публиковать API, а в raspbian только 2.42. dbus-python объявлен устаревшим и неподдерживаемым. Короче, пока петух жареный в попу не клюнет, буду разделять состояние небезопасным образом.
При использовании SNMP в своих грязных целях есть ещё одна загвоздка — а как выбрать OIDы для своих наборов данных? Для этого есть специальная ветка в private, которая называется enterprises — .1.3.6.1.4.1.<enterprise_id>. Получить уникальный enterprise ID можно у IANA [5]. Когда схема OIDов определена, неплохо было бы написать MIB, чтобы самому не забыть, где что, ну и чтобы системам мониторинга было легче. Введение в написание MIBов есть тут [6].
В какой-то момент я обнаружил ntpsnmpd с соответствующим MIBом и возрадовался было до плеши, но когда скомпилил это чудо, обнаружил, что автор удосужился только реализовать несколько констант верхнего уровня и на этом выдохся. Я немного поковырялся в коде и так до конца и не понял, каким же хитрым образом автор взаимодействовал с ntpd (или ntpq), чтобы вытащить те константы, не парся вывод. Одно я понял точно — готового python API нет, а значит, ловить нечего, придётся этот MIB самому реализовывать.
В общем и целом, вся эта конструкция работает и является весьма живучей. Discovery в заббиксе работает, итемы создаются, графики рисуются, триггеры шлют алёрты — чего ещё для счастья надо? Код ещё не финализирован, так что не публикую.
Не везде можно вкрячить юнитовый корпус, но и вешать сопли по стенам тоже не хочется. Есть очень изящное решение — DIN-рейка. Кучи конструктивов с рейкой продаются на рынке, в которые можно и блок питания реечный поставить (я использую MeanWell DR-15-5), и всякие автоматы-узо-что_угодно. Соответственно, захотелось корпус на DIN-рейку для raspi. В качестве кандидатов рассматривались вот эти два товарища — модель от Italtronic [7] и RasPiBox [8]. Преимущество RasPiBox в том, что там уже есть плата для прототипирования и ввод питания осуществляется через винтовые контакты (через стабилизатор на GPIO), что удобно, но может быть небезопасно. Но стоит он больше, чем в 3 раза дороже, занимает больше места на рейке и не имеет прозрачного окошка. Модель от Italtronic также не идеальна — ширина её такова, что все готовые LCD-экраны 16х2 туда не влазят по ширине, то есть ценность прозрачного окошка резко падает, но за низкую цену я был готов этот недостаток простить.
Корпус оказался достаточно удобен, имеет места под крепления (а точнее, под установку) двух печатных плат или листа чего угодно. Я подложки делаю из акрила, завёрнутого в токонепроводящую ESD-защитную плёнку, пилю дремелем:
Платы внутри держатся только на силе трения и на небольших уступах с двух сторон, то есть никакого жёсткого крепления внутри не предусмотрено. Несмотря на кажущуюся величину, корпус маленький и места над самой raspi остаётся не так уж много, особенно если вставлять плату на нижний уровень. А плата мне нужна, так как нужно разместить несколько светодиодов и плату с RTC.
Подключать к новой мониторилке хочу датчики температуры и влажности. Для температуры наш выбор — ds18b20, он работает, но стоит сравнивать показания с поверенным термометром, датчик может врать на полградуса по спецификации. Для компенсации добавил примитивную коррекцию показаний на константу в конфиг, проверял вот таким термометром:
Оказалось, что мои экземпляры ds18b20 вполне себе не врут. А вот следующий датчик как раз-таки врёт, причём аж на 0,6 градуса. Впрочем, опять же, зависит от экземпляра — один врал, другой почти не врал.
С влажностью оказалось всё не так просто. Дешёвое или не работает с raspi совсем (потому что аналоговое), или нет библиотек (хочу, чтобы сразу и хорошо), или дорогое как авиационные кабели. Компромисс между удобством и жабой был найден в датчике Adafruit BME280 [9], который бонусом ещё и температуру с давлением показывает (но может врать, как я выше отметил).
Если ds18b20 можно просто завернуть в термоусадку и радоваться, с ВМЕ280 такой фокус не пройдёт. Идей про корпус было немало — и оставить как есть, припаяв провода и залив их клеевыми соплями (ушки для крепления уже есть, получается), и сделать мини-корпус из того же акрила, что и подложки под компоненты, и вычудить что-нибудь с 3D-принтером, благо есть один в зоне досягаемости… Но потом я вспомнил про яйца:
Это же идеальный корпус. Для датчика места хватает, разъём поставить можно, удобный доступ для обслуживания, подвесить можно везде или просто забросить куда-нибудь.
Подключать датчики к raspi решил через DB9. В USB линий мало, розетка RJ45 не влезла по габаритам. Датчик-яйцо решил подключить по USB, потому что в шкафу обнаружились остатки резаных USB-кабелей — не пропадать же добру:
Для защиты GPIO-гребёнки на raspi и для удобства разборки корпуса взял ещё одну гребёнку и припаялся к ней. Гребёнка угловая, что дало чуть больше места по вертикали, но я немного не подрассчитал и эта гребёнка уткнулась в резисторы для светодиодов. Всё, конечно, намертво завёрнуто в термоусадку, но момент, который в будущем стоит помнить. В итоге половинки корпуса всё ещё можно разнять, чтобы, допустим, поменять батарейку в rtc или саму raspi. Всё остальное (точнее, флешка) доступно для замены без открывания корпуса.
Одна рекомендация — не экономьте на кнопках. Я вот сэкономил, так кнопка не только дребезжит (с этим можно бороться, в библиотеке RPi.GPIO защита от дребезга предусмотрена), но ещё и срабатывает только в очень конкретном положении. Кнопку я предусматривал для программного отключения устройства на случай, если надо отключить питание (уже несколько раз убил ФС на флешке неаккуратным выключением), но оказалось, что мало что-то предусмотреть — надо ещё и документацию читать. Если вы, как и я, не читаете документацию, то знайте — overlay gpio_shutdown делает вовсе не то, что можно было бы предположить, а всего лишь выставляет на некотором пине высокий/низкий уровень при отключении, чтобы, например, внешний блок питания мог погаснуть. Для того, чтобы отключать raspi по кнопке, есть ядрёный модуль rpi_power_switch [10] (но его компилять надо, а для этого kernel-headers нужны) или пользовательский демон Adafruit-GPIO-Halt [11]. У меня будет свой hostd, который будет мигать светодиодами, вот заодно и на кнопку реагировать будет.
Получился программно-аппаратный комплекс для мониторинга, расширяемый, использующий актуальные технологии, устойчивый к сбоям. Части ПО можно обновлять и перезапускать независимо то других частей (спасибо systemd, это не потребовало от меня как от разработчика никаких усилий). А самое главное — получилось получить много удовольствия от процесса и от результата. Ну и маленькая тележка новых знаний добавилась.
Спасибо за чтение!
Автор: homecreate
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/200765
Ссылки в тексте:
[1] «В меру Универсальное Устройство Управления»: https://habrahabr.ru/post/255179/
[2] https://github.com/rayed/pyagentx: https://github.com/rayed/pyagentx
[3] https://github.com/pief/python-netsnmpagent: https://github.com/pief/python-netsnmpagent
[4] пишут: http://semanchuk.com/philip/sysv_ipc
[5] у IANA: http://pen.iana.org/pen/PenApplication.page
[6] тут: http://www.net-snmp.org/wiki/index.php/Writing_your_own_MIBs
[7] Italtronic: https://www.modmypi.com/raspberry-pi/cases/din-rail-mount/italtronic-din-rail-raspberry-pi-model-b-plus-case
[8] RasPiBox: https://www.modmypi.com/raspberry-pi/cases/din-rail-mount/raspibox-open-plus-prototyping-din-rail-raspberry-pi-case
[9] Adafruit BME280: https://www.adafruit.com/product/2652
[10] rpi_power_switch: https://github.com/notro/fbtft_tools/tree/master/rpi_power_switch
[11] Adafruit-GPIO-Halt: https://github.com/adafruit/Adafruit-GPIO-Halt
[12] Источник: https://habrahabr.ru/post/311502/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.