Отечественные компании стали активно продвигать свои решения по зонтичному мониторингу и каждый приходит со своим видением как это реализовать. Некоторые решения откровенно сырые, некоторые разрабатывались под другие задачи и были натянуты на зонтичный мониторинг, некоторые зациклились на украшательстве, универсальности, распределённости и решение становиться требовательным к ресурсам.
С моей точки зрения зонтичная система — это не тот монстр, который делать все от мониторинга до ведения инцидентов. Минимальный функционал зонтичной системы – это обработать события от разнообразных систем мониторинга, отобразить события на сервисно-ресурсной модели(СРМ), сгенерировать новые события для взаимодействия с Service Desk и системами уведомлений.
Поэтому предлагаю вам пример прототипа приложения, которое отражает мои взгляды как решить задачу зонтичного мониторинга. Ссылка на репозиторий.
Описание внешних систем
С моей точки зрения зонтичный мониторинг работает в среде, в которой есть следующие информационные системы:
CMDB – система в которой есть описание информационных систем и их компонентов. Например, виртуальные машины, сервера, сетевое оборудование, базы данных и другие логические объекты. Все это представляет собой граф под названием сервисно-ресурсная модель.
Системы мониторинга – почему во множественном числе? Часто под каждую задачу можно подобрать более оптимальную систему мониторинга. Например, зачем заводить Docker в Zabbix если проще воспользоваться решениями из мира Prometheus?! И каждая система мониторинга генерирует свои события на основе порогов.
Service Desk – система управления инцидентами в которой зонтичная система создает инциденты.
Каналы уведомлений – например отправка писем и сообщений в чаты. Конечно, только определенным людям на основе информации из CMDB.
Тут я поспешил и реализация последних двух пунктов в данной статье не затрагивается.
Объекты в приложении
Приложение основано на фреймворке Spring-Boot. В процессе экспериментирования с базами данных принял решение полностью отказаться от SQL. В результате в приложении объекты храниться в HashMap с индексированием нужных полей.
Приложение оперирует всего тремя объектами:
-
Event
-
Item
-
Связи между объектами Event и Item
Объекты Item и Event отличаются не значительно и содержат следующие общие поля:
final String id; Long version = 0L; final String source; final String sourceKey; final Map<String, String> fields; final BaseStatus status; Instant createdOn = Instant.now(); Instant updatedOn = Instant.now();
-
id — должен быть уникальным и определяется внешней системой
-
version — непрерывно возрастающий счетчик. Присваивается приложением при добавлении или изменении объекта
-
fields — map для хранения любых пар ключ-значение
-
status — enum показатель критичности из пяти вариантов
-
createOn — дата создания объекта
-
updateOn — дата изменения объекта
Описание других полей будут по ходу текста, что бы не сбивать с толку лишней информацией.
Связи между объектами Event и Item реализованы отдельным объектом ради повторного использования существующего кода. Эксперименты показали, что альтернативные варианты не дают особого выигрыша в скорости и читабельности кода.
Описание REST API.
С приложением взаимодействовать можно исключительно через REST API. Где это возможно используются массивы для массового добавления или удаления объектов. Список действий API простой:
@RestController @RequestMapping("/api/v1") public class ItemController { @PostMapping("/item") public ResponseEntity<String> addOrUpdate(@RequestBody List<Item> items) @GetMapping("/item/{id}") public ResponseEntity<Item> findById(@PathVariable String id) @DeleteMapping("/item") public ResponseEntity<Integer> deleteByFilter(@RequestBody List<String> ids) @GetMapping("/item/{id}/children") ResponseEntity<List<Item>> findChildrenById(@PathVariable String id) @GetMapping("/item/{id}/events") public ResponseEntity<List<Event>> findAllEventsById(@PathVariable String id) }
-
addOrUpdate — добавить объекты. Работает исключительно с массивом объектов
-
findById — получить объект по id
-
deleteByFilter — удалить объекты по списку id или по фильтру
-
findChildrenById — получить все дочерние объекты Item
-
findAllEventsById — получить все события объекта Item и его дочерних объектов
Описание логики
При добавлении объекта Event выполняются следующие шаги:
-
Сохранить или обновить существующий объект. Ответственность за уникальный id на внешней системе
-
Найти объект Item на который влияет объект Event
-
Рассчитать влияние объекта Event на объект Item
-
Создать связь между объектом Event и объектом Item
-
Запустить расчет статусов всех родительских объектов для объекта Item до самого верхнего
Поиск Item
Теперь подробней о том, как найти объект Item на основе объекта Event. Объект Item содержит поле filters.
public class ItemFilter { private final BaseStatus resultStatus; private final boolean usingResultStatus; private final Map<String, String> equalFields; }
Фильтр содержит список ключ-значение которое должно существовать у объекта Event в поле fields (набор ключ-значение). Так же фильтр определяет результирующую критичность объекта Event. Простой случай, когда результирующее влияние совпадет с статусом объекта Event.
Расчет статусов компонентов СРМ
В первую очередь на объект Item влияют объекты Event по простой логике: функция max.
Расчет влияния дочерних объектов Item на родительский зависит от поля rules. На данный момент всего два варианта алгоритма:
Default – поиск максимального статуса дочерних объектов
Cluster – возможно название уже не отражает сути, так как неожиданно нашел схему позволяющую закрыть разные ситуации расчета статуса одним решением
Правило Cluster содержит несколько параметров:
public class ItemRule { final BaseStatus resultStatus; final boolean usingResultStatus; final BaseStatus statusThreshold; final Integer valueThreshold; }
-
statusThreshold – порог допустимых статусов дочерних объектов. Другими словами фильтр по статусу дочерних объектов
-
valueThreshold – порог в процентах. Сколько дочерних объектов имеют статус выше, чем значение из п.1
-
resultStatus – результирующее значение статуса, которое будет принято если превышен порог из п.2 Если usingResultStatus=false то используется статус события
Хранение графа СРМ
Связи между объектами Item реализованы самым простым образом: объект Item содержит поле children в котором перечислены id дочерних объектов.
public class Item { /.../ final String id; private final Set<String> children; /.../ }
Легко загружать, не надо проверять существует ли дочерний объект. Легко массово обновлять. Этот список id дочерних объектов используются в момент расчёта статуса родительских объектов. Предусмотрена защита от колец в графе, но только в момент расчета статусов СРМ.
Загрузка объектов
Загрузку объектов Item и Event можно выполнять в двух режимах.
-
Внешний скрипт или приложение само определяет когда создавать, обновлять или удалять объект. Т.е. методы POST, GET, DELETE.
-
Реализована логика для тех, кто любить заливать массово скриптами по расписанию.
Загрузка объектов по расписанию работает в стиле ETL. Примерно так:
-
Выгружаем объекты из внешней системы
-
Преобразовываем объекты. Задаем фильтры и правила
-
Отдаем массив подготовленных объектов в Приложение
-
Выполняем команду удалить старые объекты, которые не обновились. Для этого REST API deleteByFilter есть альтернативные параметры. Удалить объекты у которых поле source равно параметру и sourceKey не равно параметру
Реализация отказоустойчивого решения Active-Active
Использованы идеи CRDT. Для чего и было ведено поле под именем version. Поскольку оно всегда растёт, то легко получить последние изменения зная последнее значение. Для борьбы с зацикливанием добавлено поле fromHistory. Это массив строк, куда помещается имя ноды. Разработано REST API команда findByVersionGreaterThan что возвращает все объекты у которых version больше параметра и fromHistory не содержит собственное имя ноды.
На данный момент приложение может опрашивать только одного соседа. Ограничение ничем не обосновано и можно легко доработать на массив соседей. Параметры из application.properties. Возникают мысли хранить конфигурацию в базе данных.
Самая больная тема — это удаленные объекты. Все удаленные объекты перемещаются в отдельную HashMap. Каждому удаленному объекту выставляется дата удаления и новое значение поле version. Метод REST API findByVersionGreaterThan возвращает в том числе и удаленные объекты. Когда нода получает объект у которого заполнено поле deletedOn то при наличии его в своем HashMap выполняет процедуру удаления. По расписанию объекты с датой удаления больше порога удаляются из HashMap.
Визуализация
Что бы как то визуализировать результат разработан минимальный интерфейс на основе vue.js. и библиотеки primevue. Интерфейс для демонстрации и дерево начинается с Item у которого Id = 1. Ссылка на репозиторий.
В репозитории можно найти примитивный скрипт load_obj_random.py что генерирует 10 тысяч Item и 10 тысяч Event объектов. Весь скрипт исполняется за 2-3 секунды.

Заключение
Для меня зонтичный мониторинг в первую очередь работа с событиями и СРМ. Источником событий может быть любая система мониторинга. Результатом работы этого приложения являются дашборды статусов СРМ, уведомления и инциденты. На самом деле это решение работает параллельно продуктивному решению на том же объеме данных и я планирую в свободное время устранять найденые баги и разрабатывать типовые интеграции.
ссылка на оригинал статьи https://habr.com/ru/articles/806785/
Добавить комментарий