Grafana Alerting. Понять и простить.

Мелкий графаненышь несется с оповещением

Как-то очень незаметно в мою жизнь вошли системы мониторинга. Сначала простенькие измерители температуры и влажности поселились на даче, потом они расползлись на городскую квартиру, к ним добавились датчики давления, освещенности, углекислого газа, ионизирующего излучения. Удалось вовремя остановиться, но на рабочем месте датчики моментально покрыли все критическое технологическое оборудование. В рабочем процессе получать данные о его состоянии в режиме онлайн уже благо, а иметь возможность проанализировать исторические данные, например, за годовой период вообще манна небесная. Осознание этой простой истины приходит не сразу, но постепенно добирается и до самого консервативного работника.

Сами датчики, как правило, реализуются на основе плат ESP8266. Они недороги, в меру производительны и позволяют без лишних затрат отправлять данные по Wi-Fi. А вот место, куда данные отправляются не так просто, по крайней мере, в моих случаях. ESP8266 подключившись к Wi-Fi сети, отправляет данные по протоколу MQTT на MQTT сервер (здесь и далее, все ПО установлено на одном сервере). Оттуда данные забирает Node-RED, где происходит их предварительная фильтрация, проверка и обработка. Часть данных напрямую отправляются во временную базу данных Influx второй версии (хотя в отдельных локациях все еще остались установки первой версии Influx), отдельные данные подвергаются трансформации, отрабатывает простенькая система оповещений (alerting). В общем на Node-RED работает бизнес-логика управления получаемыми данными от системы датчиков, генерируются события. То, что попадает в Influx, может быть обработано в самой базе данных. В Influx есть система визуализации и алертинга. Но для визуализации я использую существенно более мощный инструмент — Grafana.

Grafana предназначена непосредственно для визуализации данных в виде графиков, диаграмм, таблиц, показометров и любым другим доступным для понимания человеком способом. Свою работу система выполняет очень хорошо, поэтому ее все и используют, благо доступны как корпоративные версии, так и свободные. Помимо графиков в Grafana присутствует еще одна важная составляющая — алертинг (alerting). Это подсистема, которая по заданным правилам анализирует данные и генерирует предупреждения в виде оповещений (alerts). И собственно именно о ней дальнейший сказ.

Основной вопрос к alerting-у в Grafana — очень интересно, но ничего не понятно. Особенно после модернизации самой системы генерации предупреждений. Она стала существенно мощнее и более непонятной. Причем характерно, что чтение документации не делает систему предупреждений (ну или оповещений, кому как нравится) более понятной, так как в документации есть все, кроме того, что действительно нужно для работы. Поэтому большинство пользователей подсистемы начинают осваивать ее методом тыка, что несколько тормозит процесс внедрения, а в некоторых случая озадачивает.

Для чего нужен Alerting в Grafana

Несмотря на то, что Grafana — отличный инструмент для постройки многообразных панелей мониторинга, в которых отображается многочисленная и, безусловно, полезная информация, на которую должен реагировать человек, ведь именно для него и строятся все эти красивые графики и «приборные доски». Но все упирается неизменно в самого человека. Он не смотрит целый день в монитор на диаграммы и таблицы, он занят другой деятельностью, а в Grafana заглядывает от случая к случаю. И получается, что человек может банально пропустить развитие опасной ситуации. Например, температура в теплице опустилась ниже допустимой (разбито окно и холодный воздух поступает извне), уровень сточных вод в канализационном колодце выше критического (забит патрубок дренажного насоса), количество пакетов с нулевой метрикой превысило порог (в сети появилась петля из-за сбойного коммутатора), свободное место на диске с базой данных составляет 10% (данных слишком много, требуется упаковка базы). На таком этапе «показометры» на панели управления станут оранжевыми или красными и ситуацию можно относительно безболезненно исправить, если только человек отреагирует.

Но человек занят своими делами и не ведает о том, что происходит в его хозяйстве. Именно для таких случаев применяется система алертинга, система оповещения о наступлении нежелательного события. Иными словами, компьютер выдает пинок человеку с информацией о том, что нужно предпринимать какие-то действия для устранения возникшей ситуации. Сообщение алертинга может быть послано человеку или группе лиц любым из поддерживаемых способов: электронная почта, Slack, Telegram, Webex, Discord, Teams и многими прочими способами. Остается только натренировать самого человека реагировать должным образом на получаемые оповещения  (у меня разработана аппаратная реализация включающая довольно яркую мигалку в офисном помещении, чтобы привлечь внимание сотрудника к проблеме).

Иными словами — алертинг является средством оперативного реагирования о наступлении событий.

Как работает Alerting в Grafana

На ранних этапах развития Grafana алертинг в ней был не слишком функциональным, но оставался простым. Затем набор функций информирования расширился и одновременно усложнился, что стало причиной бесконечных вопросов пользователей. В рамках этой статьи я буду рассматривать настройку и тюнинг системы оповещений на основе Open Source Grafana версии 10.0.3. Вероятно, что с новыми релизами подсистема изменится и некоторые положения из статьи окажутся неактуальными.

Страница Grafana про алертинги
Страница Grafana про алертинги


В Grafana понимают, что создали непостижимого монстра, поэтому сразу же на странице подсистемы оповещений предлагают ознакомиться с документацией и видео как с ней работать.

Поэтому для начала успешной работы с оповещениями необходимо усвоить принципы, на которых основывается работа Grafana. После понимание основ, все остальное пойдет несколько легче. После изучения документации и анализа информации из сторонних источников стало ясно, что Grafana может использовать свой Alert Manager, а может выступать только в качестве системы, которая генерирует алерты, а их доставкой и обработкой занимаются внешние системы. В рамках данной статьи будем работать со встроенным Alert Manager, так как у нас задача разобраться именно с подсистемой оповещений в Grafana.

Grafana может работать с огромным количеством источников данных. В моем случае это база данных временных рядов (Influx), но в качестве источника может выступать все что угодно. Оповещения генерируются на основе получаемых Grafana данных. Грубо говоря, для формирования оповещения требуется превышение заданного порога значениями в данных, поступающих в режиме реального времени. Но, поскольку, в большинстве низлежащих систем есть возможность по манипуляции данными, их трансформацией, то Grafana может запускать оповещения и по историческим данным, если так будет настроено оповещение.

Как работает алертинг в Grafana
Как работает алертинг в Grafana

Итак, в основе всего лежат данные. Именно данные являются базисом для обработки и источником информации для оповещения. Но само оповещение генерируется не в самих данных, а в правилах их обработки (Alert rules в терминах Grafana). Именно в правилах обработки прописываются критерии определения наступления события, которое вызывает метаоповещение. Здесь рождается не сообщение, которое будет отправлено пользователям, а лишь потребность в отправке оповещений пользователям. А сами оповещения формируются после прохождения через политики оповещений (Notification policies) и в результате работы политик формируются сами оповещения (Alerting instances) которые по отдельности и доставляются потребителям, независимо друг от друга.

