feat(self-healing): adiciona o bridge server do Grafana para Gitea e guia de arquitetura
continuous-integration/webhook Deploy concluido com sucesso (VPS 4)
continuous-integration/webhook Deploy concluido com sucesso (VPS 4)
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# 🔄 Loop de Auto-Cura (Self-Healing DevOps) com Gitea + Grafana + Loki
|
||||
|
||||
Este diretório contém a infraestrutura e os scripts necessários para integrar o seu sistema de monitoramento centralizado (**Grafana + Loki**) ao **Gitea**, permitindo que erros de runtime nos containers de produção abram Issues automaticamente para serem corrigidas pelo Agente de IA.
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Visão Geral da Arquitetura
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Docker Container na VPS 1] -->|Gera Logs de Erro| B[Promtail / Loki]
|
||||
B -->|Métricas & Alertas| C[Grafana Alerting]
|
||||
C -->|Webhook de Alerta| D[Grafana-Gitea Bridge]
|
||||
D -->|API POST /issues| E[Gitea Server - VPS 3]
|
||||
E -->|Abre Issue de Bug| F[Agente de IA]
|
||||
F -->|Corrige, Comita e Dá Push| A
|
||||
```
|
||||
|
||||
Como o formato de payload de alertas do Grafana é diferente do formato que o Gitea espera na API de Issues, o **`bridge.js`** atua como o tradutor inteligente desse fluxo, formatando o alerta em um relatório em Markdown limpo e amigável para o Agente.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Como Configurar e Ativar o Fluxo
|
||||
|
||||
### Passo 1: Iniciar a Bridge (`bridge.js`)
|
||||
A Bridge pode rodar na **VPS 4 (Dev/Builder)** ou na **VPS 1 (Produção/SOC)**. Ela escuta na porta `9091` por padrão.
|
||||
|
||||
Para rodá-la em segundo plano com PM2 ou Node:
|
||||
```bash
|
||||
# Iniciar a bridge informando o Token do Gitea
|
||||
GITEA_TOKEN="ada47ca2fb196319dd10245cc216a327dac857ec" node bridge.js
|
||||
```
|
||||
|
||||
### Passo 2: Configurar o Canal de Alerta no Grafana (VPS 1)
|
||||
1. Acesse o seu Grafana em `http://10.99.0.1:3000`.
|
||||
2. Vá em **Alerting** -> **Contact points** -> **New contact point**.
|
||||
3. Escolha um nome (ex: `Gitea-Bridge`).
|
||||
4. Selecione a integração **Webhook**.
|
||||
5. No campo **URL**, aponte para a Bridge: `http://10.99.0.4:9091/alerts` (ou o IP onde você subiu a bridge).
|
||||
6. Clique em **Save contact point**.
|
||||
|
||||
### Passo 3: Criar a Regra de Alerta no Grafana (Loki Query)
|
||||
1. Vá em **Alerting** -> **Alert rules** -> **New alert rule**.
|
||||
2. Defina uma consulta LogQL no Loki para monitorar erros no seu container WhatsApp. Exemplo:
|
||||
```logql
|
||||
sum(rate({vps="vps4_whatsapp", job="varlogs"} |= "error" or "Exception" or "Uncaught"[5m])) > 0
|
||||
```
|
||||
3. Defina as anotações do Alerta:
|
||||
* **Summary:** `Foi detectado um erro crítico de execução no container.`
|
||||
* **Description:** `{{ $values.var_name }} erros detectados nos últimos 5 minutos.`
|
||||
4. Na seção **Notifications**, vincule o alerta ao Contact Point `Gitea-Bridge` que criamos.
|
||||
5. Salve a regra.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Testando o Fluxo de Auto-Cura
|
||||
|
||||
Quando ocorrer qualquer erro no container, o fluxo será disparado:
|
||||
1. O Grafana detectará e enviará o alerta para a Bridge.
|
||||
2. A Bridge criará uma Issue no Gitea com o título: `🚨 [AUTO-BUG] Docker Container Error detectado na vps4_whatsapp`.
|
||||
3. O corpo da Issue conterá a stack trace do erro.
|
||||
4. O Agente de IA poderá ler a Issue e realizar o reparo autônomo do código!
|
||||
@@ -0,0 +1,127 @@
|
||||
const http = require('http');
|
||||
|
||||
const PORT = process.env.PORT || 9091;
|
||||
const HOST = '0.0.0.0'; // Escuta em todas as interfaces (incluindo VPN)
|
||||
const GITEA_API_URL = process.env.GITEA_API_URL || 'http://10.99.0.3:3000/api/v1';
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN || 'ada47ca2fb196319dd10245cc216a327dac857ec';
|
||||
const GITEA_REPO_OWNER = process.env.GITEA_REPO_OWNER || 'ruicesar';
|
||||
const GITEA_REPO_NAME = process.env.GITEA_REPO_NAME || 'clube67_newwhats.local';
|
||||
|
||||
function log(message) {
|
||||
console.log(`[${new Date().toISOString()}] ${message}`);
|
||||
}
|
||||
|
||||
// Envia a Issue para o Gitea
|
||||
function createGiteaIssue(title, body) {
|
||||
const postData = JSON.stringify({
|
||||
title: title,
|
||||
body: body,
|
||||
assignees: [GITEA_REPO_OWNER]
|
||||
});
|
||||
|
||||
const url = new URL(`${GITEA_API_URL}/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}/issues`);
|
||||
|
||||
const options = {
|
||||
hostname: url.hostname,
|
||||
port: url.port,
|
||||
path: url.pathname,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token ' + GITEA_TOKEN,
|
||||
'Content-Length': Buffer.byteLength(postData)
|
||||
}
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let responseData = '';
|
||||
res.on('data', chunk => responseData += chunk);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 201) {
|
||||
log(`Sucesso: Issue criada no Gitea para o alerta.`);
|
||||
} else {
|
||||
log(`Erro ao criar Issue no Gitea (HTTP ${res.statusCode}): ${responseData}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (e) => {
|
||||
log(`Erro na requisição para o Gitea: ${e.message}`);
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method !== 'POST' || req.url !== '/alerts') {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
return res.end(JSON.stringify({ error: 'Endpoint não encontrado' }));
|
||||
}
|
||||
|
||||
let body = '';
|
||||
req.on('data', chunk => { body += chunk.toString(); });
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const payload = JSON.parse(body);
|
||||
log(`Recebido payload de alerta do Grafana.`);
|
||||
|
||||
// Trata múltiplos alertas enviados no mesmo payload pelo Grafana
|
||||
const alerts = payload.alerts || [];
|
||||
if (alerts.length === 0) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
return res.end(JSON.stringify({ status: 'ignored', reason: 'Nenhum alerta ativo' }));
|
||||
}
|
||||
|
||||
alerts.forEach(alert => {
|
||||
const status = alert.status; // firing ou resolved
|
||||
if (status !== 'firing') return; // Ignora alertas resolvidos no momento
|
||||
|
||||
const alertName = alert.labels.alertname || 'Alerta Sem Nome';
|
||||
const vpsName = alert.labels.vps || 'Desconhecida';
|
||||
const summary = alert.annotations.summary || alert.annotations.description || 'Nenhum sumário fornecido.';
|
||||
const fullLogs = alert.annotations.logs || 'Sem logs anexados.';
|
||||
|
||||
const issueTitle = `🚨 [AUTO-BUG] ${alertName} detectado na ${vpsName}`;
|
||||
const issueBody = `### 📟 RELATÓRIO DE ERRO AUTOMÁTICO (SELF-HEALING)
|
||||
|
||||
O monitoramento centralizado (Grafana/Loki) detectou uma falha crítica em tempo de execução.
|
||||
|
||||
* **Falha:** \`${alertName}\`
|
||||
* **Origem:** \`${vpsName}\`
|
||||
* **Estado:** \`Disparando\`
|
||||
* **Hora do Evento:** \`${alert.startsAt || new Date().toISOString()}\`
|
||||
|
||||
---
|
||||
|
||||
#### 📝 Detalhes & Logs Extraídos:
|
||||
\`\`\`logs
|
||||
${summary}
|
||||
${fullLogs}
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Ação Recomendada para o Agente de IA:**
|
||||
> 1. Analise a stack trace ou a mensagem de erro acima.
|
||||
> 2. Localize o arquivo de código fonte correspondente no repositório.
|
||||
> 3. Implemente a correção, realize os testes locais e execute o commit/push.
|
||||
`;
|
||||
|
||||
createGiteaIssue(issueTitle, issueBody);
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'processed' }));
|
||||
} catch (e) {
|
||||
log(`Erro ao processar payload: ${e.message}`);
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Payload inválido' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
log(`Grafana-Gitea Bridge ativa em http://${HOST}:${PORT}/alerts`);
|
||||
});
|
||||
Reference in New Issue
Block a user