Внешние скрипты для оповещений¶
Канал «Процесс» внешней доставки оповещений запускает на узле вашу
программу или скрипт на каждое событие оповещения: возникновение
инцидента и его снятие. Так оповещения попадают в любую систему, которую можно
вызвать из скрипта: SMS-шлюз, мессенджер, вебхук, собственный мониторинг или
система заявок. Страница описывает, как узел запускает команду, что она получает и
как обрабатывается результат, и даёт две готовые заготовки: на /bin/sh и на
Python 3.
Общая модель внешней доставки и два других канала, Telegram и почта, описаны в Внешняя доставка алертов, окно настройки — в Назначения оповещений.
Настройка канала¶
Канал настраивается на вкладке «Процесс» окна «Назначения оповещений». Окно
открывается кнопкой «Назначения оповещений…» в настройках оповещателя
(Назначения оповещений). Те же значения лежат в секции alerter файла
настроек.
Поле |
Ключ |
Назначение |
|---|---|---|
«Включить назначение «Процесс»» |
|
Включает канал. По умолчанию выключен. |
«Команда» |
|
Командная строка: программа и её аргументы. По умолчанию пусто; включённый канал с пустой командой ничего не запускает. |
«Минимальная важность» |
|
Нижний порог важности события: |
«Таймаут, ms» |
|
Сколько миллисекунд даётся на один запуск: от 100 до 600000, по умолчанию 5000. |
Все четыре значения читаются при каждом событии, поэтому изменение действует со следующего оповещения, без перезапуска службы.
Как запускается команда¶
Без оболочки. Строка «Команда» делится на слова по правилам оболочки, но сама
оболочка не запускается. Слова разделяются пробелами; текст в одинарных кавычках
берётся как есть; в двойных кавычках обратная косая черта экранирует только "
и \; вне кавычек обратная косая черта экранирует следующий символ; пустые
кавычки '' дают пустой аргумент. Первое слово — программа, остальные — её
аргументы. Конвейеры |, перенаправления >, подстановка переменных $VAR,
~ и шаблоны имён файлов не работают: они уходят программе обычным текстом.
Всё это делается внутри скрипта.
Поиск программы. Если в первом слове есть /, это путь к файлу: файл должен
существовать и быть исполняемым, иначе каждое событие заканчивается ошибкой
process-command not executable. Имя без / ищется по PATH службы.
Надёжнее всегда указывать полный путь.
Окружение запуска.
Пользователь и группа — те же, что у службы:
pss.Рабочий каталог — корень
/; пути в скрипте указывайте полностью.Переменные окружения наследуются от службы (в том числе её
PATH), к ним добавляются переменныеPSS_ALERT_*.Открытые файлы и сокеты службы скрипту не передаются: у него есть только стандартные вход, вывод и поток ошибок.
Стандартный вывод скрипта уходит в системный журнал службы, то есть в вывод
journalctl -u pss(Журнал службы). Стандартный поток ошибок узел читает сам, после завершения скрипта (Результат, таймаут и журнал).
Что получает скрипт¶
Каждое событие передаётся двумя способами одновременно: полями в переменных окружения и целиком в формате JSON на стандартный вход. Аргументов от узла нет — только те, что записаны в «Команде».
Переменные окружения
Переменная |
Значение |
|---|---|
|
Устойчивый идентификатор инцидента, одинаковый у его возникновения и снятия,
например |
|
|
|
Важность числом: 3–7 (см. «Минимальная важность» выше). |
|
Важность словом: |
|
Код вида инцидента числом (Каталог кодов алертов). |
|
Тот же код текстовым обозначением, например |
|
Тип источника: |
|
Идентификатор источника, например номер потока. |
|
Имя узла, на котором возник инцидент. У оповещения, полученного от соседа по домену Meshwork, это имя соседа. |
|
Имя объекта для человека, например имя потока. |
|
Текст оповещения. |
|
Время этого события, секунды Unix (UTC). |
|
Время возникновения инцидента. |
|
Время снятия; |
Переменные удобны в /bin/sh: текст с пробелами и переводами строк передаётся
в них без искажений, а в списке процессов (ps) они не видны.
JSON на стандартном входе. Одна строка без завершающего перевода строки, после
неё вход закрывается. В JSON те же сведения, но важность и тип источника заданы
только числами (тип источника: 1 — System, 2 — Stream, 3 — Cluster,
4 — EPG, 5 — Mosaic, 6 — DVB, 7 — CS), а текстовых обозначений
кода и важности нет. Поля:
всегда —
id,state,ts,raised_ts,cleared_ts,severity,src_type,src_id,node,display_name,code,message;у инцидентов отдельного входа или выхода —
io_idиio_kind(inputилиoutput);у оповещений
AdminAction—admin_action_src;у «мерцающего» инцидента —
flapping(true) иflap_cycles, число его циклов (Когда запускается скрипт).
Читать стандартный вход скрипт не обязан: всё нужное есть и в переменных. Если же он читает вход, читать нужно до конца, а не одну строку.
Пример: на узле edge-1 у потока Sport HD (номер 12) 15 секунд нет данных.
Возникновение:
{"id":"2:12:3","state":"raised","ts":1761134400,"raised_ts":1761134400,
"cleared_ts":0,"severity":4,"src_type":2,"src_id":12,"node":"edge-1",
"display_name":"Sport HD","code":3,
"message":"[Sport HD] no data 15s (no-data threshold 15s)"}
Снятие того же инцидента через две минуты:
{"id":"2:12:3","state":"cleared","ts":1761134520,"raised_ts":1761134400,
"cleared_ts":1761134520,"severity":4,"src_type":2,"src_id":12,"node":"edge-1",
"display_name":"Sport HD","code":3,
"message":"[Sport HD] no data 15s (no-data threshold 15s)"}
Здесь JSON для наглядности разбит на строки; узел передаёт его одной строкой. У
снятия сохраняются важность и текст инцидента — текста «восстановлено» нет, о
снятии говорит поле state. Переменные окружения того же возникновения:
PSS_ALERT_ID=2:12:3
PSS_ALERT_STATE=raised
PSS_ALERT_SEVERITY=4
PSS_ALERT_SEVERITY_STR=Warning
PSS_ALERT_CODE=3
PSS_ALERT_CODE_STR=no-data
PSS_ALERT_SRC_TYPE=Stream
PSS_ALERT_SRC_ID=12
PSS_ALERT_NODE=edge-1
PSS_ALERT_DISPLAY_NAME=Sport HD
PSS_ALERT_MESSAGE=[Sport HD] no data 15s (no-data threshold 15s)
PSS_ALERT_TS=1761134400
PSS_ALERT_RAISED_TS=1761134400
PSS_ALERT_CLEARED_TS=0
Когда запускается скрипт¶
Возникновение и снятие. Скрипт запускается один раз при возникновении инцидента и один раз при его снятии. Промежуточные обновления активного инцидента (повтор, новый текст, смена важности) скрипту не передаются.
Порог важности. «Минимальная важность» применяется к инциденту, а не к каждому событию. Инцидент ниже порога не передаётся. Если его важность позже поднялась до порога, скрипт один раз получает
raisedв момент этого повышения.clearedприходит тогда и только тогда, когда было переданоraised, какой бы ни была важность в момент снятия. Переданным считается событие, отданное каналу: если доставка не удалась, инцидент всё равно считается открытым, и его снятие будет передано.Главный выключатель. При выключенном оповещателе узла (Оповещатель) скрипт не запускается вовсе, в том числе для снятия инцидента, о котором уже сообщалось.
Оповещения домена. При разносе оповещений по домену Meshwork скрипт получает и оповещения соседей; узел-источник указан в
PSS_ALERT_NODE(Разнос оповещений по домену). Если канал включён на нескольких узлах домена, об одном инциденте сообщит каждый из них. Чтобы не было дублей, включайте канал на одном узле.Мерцающие инциденты. Если инцидент раз за разом возникает и снимается (по умолчанию три цикла подряд с промежутками не больше 600 секунд), узел перестаёт снимать его на каждом цикле: инцидент остаётся активным, а одно снятие приходит, когда условие продержится восстановленным не меньше 600 секунд. Скрипт получает одно возникновение и одно снятие вместо пары на каждый цикл. Значения задаются ключами
flap-suppress,flap-cycles,flap-window-secиflap-settle-secсекцииalerterфайла настроек (Файл настроек pss.json).Липкие оповещения, которые снимаются только вручную, присылают снятие, когда оператор снимает оповещение (Просмотр и снятие алертов).
Перезапуск службы. Список активных инцидентов хранится в памяти и при перезапуске службы теряется без снятий. Условие, которое всё ещё действует, возникает заново, и скрипт получает второе
raisedс тем жеPSS_ALERT_ID.
Поэтому событие лучше обрабатывать по PSS_ALERT_ID и PSS_ALERT_STATE: повторное
raised обновляет известный инцидент, а не открывает новый.
Очередь и порядок¶
Скрипт запускается строго по одному: следующий запуск начинается после завершения предыдущего, события идут в порядке поступления.
Запуски ведёт отдельный фоновый поток канала. Медленный скрипт не задерживает ни другие каналы, ни работу узла, но задерживает следующие события этого же канала.
Пока скрипт работает, новые события ждут в очереди канала на 512 событий. Когда очередь заполнена, самые новые события отбрасываются, а в журнал службы записывается
delivery queue full, dropped alert(s).При остановке и перезапуске службы события из очереди отбрасываются; служба дожидается только запуска, который уже идёт.
Считайте бюджет: при шторме из сотни событий скрипт, работающий две секунды, отстаёт от событий на три с лишним минуты. Долгую доставку выносите в фон (см. следующий раздел).
Результат, таймаут и журнал¶
Код завершения 0 — событие доставлено.
Ненулевой код — ошибка доставки. В журнал службы (Журнал службы) пишется строка вида
[alert:process] deliver failed: process-command '/usr/local/bin/pss-alert.sh' exit 1: <текст из stderr>с последними 4 КБ потока ошибок.Поток ошибок узел читает, пока скрипт работает, и показывает только вместе с ненулевым кодом. Вывод в stderr при коде 0 ошибкой не считается и ни на что не влияет; обильный вывод в stderr не замедляет скрипт и не приводит к таймауту.
Таймаут. Если скрипт не завершился за «Таймаут, ms», узел принудительно завершает его (
SIGKILL) и пишетtimed out after <N> ms. Завершается только сам скрипт; запущенные им процессы продолжают работать.Повторов нет. Неудачное событие не отправляется повторно; канал переходит к следующему.
Ограничение частоты записей. Первая ошибка канала пишется сразу, следующие — не чаще раза в минуту, с числом ошибок за это время в конце строки (
(x12)).
Примечание
Фоновые процессы. Процесс, оставленный скриптом в фоне, следующие события канала не задерживает, даже если унаследовал поток ошибок. В версиях до 2.0.2.415 такой процесс останавливал канал до своего завершения, поэтому на узлах, где могут работать прежние версии, и вообще для аккуратности запускайте фоновую работу с закрытым вводом и выводом:
some-command </dev/null >/dev/null 2>&1 &
или с перенаправлением в собственный файл журнала:
setsid some-command </dev/null >>/var/log/pss-alerts/bg.log 2>&1 &
Безопасность¶
Скрипт работает с правами службы, пользователя
pss. Владельцем файла скрипта делайтеroot, а пользователюpssдавайте только чтение и запуск (режим750, группаpss): тогда служба не может изменить скрипт, который сама запускает.Не передавайте секреты в аргументах «Команды». Аргументы видны всем пользователям системы в списке процессов (
ps), хранятся в файле настроек и видны в веб-интерфейсе роли Admin (ролям Restricted admin и Viewer строка команды показывается скрытой). Токены и пароли храните в самом скрипте или в отдельном файле, доступном на чтение только пользователюpss.Текст оповещения (имя потока, сообщение) приходит из настроек и с источников. Не подставляйте его в команды оболочки без кавычек и не передавайте в
eval. Заготовки ниже передают значения только в кавычках.
Заготовка на /bin/sh¶
Скрипт пишет одну строку в журнал на каждое событие и завершается с кодом 0. Место для настоящей доставки отмечено комментарием.
#!/bin/sh
# Получатель оповещений Perfect Streamer (канал «Процесс»).
# Узел запускает скрипт на каждое возникновение и снятие инцидента.
# Поля события лежат в переменных PSS_ALERT_*, весь JSON - на стандартном входе.
LOG=/var/log/pss-alerts/alerts.log
# JSON со стандартного входа: одна строка без перевода строки в конце,
# поэтому читаем через cat, а не через read.
json=$(cat)
# Одна строка в журнал. Ошибка записи - ненулевой код: узел запишет её
# в свой журнал вместе с текстом из stderr.
printf '%s %s %s %s [%s] %s: %s\n' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$PSS_ALERT_STATE" "$PSS_ALERT_SEVERITY_STR" \
"$PSS_ALERT_NODE" "$PSS_ALERT_CODE_STR" "$PSS_ALERT_DISPLAY_NAME" \
"$PSS_ALERT_MESSAGE" >> "$LOG" || exit 1
# Здесь подключается настоящая доставка, например вебхук.
# Синхронный вызов должен уложиться в «Таймаут, ms» (по умолчанию 5 с):
#
# printf '%s' "$json" | curl -sS -m 3 -X POST \
# -H 'Content-Type: application/json' --data-binary @- \
# https://hooks.example.com/pss >/dev/null || exit 1
#
# Долгую доставку - только в фон и только с закрытым вводом и выводом,
# иначе канал встанет (см. «Результат, таймаут и журнал»):
#
# ( printf '%s' "$json" | curl -sS -m 30 -X POST \
# -H 'Content-Type: application/json' --data-binary @- \
# https://hooks.example.com/pss ) </dev/null >/dev/null 2>&1 &
exit 0
Заготовка на Python 3¶
То же на Python 3, только стандартная библиотека (Python 3.6 и новее).
#!/usr/bin/env python3
# Получатель оповещений Perfect Streamer (канал «Процесс»).
# Узел запускает скрипт на каждое возникновение и снятие инцидента.
# Поля события лежат в переменных PSS_ALERT_*, весь JSON - на стандартном входе.
import json
import os
import sys
import time
LOG = "/var/log/pss-alerts/alerts.log"
def main():
# Весь стандартный вход: одна строка JSON без перевода строки в конце.
raw = sys.stdin.read()
alert = json.loads(raw) if raw else {}
env = os.environ
line = "{} {} {} {} [{}] {}: {}\n".format(
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
alert.get("state", env.get("PSS_ALERT_STATE", "")),
env.get("PSS_ALERT_SEVERITY_STR", ""),
alert.get("node", ""),
env.get("PSS_ALERT_CODE_STR", ""),
alert.get("display_name", ""),
alert.get("message", ""),
)
# Ошибка записи завершит скрипт с ненулевым кодом, а текст ошибки
# уйдёт в stderr - узел запишет его в свой журнал.
with open(LOG, "a", encoding="utf-8") as f:
f.write(line)
# Здесь подключается настоящая доставка, например вебхук.
# Вызов должен уложиться в «Таймаут, ms» (по умолчанию 5 с):
#
# import urllib.request
# req = urllib.request.Request(
# "https://hooks.example.com/pss",
# data=raw.encode("utf-8"),
# headers={"Content-Type": "application/json"},
# )
# urllib.request.urlopen(req, timeout=3).close()
#
# Фоновые процессы (subprocess.Popen) запускайте с
# stdin/stdout/stderr=subprocess.DEVNULL и start_new_session=True
# (см. «Результат, таймаут и журнал»).
return 0
if __name__ == "__main__":
sys.exit(main())
Установка и проверка¶
Создайте каталог журнала, доступный на запись пользователю
pss:sudo install -d -o pss -g pss -m 750 /var/log/pss-alerts
Сохраните заготовку, например как
/usr/local/bin/pss-alert.sh(илиpss-alert.py), и выставьте владельца и права:sudo chown root:pss /usr/local/bin/pss-alert.sh sudo chmod 750 /usr/local/bin/pss-alert.sh
Проверьте скрипт вручную от имени пользователя
pssс образцом события:echo '{"id":"2:12:3","state":"raised","severity":4,"node":"edge-1","display_name":"Sport HD","code":3,"message":"test"}' | \ sudo -u pss env PSS_ALERT_ID=2:12:3 PSS_ALERT_STATE=raised \ PSS_ALERT_SEVERITY=4 PSS_ALERT_SEVERITY_STR=Warning \ PSS_ALERT_CODE=3 PSS_ALERT_CODE_STR=no-data \ PSS_ALERT_NODE=edge-1 PSS_ALERT_DISPLAY_NAME='Sport HD' \ PSS_ALERT_MESSAGE=test \ /usr/local/bin/pss-alert.sh echo "exit=$?" tail -n 1 /var/log/pss-alerts/alerts.log
Ожидается
exit=0и новая строка в журнале. Заодно засеките время: оно должно быть заметно меньше «Таймаута, ms».На вкладке «Процесс» окна «Назначения оповещений» укажите в «Команде» полный путь
/usr/local/bin/pss-alert.sh, задайте «Минимальную важность», включите назначение и сохраните.Проверьте доставку на тестовом потоке: прервите подачу данных на его вход (например, остановите передатчик). Через таймаут отсутствия данных (по умолчанию 15 секунд, Настройки алертера) возникнет оповещение
no-dataс важностьюWarning, а после возобновления данных — его снятие. В журнале скрипта должны появиться строкиraisedи затемcleared.
Типичные проблемы¶
Скрипт не запускается вовсе. Проверьте главный выключатель оповещателя и «Включить назначение «Процесс»», что «Команда» не пуста и что важность события не ниже порога. По умолчанию порог
Warning: событияInfoнаружу не уходят.В журнале службы
process-command not executable. Файл по указанному пути не существует или у пользователяpssнет права на запуск. Проверьтеls -lи права на все каталоги пути.В журнале службы
posix_spawnp failed. Программу не удалось запустить: чаще всего имя указано без пути и не найдено вPATHслужбы, или неверна первая строка скрипта (#!). Указывайте полный путь.В журнале службы
exit <N>. Скрипт завершился с ошибкой, её текст из stderr приведён в той же строке. Частая причина — нет права на запись в каталог журнала или в рабочие файлы: скрипт работает отpss, а не отroot, и в каталоге/.В журнале службы
timed out after <N> ms. Скрипт не уложился в таймаут: ускорьте доставку (таймаут сетевого вызова меньше «Таймаута, ms»), увеличьте «Таймаут, ms» или вынесите долгую часть в фон с закрытым выводом.Оповещения приходят с большой задержкой или пропадают; в журнале
delivery queue full. Скрипт слишком медленный для потока событий: очередь канала переполнена. Ускорьте скрипт или поднимите «Минимальную важность».Канал замолчал без единой ошибки в журнале (версии до 2.0.2.415). Скорее всего, скрипт оставил фоновый процесс с унаследованным потоком ошибок, и узел ждёт его завершения. Найдите процесс (
ps -u pss -f), завершите его и исправьте запуск фона: с</dev/null >/dev/null 2>&1или с перенаправлением в файл. С версии 2.0.2.415 такой процесс канал не останавливает.Скрипт работает вручную, но не из узла. Причина в окружении: другой пользователь (
pss), рабочий каталог/, другойPATH, нет переменных вашего входа в систему. Проверяйте командой из шага 3 раздела Установка и проверка, черезsudo -u pss.Пришло второе
raisedс тем жеPSS_ALERT_ID. Служба перезапускалась, и действующий инцидент был обнаружен заново (см. Когда запускается скрипт).Не пришло
cleared. Оповещатель узла выключили, пока инцидент был активен, либо служба перезапустилась раньше снятия (см. Когда запускается скрипт).Одно и то же оповещение приходит с нескольких узлов. Канал включён на нескольких узлах домена, разносящих оповещения. Оставьте его на одном.
Вывод скрипта не виден. Стандартный вывод уходит в
journalctl -u pss, а поток ошибок попадает в журнал службы только при ненулевом коде завершения. Пишите диагностику в собственный файл журнала.