Notification policy, пожалуй, самая сложная для понимания на начальном этапе часть, так как она предназначена для размножения оповещений в зависимости от настроенных правил. Может содержать вложенные правила, последовательно обрабатываемые правила, может группироваться по папкам. Но само устройство политики довольно просто, она банально фильтрует и маршрутизирует оповещения основываясь на метках оповещения и группах оповещений.

Предположим у нас есть некий объект мониторинга, например, торговый центр. В нем происходит мониторинг протечек.  Всего есть два помещения для мониторинга: подсобка и банк. Для оповещения доступны две группы: охрана торгового центра и полиция. В случае если в ночное время произойдет протечка в подсобном помещении, то только охрана торгового центра должна быть оповещена, а если датчик протечки среагирует на воду в банке, то в этом случае необходимо помимо охраны вызвать еще и полицию. Реализовывается подобная логика на основе меток (Labels), которые создаются при настройке оповещения. Метки существуют не просто так, а имеют значения. Поэтому вариантов настроек логики работы политик оповещений может быть довольно много. Кстати, хоть метки и можно использовать для  хранения и выведения на экран дополнительной информации, но для этой цели лучше применять аннотации, а не метки.

Для примера возьму самый простой вариант, когда у оповещений есть две метки: Security и Police. Если метка имеет значение >0, то Notification Policy отправляет оповещение в охрану и полицию соответственно.

Метки в Grafana
Метки в Grafana

Соответственно при срабатывании датчика в подсобке будет сгенерировано событие, которое уйдет только в охрану, а вот сигнал из банка пойдет не только на пульт к охране, но и в полицию.

Alerting в Grafana — структура и детали

Для работы с оповещения в Grafana есть целое меню состоящее из:

  • Alert Rules — тут непосредственно настраиваем правила по которым генерируется метаоповещение.
  • Contact Points — в этом разделе настраиваем получателей оповещений, например, электронную почту конкретного сотрудника или общий электронный адрес. Здесь же указываются и шаблоны отправляемых оповещений.
  • Notification Policies — непосредственно правила создания и маршрутизации оповещений, которые будут отправлены получателям (Contact Points).
  • Silencers — фильтры по отключению оповещений по какому-либо правилу, для исключения перегрузки получателей дублирующими оповещениями или приостановки некритичных оповещений в неурочное время.
  • Groups — конфигурирование групповых рассылок.

Далее пройдем поэтапно полный процесс создания оповещения.

Alert Rules

Начинать создание оповещения можно либо с Alert Rules, либо с Contact Points. В логике Grafana предполагается для начала создать оповещение в Alert Rules, а затем уже создать все остальное, правила его обработки и куда отсылать. Буду придерживаться именно логики Grafana.

Создать новый Alert можно непосредственно в Alert Rules, а можно из стандартного графика типа Time Series. Остальные типы визуализации не поддерживают создание оповещений. Я рекомендую там, где это возможно, использовать создание оповещения непосредственно из графика, так как в этом случае все выборки данных уже будут добавлены в форму Alert.

Алертинг можно создать или просмотреть непосредственно в графике Grafana
Алертинг можно создать или просмотреть непосредственно в графике Grafana

Для тестирования я выбрал график с данными времени отклика (round trip) мониторинга доступности сервера kvv213.com и сервера провайдера интернет. График выводит время полученное в результаты работе утилиты ping в среде Node-RED по двум серверам и медианное время в усреднении за 1 час. Сами данные хранятся в базе данных временных рядов Influx первой версии, поэтому все запросы к ней выглядят примерно так:

SELECT 
     "value" 
FROM 
     "InternetServers" 
WHERE 
     ("Server" = 'kvv213.com' AND "Type" = 'IPv4-ping') AND $timeFilter

В приведенном выше графике используется четыре независимых выборки, хотя их количество можно было и сократить, получая одним запросом данные сразу по нескольких объектам мониторинга. Для отработки создания оповещений я выберу только время отклика от сервера kvv213.com.

Создадим из графика новое оповещение, нажав на кнопку создания оповещения, и система перенесет нас в режим редактирования настроек оповещения с предзаполненными данными из графика.

Этап 1

Grafana автоматически дала название оповещению в полном соответствии с названием самого графика.

Наименование алертинга
Наименование алертинга

Название меня вполне устраивает, но его можно поменять на любое другое.

Этап 2

При создании оповещения Grafana взяла все четыре запроса из графика и добавила их в Alert. Поэтому я удалю все перенесенные запросы и оставлю только один, который касается времени отклика сервера kvv213.com.

Запросы и выражения алертинга Grafana
Запросы и выражения алертинга Grafana

На этапе 2 настройки Grafana предлагает выбрать, что будет выступать источником оповещения: сама Grafana, Mimir или Loki без поддержки выражений или Mimir или Loki с предрассчитанными выражениями (в версии Grafana 10.1.0 данный выбор уже отсутствует). Берем вариант Grafana, так как у меня нет ни Mimir-а, ни Loki.

Mimir и Loki, что за?
Mimir — одна из разновидностей базы данных временных рядов (TSDB) предназначенной для использования совместно с Prometheus
Prometheus — база данных временных рядов (TSDB), система мониторинга и оповещений.
Loki — система агрегации и анализа лог-файлов.

Получается масло масляное, дублирующие и передублирующие друг друга системы. Но таков волшебный мир Open Source.

Как видно помимо самого запроса к данным, он помечен как A, в настройке Alert-а присутствует еще два выражения: E и F. Оповещения поддерживают несколько запросов к данным и/или несколько выражений, в этом случае каждый из них будет иметь собственную букву (наименование). Когда буквы заканчиваются, система начинает их нумеровать двойными буквами: AA, AB, AC и так далее. Но можно задать и свое имя запросу или выражению. Запросы нужны для получения данных, а выражения для выделения и определения параметра для оповещения.

Кстати, при настройке оповещения я столкнулся проблемой, что оповещение не может визуализировать полученные данные, мне выдавалась ошибка.

Ошибка из-за неверно указанной схемы доступа
Ошибка из-за неверно указанной схемы доступа

Хотя сам график работал без проблем. Ошибка крылась в описании схемы доступа к базе данных Influx.

Где необходимо указать схему в Grafana
Где необходимо указать схему в Grafana

Проблема заключалась в отсутствии указания непосредственно схемы доступа. И Grafana и Influx у меня установлены на одном сервере, поэтому доступ к базе данных в Grafana у меня был прописан просто как localhost:8086, для исправления ошибки оказалось достаточно добавить схему доступа, в моем случае это протокол HTTP.

