Braço robótico industrial em oficina de automação com tecnologia de controle automatizado

Como Construí um Robô de Monitoramento que Funciona Enquanto Você Dorme — Com Shell Script e GitOps

Como Construí um Robô de Monitoramento que Funciona Enquanto Você Dorme — Com Shell Script e GitOps

Eu perdi uma madrugada inteira debugando um servidor que caiu às 3 da manhã. Não porque eu não tinha ferramenta de monitoramento. Eu tinha. O problema era que a ferramenta estava configurada no laptop do meu antigo sysadmin, e ele tinha saído da empresa há três meses.

Cenário comum? Absurdamente comum.

Hoje vou te mostrar como construirei — do zero — um robô de monitoramento robusto usando apenas Shell Script, Cron e GitOps. Sem ferramentas mágicas. Sem dashboards caríssimos. Sem aquela feeling de “espero que ninguém perceba que meu Nagios está quebrado há duas semanas”.

O barato funciona. E quando quebra, você sabe exatamente por quê.

Por que Shell Script Ainda é a Melhor Ferramenta para Monitoramento

Vamos ser honestos: a maioria das ferramentas de monitoramento modernos são construídas sobre camadas sobre camadas de abstrações. Você configura um agente, conecta a um dashboard, define alertas no painel administrativo, e quando algo quebra, você abre outro painel para ver os logs do painel que mostra o erro.

Shell Script não tem isso. Quando um script falha, você vê o erro. Ponto.

Outros motivos:

  • Portabilidade: Funciona em qualquer sistema Unix-like. Aquele servidor RHEL 6 que ninguém quer atualizar? shell script roda lá sem drama.
  • Debugabilidade: Adicione um set -x no topo e você vê cada comando sendo executado.
  • Simplicidade operacional: Sem agentes para instalar, sem repositórios para configurar. Apenas um arquivo .sh que você versiona com Git.
  • Velocidade: Um script de monitoramento básico leva 5 minutos para escrever e 30 segundos para rodar.

A desvantagem? Você precisa pensar como automação. Mas essa é uma skill que se paga — literalmente.

Arquitetura: Os Três Pilares do Sistema

Antes de abrir o editor, vamos definir a arquitetura. O sistema tem três partes:

  1. Scripts de verificação: O que checa o serviço.
  2. Scripts de notificação: O que faz quando algo está errado.
  3. Scheduler (Cron): O que decide quando rodar cada coisa.

Simples assim. Não complique.

Passo 1: O Script de Verificação — Checando Serviços como um Pro

Crie o diretório onde tudo vai morar:

mkdir -p /opt/monitor/{scripts,logs,config}
cd /opt/monitor
git init
echo "*.log" > .gitignore
echo "config/*.env" >> .gitignore

O script principal que verifica serviços — chame de check-services.sh:

#!/bin/bash
# check-services.sh - Monitora serviços e dispara alertas
# Uso: ./check-services.sh [servico|all]

set -euo pipefail
LOGDIR="/opt/monitor/logs"
CONFIGDIR="/opt/monitor/config"
ALERT_SCRIPT="/opt/monitor/scripts/alert.sh"

# Cores para output (se alguém rodar manualmente)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGDIR/checks.log"
}

check_http() {
    local url=$1
    local name=$2
    local expected_code=${3:-200}
    
    response=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "$url" 2>/dev/null)
    
    if [[ "$response" == "$expected_code" ]]; then
        log "[OK] $name ($url) → HTTP $response"
        return 0
    else
        log "[FAIL] $name ($url) → HTTP $response (esperado: $expected_code)"
        return 1
    fi
}

check_port() {
    local host=$1
    local port=$2
    local name=$3
    
    if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$host/$port" 2>/dev/null; then
        log "[OK] $name ($host:$port) → disponível"
        return 0
    else
        log "[FAIL] $name ($host:$port) → indisponível"
        return 1
    fi
}

check_process() {
    local process_name=$1
    local service_name=$2
    
    if pgrep -x "$process_name" > /dev/null; then
        log "[OK] $service_name → processo ativo (PID: $(pgrep -x $process_name))"
        return 0
    else
        log "[FAIL] $service_name → processo não encontrado"
        return 1
    fi
}

send_alert() {
    local service=$1
    local message=$2
    local severity=${3:-warning}
    
    bash "$ALERT_SCRIPT" "$service" "$message" "$severity"
}

# === EXECUÇÃO PRINCIPAL ===

