Отечественные компании стали активно продвигать свои решения по зонтичному мониторингу и каждый приходит со своим видением как это реализовать. Некоторые решения откровенно сырые, некоторые разрабатывались под другие задачи и были натянуты на зонтичный мониторинг, некоторые зациклились на украшательстве, универсальности, распределённости и решение становиться требовательным к ресурсам.

С моей точки зрения зонтичная система — это не тот монстр, который делать все от мониторинга до ведения инцидентов. Минимальный функционал зонтичной системы – это обработать события от разнообразных систем мониторинга, отобразить события на сервисно-ресурсной модели(СРМ), сгенерировать новые события для взаимодействия с 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;
}
  1. statusThreshold – порог допустимых статусов дочерних объектов. Другими словами фильтр по статусу дочерних объектов

  2. valueThreshold – порог в процентах. Сколько дочерних объектов имеют статус выше, чем значение из п.1

  3. 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 не содержит собственное имя ноды.

Active-Active
Active-Active

На данный момент приложение может опрашивать только одного соседа. Ограничение ничем не обосновано и можно легко доработать на массив соседей. Параметры из 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 секунды.

Заключение

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

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