Предварительный просмотр запроса и выражений алертинга в Grafana
Предварительный просмотр запроса и выражений алертинга в Grafana

После исправления ошибки доступа к данным я настроил выражения. Выражение E берет самое последнее значение из полученных данных запроса A (оно составляет 13.4 миллисекунды), а в запросе F происходит сравнение полученного значения выражения E с заданной величиной (она задана на уровне 10 мс). Поскольку выражение F назначено условием для срабатывания оповещения, то оно сразу же и отображает, что условие нарушено и оповещение срабатывает (Firing).

У оповещения может быть несколько состояний:

  • ОК — означает, что условие не нарушено.
  • Pending — означает, что условие нарушено, но еще не вышел временной интервал, который дает шанс на возвращение условия в определенные границы.
  • Firing — если условие не вернулось за время состояния Pending к нормальному значению, то оповещение срабатывает и происходит информирование контактов.
  • Resolved — после срабатывания оповещения оно вернулось к нормальному состоянию.

Любой из запросов или выражение оповещения может быть назначен в качестве условия срабатывания оповещения посредством нажатия на Make this the alert condition. Нарушением условия для срабатывания оповещения считается возвращение запросом или выражения результата большего или равного единице. Соответственно, если в приведенном примере назначить либо запрос, либо выражение E в качестве условия для оповещения, то оно сработает (состояние Firing), так как round trip до сервера существенно больше 1.

Этап 3

На этом этапе предварительно настраиваются права доступа и временные интервалы срабатывания оповещения.

Параметры маршрутизации оповещения
Параметры маршрутизации оповещения

Сперва необходимо выбрать (или создать) папку, в которой будет храниться создаваемое оповещение. Папка нужна для определения прав доступа к оповещению. Затем необходимо выбрать (или создать) группу оценки (Evaluation group). Она нужна для определения времени проверки нарушения условия оповещения. Применяется ко всем оповещениям в группе. Я выбрал значение 1 минута, соответственно Grafana раз в минуту будет осуществлять проверку значения. Без выбора папки и группы оценки система не даст сохранить оповещение.

Еще один параметр я установил в значение 5 минут. Этот параметр отвечает как раз за нахождение оповещения в состоянии Pending. На этапе 3 присутствует еще два параметра отвечающие за обработку состояний данных No Data (когда запрос не возвращает никаких данных или возвращаются Null) и Error (когда запрос возвращает ошибку вместо данных или же превышен предел ожидания). Возможны следующие варианты:

  • Оставить как есть, т.е. нет данных, значит нет данных или ошибка, ну и ладно.
  • OK — если данные не поступают или вообще недоступны, значит все хорошо, ситуация нормализовалась.
  • Alerting — вызывать оповещение, ситуация ненормальная.

Этап 4

На этом этапе добавляются дополнительные данные для оповещения. Изначально тут присутствуют ссылки на конкретную панель и график, дабы из оповещения путем простого нажатия на ссылку можно было бы вызвать соответствующий график или панель непосредственно в Grafana и постараться уяснить ситуацию на большем масштабе.

Добавление аннотаций к алерту
Добавление аннотаций к алерту

По своему желанию можно добавлять или удалять дополнительные данные, как и создавать свои. Например, я добавил дополнительный тип данных WhatToDo, в который прописал краткую инструкцию, что делать, если придет такое оповещение. Этап настройки дополнительных данных довольно важен, так как на его основе строятся шаблоны оповещений.

Этап 5

На этом этапе происходит настройка меток оповещения. Впоследствии маршрутизация посредством Notification policies, будет происходить основываясь на метках в оповещении.

Метки на основе которых будет происходить маршрутизация оповещения
Метки на основе которых будет происходить маршрутизация оповещения

Если не добавить метки, то обработка оповещения пройдет по основному (корневому в терминах Grafana) маршруту. Я добавил свой тип метки MyType и присвоил ему значение Server. На этом настройка оповещения завершена. Можно его сохранить.

После создания оповещения его можно просмотреть как на вкладке Alert графика к которому привязано оповещение, так и в Alert rules подсистемы оповещения.

Общий вид алертов
Общий вид алертов

На приведенной копии экрана видно, что в Main Folder и группе My_Myself_and_I присутствует одно оповещение, оно находится в состоянии Pending. Тут же можно посмотреть дополнительную информацию об оповещении, например, когда оно срабатывало или какие метки содержит.

Contact points

Следующим этапом имеет смысл настроить получателей оповещений. Прошу обратить внимание, что настраиваются непосредственно получатели, а не каналы. Для Grafana доступно два десятка встроенных типов интеграции (каналов для отправки оповещений), на текущий момент нет возможности расширять их количество посредством плагинов. Но благодаря наличию Webhook интеграции, можно самостоятельно приделать необходимый тип канала для отправки оповещений.

В качестве канала доставки оповещений, в рамках настоящей статьи, я использую Telegram. Для этого необходимо завести бота Telegram и узнать идентификатор чата, в который отправлять сообщения. Чат может быть личный, тогда бот будет слать сообщения конкретному человеку или же группой, в этом случае получателей будет несколько.

Настройка Contact Point-а
Настройка Contact Point-а

Вводим название, выбираем тип интеграции Telegram, вводим Token API бота (его, разумеется, следует создать заранее), вводим идентификатор чата. В принципе, этим уже можно начинать пользоваться. Для проверки можно воспользоваться кнопкой Test, но при остальных настройках контакта получателя послать тестовое сообщение через Telegram не выйдет. Проблема кроется в том, что при отправке тестового сообщения не генерируется необходимый набор данных и на сервера Telegram уходит пустое сообщение, в результате получаем ошибку 400 или 404 при работающем и верно настроенном канале.

Помимо основных настроек для интеграции Telegram, есть еще несколько опциональных:

  • Message — определяет сообщение, которое отправляется получателю, записывается в формате Go template (с ним будем разбираться позже), по умолчанию используется {{ template “default.message” .”}}.
  • Parse Mode — формат отправки сообщения, доступны HTML и два варианта Markdown.
  • Notification settings — можно отключить уведомления у получателей о пришедшем оповещении.

Общий вид Contact Point-ов
Общий вид Contact Point-ов

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

**Firing**  
  
Value: E=13.4, F=1  
Labels:  
 - alertname = Internet Servers responses  
 - MyType = Server  
 - grafana_folder = Main Folder  
Annotations:  
 - WhatToDo = Перезагрузить сервер  
Source: http://localhost:3000/alerting/grafana/b6e2eb53-41df-4c8b-b3a8-b8b421b5d544/view?orgId=1  
Silence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=MyType%3DServer&matcher=alertname%3DInternet+Servers+responses&matcher=grafana_folder%3DMain+Folder&orgId=1  
Dashboard: http://localhost:3000/d/FjCljMWMz?orgId=1  
Panel: http://localhost:3000/d/FjCljMWMz?orgId=1&viewPanel=2

