Scripts externos para alertas

O canal «Processo» da entrega externa de alertas executa no nó o seu programa ou script a cada evento de alerta: a geração de um incidente e a sua resolução. Assim, os alertas chegam a qualquer sistema que possa ser chamado a partir de um script: um gateway de SMS, um mensageiro, um webhook, o seu próprio monitoramento ou um sistema de chamados. Esta página descreve como o nó executa o comando, o que ele recebe e como o resultado é tratado, e traz dois modelos prontos: em /bin/sh e em Python 3.

O funcionamento geral da entrega externa e os outros dois canais, Telegram e e-mail, estão descritos em Entrega externa de alertas; a janela de configuração, em Destinos de alerta.

Configuração do canal

O canal é configurado na aba «Processo» da janela «Destinos de alerta». A janela é aberta pelo botão «Destinos de alerta…» nas configurações do alertador (Destinos de alerta). Os mesmos valores ficam na seção alerter do arquivo de configuração.

Campo

Chave

Finalidade

«Ativar destino de processo»

process-enable

Ativa o canal. Desativado por padrão.

«Comando»

process-command

Linha de comando: o programa e os seus argumentos. Vazia por padrão; um canal ativado com o comando vazio não executa nada.

«Severidade mínima»

process-min-severity

Limiar inferior de severidade do evento: Info (3), Warning (4, padrão), Error (5), Critical (6), AdminAction (7).

«Timeout, ms»

process-timeout-ms

Tempo em milissegundos concedido a cada execução: de 100 a 600000, padrão 5000.

Os quatro valores são lidos a cada evento, por isso uma alteração vale a partir do próximo alerta, sem reiniciar o serviço.

Como o comando é executado

Sem shell. A linha «Comando» é dividida em palavras pelas regras do shell, mas o shell em si não é iniciado. As palavras são separadas por espaços; o texto entre aspas simples é tomado como está; entre aspas duplas, a barra invertida escapa apenas " e \; fora das aspas, a barra invertida escapa o caractere seguinte; aspas vazias '' geram um argumento vazio. A primeira palavra é o programa, as demais são os seus argumentos. Pipes |, redirecionamentos >, substituição de variáveis $VAR, ~ e padrões de nomes de arquivo não funcionam: chegam ao programa como texto comum. Tudo isso deve ser feito dentro do script.

Busca do programa. Se a primeira palavra contém /, ela é o caminho de um arquivo: o arquivo deve existir e ser executável; caso contrário, cada evento termina com o erro process-command not executable. Um nome sem / é procurado no PATH do serviço. O mais seguro é indicar sempre o caminho completo.

Ambiente de execução.

  • Usuário e grupo: os mesmos do serviço, pss.

  • Diretório de trabalho: a raiz /; no script, indique os caminhos completos.

  • As variáveis de ambiente são herdadas do serviço (inclusive o seu PATH), e a elas se somam as variáveis PSS_ALERT_*.

  • Os arquivos e sockets abertos do serviço não são passados ao script: ele tem apenas a entrada padrão, a saída padrão e a saída de erro padrão.

  • A saída padrão do script vai para o registro do sistema do serviço, ou seja, para a saída de journalctl -u pss (Registro do serviço). A saída de erro padrão é lida pelo próprio nó, depois que o script termina (Resultado, tempo limite e registro).

O que o script recebe

Cada evento é passado de duas formas ao mesmo tempo: em campos nas variáveis de ambiente e por inteiro em formato JSON na entrada padrão. O nó não passa argumentos: há apenas os que estão escritos em «Comando».

Variáveis de ambiente

Variável

Valor

PSS_ALERT_ID

Identificador estável do incidente, o mesmo na sua geração e na sua resolução, por exemplo 2:12:3.

PSS_ALERT_STATE

raised — o incidente foi gerado, cleared — foi resolvido.

PSS_ALERT_SEVERITY

Severidade em número: 3–7 (veja «Severidade mínima» acima).

PSS_ALERT_SEVERITY_STR

Severidade por extenso: Info, Warning, Error, Critical, AdminAction.

PSS_ALERT_CODE

Código numérico do tipo de incidente (Catálogo de códigos de alerta).

PSS_ALERT_CODE_STR

O mesmo código como designação textual, por exemplo no-data, io-error.

PSS_ALERT_SRC_TYPE

Tipo de fonte: System, Stream, Cluster, EPG, Mosaic, DVB ou CS.

PSS_ALERT_SRC_ID

Identificador da fonte, por exemplo o número do fluxo.

PSS_ALERT_NODE

Nome do nó em que o incidente foi gerado. Em um alerta recebido de um vizinho do domínio Meshwork, é o nome desse vizinho.

PSS_ALERT_DISPLAY_NAME

Nome legível do objeto, por exemplo o nome do fluxo.

PSS_ALERT_MESSAGE

