Сигналы
Рекомендации по проектированию сценариев обработки событий и генерации Сигналов
Доступные примеры сценариев обработки событий
Проектирование сценария
Добавление сценария
В данном разделе документации рассматриваются общие принципы и инструменты обработки событий в Сигналы функционалом Автоматизации.
Вы также можете ознакомиться с нашей статьей на Habr про обработку событий от Zabbix.
Также в статье есть ссылки на видео-версию статьи.
Рекомендации по проектированию сценариев обработки событий и генерации Сигналов
Вот несколько общих рекомендаций, которые помогут разработать сценарий автоматизации для работы с Сигналами:
-
Изучите первичные события от источника данных. Для закладывания корректной логики работы алгоритма необходимо понимать входные данные. Это позволит избежать возможных ошибок в работе сценария, когда, например, в функцию попадает значение, тип которого она не способна корректно обработать.
- Какие в событии поля обязательны?
- Какие опциональны?
- Какие значения могут принимать?
- Могут ли быть вложенные сообщения и потребуется ли обрабатывать единичные объекты и/или массивы?
- Есть ли в событии идентификаторы, позволяющие связать событие с конкретной КЕ?
-
Изучите или разработайте требования к работе с сигналами конечными пользователями. Необходимо ответить на вопросы:
- Сигналы должны закрываться только оператором или это может делать автоматика?
- Для закрытия обязательно ли ожидание подтверждающих событий и есть ли они? Или допустимо закрытие через какой-то период времени?
- Нужно ли сигнал помечать какими-либо тегами для их дальнейшей фильтрации или составления отчетности?
- Должна ли выполняться какая-то корреляция по открытым сигналам?
- Есть ли события, которые должны игнорироваться?
- Предполагается ли какая-то автоматизация через бизнес-процессы? Возможно для этого в сигнале потребуется какая-то мета-информация и при их открытии Сигнал необходимо обогатить какими-либо данными (например, из связанной КЕ)?
-
Оцените требования к нагрузке. Необходимо понимать какое количество событий в единицу времени предполагается обрабатывать и сообщения какого размера будут подаваться на вход сценария. Требования к нагрузке позволят определить какие функции автоматизации стоит использовать: необходимы ли batch функции или они не обязательны.
-
Составление схемы. Перед непосредственной разработкой сценария будет полезно задокументировать в схематичном виде шаги сценария (Visio, PowerPoint и др.). Это поможет выявить до начала разработки возможные развилки сценария и предусмотреть их корректное разрешение. А также при необходимости доработки сценария иметь отправную точку от "AS IS" на пути к новому "TO BE".
-
Проведите негативное тестирование сценария. Подайте недопустимые входные данные, чтобы посмотреть, как сценарий справится с этими данными. Это позволит выявить все ли возможные ошибки будут корректно обработаны и в том числе не "уйдет ли сценарий в цикл?".
-
Проведите нагрузочное тестирование. Справляется ли сценарий с поступающей нагрузкой? Накапливаются ли очереди? Устраивает ли вас время выполнения полученного сценария? Если ответ на один из вопросов выше "Да", то сценарий необходимо оптимизировать (рассмотреть использование batch-функций, кеширование части данных) или спланировать увеличивать количества обработчиков. Также стоит обратить внимание на потребление памяти обработчиком при выполнении сценария - есть ли риски возникновения OOM?
-
Соберите обратную связь от конечных пользователей. Достаточно ли данных о проблеме в Сигнале? Нет ли дублирующих или пропущенных событий и лишнего шума? Что можно улучшить?
Доступные примеры сценариев обработки событий
В качестве дополнительного контента, команда разработчиков предоставляет доступ к типовым сценариям обработки первичных событий.
Все сценарии доступны в публичном репозитории Monq на платформе GitHub
Проектирование сценария
Давайте рассмотрим следующий алгоритм обработки событий в Сигналы и создадим по нему наш сценарий:
- Первичное событие - анализируем, что есть у нас на входе сценария и с какими данными предстоит работать
- Фильтрация событий по потоку данных и определение переменных - отбрасываем лишние события из сценария, которые могут вызывать ошибки в работе сценария из-за отличающейся структуры первичного события. А также определяем переменные для удобства работы со сценарием на холсте.
- Поиск открытых сигналов по первичному событию - производим базовую дедупликацию событий, схлопывая повторяющиеся события в одном Сигнале
- Есть открытый сигнал? - определяем дальнейший ход в сценарии (создание/закрытие/подтверждение)
Далее перейдем к добавлению сценария в системе.
Перед непосредственным созданием вашего первого сценария обработки событий рекомендуется ознакомиться с терминологией и общими понятиями о функционале Автоматизации:
Добавление сценария
-
Перейдите через основное меню в раздел Автоматизация - Сценарии - откроется экран управления сценариями.
-
Для добавления в систему нового сценария нажмите кнопку ➕ Создать сценарий в правом верхнем углу экрана.
-
Заполните основные параметры создаваемого сценария:
- Владелец сценария - Рабочая группа, которой будет принадлежать сценарий.
- Название - логически понятное название сценария.
- Тип - Signals Processor.
- Описание (опционально).
- Импорт сценария (опционально).
-
Нажмите кнопку Создать - сценарий будет создан и откроется конструктор сценария.
-
Каждый сценарий начинает работу с События запуска - блока
OnLogEvent
,
автоматически добавляемого при создании каждого сценария.- Удалить или изменить блок
OnLogEvent
нельзя. - В исходящем пине
Value
блокаOnLogEvent
содержится значение того самого события, которое мы рассматриваем
- Удалить или изменить блок
Пример события из Prometheus
Перед созданием любого сценария обработки первичных событий, нужно ознакомится с телом этого события. Рассмотрим модель события из "Prometheus Alert Manager":
{
"status": "firing",
"labels": {
"alertname": "KubeDaemonSetNotScheduled",
"container": "kube-state-metrics",
"daemonset": "fluent-bit",
"instance": "10.244.4.75:8080",
"job": "kube-state-metrics",
"namespace": "kube-system",
"prometheus": "monitoring/k8s",
"severity": "warning"
},
"annotations": {
"description": "3 Pods of DaemonSet kube-system/fluent-bit are not scheduled.",
"runbook_url": "https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubedaemonsetnotscheduled",
"summary": "DaemonSet pods are not scheduled."
},
"startsAt": "2024-02-01T05:48:08.4Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://prometheus-k8s-0:9090/graph?g0.expr=kube_daemonset_status_desired_number_scheduled%7Bjob%3D%22kube-state-metrics%22%7D+-+kube_daemonset_status_current_number_scheduled%7Bjob%3D%22kube-state-metrics%22%7D+%3E+0&g0.tab=1",
"fingerprint": "f96a727d3fa9501d"
}
Сперва нужно проанализировать содержимое события и понять, какие данные из него нам понадобятся. Давайте рассмотрим из каких же полей состоит данное событие:
status
- информация о статусе события, произошел какой-то сбой или наоборот восстановилась работа какого-либо сервиса. Возможные значения:firing
- авария,resolved
- восстановление.labels
- объект содержащий метки, переданные в событие из метрики. Набор меток может быть произвольным, но должен как минимум иметь набор меток для осуществления привязки к КЕ (например:daemonset
иnamespace
). А также полеseverity
, отражающее важность данного события.annotations
- объект содержащий аннотации из правила Alert Manager.startsAt
- время начала аварии.endsAt
- время восстановления. Содержит информацию только, еслиstatus
=resolved
.generatorURL
- ссылка на информацию с сырыми данными в Prometheus.fingerprint
- "отпечаток пальца", он же идентификатор события об аварии.
Проанализировав информацию, содержащуюся в событии, можно сделать вывод, что нам в первую очередь потребуются поля:
status
- в зависимости от значения данного поля мы будем понимать, что нам делать с сигналом - "открывать или закрывать?"fingerprint
- данное поле позволит нам осуществить дедупликацию событий и найти уже имеющийся сигнал в системе, если он естьlabels.severity
- поле, которое дает нам понять, сигнал какой критичность нужно открытьlabels.namespace
иlabels.daemonset
- поля, которые будем использовать для поиска конфигурационных единицannotations.description
- используем для указания названия сигнала
Следующим действием необходимо произвести фильтрацию поступающих событий в наш сценарий.
Фильтрация событий и определение перемененных
Процедура фильтрация необходима для того, чтобы не обрабатывать "чужие" события, которые могут отличаться по структуре и мешать работе нашего сценария в будущем.
-
Добавьте функцию
FilterByStreamId
в сценарий (справка):Функция позволяет осуществлять фильтрацию принимаемого объекта по полю id структуры _stream.
-
Соедините пины
Out
иIn
блоковOnLogEvent
иFilterByStreamId
Данным действием, после получения события передаем управление функции
FilterByStreamId
-
Чтобы получить значение идентификатора потока, по которому пришло событие необходимо разложить содержимое пина
Value
на составляющие. Добавьте в сценарий функциюBreakStruct
и соедините её с пиномValue
Служебная информация о потоке содержится в поле
_stream
соответствующего исходящего пина функцииBreakStruct
. -
Соедините пин
_stream
функцииBreakStruct
с пиномStream
функцииFilterByStreamId
. Таким образом вы передадите информацию о потоке данных, по которому пришло событие в функцию фильтрации. -
Укажите в функции
FilterByStreamId
значение пинаStreamId
равным идентификатору потока, события которого планируется обрабатывать в сценарии.В случае соответствия идентификатора Потока данных идентификатору, который содержится в первичном событии, дальнейшее управление пойдет по управляющему пину
Ok
, иначе - по пинуFailed
.
Как мы условились ранее, нам понадобятся данные из полей первичного события: status
, fingerprint
, labels.severity
, labels.namespace
, labels.daemonset
и annotations.description
. Определим переменные в сценарии и запишем значения данных полей в них:
-
Слева, в "Диспетчере объектов" нажмите на кнопку "+" в секции "Переменные" - "Локальные", чтобы добавить переменную "Variable"
-
Кликните курсором по добавленной переменной "Variable" и справа, в "Инспекторе объектов" задайте ей название, например
srcStatus
Проделайте данную операцию, чтобы создать переменные для всех наших полей.
-
Определите значение переменных, добавив на холст сценария переменные через функцию "SET"
Проделайте данную операцию для каждой переменной
-
Добавьте на холст функцию "BreakDynamic" и соедините ее с пином "source", чтобы извлечь данные из полей первичного события.
Дважды кликните по функции "BreakDynamic" и в "Инспекторе объектов" определите поля которые нужно извлечь из объекта
source
первичного события (используйте кнопку "+ Добавить пин"):Подобным образом нужно извлечь данные из объекта
labels
и соединить соответствующие пины с назначаемыми переменными:Не забудьте соединить пины управления.
-
Добавим еще одну переменную
srcEvent
с типомDynamic
, в которую запишем всё событие целиком, чтобы прикрепить потом слепок этого события к Сигналу
Поиск открытых сигналов по первичному событию
Перед тем как продолжить, нужно проверить наличие, возможно уже открытых Сигналов. Это необходимо делать для дедупликации событий и "схлопывания" повторяющихся событий в одном Сигнале.
Чтобы осуществить поиск, необходимо воспользоваться запросом к API Сигналов через функцию автоматизации FilterSignalsExpanded
.
-
Добавьте на холст функцию FilterSignalsExpanded и сразу задайте параметры функции:
- Scenario - системная переменная
Scenario
- CreatedAt - не используем, передаем
null
- ClosedAt - не используем, передаем
null
- DurationMilliseconds - не используем, передаем
null
- OwnerWorkGroupIds - системная переменная
OwnerWorkGroupId
через функциюArrayCreate
для преобразования в массив
В таком виде, при вызове функции, результат будет содержать информацию обо всех имеющихся сигналах в системе.
Для дедупликации нам не нужна информация:
- по сигналам, которые уже закрыты
- по сигналам, которые не относятся к данному первичному событию
- Scenario - системная переменная
-
Добавьте дополнительные параметры фильтрации сигналов:
-
Statuses -
Open
-
Labels - объект, содержащий информацию:
"fingerprint": "идентификатор события"
Для определения модели
labels
понадобиться создать локальную структуру через "Диспетчер объектов" и определить ее свойства:Добавьте на холст функцию
ArrayCreate
и соедините ее с пиномLabels
функцииFilterSignalsExpanded
. В качестве передаваемого элемента массива (пинa
) является системная структураLabelsFilter
. При помощи функцииMakeStruct
в значение пинаValue
необходимо передать нашу заполненную локальную структуруSignalFilter
, пинKey
остается пустым.Чтобы заполнить нашу структуру и преобразовать в тип
Dynamic
добавьте функциюConvertToDynamic
, в "Инспекторе объекта" этой функции укажите тип - локальную структуруSignalFilter
:.
Соедините все соответствующие пины между собой:
.
Добавьте на холст переменную
srcFingerprint
через функцию "GET" и передайте ее значение через еще одинMakeStruct
в пинValue
функцииConvertToDynamic
.
Таким образом, при вызове функции, она вернет нам только открытые сигналы, в метках которых есть поле
fingerprint
, равное значению из первичного события. -
Проверка результатов поиска сигнала
В зависимости от результата выполнения фильтрации при помощи функции FilterSignalsExpanded
мы определяем дальнейший ход выполнения сценария. Если сигнала еще нет и пришло аварийное событие - мы будем создавать сигнал, а если открытый сигнал найден - либо закроем, либо привяжем к нему событие. В зависимости от того какое событие пришло.
-
Добавьте на холст блок
ArrayAny
и соедините его с пиномSignals
функции фильтрации сигналов..
В зависимости от размера массива на входе функции устанавливается логическое значение пина
Result
. Если размер массива равен нулю -False
, и наоборот -True
-
Добавьте на холст функцию ветвления
Branch
и соедините входящий пинCondition
с пиномResult
блокаArrayAny
.Если сигналов нет, управление передается по пину
False
- рассматриваем сейчас. Если найден сигнал, управление передается по пинуTrue
- рассмотрим в следующем подразделе..
-
Перед тем как создать сигнал, нужно определить статус пришедшего события (мы сохранили его в переменной
srcStatus
). Если значение переменной равноfiring
- будем создавать сигнал, еслиresolve
- сигнал нужно закрыть, но закрывать нечего (открытый сигнал мы не нашли), значит просто выходим из сценария.Воспользуемся блоком
Switch
и передадим в него значение переменнойsrcStatus
:В зависимости от возможных значений, через "Инспектор объектов" добавим исходящие
Exec
пины -resolved
иfiring
:Если
srcStatus
=resolved
- выходим из сценария, так как открытый сигнал по закрывающему событию не был найден.Если
srcStatus
=firing
- продолжаем выполнение сценария и переходим к поиску связанной КЕ.
В зависимости от результата выполнения фильтрации функции FilterSignalsExpanded
и проверки статуса в первичном событии, мы определили дальнейший ход выполнения сценария. Допустим функция фильтрации ничего не нашла и вернула пустой массив Signals
в соответствующем исходящем пине и нам нужно создать сигнал с привязкой к КЕ.
Поиск связанных КЕ
Чтобы при создании сигнала его можно было привязать к КЕ, ее сперва нужно найти. Подробнее прочитать о методике поиска КЕ можно в этом разделе документации.
В данной статье рассмотрим использование функции GetConfigItemByUniqueKey
и предположим, что атрибуты namespace
и daemonset
являются ключевыми для некоторого типа КЕ.
В качестве входных параметров функция принимает:
- ConfigItemTypeId - идентификатор типа КЕ
- Attributes - модель атрибута
Допустим, что мы используем тип КЕ по умолчанию, и оставим ConfigItemTypeId=0. А для поля Attribute, по аналогии с фильтрацией сигналов по меткам, создадим локальную структуру - ConfigItemFilter
:
Обратите внимание на поле
@namespace
. Так как словоnamespace
является зарезервированным в C# мы должны добавить в начало символ -@
.
При помощи функций ConvertToDynamic
и MakeStruct
передаем в качестве атрибутов значения переменных srcNamespace
и srcDaemonset
:
В функции
ConvertToDynamic
не забудьте выбрать тип входящего пина - локальную структуруConfigItemFilter
.
Если на предыдущем шаге будет найдена КЕ - можно создавать сигнал, если КЕ не найдена - решать вам, нужен ли сигнал без связи с конфигурационной единицей или нет.
Допустим, что КЕ мы нашли и по пину Ok
передаем управление следующей функции CreateSignalExpanded
, чтобы создать Сигнал.
Создание сигнала
Добавляем на холст сценария функцию CreateSignalExpanded
и формируем входные параметры функции:
-
Пин
Scenario
- подключаем системную переменнуюScenario
-
Пин
Name
- будущее название Сигнала, подтягиваем из переменнойsrcDescription
-
Пин
Description
является опциональным, пропускаем -
Пин
Labels
- метки Сигнала, которые будут использоваться для дедупликации Сигналов-
Нужно подготовить локальную структуру, например
SignalLabels
с обязательным свойствомfingerprint
, которое мы используем для поиска открытых Сигналов в начале сценария -
Сформировать модель меток (json) через функции
ConvertToDynamic
иMakeStruct
-
Передать значение
fingerprint
из заранее определенной переменнойsrcFingerprint
-
-
Пин
OwnerWorkGroupId
- владелец Сигнала, системная переменнаяOwnerWorkGroupId
(текущий владелец сценария) -
Пин
Severity
- критичность сигнала.Так как критичность в первичном событии указана в виде строки (может иметь значения: critical, warning), а функция
CreateSignalExpanded
требует значение с типомInteger
, необходимо преобразовать это значение. Простым способом преобразования является прием с функциейSwitch
:в зависимости от значения переменной
srcSeverity
устанавливаем значение новой переменнойsrcSeverityInt
(critical=2, warning=4) -
Пин
ConfigItemsIds
- массив идентификаторов КЕ, которые будут связаны с созданным Сигналом. При помощи функцийArrayCreate
сформируем массив из идентификатора КЕ, которую заберем из исходящего пинаConfigItem
функцииGetConfigItemByUniqueKey
, добравшись доId
при помощи блокаBreakStruct
-
Пин
Events
- массив событий, которые будут связаны с будущим сигналом.Воспользуемся блоками
ArrayCreate
иMakeStruct
для определения массива из одного элемента, в котором будет содержаться наше первичное событие.- Пин
StartEventId
соединяем с системной переменнойStartEventId
- Пин
Type
заполняем вручную значениемOpening
(открытие сигнала) - Пин
Body
соединяем с ранее созданной переменнойsrcEvent
- Пин
-
Пины
Tags
,ConfigItemComponentIds
,ConfigItemComponentName
являются опциональными и нужны для обогащения Сигналов дополнительными сведениями, прочитать о которых можно в описании функции CreateSignalExpanded
После подключения всех перечисленных выше пинов, функция должна выглядеть подобным образом:
На этом шаге с созданием сигналов можно закончить. Далее рассмотрим процесс закрытия сигналов.
Проверка статуса и критичности первичного события
Для подтверждения или закрытия сигнала потребуется вернуться к функции FilterSignalExpanded
и рассмотреть тот вариант, когда функция находит и возвращает открытые сигналы.
-
Воспользуемся уже имеющимися на холсте блоками
ArrayAny
иBranch
, которые расположены после функцииFilterSignalExpanded
, и продолжим настройку сценария по пинуTrue
-
Перед тем как закрывать найденный сигнал нужно убедиться в том, что в первичное событие поступило именно "закрывающее" событие
-
Добавьте на холст:
- переменную
srcStatus
(методGET
) - функцию
Branch
- функцию
Equal
При помощи данных блоков сравним значение переменной
srcStatus
с константойresolved
и в случае совпадения закроем сигнал (пинTrue
), иначе выполним операцию подтверждения сигнала (пинFalse
). - переменную
Закрытие сигнала
Добавьте на холст функцию CloseSignal
с помощью которой закроем найденный сигнал. Основным входным параметром функции является идентификатор сигнала - SignalId
.
Достать идентификатор сигнала можно из массива найденных сигналов Signals
при помощи блоков ArrayFirst
и BreakStruct
В данном случае мы получаем только первый элемент массива, только потому-что мы уверены - в ответе будет только один сигнал, так как фильтруем по уникальному полю
fingerprint
.
Подтверждение сигнала
В том случае, если значение переменной srcStatus
у нас не равно resolve
мы можем прикрепить очередное первичное событие к имеющемуся Сигналу, тем самым подтвердить его.
Для обогащения Сигнала новым событием нам понадобится функция BindEventsToSignal
. Добавьте ее на холст сценария и соедините с пином False
блока Branch
.
В функцию BindEventsToSignal
нам нужно передать идентификатор Сигнала и массив событий, которые нужно привязать к нему.
Идентификатор Сигнала можно позаимствовать у уже имеющегося на холсте блока BreakStruct
:
С пином Events
поступим также, как и при создании Сигнала. Воспользуемся блоками ArrayCreate
и MakeStruct
для определения массива из одного элемента, в котором будет содержаться наше первичное событие.
- Пин
StartEventId
соединяем с системной переменнойStartEventId
- Пин
Type
заполняем вручную значениемConfirming
(подтверждение сигнала) - Пин
Body
соединяем с ранее созданной переменнойsrcEvent
В данной статье мы рассмотрели базовые моменты по созданию и закрытию сигналов в сценариях автоматизации Monq. Больше информации вы сможете найти в профильных разделах документации.