Health checks e alertas em scripts de monitoramento

1. Fundamentos de Health Checks em Shell Script

1.1. O que são health checks e por que implementá-los no Bash

Health checks são verificações periódicas que determinam se um sistema, serviço ou aplicação está operando dentro dos parâmetros esperados. Implementá-los em Bash oferece vantagens significativas: baixo overhead, simplicidade de deploy e integração nativa com o sistema operacional Unix-like. Scripts de monitoramento em Bash são ideais para ambientes onde instalar agentes complexos não é viável ou desejável.

1.2. Tipos de verificações

As verificações mais comuns incluem:

  • Conectividade: teste de porta com /dev/tcp ou nc, ping para latência
  • Processos: verificação se um daemon está rodando com pgrep
  • Recursos do sistema: CPU, memória, disco e inodes

1.3. Estrutura básica de um script de monitoramento

#!/bin/bash

INTERVALO=60
HOST="exemplo.com"

while true; do
    TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    # Coleta de métricas
    HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}" "https://$HOST/health")
    CPU_USO=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)

    # Lógica de alerta
    if [ "$HTTP_STATUS" -ne 200 ]; then
        echo "$TIMESTAMP - ALERTA: HTTP status $HTTP_STATUS"
    fi

    sleep "$INTERVALO"
done

2. Coleta de Métricas do Sistema

2.1. CPU, memória e disco

# CPU usage (percentual)
CPU_IDLE=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | cut -d',' -f1)
CPU_USO=$(echo "100 - $CPU_IDLE" | bc)

# Memória (percentual de uso)
MEM_TOTAL=$(free -m | awk '/^Mem:/ {print $2}')
MEM_USADO=$(free -m | awk '/^Mem:/ {print $3}')
MEM_PCT=$(echo "scale=2; $MEM_USADO * 100 / $MEM_TOTAL" | bc)

# Disco (percentual de uso)
DISCO_USO=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')

2.2. Verificação de processos críticos

PROCESSO="nginx"
if pgrep -x "$PROCESSO" > /dev/null; then
    echo "Processo $PROCESSO está rodando"
else
    echo "CRITICAL: Processo $PROCESSO não encontrado"
fi

# Health endpoint via curl
HEALTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:8080/health)

2.3. Métricas de rede

# Latência e perda de pacotes
PING_RESULT=$(ping -c 5 -q 8.8.8.8 2>&1 | tail -1)
PERDA=$(echo "$PING_RESULT" | awk -F'/' '{print $6}' | tr -d '%')
LATENCIA=$(echo "$PING_RESULT" | awk -F'/' '{print $4}')

# Tempo de resposta HTTP
TEMPO_RESPOSTA=$(curl -o /dev/null -s -w "%{time_total}" https://api.exemplo.com)

3. Lógica de Thresholds e Condições de Alerta

3.1. Definição de limites

# Configuração via variáveis de ambiente
WARN_CPU=80
CRIT_CPU=95
WARN_DISCO=85
CRIT_DISCO=95

# Ou arquivo de configuração
source /etc/monitoramento.conf

3.2. Comparações numéricas e com floats

# Comparação de inteiros
if [ "$DISCO_USO" -ge "$CRIT_DISCO" ]; then
    echo "CRITICAL: Disco em $DISCO_USO%"
elif [ "$DISCO_USO" -ge "$WARN_DISCO" ]; then
    echo "WARNING: Disco em $DISCO_USO%"
fi

# Comparação com floats usando bc
if (( $(echo "$CPU_USO > $CRIT_CPU" | bc -l) )); then
    echo "CRITICAL: CPU em $CPU_USO%"
fi

# Uso de awk para cálculos
MEM_CRITICO=$(echo "$MEM_PCT $CRIT_MEM" | awk '{if ($1 > $2) print 1; else print 0}')

3.3. Estados de saúde e códigos de saída

# Mapeamento padrão Nagios/Icinga
OK=0
WARNING=1
CRITICAL=2
UNKNOWN=3

verifica_servico() {
    local status=$1
    case $status in
        200) return $OK ;;
        301|302) return $WARNING ;;
        500|502|503) return $CRITICAL ;;
        *) return $UNKNOWN ;;
    esac
}

4. Mecanismos de Notificação e Alertas

4.1. Envio de alertas via linha de comando

# Email via mail
echo "ALERTA: Servidor $HOSTNAME - CPU em $CPU_USO%" | mail -s "[CRITICAL] Monitoramento" admin@exemplo.com

# Slack via webhook
curl -s -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"🚨 ALERTA: Servidor $HOSTNAME\\nCPU: $CPU_USO%\\nDisco: $DISCO_USO%\"}" \
    https://hooks.slack.com/services/TOKEN

# Telegram
curl -s -X POST "https://api.telegram.org/botTOKEN/sendMessage" \
    -d "chat_id=CHAT_ID&text=ALERTA: $HOSTNAME - CPU $CPU_USO%"

4.2. Acumuladores e debounce

ARQUIVO_ESTADO="/tmp/monitor_estado_$HOSTNAME"
CONTAGEM_ALERTAS=0
LIMITE_ALERTAS=3

if [ "$DISCO_USO" -ge "$CRIT_DISCO" ]; then
    CONTAGEM_ALERTAS=$((CONTAGEM_ALERTAS + 1))
    echo "$CONTAGEM_ALERTAS" > "$ARQUIVO_ESTADO"

    if [ "$CONTAGEM_ALERTAS" -ge "$LIMITE_ALERTAS" ]; then
        enviar_alerta "Disco crítico por $CONTAGEM_ALERTAS verificações consecutivas"
        echo 0 > "$ARQUIVO_ESTADO"  # Reset após alerta
    fi
else
    echo 0 > "$ARQUIVO_ESTADO"
