Monitoramento de serviços com scripts

1. Fundamentos do Monitoramento de Serviços no Bash

O monitoramento de serviços é uma prática essencial para garantir a disponibilidade e o funcionamento adequado de sistemas Linux. No contexto do Bash, isso envolve verificar periodicamente se processos, daemons ou aplicações estão rodando conforme o esperado. Os conceitos fundamentais incluem:

  • Serviço: um processo que roda em background (daemon) ou uma aplicação gerenciada pelo systemd/init.
  • Health check: verificação que confirma se o serviço está respondendo corretamente.
  • Health check ativo: teste que simula uma requisição real ao serviço.

Comandos essenciais para esse trabalho são systemctl, service, pgrep e ps. A estrutura básica de um script de monitoramento consiste em um loop infinito ou execução periódica, uma verificação de status e uma ação (alerta, reinicialização, log).

#!/bin/bash
# Estrutura básica de monitoramento

SERVICE="nginx"

while true; do
    if systemctl is-active --quiet "$SERVICE"; then
        echo "$(date): $SERVICE está rodando."
    else
        echo "$(date): $SERVICE PAROU! Reiniciando..."
        systemctl restart "$SERVICE"
    fi
    sleep 60
done

2. Verificando Status de Serviços do Sistema

O systemd oferece comandos diretos para consultar o estado de serviços. O comando systemctl is-active retorna active ou inactive, e seu código de retorno ($?) pode ser usado em condicionais. Exemplo prático:

#!/bin/bash

check_service() {
    local service="$1"
    if systemctl is-active --quiet "$service"; then
        echo "OK: $service está ativo"
        return 0
    else
        echo "FALHA: $service não está ativo"
        return 1
    fi
}

check_service "sshd"
check_service "apache2"

Para parsing mais detalhado, podemos usar systemctl status com grep e awk:

#!/bin/bash

get_service_status() {
    local service="$1"
    local status=$(systemctl status "$service" 2>/dev/null | grep "Active:" | awk '{print $2}')
    echo "$service: $status"
}

get_service_status "postgresql"
get_service_status "redis-server"

O código de retorno ($?) é particularmente útil para decisões condicionais:

systemctl is-active --quiet "mysql"
if [ $? -ne 0 ]; then
    echo "MySQL está inativo. Iniciando..."
    systemctl start mysql
fi

3. Health Checks de Aplicações Web e TCP

Para serviços web, o curl é a ferramenta padrão para health checks HTTP/HTTPS. Podemos verificar códigos de status, tempos de resposta e conteúdo da resposta:

#!/bin/bash

URL="http://localhost:8080/health"
EXPECTED_CODE=200
TIMEOUT=10

http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout "$TIMEOUT" "$URL")

if [ "$http_code" -eq "$EXPECTED_CODE" ]; then
    echo "Health check OK: código $http_code"
else
    echo "Health check FALHOU: código $http_code (esperado $EXPECTED_CODE)"
fi

Para verificações de porta TCP, podemos usar nc (netcat) ou o pseudo-dispositivo /dev/tcp do Bash:

#!/bin/bash

HOST="127.0.0.1"
PORT=3306
TIMEOUT=5

# Usando /dev/tcp
if timeout "$TIMEOUT" bash -c "echo > /dev/tcp/$HOST/$PORT" 2>/dev/null; then
    echo "Porta $PORT está aberta"
else
    echo "Porta $PORT está fechada ou inacessível"
fi

# Alternativa com nc
if nc -z -w "$TIMEOUT" "$HOST" "$PORT"; then
    echo "Porta $PORT respondendo (nc)"
fi

Health checks mais robustos incluem retentativas e timeouts:

#!/bin/bash

health_check_with_retry() {
    local url="$1"
    local max_retries=3
    local retry_delay=5
    local attempt=1

    while [ $attempt -le $max_retries ]; do
        if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "$url" | grep -q "200"; then
            echo "Health check bem-sucedido na tentativa $attempt"
            return 0
        fi
        echo "Tentativa $attempt falhou. Aguardando $retry_delay segundos..."
        sleep "$retry_delay"
        attempt=$((attempt + 1))
    done

    echo "Health check falhou após $max_retries tentativas"
    return 1
}

health_check_with_retry "http://localhost:3000/health"

4. Notificações e Alertas Automáticos

Notificações são cruciais para que administradores saibam de problemas imediatamente. O envio de e-mail pode ser feito com mail ou sendmail:

#!/bin/bash

send_email_alert() {
    local subject="$1"
    local body="$2"
    local recipient="admin@exemplo.com"

    echo "$body" | mail -s "$subject" "$recipient"
}

# Exemplo de uso
send_email_alert "ALERTA: Servidor Web Parou" "O serviço nginx parou inesperadamente em $(date)."

Para integração com Slack via webhook:

#!/bin/bash

WEBHOOK_URL="https://hooks.slack.com/services/SEU/WEBHOOK/AQUI"