Texto do alerta.

PSS_ALERT_TS

Horário deste evento, em segundos Unix (UTC).

PSS_ALERT_RAISED_TS

Horário da geração do incidente.

PSS_ALERT_CLEARED_TS

Horário da resolução; 0 enquanto o incidente está ativo.

As variáveis são práticas em /bin/sh: textos com espaços e quebras de linha passam por elas sem distorção, e elas não aparecem na lista de processos (ps).

JSON na entrada padrão. Uma única linha sem quebra de linha final; depois dela, a entrada é fechada. O JSON traz as mesmas informações, mas a severidade e o tipo de fonte vêm apenas como números (tipo de fonte: 1 — System, 2 — Stream, 3 — Cluster, 4 — EPG, 5 — Mosaic, 6 — DVB, 7 — CS), e não há designações textuais do código e da severidade. Campos:

  • sempre: id, state, ts, raised_ts, cleared_ts, severity, src_type, src_id, node, display_name, code, message;

  • nos incidentes de uma entrada ou saída específica: io_id e io_kind (input ou output);

  • nos alertas AdminAction: admin_action_src;

  • em um incidente «oscilante»: flapping (true) e flap_cycles, o número dos seus ciclos (Quando o script é executado).

O script não é obrigado a ler a entrada padrão: tudo o que é necessário também está nas variáveis. Mas, se ele lê a entrada, deve lê-la até o fim, e não apenas uma linha.

Exemplo: no nó edge-1, o fluxo Sport HD (número 12) está há 15 segundos sem dados. Geração:

{"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)"}

Resolução do mesmo incidente dois minutos depois:

{"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)"}

Aqui o JSON foi dividido em linhas para facilitar a leitura; o nó o passa em uma única linha. A resolução mantém a severidade e o texto do incidente; não existe um texto «restabelecido», e quem indica a resolução é o campo state. As variáveis de ambiente da mesma geração:

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

Quando o script é executado

  • Geração e resolução. O script é executado uma vez quando o incidente é gerado e uma vez quando ele é resolvido. As atualizações intermediárias de um incidente ativo (repetição, novo texto, mudança de severidade) não são passadas ao script.

  • Limiar de severidade. A «Severidade mínima» se aplica ao incidente, e não a cada evento. Um incidente abaixo do limiar não é passado. Se a sua severidade subiu depois até o limiar, o script recebe raised uma única vez, no momento desse aumento. cleared chega se e somente se raised foi passado, qualquer que seja a severidade no momento da resolução. Considera-se passado o evento entregue ao canal: se a entrega falhou, o incidente continua sendo considerado aberto, e a sua resolução será passada.

  • Interruptor principal. Com o alertador do nó desativado (Alertador), o script não é executado em nenhum caso, nem mesmo para a resolução de um incidente já comunicado.

  • Alertas do domínio. Com a replicação de alertas pelo domínio Meshwork, o script também recebe os alertas dos vizinhos; o nó de origem é indicado em PSS_ALERT_NODE (Replicação de alertas em todo o domínio). Se o canal estiver ativado em vários nós do domínio, cada um deles comunicará o mesmo incidente. Para evitar duplicatas, ative o canal em um único nó.

  • Incidentes oscilantes. Se um incidente é gerado e resolvido repetidamente (por padrão, três ciclos seguidos com intervalos de no máximo 600 segundos), o nó deixa de resolvê-lo a cada ciclo: o incidente permanece ativo, e uma única resolução chega quando a condição se mantiver restabelecida por pelo menos 600 segundos. O script recebe uma geração e uma resolução, em vez de um par a cada ciclo. Os valores são definidos pelas chaves flap-suppress, flap-cycles, flap-window-sec e flap-settle-sec da seção alerter do arquivo de configuração (O arquivo de configuração pss.json).

  • Alertas persistentes, que só são resolvidos manualmente, enviam a resolução quando o operador resolve o alerta (Visualização e resolução de alertas).

  • Reinício do serviço. A lista de incidentes ativos fica na memória e se perde no reinício do serviço, sem resoluções. Uma condição que ainda persiste é gerada de novo, e o script recebe um segundo raised com o mesmo PSS_ALERT_ID.

Por isso, é melhor tratar o evento por PSS_ALERT_ID e PSS_ALERT_STATE: um raised repetido atualiza um incidente já conhecido em vez de abrir um novo.

Fila e ordem

  • O script é executado estritamente um de cada vez: a execução seguinte começa depois que a anterior termina, e os eventos seguem a ordem de chegada.

  • As execuções são conduzidas por uma thread em segundo plano própria do canal. Um script lento não atrasa os outros canais nem o funcionamento do nó, mas atrasa os eventos seguintes do mesmo canal.

  • Enquanto o script está em execução, os novos eventos aguardam na fila do canal, com capacidade para 512 eventos. Quando a fila está cheia, os eventos mais novos são descartados, e o registro do serviço recebe a linha delivery queue full, dropped alert(s).

  • Na parada e no reinício do serviço, os eventos da fila são descartados; o serviço aguarda apenas a execução que já está em andamento.