fi

4.3. Formatação de mensagens

formatar_alerta() {
    local nivel=$1
    local mensagem=$2
    local metrica=$3
    local valor=$4
    local limite=$5

    cat <<EOF
=== ALERTA DE MONITORAMENTO ===
Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
Hostname: $(hostname)
Nível: $nivel
Métrica: $metrica
Valor atual: $valor
Limite: $limite
Mensagem: $mensagem
===============================
EOF
}

5. Logging e Rastreabilidade

5.1. Logs locais com logger e logrotate

# Envio para syslog
logger -t "monitor" "CRITICAL: Disco em $DISCO_USO% no servidor $(hostname)"

# Log para arquivo com rotação
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")|$HOSTNAME|CPU|$CPU_USO|$ESTADO" >> /var/log/monitoramento.log

5.2. JSON logs estruturados

gerar_json_log() {
    local nivel=$1
    local metrica=$2
    local valor=$3

    cat <<EOF
{
  "timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
  "hostname": "$(hostname)",
  "nivel": "$nivel",
  "metrica": "$metrica",
  "valor": $valor,
  "servico": "healthcheck"
}
EOF
}

# Exemplo de uso
gerar_json_log "WARNING" "cpu" 85.5 >> /var/log/monitoramento.json

5.3. Timestamps e persistência

# Timestamp ISO 8601
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Arquivo de histórico
HISTORICO="/var/log/monitor_historico.csv"
echo "$TIMESTAMP,$HOSTNAME,$CPU_USO,$MEM_PCT,$DISCO_USO,$HTTP_STATUS" >> "$HISTORICO"

6. Resiliência e Tratamento de Falhas

6.1. Timeout e retry

executar_com_timeout() {
    local comando=$1
    local timeout=$2
    local tentativas=$3
    local contador=0

    while [ $contador -lt $tentativas ]; do
        if timeout "$timeout" bash -c "$comando" 2>/dev/null; then
            return 0
        fi
        contador=$((contador + 1))
        sleep 2
    done
    return 1
}

# Uso
if ! executar_com_timeout "curl -s https://api.exemplo.com" 5 3; then
    echo "Falha após 3 tentativas"
fi

6.2. Captura de sinais com trap

cleanup() {
    echo "Finalizando monitoramento graciosamente..."
    rm -f /tmp/monitor_*.lock
    exit 0
}

trap cleanup SIGINT SIGTERM SIGHUP

# Loop principal com proteção contra interrupções
while true; do
    # ... lógica de monitoramento ...
    sleep 60
done

6.3. Lockfiles para evitar concorrência

LOCKFILE="/var/run/monitoramento.lock"

exec 200>"$LOCKFILE"
if ! flock -n 200; then
    echo "Script já está em execução"
    exit 1
fi

# ... lógica do script ...

flock -u 200
rm -f "$LOCKFILE"

7. Exemplo Prático: Script de Monitoramento de Serviço Web

7.1. Script completo

#!/bin/bash

# Configurações
URL="https://meuservico.com/health"
ALERTA_EMAIL="admin@exemplo.com"
LIMITE_CPU=90
LIMITE_MEM=85
INTERVALO=300
ARQUIVO_ESTADO="/tmp/health_estado.txt"

# Funções
enviar_alerta() {
    local nivel=$1
    local mensagem=$2
    echo "$mensagem" | mail -s "[$nivel] Monitoramento - $(hostname)" "$ALERTA_EMAIL"
}

verificar_servico() {
    local status=$(curl -o /dev/null -s -w "%{http_code}" --connect-timeout 10 "$URL")
    local tempo=$(curl -o /dev/null -s -w "%{time_total}" "$URL")

    if [ "$status" -ne 200 ]; then
        enviar_alerta "CRITICAL" "HTTP $status - Tempo: ${tempo}s - URL: $URL"
        return 2
    elif (( $(echo "$tempo > 3" | bc -l) )); then
        enviar_alerta "WARNING" "Resposta lenta: ${tempo}s - URL: $URL"
        return 1
    fi
    return 0
}

verificar_recursos() {
    local cpu=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    local mem=$(free -m | awk '/^Mem:/ {printf "%.1f", $3/$2 * 100}')

    if (( $(echo "$cpu > $LIMITE_CPU" | bc -l) )); then
        enviar_alerta "CRITICAL" "CPU em ${cpu}% (limite: ${LIMITE_CPU}%)"
    fi

    if (( $(echo "$mem > $LIMITE_MEM" | bc -l) )); then
        enviar_alerta "WARNING" "Memória em ${mem}% (limite: ${LIMITE_MEM}%)"
    fi
}

# Loop principal
while true; do
    TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    echo "[$TIMESTAMP] Iniciando verificação..."

    verificar_servico
    verificar_recursos

    # Log estruturado
    echo "{\"timestamp\":\"$TIMESTAMP\",\"host\":\"$(hostname)\",\"status\":\"ok\"}" >> /var/log/health.json

    sleep "$INTERVALO"
done

7.2. Integração com systemd/timer

# /etc/systemd/system/healthcheck.service
[Unit]
Description=Health Check Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/healthcheck.sh
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

# Timer para execução periódica
# /etc/systemd/system/healthcheck.timer
[Unit]
Description=Executa health check a cada 5 minutos

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

7.3. Testes e validação

# Teste de falha simulada
# Parar o serviço web
systemctl stop nginx

# Verificar se o alerta é gerado
./healthcheck.sh
# Deve gerar alerta: HTTP 000 ou conexão recusada

# Teste de recuperação
systemctl start nginx
./healthcheck.sh
# Deve retornar status OK

# Teste de limite de CPU
stress --cpu 4 --timeout 30 &
./healthcheck.sh
# Deve gerar alerta de CPU acima do limite

Referências