Если сейчас остановиться, то в Telegram (да и по другим каналам) будут приходить примерно следующие сообщения, как приведено выше. Да, ими уже можно пользоваться, но информативность их довольно низкая, особенно когда весь текст без цветовых выделений, а его часть перенесена на несколько строк. Разобраться в подобном нагромождении довольно тяжело. Адрес сервера, в приведенном коде он localhost, настраивается в конфигурационном файле Grafana. Я рекомендую его все же настроить на вид, доступный для получателя, т.е. либо это внутренний сетевой адрес, либо внешний, если Grafana доступна за пределами локальной сети.

Для более красивого вида непосредственно оповещений, рассылаемых получателям, необходимо настроить шаблоны, к ним мы вернемся несколько позже, так как они довольно сложны. А сейчас продолжим настройку оповещений.

Silences

Допустим мне очень важно, что Grafana в автомате следит за тем, как пингуется мой сервер там, в снегах Петербурга, но я не хочу получать уведомления завтра, во время просмотра «Тупой и еще тупее 2». Для этих целей есть специальный механизм Silences, где можно задать период когда не следует отправлять оповещения, причем режим тишины можно настроить только для уведомлений имеющих определенные метки (для всех сразу заглушить оповещения нельзя).

Если перейти по ссылке из самого оповещения, то в настройку «глушителя» сразу добавляются все необходимые метки, дабы заткнуть на время именно это оповещение. Но здесь необходимо учитывать, что для успешной заглушки оповещения у пользователя должны быть права на проведение этой операции. Соответственно придется аутентифицироваться, что несколько неудобно, если с Grafana работаешь с компьютера, а оповещение приходит потом на телефон.

Silences — это временное отключение оповещений, постоянно расписание задается в Mute timings подраздела Notification policies (следующий раздел).

Notification policies

В этом разделе устанавливаются правила отправки оповещений конкретным получателям, а заодно настраиваются значения по таким отправкам по умолчанию.

Настройка политик уведомлений
Настройка политик уведомлений

При первоначальной установке или в случае когда не требуется разделять получателей оповещения по типам (меткам последних), можно ничего не настраивать. Значения заданные при установке Grafana вполне работоспособны.

В примере выше редактируется единственная политика по умолчанию для всего уведомлений. Выбрат контакт используемый в случае, когда не выбрано ничего другого (в этом случае созданный получатель в Telegram). Далее устанавливается принципы группировки всех политик, группировку можно отключить.

Также в политике устанавливаются временны́е значения для оповещений:

  • Group wait — время в течение которого оповещение не отправляется контактам в группе.
  • Group interval — время в течение которого не отправляются новые оповещения группе, которой уже было отправлено оповещение.
  • Repeat interval — время от отправки повторного оповещения после отправки изначального, если критическая ситуация не была устранена.

Помимо общей политики по оповещениям, можно создать вложенные политики, при помощи которых можно настроить индивидуальные параметры отправки оповещений, базируясь на метках и получателях. Во вложенных политиках можно переназначить параметры корневой политики, например, временны́е параметры или же выбрать конкретную настройку не отправки оповещений. Последний пункт, в отличие от Silencer, позволяет задать постоянные интервалы, когда не следует отправлять оповещения. Например, с 23:00 до 7:00 каждый вторник и четверг, а в остальные дни, с 23:00 до 7:30. Эту настройку стоит завести на вкладке Mute Timings в Notification policies.

Можно создавать вложенные политики от вложенных политик, при этом применяется наследование некоторых параметров от корневой политики. В самом простом случае это группировка.

Groups и Admin

Две сервисные группы, позволяющие окинуть единым взглядом все активные оповещения и настроить всю систему оповещений при помощи редактирования конфигурации подсистемы оповещений.

Административная панель оповещений
Административная панель оповещений

Информацию о сработавших оповещениях можно смотреть и на Alert Rules, но в Groups можно увидеть детали конкретного оповещения, например, какие значения были использованы во время генерации оповещения.

Аннотации при срабатывании алертов
Аннотации при срабатывании алертов

При срабатывании оповещения, на привязанном графике автоматически ставятся «зарубки» аннотаций (отображаются вертикальными пунктирами). Синий цвет означает ошибки, желтый перевод оповещения в статус Pending, а красный — срабатывание оповещения (статус Firing). При нормализации ситуации ставится зарубка зеленого цвета.

И да, в свежих версиях Grafana отсутствует ручной способ поменять состояние оповещения, его нельзя переключить из Firing, в OK. Поэтому следует аккуратно настраивать реакцию системы на ошибочные данные или на отсутствие данных.

Настройка и тюнинг сообщений в Grafana

Пожалуй, это самый сложный, но одновременно и интересный раздел. Его суть — настроить внешний вид оповещений так, чтобы было быстро понятно что произошло и куда собственно бежать. Настройка осуществляется посредством шаблонов (Templates), которые мы встречали в настройках получателя и в общей группе получателей. Шаблоны предназначены для применения для конкретных получателей (что спорно, так как эффективнее привязывать шаблоны не к получателю, а к оповещению), а не для интеграций (почта или Telegram), дополнительно шаблоны можно использовать и в настройках оповещений (в полях Summary and annotations).

Ситуация с настройкой шаблонов усугубляется тем, что далеко не все умеют в Go templates, далеко не все поля в оповещении есть в документации (скорее ситуация обратная). Для облегчения ситуации разработчики Grafana соорудили специальный симулятор оформления оповещений. Но ситуация все равно не разусугубилась, так как далеко не все поля доступны в симуляторе (по заверениям пользователей). Попробуем разобраться. В 10-й версии Grafana, и далее, благодаря симулятору стало несколько проще разбираться с вопросом.

При создании нового шаблона мы попадаем в инструмент по созданию шаблона оповещений.

Инструмент для настройки шаблонов alerts в Grafana
Инструмент для настройки шаблонов alerts в Grafana

Как и положено, сперва заполняем наименование шаблона, а затем переходим к его настройке. В симуляторе удобно то, что в него можно добавить типовое оповещение, свое оповещение или загрузить уже сработавшее оповещение. Все это попадает в поле Payload data. Причем функция с загрузкой уже сработавшего оповещения наиболее полезна, так как позволяет настроить шаблон непосредственно на то оповещение, которое уже было выполнено системой. Само оповещение — нормально оформленный JSON, но опять же, не все поля, что действительно сопоставлены с оповещением, есть в самом оповещении (в его JSON форме).

В левой части доступно сразу два окна: Content — здесь записывается шаблон и Preview — здесь отображается результат работы шаблона по данным из Payload data.

Подсказки по данным для построения шаблонов
Подсказки по данным для построения шаблонов

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