Faça as contas: em uma tempestade de cem eventos, um script que leva dois segundos fica mais de três minutos atrás dos eventos. Mova a entrega demorada para segundo plano (veja a próxima seção).

Resultado, tempo limite e registro

  • Código de saída 0: o evento foi entregue.

  • Código diferente de zero: falha na entrega. No registro do serviço (Registro do serviço) é gravada uma linha como [alert:process] deliver failed: process-command '/usr/local/bin/pss-alert.sh' exit 1: <texto do stderr> com os últimos 4 KB da saída de erro.

  • A saída de erro é lida pelo nó enquanto o script é executado e só é exibida junto com um código diferente de zero. A saída no stderr com o código 0 não é considerada erro e não afeta nada; uma saída abundante no stderr não deixa o script mais lento e não leva ao tempo limite.

  • Tempo limite. Se o script não terminar dentro do «Timeout, ms», o nó o encerra à força (SIGKILL) e grava timed out after <N> ms. Só o próprio script é encerrado; os processos iniciados por ele continuam em execução.

  • Não há novas tentativas. Um evento que falhou não é reenviado; o canal passa ao seguinte.

  • Limitação da frequência de registros. O primeiro erro do canal é gravado imediatamente; os seguintes, no máximo uma vez por minuto, com o número de erros desse período no fim da linha ((x12)).

Nota

Processos em segundo plano. Um processo que o script deixa em segundo plano não atrasa os próximos eventos do canal, mesmo que tenha herdado a saída de erro. Nas versões anteriores à 2.0.2.415, esse processo parava o canal até terminar; por isso, nos nós onde podem funcionar versões anteriores, e em geral por cuidado, inicie trabalhos em segundo plano com a entrada e a saída fechadas:

some-command </dev/null >/dev/null 2>&1 &

ou com redirecionamento para um arquivo de registro próprio:

setsid some-command </dev/null >>/var/log/pss-alerts/bg.log 2>&1 &

Segurança

  • O script é executado com os privilégios do serviço, do usuário pss. Defina root como dono do arquivo do script e dê ao usuário pss apenas leitura e execução (modo 750, grupo pss): assim, o serviço não consegue alterar o script que ele mesmo executa.

  • Não passe segredos nos argumentos do «Comando». Os argumentos ficam visíveis para todos os usuários do sistema na lista de processos (ps), são armazenados no arquivo de configuração e aparecem na interface web para o papel Admin (para os papéis Restricted admin e Viewer, a linha de comando é exibida oculta). Guarde tokens e senhas no próprio script ou em um arquivo separado que só o usuário pss possa ler.

  • O texto do alerta (o nome do fluxo, a mensagem) vem das configurações e das fontes. Não o insira em comandos de shell sem aspas e não o passe para eval. Os modelos abaixo passam os valores somente entre aspas.

Modelo em /bin/sh

O script grava uma linha no registro a cada evento e termina com o código 0. O lugar da entrega real está marcado com um comentário.

#!/bin/sh
# Receptor de alertas do Perfect Streamer (canal «Processo»).
# O nó executa o script a cada geração e resolução de um incidente.
# Os campos do evento estão nas variáveis PSS_ALERT_*, o JSON inteiro - na entrada padrão.

LOG=/var/log/pss-alerts/alerts.log

# JSON da entrada padrão: uma linha sem quebra de linha no fim,
# por isso lemos com cat, e não com read.
json=$(cat)

# Uma linha no registro. Erro de gravação - código diferente de zero: o nó o grava
# no próprio registro junto com o texto do 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

# Aqui entra a entrega real, por exemplo um webhook.
# A chamada síncrona deve caber no «Timeout, ms» (padrão 5 s):
#
# 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
#
# Entrega demorada - só em segundo plano e só com entrada e saída fechadas,
# senão o canal para (veja «Resultado, tempo limite e registro»):
#
# ( 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

Modelo em Python 3

O mesmo em Python 3, apenas com a biblioteca padrão (Python 3.6 ou mais recente).

#!/usr/bin/env python3
# Receptor de alertas do Perfect Streamer (canal «Processo»).
# O nó executa o script a cada geração e resolução de um incidente.
# Os campos do evento estão nas variáveis PSS_ALERT_*, o JSON inteiro - na entrada padrão.

import json
import os
import sys
import time

LOG = "/var/log/pss-alerts/alerts.log"