if [[ $# -eq 0 ]]; then
    echo "Uso: $0 [servico|all]"
    echo "Serviços: http, port, process, all"
    exit 1
fi

SERVICE=${1:-all}
FAILED=0

log "======== INICIANDO VERIFICAÇÃO ($SERVICE) ========"

case $SERVICE in
    http)
        source "$CONFIGDIR/http-services.env"
        for entry in "${HTTP_CHECKS[@]}"; do
            # Formato: "nome|url|expected_code"
            IFS='|' read -r name url expected <<< "$entry"
            check_http "$url" "$name" "$expected" || { send_alert "$name" "HTTP check failed"; ((FAILED++)); }
        done
        ;;
    port)
        source "$CONFIGDIR/port-services.env"
        for entry in "${PORT_CHECKS[@]}"; do
            IFS='|' read -r name host port <<< "$entry"
            check_port "$host" "$port" "$name" || { send_alert "$name" "Port check failed"; ((FAILED++)); }
        done
        ;;
    process)
        source "$CONFIGDIR/process-services.env"
        for entry in "${PROCESS_CHECKS[@]}"; do
            IFS='|' read -r name process <<< "$entry"
            check_process "$process" "$name" || { send_alert "$name" "Process died"; ((FAILED++)); }
        done
        ;;
    all)
        $0 http || ((FAILED++))
        $0 port || ((FAILED++))
        $0 process || ((FAILED++))
        ;;
    *)
        echo "Serviço desconhecido: $SERVICE"
        exit 1
        ;;
esac

log "======== VERIFICAÇÃO COMPLETA (falhas: $FAILED) ========"

if [[ $FAILED -gt 0 ]]; then
    exit 2
fi
exit 0

Sim, eu uso arrays associativos e sourcing de arquivos de config. Isso faz o script ficar maior, mas mais fácil de manter. Guarde a versão verbose para quando precisar debugar às 2 da manhã.

Passo 2: O Script de Alerta — Notificações que Realmente Chegam

Este é o alert.sh:

#!/bin/bash
# alert.sh - Sistema de alertas multi-canal
# Uso: ./alert.sh [servico] [mensagem] [severity]

SERVICE="$1"
MESSAGE="$2"
SEVERITY="${3:-info}"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# Arquivo de log de alertas
ALERT_LOG="/opt/monitor/logs/alerts.log"

# Telegram (configure TOKEN e CHAT_ID no config)
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"

# Email (configure no config)
SMTP_HOST="${SMTP_HOST:-}"
SMTP_TO="${SMTP_TO:-}"

log_alert() {
    echo "[$SEVERITY] [$TIMESTAMP] $SERVICE: $MESSAGE" >> "$ALERT_LOG"
}

send_telegram() {
    if [[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
        emoji="ℹ️"
        case $SEVERITY in
            critical) emoji="🚨" ;;
            error) emoji="❌" ;;
            warning) emoji="⚠️" ;;
        esac
        
        payload=$(cat < /dev/null 2>&1
    fi
}

send_email() {
    if [[ -n "$SMTP_HOST" && -n "$SMTP_TO" ]]; then
        echo -e "Subject: [$SEVERITY] $SERVICE - $TIMESTAMP\n\n$MESSAGE" | \
            sendmail -f "$SMTP_FROM" "$SMTP_TO" 2>/dev/null || true
    fi
}

send_webhook() {
    if [[ -n "$WEBHOOK_URL" ]]; then
        curl -s -X POST "$WEBHOOK_URL" \
            -H "Content-Type: application/json" \
            -d "{\"service\":\"$SERVICE\",\"message\":\"$MESSAGE\",\"severity\":\"$SEVERITY\",\"timestamp\":\"$TIMESTAMP\"}" \
            > /dev/null 2>&1 || true
    fi
}

# Executa todos os canais
log_alert
send_telegram
send_email
send_webhook

Aqui está o pulo do gato: eu não escolho um canal. Eu tento todos. Se o Telegram estiver fora, o email entra. Se o email falhar, o webhook funciona. redundância não é overengineering — é engenharia de produção.

Passo 3: Configuração — O Arquivo .env que Você Versiona

Crie /opt/monitor/config/http-services.env:

#!/bin/bash
# Configuração de checks HTTP
declare -a HTTP_CHECKS=(
    "Automente Homepage|https://automente.com.br|200"
    "WordPress API|https://automente.com.br/wp-json/wp/v2/posts|200"
    "Pexels API|https://api.pexels.com/v1/search?query=test|200"
)

/opt/monitor/config/port-services.env:

#!/bin/bash
# Checks de porta TCP
declare -a PORT_CHECKS=(
    "MySQL Local|localhost|3306"
    "Redis Local|localhost|6379"
    "SSH Server|localhost|22"
)

/opt/monitor/config/process-services.env:

