Внешние скрипты для оповещений

Канал «Процесс» внешней доставки оповещений запускает на узле вашу программу или скрипт на каждое событие оповещения: возникновение инцидента и его снятие. Так оповещения попадают в любую систему, которую можно вызвать из скрипта: SMS-шлюз, мессенджер, вебхук, собственный мониторинг или система заявок. Страница описывает, как узел запускает команду, что она получает и как обрабатывается результат, и даёт две готовые заготовки: на /bin/sh и на Python 3.

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

Настройка канала

Канал настраивается на вкладке «Процесс» окна «Назначения оповещений». Окно открывается кнопкой «Назначения оповещений…» в настройках оповещателя (Назначения оповещений). Те же значения лежат в секции alerter файла настроек.

Поле

Ключ

Назначение

«Включить назначение «Процесс»»

process-enable

Включает канал. По умолчанию выключен.

«Команда»

process-command

Командная строка: программа и её аргументы. По умолчанию пусто; включённый канал с пустой командой ничего не запускает.

«Минимальная важность»

process-min-severity

Нижний порог важности события: Info (3), Warning (4, по умолчанию), Error (5), Critical (6), AdminAction (7).

«Таймаут, ms»

process-timeout-ms

Сколько миллисекунд даётся на один запуск: от 100 до 600000, по умолчанию 5000.

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

Как запускается команда

Без оболочки. Строка «Команда» делится на слова по правилам оболочки, но сама оболочка не запускается. Слова разделяются пробелами; текст в одинарных кавычках берётся как есть; в двойных кавычках обратная косая черта экранирует только " и \; вне кавычек обратная косая черта экранирует следующий символ; пустые кавычки '' дают пустой аргумент. Первое слово — программа, остальные — её аргументы. Конвейеры |, перенаправления >, подстановка переменных $VAR, ~ и шаблоны имён файлов не работают: они уходят программе обычным текстом. Всё это делается внутри скрипта.

Поиск программы. Если в первом слове есть /, это путь к файлу: файл должен существовать и быть исполняемым, иначе каждое событие заканчивается ошибкой process-command not executable. Имя без / ищется по PATH службы. Надёжнее всегда указывать полный путь.

Окружение запуска.

  • Пользователь и группа — те же, что у службы: pss.

  • Рабочий каталог — корень /; пути в скрипте указывайте полностью.

  • Переменные окружения наследуются от службы (в том числе её PATH), к ним добавляются переменные PSS_ALERT_*.

  • Открытые файлы и сокеты службы скрипту не передаются: у него есть только стандартные вход, вывод и поток ошибок.

  • Стандартный вывод скрипта уходит в системный журнал службы, то есть в вывод journalctl -u pss (Журнал службы). Стандартный поток ошибок узел читает сам, после завершения скрипта (Результат, таймаут и журнал).

Что получает скрипт

Каждое событие передаётся двумя способами одновременно: полями в переменных окружения и целиком в формате JSON на стандартный вход. Аргументов от узла нет — только те, что записаны в «Команде».

Переменные окружения

Переменная

Значение

PSS_ALERT_ID

Устойчивый идентификатор инцидента, одинаковый у его возникновения и снятия, например 2:12:3.

PSS_ALERT_STATE

raised — инцидент возник, cleared — снят.

PSS_ALERT_SEVERITY

Важность числом: 3–7 (см. «Минимальная важность» выше).

PSS_ALERT_SEVERITY_STR

Важность словом: Info, Warning, Error, Critical, AdminAction.

PSS_ALERT_CODE

Код вида инцидента числом (Каталог кодов алертов).

PSS_ALERT_CODE_STR

Тот же код текстовым обозначением, например no-data, io-error.

PSS_ALERT_SRC_TYPE

Тип источника: System, Stream, Cluster, EPG, Mosaic, DVB или CS.

PSS_ALERT_SRC_ID

Идентификатор источника, например номер потока.

PSS_ALERT_NODE

Имя узла, на котором возник инцидент. У оповещения, полученного от соседа по домену Meshwork, это имя соседа.

PSS_ALERT_DISPLAY_NAME

Имя объекта для человека, например имя потока.

PSS_ALERT_MESSAGE

Текст оповещения.

PSS_ALERT_TS

Время этого события, секунды Unix (UTC).

PSS_ALERT_RAISED_TS

Время возникновения инцидента.

PSS_ALERT_CLEARED_TS

Время снятия; 0, пока инцидент активен.

Переменные удобны в /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())

Установка и проверка

  1. Создайте каталог журнала, доступный на запись пользователю pss:

    sudo install -d -o pss -g pss -m 750 /var/log/pss-alerts
    
  2. Сохраните заготовку, например как /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
    
  3. Проверьте скрипт вручную от имени пользователя 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».

  4. На вкладке «Процесс» окна «Назначения оповещений» укажите в «Команде» полный путь /usr/local/bin/pss-alert.sh, задайте «Минимальную важность», включите назначение и сохраните.

  5. Проверьте доставку на тестовом потоке: прервите подачу данных на его вход (например, остановите передатчик). Через таймаут отсутствия данных (по умолчанию 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, а поток ошибок попадает в журнал службы только при ненулевом коде завершения. Пишите диагностику в собственный файл журнала.