send_slack_alert() {
    local message="$1"
    local payload="{\"text\": \"$message\"}"

    curl -s -X POST -H "Content-Type: application/json" -d "$payload" "$WEBHOOK_URL"
}

send_slack_alert "🚨 *ALERTA CRÍTICO*: Serviço PostgreSQL parou em $(hostname) às $(date)"

Logging com timestamps e níveis de severidade:

#!/bin/bash

LOG_FILE="/var/log/monitoramento.log"
LOG_LEVEL=("DEBUG" "INFO" "WARN" "ERROR")

log_event() {
    local level="$1"
    local message="$2"
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
}

log_event "INFO" "Monitoramento iniciado"
log_event "WARN" "Uso de memória acima de 80%"
log_event "ERROR" "Serviço mysql não responde"

5. Estratégias de Reinicialização e Recuperação

Quando um serviço falha, o script pode tentar reiniciá-lo automaticamente. É importante implementar um backoff exponencial para evitar loops de reinicialização:

#!/bin/bash

SERVICE="apache2"
MAX_RETRIES=5
BASE_DELAY=10

restart_with_backoff() {
    local attempt=1
    local delay=$BASE_DELAY

    while [ $attempt -le $MAX_RETRIES ]; do
        echo "Tentativa $attempt de reiniciar $SERVICE..."
        systemctl restart "$SERVICE"

        sleep 5

        if systemctl is-active --quiet "$SERVICE"; then
            echo "$SERVICE reiniciado com sucesso na tentativa $attempt"
            return 0
        fi

        echo "Falha na tentativa $attempt. Aguardando $delay segundos..."
        sleep "$delay"
        delay=$((delay * 2))  # Backoff exponencial
        attempt=$((attempt + 1))
    done

    echo "Falha ao reiniciar $SERVICE após $MAX_RETRIES tentativas. Escalonando alerta..."
    send_slack_alert "🔥 $SERVICE falhou após $MAX_RETRIES tentativas de reinicialização em $(hostname)"
    return 1
}

restart_with_backoff

6. Monitoramento de Recursos (CPU, Memória, Disco)

O monitoramento de recursos complementa a verificação de serviços. Um serviço pode estar rodando mas travado por falta de memória:

#!/bin/bash

THRESHOLD_CPU=90
THRESHOLD_MEM=85
THRESHOLD_DISK=90

check_cpu() {
    local usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    if (( $(echo "$usage > $THRESHOLD_CPU" | bc -l) )); then
        log_event "WARN" "CPU em $usage% (threshold: $THRESHOLD_CPU%)"
        return 1
    fi
    return 0
}

check_memory() {
    local usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}' | cut -d'.' -f1)
    if [ "$usage" -gt "$THRESHOLD_MEM" ]; then
        log_event "WARN" "Memória em $usage% (threshold: $THRESHOLD_MEM%)"
        return 1
    fi
    return 0
}

check_disk() {
    local usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
    if [ "$usage" -gt "$THRESHOLD_DISK" ]; then
        log_event "WARN" "Disco em $usage% (threshold: $THRESHOLD_DISK%)"
        return 1
    fi
    return 0
}

# Integração com monitoramento de serviço
if ! check_memory; then
    echo "Alta utilização de memória pode afetar serviços. Verificando serviços..."
    if systemctl is-active --quiet "mysql"; then
        log_event "INFO" "MySQL ainda está ativo, mas monitorando memória"
    fi
fi

7. Automatização com Cron e Daemons

Para monitoramento contínuo sem loops infinitos, o cron é a solução mais simples:

# crontab -e
# Executar a cada 5 minutos
*/5 * * * * /usr/local/bin/monitor_servicos.sh >> /var/log/monitor_cron.log 2>&1

# Executar a cada hora no minuto 0
0 * * * * /usr/local/bin/health_check_completo.sh

Para scripts que precisam rodar como serviço systemd, crie um arquivo de unit:

[Unit]
Description=Monitor de Serviços Customizado
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/monitor_daemon.sh
Restart=always
RestartSec=30
User=root
Group=root

[Install]
WantedBy=multi-user.target

Salve em /etc/systemd/system/monitor.service e ative:

sudo systemctl daemon-reload
sudo systemctl enable monitor.service
sudo systemctl start monitor.service

Boas práticas importantes:

  • Logs rotativos: use logrotate para evitar que logs cresçam infinitamente.
  • Lockfiles: evite execução simultânea do mesmo script:
#!/bin/bash
LOCKFILE="/var/run/monitor.pid"

if [ -f "$LOCKFILE" ] && kill -0 $(cat "$LOCKFILE") 2>/dev/null; then
    echo "Script já está em execução"
    exit 1
fi

echo $$ > "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT

# Código de monitoramento aqui
  • Execução em background: para scripts longos, use nohup ou &:
nohup /usr/local/bin/monitor_servicos.sh &

Referências