#!/bin/bash
# Checks de processos
declare -a PROCESS_CHECKS=(
    "Nginx|nginx"
    "PHP-FPM|php-fpm"
    "MySQL|mysqld"
)

🛠 O Perrengue: Na primeira vez que rodei isso em produção, o Cron disparou o alerta mas o Telegram BOT tinha sido desativado por falta de uso. Passei 20 minutos achando que era problema no script até perceber que simplesmente não recebi a mensagem. Solução: agora eu faço um "heartbeat" semanal que testa todos os canais de comunicação, não só os serviços monitorados.

Passo 4: Cron — O Maestro Silencioso

Agora o scheduling. Edite o crontab com crontab -e:

# Monitoramento AutoMente - GitOps Style
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""

# Verificações HTTP a cada 5 minutos
*/5 * * * * /opt/monitor/scripts/check-services.sh http >> /opt/monitor/logs/cron.log 2>&1

# Verificações de porta a cada 5 minutos
*/5 * * * * /opt/monitor/scripts/check-services.sh port >> /opt/monitor/logs/cron.log 2>&1

# Verificações de processo a cada 2 minutos (mais frequente pois processos morrem rápido)
*/2 * * * * /opt/monitor/scripts/check-services.sh process >> /opt/monitor/logs/cron.log 2>&1

# Heartbeat semanal (testa canais de alerta)
0 9 * * 1 /opt/monitor/scripts/heartbeat.sh >> /opt/monitor/logs/heartbeat.log 2>&1

Nota: eu coloco o caminho completo em todos os comandos. Nunca confie que o PATH do cron é igual ao seu PATH interativo. Essa lição me custou 3 horas de debugging uma vez.

Passo 5: GitOps — Versionando Sua Infraestrutura de Monitoramento

A beleza do GitOps não está em usar Kubernetes — está em versionar configuração. O script abaixo sincroniza sua pasta de monitoramento com um repositório Git:

#!/bin/bash
# gitops-sync.sh - Sincroniza configuração com repositório
# Coloque no cron ou como webhook

REPO_DIR="/opt/monitor"
GIT_REMOTE="${GIT_REMOTE:-origin}"
BACKUP_BRANCH="backup/$(date '+%Y%m%d-%H%M%S')"

cd "$REPO_DIR" || exit 1

# Adiciona scripts e configs (não logs)
git add scripts/ config/

# Commit automático se houver mudanças
if git diff --staged --quiet; then
    echo "[$(date)] Nenhuma mudança para commitar"
    exit 0
fi

git commit -m "Autocommit: $(date '+%Y-%m-%d %H:%M') - $(hostname)"

# Push
git push "$GIT_REMOTE" main || {
    # Se falhar, faz backup em branch
    git checkout -b "$BACKUP_BRANCH"
    git push "$GIT_REMOTE" "$BACKUP_BRANCH"
    git checkout main
}

Com isso, você pode:

  • Ver quem mudou a configuração e quando.
  • Fazer rollback se uma mudança quebrar tudo.
  • Duplicar a configuração em outro servidor com um git clone.
  • Dormir tranquilo sabendo que a configuração do monitoramento está versionada — não depende de documentação desatualizada.

Passo 6: O Heartbeat — Descobrindo Problemas Antes deles te Descobrirem

O problema com sistemas de monitoramento é que eles não monitoram a si mesmos. Você precisa de um "meta-monitoramento". O heartbeat verifica se o sistema de monitoramento está vivo:

#!/bin/bash
# heartbeat.sh - Testa se os sistemas de alerta estão funcionando

ALERT_LOG="/opt/monitor/logs/alerts.log"
LAST_ALERT_FILE="/tmp/last-alert-check"

log() { echo "[$(date '+%Y-%m-%d %H:%M')] $1"; }

# Verifica se o log de alertas foi modificado nas últimas 25 horas
if [[ -f "$ALERT_LOG" ]]; then
    LAST_MOD=$(stat -c %Y "$ALERT_LOG" 2>/dev/null || stat -f %m "$ALERT_LOG" 2>/dev/null)
    NOW=$(date +%s)
    AGE=$(( (NOW - LAST_MOD) / 3600 ))
    
    if [[ $AGE -gt 25 ]]; then
        log "[WARN] Nenhum alerta nos últimos $AGE horas — verificando se o sistema está rodando"
        
        # Testa manualmente os checks
        if ! /opt/monitor/scripts/check-services.sh all > /dev/null 2>&1; then
            log "[ERROR] Checks estão falhando mas nenhum alerta foi disparado!"
        fi
    fi
fi