Помимо чистого Go templating в шаблонах Grafana можно использовать функции, применяемые в Grafana, например, логические конструкции с if и while или регулярные выражения. При всей этой мощи можно сделать очень гибкие шаблоны, позволяющие оформить оповещения в нужном стиле и с нужным набором данных. Но иногда, особенно когда документация не дает четкого ответа на необходимые вопросы, на свет появляются всамделишные монстры. Например, ребята с Habr-а, в попытке настроить шаблоны под себя изобрели такой вот код:

{{ define "tgshortbody" }}{{ range . }} <b>{{ or .Annotations.message .Labels.alertname }}</b> 
{{ range .Annotations.SortedPairs }}{{ if ne .Name "message" }}{{ .Name }}: {{ .Value }} {{ end }}{{ end }} 
{{ with .ValueString }}{{ reReplaceAll "[[][^]]*metric='{?([^}']*)}?'[^]]*value=([0-9]*([.][0-9]{,3})?)[^]]*](, )?" "$1: <b>$2</b>\n" . }} 
{{ end }}{{ with .PanelURL }}<a href="{{ . }}">Chart</a> {{ end }}{{ with .DashboardURL }}@<a href="{{ . }}">Dashboard</a> {{ end }}{{ with .GeneratorURL }} <a href="{{ . }}">Edit</a> {{ end }}{{ with .SilenceURL }} <a href="{{ . }}">Mute</a> {{ end }} 
{{ end }}{{ end }} 
{{ define "tgshort" }}{{ with .Alerts.Firing }}⚠️{{ template "tgshortbody" . }}{{ end }}{{ with .Alerts.Resolved }}✅{{ template "tgshortbody" . }}{{ end }}{{ end }}

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

Тут объявлено два шаблона: tgshortbody и tgshort. Причем второй предназначен исключительно для вывода графического отображения в виде ⚠️ или ✅ в зависимости от статуса оповещения. Значки, кстати, не являются какими-либо программными конструкциями. Это символы Unicode, такие же как 🔥, ☺, 🔩или 🚩. Отображение значка зависит от поддержки Unicode в операционной системе на которой просматривают оповещение и конкретного набора шрифтов.

Дополнительно хочется остановиться на жутком по виду регулярном выражении по форматированию ValueString, свойства, которое содержит метки и все выражения, примененные (смотри Grafana Alerting. Понять и простить. Этап 2.) в оповещении. Решение не только некрасивое, но, как и отмечает сам автор, абсолютно ненадежное, так как малейшие изменения в настройке оповещения приводят к неработоспособности этого регулярного выражения.

Общественность Grafana оказалась не менее озабоченной данным вопросом, поэтому на профильном форуме была поднята тема по решению проблемы правильного форматирования оповещений и конкретно вытягиванием нужного значения, по которому сработало оповещение. Результат получился следующий.

{{ with $values }} 
{{ range $k, $v := . }} 
	Location: {{$v.Labels.location}} 
	Variable: {{$v.Labels.variable}} 
	Alerting value: {{ $v }} 
{{ end }} {{ end }}

Этот кусочек Go template добавляется в само оповещение в качестве аннотации и под именем AlertValues.

Шаблоны Go и в описании оповещения
Шаблоны Go и в описании оповещения

Тут необходимо дать пояснение по поводу переменных. В шаблоне AlertValues делается перебор переменных в переменной $values, которая содержит результаты выполнения всех запросов и выражений, но не содержит саму выборку данных из запроса. Переменные $k и $v соответственно получают значение ключа (названия запроса или выражения) и его значения. Но это только полдела, так как далее уже непосредственно в шаблонах заводится следующий шаблон:

{{ define "myalert" }} 
[{{.Status}}] {{ .Labels.alertname }} 
{{ .Annotations.AlertValues }} 
{{ end }}
{{ define "mymessage" }} 
	{{ if gt (len .Alerts.Firing) 0 }} 
		{{ len .Alerts.Firing }} firing: 
		{{ range .Alerts.Firing }} {{ template "myalert" .}} {{ end }} 
	{{ end }} 
	{{ if gt (len .Alerts.Resolved) 0 }} 
		{{ len .Alerts.Resolved }} resolved: 
		{{ range .Alerts.Resolved }} {{ template "myalert" .}} {{ end }} 
	{{ end }}
{{ end }}

Здесь определяются целых два шаблона myalert и mymessage. Шаблон myalert ссылается на созданный ранее шаблончик в виде аннотации оповещения с именем AlertValues. Затем шаблон mymessage ссылается на myalert. В результате должна выдаваться вполне адекватная картинка.

В Grafana есть встроенные шаблоны, которые нельзя увидеть или отредактировать через интерфейс, но они используются если не завести свои. Определяются они следующим образом:

{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }}

{{ define "__text_values_list" }}{{ if len .Values }}{{ $first := true }}{{ range $refID, $value := .Values -}}
{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ $refID }}={{ $value }}{{ end -}}
{{ else }}[no value]{{ end }}{{ end }}