def main():
    # Toda a entrada padrão: uma linha de JSON sem quebra de linha no fim.
    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", ""),
    )
    # Um erro de gravação encerra o script com código diferente de zero, e o texto
    # do erro vai para o stderr - o nó o grava no próprio registro.
    with open(LOG, "a", encoding="utf-8") as f:
        f.write(line)

    # Aqui entra a entrega real, por exemplo um webhook.
    # A chamada deve caber no «Timeout, ms» (padrão 5 s):
    #
    # 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()
    #
    # Inicie processos em segundo plano (subprocess.Popen) com
    # stdin/stdout/stderr=subprocess.DEVNULL e start_new_session=True
    # (veja «Resultado, tempo limite e registro»).

    return 0


if __name__ == "__main__":
    sys.exit(main())

Instalação e verificação

  1. Crie o diretório de registro com permissão de escrita para o usuário pss:

    sudo install -d -o pss -g pss -m 750 /var/log/pss-alerts
    
  2. Salve o modelo, por exemplo como /usr/local/bin/pss-alert.sh (ou pss-alert.py), e defina o dono e as permissões:

    sudo chown root:pss /usr/local/bin/pss-alert.sh
    sudo chmod 750 /usr/local/bin/pss-alert.sh
    
  3. Teste o script manualmente como usuário pss, com um evento de exemplo:

    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
    

    Espera-se exit=0 e uma nova linha no registro. Aproveite para medir o tempo: ele deve ser bem menor que o «Timeout, ms».

  4. Na aba «Processo» da janela «Destinos de alerta», informe em «Comando» o caminho completo /usr/local/bin/pss-alert.sh, defina a «Severidade mínima», ative o destino e salve.

  5. Verifique a entrega em um fluxo de teste: interrompa o envio de dados para a entrada dele (por exemplo, pare o transmissor). Após o tempo limite de ausência de dados (padrão 15 segundos, Configurações do alerter), será gerado um alerta no-data com severidade Warning e, quando os dados voltarem, a sua resolução. No registro do script devem aparecer as linhas raised e, depois, cleared.

Problemas comuns

  • O script não é executado em nenhum caso. Verifique o interruptor principal do alertador e «Ativar destino de processo», se o «Comando» não está vazio e se a severidade do evento não está abaixo do limiar. Por padrão, o limiar é Warning: eventos Info não são enviados para fora.

  • No registro do serviço: process-command not executable. O arquivo no caminho indicado não existe ou o usuário pss não tem permissão de execução. Verifique com ls -l, incluindo as permissões de todos os diretórios do caminho.

  • No registro do serviço: posix_spawnp failed. Não foi possível iniciar o programa: na maioria das vezes, o nome foi indicado sem caminho e não foi encontrado no PATH do serviço, ou a primeira linha do script (#!) está errada. Indique o caminho completo.

  • No registro do serviço: exit <N>. O script terminou com erro, e o texto do stderr aparece na mesma linha. Uma causa frequente é a falta de permissão de escrita no diretório de registro ou nos arquivos de trabalho: o script é executado como pss, e não como root, e no diretório /.

  • No registro do serviço: timed out after <N> ms. O script não terminou dentro do tempo limite: acelere a entrega (com um tempo limite da chamada de rede menor que o «Timeout, ms»), aumente o «Timeout, ms» ou mova a parte demorada para segundo plano com a saída fechada.

  • Os alertas chegam com grande atraso ou se perdem; no registro, delivery queue full. O script é lento demais para o volume de eventos: a fila do canal está cheia. Acelere o script ou aumente a «Severidade mínima».

  • O canal ficou mudo sem nenhum erro no registro (versões anteriores à 2.0.2.415). O mais provável é que o script tenha deixado um processo em segundo plano com a saída de erro herdada e que o nó esteja aguardando o término dele. Encontre o processo (ps -u pss -f), encerre-o e corrija a forma de iniciar o segundo plano: com </dev/null >/dev/null 2>&1 ou com redirecionamento para um arquivo. A partir da versão 2.0.2.415, esse processo não para o canal.

  • O script funciona manualmente, mas não a partir do nó. A causa está no ambiente: outro usuário (pss), o diretório de trabalho /, outro PATH, a ausência das variáveis da sua sessão. Teste com o comando do passo 3 da seção Instalação e verificação, via sudo -u pss.

  • Chegou um segundo raised com o mesmo PSS_ALERT_ID. O serviço foi reiniciado, e o incidente ativo foi detectado novamente (veja Quando o script é executado).

  • Não chegou cleared. O alertador do nó foi desativado enquanto o incidente estava ativo, ou o serviço foi reiniciado antes da resolução (veja Quando o script é executado).

  • O mesmo alerta chega de vários nós. O canal está ativado em vários nós do domínio que replicam alertas. Deixe-o ativado em apenas um.

  • A saída do script não aparece. A saída padrão vai para journalctl -u pss, e a saída de erro só chega ao registro do serviço com um código de saída diferente de zero. Grave o diagnóstico em um arquivo de registro próprio.