# Verifica se o cron está ativo
if ! pgrep -f "check-services.sh" > /dev/null; then
    log "[CRITICAL] Cron não está rodando os checks! Reiniciando..."
    # Recarrega crontab
    crontab /var/spool/cron/crontabs/root 2>/dev/null || true
fi

# Testa Telegram (envia mensagem de teste)
TOKEN="${TELEGRAM_BOT_TOKEN}"
CHAT="${TELEGRAM_CHAT_ID}"
if [[ -n "$TOKEN" && -n "$CHAT" ]]; then
    RESP=$(curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
        -d "chat_id=$CHAT" \
        -d "text=🫀 Heartbeat OK — Monitoramento ativo em $(hostname) ($(date '+%H:%M'))" \
        -d "parse_mode=HTML")
    
    if echo "$RESP" | grep -q '"ok":true'; then
        log "[OK] Telegram funcionando"
    else
        log "[ERROR] Telegram falhou: $RESP"
    fi
fi

log "Heartbeat completo — sistema saudável"

🔧 Debugging na Prática: Uma vez o heartbeat me mandou um alerta de "Telegram falhou" às 9h da manhã. O bot estava funcionando perfeitamente — o problema era que eu tinha alterado o token no script mas não tinha exportado a variável de ambiente. O echo "$RESP" me salvou: vi que o Telegram retornava Forbidden: bot was blocked by the user. Eu tinha bloqueado o bot sem querer no meu próprio Telegram. Desbloqueie e problema resolvido. Moral: sempre prints de debug nos seus scripts de alerta.

Testando Tudo: O Script que Simula Falhas

Antes de colocar em produção, você precisa garantir que os alertas realmente funcionam. Este script simula falhas de propósito:

#!/bin/bash
# chaos-monkey.sh - Simula falhas para testar o sistema de monitoramento

set -euo pipefail

echo "⚠️ ATENÇÃO: Isso vai simular falhas no sistema"
echo "Pressione Ctrl+C em 5 segundos para cancelar..."
sleep 5

# Simula alerta
/opt/monitor/scripts/alert.sh "CHAOS-TEST" "Simulação de falha - ingnore este alerta" "warning"

# Simula falha de HTTP (vai falhar propositalmente)
curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:99999/nonexistent" || true

echo "✅ Teste completo. Verifique se recebeu os alertas."

Colocando em Produção: A Checklist Final

Antes de fazer o deploy, rode esta checklist:

  • Permissões: Scripts são executáveis? chmod +x /opt/monitor/scripts/*.sh
  • Logs: Diretórios existem? mkdir -p /opt/monitor/logs
  • Variáveis: Tokens e credenciais estão no environment? Teste com echo $TELEGRAM_BOT_TOKEN
  • Cron: Está rodando? grep check-services /var/log/cron.log
  • Git: Repositório está sincronizado? git status
  • Chaos: Rodou o teste de falhas? Este passo é obrigatório.

Escalando: Quando um Servidor Não é Enough

Esse sistema funciona para 1-5 servidores. Acima disso, você vai querer:

  • Inventário centralizado: Um arquivo servers.conf que lista todos os servidores e seus checks específicos.
  • Agente ringan: Ao invés de cron em cada máquina, um único servidor central que faz ssh em todos e coleta resultados.
  • Métricas históricas: Integre com InfluxDB para gráficos de uptime/downtime.

Mas para a maioria dos projetos pessoais e pequenos negócios, um servidormonitorando a si mesmo é mais que suficiente. Eu conheço empresas pagando R$500/mês em ferramentas de monitoramento que poderiam usar exatamente isso que mostrei — e dariam menos dor de cabeça.

Conclusão: Monitore como se Importasse

A diferença entre um sysadmin que dorme à noite e um que acorda às 3h com ligações do cliente não é a ferramenta — é a atitude. Ferramenta boa ajuda, mas cultura de monitoramento é o que realmente importa.

Esse sistema não é bonito. Não tem dashboard colorido. Não integra com Slack oficialmente. Mas ele funciona. E quando algo quebra, você sabe exatamente o que está errado porque você escreveu o script, você definiu os critérios de sucesso, e você versionou tudo no Git.

Isso é Infraestrutura como Código. Isso é GitOps. Isso funciona.


🔔 E você? Qual serviço você gostaria de automatizar o monitoramento? Me conta nos comentários — pode ser algo simples como verificar se seu site está no ar, ou algo mais complexo como monitorar uso de disco e espaço em servidores remotos. Se houver interesse, o próximo artigo pode ser exatamente sobre isso: como expandir esse sistema para monitorar múltiplos servidores com relatórios diários por email.

Enquanto isso, vá lá e coloca pelo menos o basicão pra rodar. Sua noite de sono futura vai agradecer.

Posts Similares