{{ define "__text_alert_list" }}{{ range . }}
Value: {{ template "__text_values_list" . }}
Labels:
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}{{ if gt (len .GeneratorURL) 0 }}Source: {{ .GeneratorURL }}
{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: {{ .SilenceURL }}
{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: {{ .DashboardURL }}
{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: {{ .PanelURL }}
{{ end }}{{ end }}{{ end }}

{{ define "default.title" }}{{ template "__subject" . }}{{ end }}

{{ define "default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing**
{{ template "__text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}

{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved**
{{ template "__text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }}

{{ define "__teams_text_alert_list" }}{{ range . }}
Value: {{ template "__text_values_list" . }}
Labels:
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}
Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}
{{ if gt (len .GeneratorURL) 0 }}Source: [{{ .GeneratorURL }}]({{ .GeneratorURL }})
{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: [{{ .SilenceURL }}]({{ .SilenceURL }})
{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: [{{ .DashboardURL }}]({{ .DashboardURL }})
{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: [{{ .PanelURL }}]({{ .PanelURL }})
{{ end }}
{{ end }}{{ end }}

{{ define "teams.default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing**
{{ template "__teams_text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}

{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved**
{{ template "__teams_text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }}

Шаблон используемый по умолчанию, может служить хорошим примером того, как следует создавать свои шаблоны. Язык Go создавался с благими намерениями. Разработчики планировали упросить язык, убрать из него все приводящее к многочисленным ошибкам при разработке, а заодно увеличить скорость его обработки. Миссия достойная уважения, но вот того, кто придумал Go templating, будет жарить Бельфегор на сковороде очень долго и нудно. А того, кто придумал прицепить Go templating к форматированию оповещений в Grafana, следует упечь в эцих с гвоздями вместе с сенобитом. Пользоваться и отлаживать шаблоны в Grafana невероятно неудобно. Тем не менее попробую нарисовать свой шаблон для отображения нужной мне информации. В качестве базиса возьму типовой шаблон из поставки Grafana и отсеку от него все лишнее и ненужное.

{{ define "ping"}}
{{ template "ping_body" . }}
{{ end }}

{{ define "ping_subject" }}{{ $firing := false }}**{{if gt (.Alerts.Firing | len) 0 }}FIRING:{{ .Alerts.Firing | len }}{{ $firing = true }}{{ end }}{{ if gt (.Alerts.Resolved | len) 0 }}{{ if eq $firing true}},{{ end }} RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}** {{ $first := true }}{{ range .Alerts.Firing }}{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ .Labels.alertname }}{{ end }}{{ range .Alerts.Resolved }}{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ .Labels.alertname }}{{ end }}{{ end }}

{{ define "ping_internal_title" }}Alert: {{ .Labels.alertname }}{{ end }}
{{ define "ping_text_alert_list" }}{{ range . }}
Value: {{ template "ping_text_values_list" . }}

{{ if gt (len .Labels) 2 }}Labels:
{{ range .Labels.SortedPairs }}{{ if ne .Name "alertname" | and (ne .Name "grafana_folder") }} - {{ .Name }} = {{ .Value }}{{ end }}{{ end }}
{{ end }}
{{ if gt (len .Annotations) 0 }}Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}{{ end }}
{{ if gt (len .SilenceURL) 0 }}[Silence]({{ .SilenceURL }})
{{ end }}{{ end }}{{ end }}
{{ define "ping_text_values_list" }}{{ if len .Values }}{{ $first := true }}{{ range $refID, $value := .Values -}}
{{ if $first }}{{ $first = false }}{{ else }} {{ end }}{{ if gt (len $refID) 5 }}{{ $value }}{{ end }}{{ end -}}
{{ else }}[no value]{{ end }}{{ end }}

{{ define "ping_body" }}{{ if gt (len .Alerts.Firing) 0 }}🔥 Firing:{{ $first := true }} {{ range .Alerts.Firing }}{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ .Labels.alertname }}{{ end }}
{{ template "ping_text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}
{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}🆗 Resolved:{{ $first := true }} {{ range .Alerts.Resolved }}{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ .Labels.alertname }}{{end}}
{{ template "ping_text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }}

Получившийся набор шаблонов можно разнести по разным «файлам», а можно оставить все в одном. При обработке шаблонов Grafana читает их все и собирает в единое полотно. Моей задачей стало отображение минимума информации для быстрого восприятия информации. Шаблон ориентирован на Telegram с форматированием Markdown, но вполне подойдет и для других средств коммуникации. Одной из основных задач было выделение и отдельное указание значение параметра, которое и привело к срабатыванию оповещения. Пройдемся по всему шаблону.

Основной вызов делается через шаблон ping, сюда можно добавлять дополнительные вызовы остальных шаблонов, если это требуется. Особое внимание следует уделить синтаксису вызова шаблона из Contact point и подшаблонов в ping. Правильный способ {{ template "ping_body" . }} именно так, с пробелами и точкой, означающей контекст передаваемый в шаблон (точка в этом случае передает весь контекст оповещения).

Шаблон ping_subject формирует итоговый ответ, в котором содержится информация об общем количестве сработавших и исправленных оповещениях, а также их перечисление. Выглядит это примерно так: FIRING:1 Internet Servers responses ([kvv213.com](https://kvv213.com/ "https://kvv213.com")). Использовать данный шаблон для Telegram нет особого смысла, так как он больше подходит для оповещений, доставляемых по медленным каналам, например, электронной почте, когда оповещений много и они группируются в одно сообщение. В Telegram обычно все уходит моментально.

Шаблон ping_body — ключевой по формированию сообщения об оповещении. Результат его работы следующий:

Пример получившегося оповещения в Telegram
Пример получившегося оповещения в Telegram

Несмотря на то, что Telegram, в большинстве случаев, отрабатывает оповещения по одному, здесь заложена возможность отправки сразу нескольких оповещений в одном сообщении. Между ними ставятся запятые. Шаблон ping_text_alert_list формирует основное тело сообщения, выводя всю необходимую информацию. Особо хочется остановиться на шаблоне ping_text_values_list именно он выводит данные о значении, приведшем к вызову срабатывания оповещения. Он выводит значения, полученные всеми запросами и выражениями оповещения. Значений может быть несколько, например, если был использован классический вариант обработки значения оповещения, где может сработать сразу несколько условий.

Классическое выражение как критерий для срабатывания оповещения
Классическое выражение как критерий для срабатывания оповещения

Например, в этом классическом условии проверяется сразу несколько (результаты А, B и C) значений на превышение порога. И если хотя бы одно из них срабатывает, то оповещение генерируется и отправляется по маршрутам потребителям. Сработать может сразу несколько, поэтому в шаблоне применяется конструкция с применением, в том числе, определения необходимости добавления запятой, после значения. Операция строится на основе эксплуатации переменной $first. И здесь я слегонца схитрил: если не применять больше никакой фильтрации, то система выведет результаты всех запросов, а не только того, который мне необходим (непосредственно классическое условие). Для этой цели я фильтрую все названия запросов и выражений по длине, так как все они называются одиночными буквами, а если их несколько, то к ним добавляются еще и цифры. Но, поскольку название можно придумать самостоятельно, то для нужного мне выражения я использую заведомо длинное наименование, что позволяет отфильтровать только его (в приведенном примере я его назвал RESULT).

Однако, а что делать, если хочется понимать четко, какой из датчиков вызвал оповещение? Ведь в большинстве отслеживаемых графиков используется множество источников для вывода информации и далеко не всегда очевидно, что превысило порог. По милости разработчиков Grafana, данная информация все еще может быть добыта, но уже с некоторыми сложностями.

{
    "annotations": {
      "__alertImageToken__": "39e630fa-4ba7-4f9e-acd4-3247c6861411",
      "__dashboardUid__": "pXgFcMZMk",
      "__orgId__": "1",
      "__panelId__": "14",
      "__value_string__": "[ var='RESULT0' metric='Heater' labels={} value=113.29 ]",
      "__values__": "{\"RESULT0\":113.29}"
    },
    "labels": {
      "__alert_rule_uid__": "b58897ab-a536-44b5-bfba-d521bc471afe",
      "alertname": "Gas Heater",
      "grafana_folder": "Main Folder"
    },
    "startsAt": "2023-08-27T14:28:00.000+03:00",
    "endsAt": "2023-08-27T14:44:00.000+03:00"
  }

Вот в таком виде, это JSON, оповещения прилетают обработчику Go templates. И здесь видно, что единственное место, откуда можно раздобыть информацию о сбойном датчике это __value_string__. Сразу обращаю внимание, что значений тут может быть несколько. Но вот только как их получить-то? На выручку смогут прийти только регулярные выражения, так как в Grafana для Go templates можно использовать только реализованные в ней функции.

{{ reReplaceAll "localhost:(.*)" "example.com:$1" "localhost:8080" }}

Вот так вот сухо приводится функция по модификации строк в Grafana. Первым параметром идет регулярное выражение, в данном случае определяются все символы после localhost:, затем формируется строка, состоящая из example.com, и к ней добавляются символы, полученные из третьего параметра. На словах всех выглядит довольно четко, однако реализация для неподготовленного человека может оказаться излишне сложной. Поэтому я поступил довольно просто. Взял регулярное выражение из одного из примеров выше. Оно работает вполне прилично, хотя в него закралась маленькая ошибочка, связанная с включением в заменяемые параметры десятичной точки и значения после нее. Еще одним моментом является неработоспособность регулярного выражения, если в исходной строке нет metric (metric в моем случае это название поля в базе данных) и value. В этом случае выводится вся строка ValueString. Например, если оповещение сформировано вот таким вот образом (вариант предлагаемый в Grafana по умолчанию).

  {
    "annotations": {
      "summary": "Instance instance1 has been down for more than 5 minutes"
    },
    "labels": {
      "instance": "instance1"
    },
    "startsAt": "2023-08-26T13:19:02.259Z"
  }

ValueString тут нет, но она все равно присутствует вот в таком вот виде:

[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], 
[ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]

В результате получился шаблон, который устраняет проблему вывода лишней информации если необходимая отсутствует:

{{ define "ping_extra_values" }}
  {{- range .Alerts -}}
    {{- with .ValueString -}}
      {{- $fort_nite := reReplaceAll "[[][^]]*(metric=)'{?([^}']*)}?'[^]]*(value=)([0-9]*([.][0-9]{0,3})?)[^]]*](, )?" "$1$3" . -}}
      {{- if eq $fort_nite "metric=value=" -}}
        {{ reReplaceAll "[[][^]]*metric='{?([^}']*)}?'[^]]*value=([0-9]*([.][0-9]{0,3})?)[^]]*](, )?" "$1: $2" . }}
      {{- end -}}
    {{- end -}}
  {{- end -}}
{{ end }}

В шаблоне добавлена предварительная проверка исходной строки на наличие двух литералов metric= (так называются параметры в моей базе временных рядов, у читателя они могут иметь отлично значение) и value=. И только если они присутствуют, происходит генерация правильной пары сенсора и его значения. Дополнительно обращаю внимание на знаки - в начале и конце шаблонных предикатов. Символы используются для удаления лидирующих и конечных пробелов, символов переноса строки, т.к. вывод на экран в Go templating зависит от местоположения предиката в шаблоне (влево, вправо, вверх, вниз), а традиционное форматирование все портит.

 - {{ template "ping_extra_values" . }}

Для вызова шаблона изнутри другого шаблона необходимо использовать контекст ., однако, далеко не всегда он доступен при вызове шаблона внутри другого шаблона.

При программировании шаблона важно не ошибиться, особенно при вызове других шаблонов, в установке контекста. В некоторых случаях Grafana обрабатывает такие ошибки, как критические, делает запись в логе и не отправляет вообще никакого оповещения. Что может сослужить дурную службу, оповещения возникают, но никто про них не знает, так как все ожидают, что им придет уведомление.

При создании шаблонов возникают сложности при отладке. Зачастую требуется получить несколько больше вариантов алертов для конструктора шаблонов, чтобы протестировать все возможные варианты комбинаций возникающий оповещений. Но, в конструкторе отображается только один последний алерт, да и то, если их давно не было, то и не отображается. Тем не менее Grafana имеет собственное хранилище данных, в которых хранятся аннотации и оповещения, последние сделаны как раз на основе аннотаций. Добраться до них можно посредством HTTP API. Получить все оповещения проходившие по Grafana и оставшиеся в системе можно посредством простенького запроса http://192.168.0.19:3000/api/annotations?limit=100&&type=alert который вводится прямиком в залогиненном в Grafana браузере (здесь 192.168.0.19 адрес сервера, где установлена моя Grafana). На выходе получаем огроменную простыню с алертами в виде JSON-а. Что-то типа того:

{
        "id": 344,
        "alertId": 3,
        "alertName": "",
        "dashboardId": 1,
        "dashboardUID": "pXgFcMZMk",
        "panelId": 14,
        "userId": 0,
        "newState": "Alerting",
        "prevState": "Pending",
        "created": 1693189564532,
        "updated": 1693189564532,
        "time": 1693189560000,
        "timeEnd": 1693189560000,
        "text": "Gas Heater {alertname=Gas Heater, grafana_folder=Main Folder} - RESULT0=104.140000, RESULT1=115.140000",
        "tags": [],
        "login": "",
        "email": "",
        "avatarUrl": "",
        "data": {
            "values": {
                "RESULT0": 104.14,
                "RESULT1": 115.14
            }
        }
    }

Пример вывода примечателен тем, что содержит превышения порога сразу по двум датчикам. Но, увы, никакой ValueString тут нет. В этом и заключается одна из фундаментальных проблем системы оповещения Grafana — отсутствует единый и полноценный формат данных для оповещений. В разных местах они выглядят по–разному, происходят «подковерные» перерасчеты параметров. Тем не менее настойчивости мне не занимать и пришлось искусственно сформировать оповещение, которое содержит сразу два сообщения.

{
    "annotations": {
      "__alertImageToken__": "784b6414-dda6-4350-ae8f-44adde7e081d",
      "__dashboardUid__": "pXgFcMZMk",
      "__orgId__": "1",
      "__panelId__": "14",
      "__value_string__": "[ var='RESULT0' metric='Heater Outtake' labels={} value=55.96 ], [ var='RESULT1' metric='Heater' labels={} value=65.95 ]",
      "__values__": "{\"RESULT0\":55.96,\"RESULT1\":65.95}"
    },
    "labels": {
      "__alert_rule_uid__": "b58897ab-a536-44b5-bfba-d521bc471afe",
      "alertname": "Gas Heater",
      "grafana_folder": "Main Folder"
    },
    "startsAt": "2023-08-28T21:14:00.000+03:00",
    "endsAt": "2023-08-28T21:17:00.000+03:00"
  }

Но, вся пакость ситуации заключается в том, что из-за внутреннего пересчета и формирования некоторых переменных, в том числе ValueString, нельзя просто так взять и использовать JSON описание оповещения, если его нет в собственной базе Grafana. А его там может не быть по причине того, что прошло уже слишком много времени или же условия срабатывания оповещения были изменены и все данные из внутреннего хранилища были стерты. В таком случае система возвращает вместо ожидаемой ValueString с кошерными значениями нечто-то типа следующего (равно как страдают и остальные вычисляемые переменные):

[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], 
[ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]

Все это существенно затрудняет вопросы, связанные с отладкой шаблонов. Очевидно, что шаблон ping_extra_values требует доработки для поддержки многорезультативных оповещений. А сам шаблон требует некоторых усилий для интеграции его в общий шаблон из-за контекста получения информации.

Картинки

Для лучшего понимания ситуации с наблюдаемыми параметрами к сухим цифрам имеет смысл добавить еще и вывод изображений, которые наглядно отображают изменение контролируемой величины.

Реализовать данную функцию довольно просто. Необходимо чтобы в Grafana был доступен плагин Image Renderer, а в конфигурационны файл добавить строчку caputre = true в группе [unified_alerting.screenshots] (и перезагрузить). В большинстве случаев этого достаточно, при отправке оповещения Grafana прикрепит к нему одно или несколько изображений отслеживаемой величины. Период, который берется для отслеживания, задается в настройках оповещения, в моем примере это now-3h, что означает последние три часа. Плагин генерации изображений тупо берет последний час, настройка отсутствует в том числе и в конфигурационном файле плагина.

Настройки генерации изображений для оповещений
Настройки генерации изображений для оповещений

Следует учитывать, что изображения могут быть добавлены как вложение к оповещению, такое работает в Telegram, E-Mail, Discord или Slack. Для остальных может потребоваться загружать изображения на облачные сервера (или веб-сервера), такое поведение у Google Hangsout или Teams. Но а есть такие, которые вообще не поддерживают изображения. И именно тут тот случай, когда лучше посмотреть в документацию.

Однако, далеко не всегда все работает так, как написано. Например, на моем сервере плагин был установлен, но отключен из-за отсутствия подписи.

Plugin disabled
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we discovered that there is no signature for this plugin. We can not guarantee the trustworthy of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of this plugin.

Для исправления ситуации я сначала удалил плагин (в качестве сервера используется Linux машина):

grafana-cli plugins remove grafana-image-renderer

А затем установил его заново:

grafana-cli plugins remove grafana-image-renderer

Попутно сопровождая все перезапуском Grafana.

Но переустановка плагина помогла лишь частично, сам плагин заработал, а вот картинки генерировать настойчиво отказывались. Просмотр файлов протоколов привело к осознанию, что картинки генерируются при помощи вызова браузера Chrome (поставляется вместе с плагином). Но Chrome не мог запуститься, т.к. отсутствует нужная ему библиотека, даже более, чем одна (они не поставляются вместе с плагином, должны быть уже в системе). Помогло чтение документации, на Ubuntu операция по исправлению следующая:

  1. Перейти в папку плагина, обычно это /var/lib/grafana/plugins/grafana-image-renderer/chrome-linux/
  2. Запустить lld libx11-6 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrender1 libxtst6 libglib2.0-0 libnss3 libcups2 libdbus-1-3 libxss1 libxrandr2 libgtk-3-0 libasound2 libxcb-dri3-0 libgbm1 libxshmfence1
  3. Если чего-то нет в наличии, то переустановить, например, apt install libx11-6 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrender1 libxtst6 libglib2.0-0 libnss3 libcups2 libdbus-1-3 libxss1 libxrandr2 libgtk-3-0 libasound2 libxcb-dri3-0 libgbm1 libxshmfence1

Проверить, не дожидаясь срабатывания оповещения, можно просто запустив в браузере URL которым вызывается запуск генератора картинок. URL отображается в логах рядом с ошибкой. На экране браузера отобразится график, а сама картинка сохранится /var/lib/grafana/png/ и будет лежать там до следующего вызова сборщика мусора (обычно раз в 10 минут).

Выводы

Grafana — мощнейший инструмент, позволяющий существенно облегчить мониторинг не только информационных систем, но и домашнего или производственного оборудования, природных или техногенных факторов. Графики Grafana строит замечательно, но вот с подсистемой оповещения в ней не все гладко, в том числе и после доработки подсистемы. Сложность заключается в очень скудной документации, от которой возникает больше вопросов, чем она дает ответов. Выбор Go templates для шаблонов оповещений — спорный момент, я бы предпочел Java Script. Сами оповещения «бегают» по системе в формате JSON, а данные в Go templates попадают в переработанном виде, что не позволяет отлаживать шаблоны на не живой системе. Доступные функции в шаблонах сильно ограничены, создать оповещение со сложной логикой — задача нетривиальная. А сделать форматирование, отличное от представления разработчиков, требует неимоверных усилий. Некоторые ошибки в шаблонах полностью выводят из строя оповещения построенные на шаблоне с ошибкой. Инструмент для построения шаблонов — замечательная штука, но работать в нем неудобно: поле для шаблона крошечное и его нельзя изменить, ползунки не всегда срабатывают, при изменении шаблона следует обязательно перезагрузить страницу Contact Points, иначе при следующем редактировании шаблона в «редактор» подгрузится предыдущая версия шаблона (данные подгружаются из браузера пользователя, а не из внутреннего хранилища Grafana).

Вопросов к подсистеме Grafana довольно много, но в целом систему можно полноценно и аккуратно использовать. Лучше уж с ней, чем без нее.

Полезные ссылки

Исходный код Grafana с описанием структур данных, полезно для построения шаблонов оповещений.
Документация Grafana по использованию Go templating для кастомизации шаблонов.
Заметка на Habr-е с попыткой адаптации шаблона под себя.
Онлайн утилита подбора символов Unicode.
Тема на форуме Grafana по вопросу форматирования шаблонов.

Update 10.2023

За время написания статьи и ее подготовки к публикации прошло несколько месяцев. За это время Grafana успела обновиться, обновление коснулось, в том числе, и подсистемы оповещений. Когда я пишу эти строки, на моем сервере уже крутится версия 10.1.5. Например, стала доступна функция отправки тестового оповещения с возможностью указания своего собственного, персонального набора данных, например, можно указать summary, description, заполнить оповещение метками.

По крайней мере, в этой версии, удалось разобраться с ошибкой, возникающей при отправке оповещения через Telegram. Загадка то работающих, то сбоящих оповещений оказалась до банальности простой. Все дело оказалось в формате, в котором отправляется оповещение через Telegram.

Проблема заключалась непосредственно в формате Markdown v2, которому периодически не нравятся символы формируемого оповещения. Для устранения проблемы можно переключиться, например, на простой Markdown или попытаться привести свое оповещение непосредственно к формату MDv2. Версия 10.1.5 уже не выдает 404, а репортирует 400-ю ошибку.

Update 12.2023

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

Работоспособность маленького и красивого регулярного выражения в эмуляторе.

Он отлично работает в эмуляторе, но в упор не хочет отрабатывать в Grafana. Увы.



Подписаться
Уведомить о
guest

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии