Отечественные компании стали активно продвигать свои решения по зонтичному мониторингу и каждый приходит со своим видением как это реализовать. Некоторые решения откровенно сырые, некоторые разрабатывались под другие задачи и были натянуты на зонтичный мониторинг, некоторые зациклились на украшательстве, универсальности, распределённости и решение становиться требовательным к ресурсам.
С моей точки зрения зонтичная система — это не тот монстр, который делать все от мониторинга до ведения инцидентов. Минимальный функционал зонтичной системы – это обработать события от разнообразных систем мониторинга, отобразить события на сервисно-ресурсной модели(СРМ), сгенерировать новые события для взаимодействия с 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 секунды.
Заключение
Для меня зонтичный мониторинг в первую очередь работа с событиями и СРМ. Источником событий может быть любая система мониторинга. Результатом работы этого приложения являются дашборды статусов СРМ, уведомления и инциденты. На самом деле это решение работает параллельно продуктивному решению на том же объеме данных и я планирую в свободное время устранять найденые баги и разрабатывать типовые интеграции.