Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Integrar todas as capacidades do módulo em um pipeline de agente único
Implementar audit log JSONL com registrar_audit() e EntradaAudit
Usar StatusIncidente como máquina de estados através do pipeline
Analisar o fluxo: alerta → análise → runbook → diagnóstico → remediação → verificação → postmortem
Executar o pipeline completo para 2 alertas de demo
Por que isso importa
O projeto integrador é a síntese do módulo 6. Cada unidade anterior construiu uma peça: geração de IaC, diagnóstico K8s, observabilidade, ChatOps, segurança, CI/CD, FinOps, RAG e auto-remediação. Aqui, todas essas peças se combinam em um agente que processa um incidente do início ao fim — como um SRE autônomo de plantão.
O audit log JSONL é crítico para produção: sem rastreabilidade completa de cada ação do agente, é impossível fazer postmortem, auditar compliance ou depurar comportamentos inesperados.
Conceitos Fundamentais
Máquina de Estados do Incidente
class StatusIncidente(Enum):
ABERTO = 'aberto'
DIAGNOSTICANDO = 'diagnosticando'
REMEDIANDO = 'remediando'
VERIFICANDO = 'verificando'
RESOLVIDO = 'resolvido'
ESCALADO = 'escalado'Cada tool muda o status:
analisar_alerta → DIAGNOSTICANDO
buscar_runbook → DIAGNOSTICANDO
executar_diagnostico → DIAGNOSTICANDO
aplicar_remediacao → REMEDIANDO
verificar_resolucao → VERIFICANDO → RESOLVIDO ou continua
gerar_postmortem → (final, status = RESOLVIDO)
escalar_incidente → ESCALADOO loop para quando incidente.status in (RESOLVIDO, ESCALADO).
Audit Log JSONL
@dataclass
class EntradaAudit:
incidente_id: str
timestamp: str
acao: str # nome da tool
agente: str # 'devops-agent-v1'
input_data: dict # args da tool
output_data: str # output truncado a 500 chars
duracao_ms: int # latência da tool
status: str # status do incidente no momento
def registrar_audit(incidente: Incidente, acao: str, input_data: dict, output: str, duracao_ms: int):
entrada = EntradaAudit(
incidente_id=incidente.id,
timestamp=datetime.utcnow().isoformat(),
acao=acao,
agente='devops-agent-v1',
input_data=input_data,
output_data=output[:500],
duracao_ms=duracao_ms,
status=incidente.status.value,
)
incidente.audit_entries.append(entrada)
with open(AUDIT_FILE, 'a', encoding='utf-8') as f:
f.write(json.dumps(asdict(entrada), ensure_ascii=False) + '\n')
mode='a': append — nunca sobrescreve o arquivo. Cada entrada é uma linha JSON completa. asdict() do dataclasses converte automaticamente para dict.Pipeline de 6 Passos
mensagens = [{
'role': 'user',
'content': f'''Incidente recebido: {json.dumps(alerta)}
Execute o pipeline completo:
1. Analisar alerta
2. Buscar runbook adequado
3. Executar diagnóstico técnico
4. Aplicar remediação (dry_run primeiro)
5. Verificar resolução
6. Gerar post-mortem ou escalar se não resolvido''',
}]O LLM recebe as 7 tools (analisar_alerta, buscar_runbook, executar_diagnostico, aplicar_remediacao, verificar_resolucao, gerar_postmortem, escalar_incidente) e executa os 6 passos em sequência.
Aprofundamento Técnico
Runbooks Rápidos por Tipo
RUNBOOKS_RAPIDOS = {
'OOMKilled': 'Aumentar memory limit em 50% + escalar HPA',
'HighCPU': 'Scale out +2 replicas + verificar query lenta',
'DiskFull': 'Limpar logs antigos + verificar growth rate',
'ConnectionPoolExhausted': 'Reiniciar connection pool + aumentar pool_size',
'CertificateExpired': 'Renovar certificado + reload nginx',
'CircuitBreakerOpen': 'Verificar downstream + aguardar recovery window',
}Este dict é o “conhecimento operacional” embutido no agente. Para produção, viria do RAG sobre runbooks reais (Unidade 10).
Postmortem Automático
elif nome == 'gerar_postmortem':
resultado = f'''POST-MORTEM — {args.get("incidente_id")}
Causa Raiz: {args.get("causa_raiz")}
Ações executadas: {", ".join(args.get("acoes", []))}
Resolvido em: {incidente.resolvido_em}
Ações preventivas: [ver exercício avançado]'''Para postmortem real com LLM:
response = client.messages.create(
model='claude-haiku-4-5-20251001',
max_tokens=600,
messages=[{
'role': 'user',
'content': f'''Gere um post-mortem estruturado:
Incidente: {incidente_id}
Causa raiz: {causa_raiz}
Ações executadas: {acoes}
Tempo de resolução: {tempo_min} minutos
Inclua: resumo executivo, timeline, causa raiz, impacto, ações preventivas.''',
}],
)Medição de Latência de Tools
def executar_tool_integrador(nome: str, args: dict, incidente: Incidente) -> str:
inicio = datetime.utcnow()
# ... execução da tool ...
duracao = int((datetime.utcnow() - inicio).total_seconds() * 1000)
registrar_audit(incidente, nome, args, resultado, duracao)
return resultadoMedir latência de cada tool no audit log permite identificar gargalos: se executar_diagnostico demora 5s consistentemente, pode ser o LLM lento ou a tool em si.
Exemplos Anotados
Exemplo 1: Trace do Pipeline para OOMKilled
[DEVOPS AGENT] Processando INC-20240115-143200: OOMKilled em checkout-service
[aberto] → analisar_alerta({'alerta': {'tipo': 'OOMKilled', ...}})
← Alerta analisado: tipo=OOMKilled, serviço=checkout-service
[diagnosticando] → buscar_runbook({'tipo_incidente': 'OOMKilled'})
← Runbook para OOMKilled: Aumentar memory limit em 50% + escalar HPA
[diagnosticando] → executar_diagnostico({'servico': 'checkout-service', 'verificacoes': ['logs', 'metricas', 'hpa']})
← Diagnóstico de checkout-service: logs=OK | metricas=OK | hpa=OK
[diagnosticando] → aplicar_remediacao({'tipo': 'aumentar_memory_limit', 'servico': 'checkout-service', 'modo': 'dry_run'})
← [DRY_RUN] Remediação aumentar_memory_limit em checkout-service: executado
[remediando] → aplicar_remediacao({'tipo': 'aumentar_memory_limit', 'servico': 'checkout-service', 'modo': 'live'})
← [LIVE] Remediação aumentar_memory_limit em checkout-service: executado
[remediando] → verificar_resolucao({'servico': 'checkout-service', 'metrica_alvo': 'memory_restarts'})
← Verificação de checkout-service: RESOLVIDO ✓
[verificando] → gerar_postmortem({'incidente_id': 'INC-20240115-143200', 'causa_raiz': 'Memory limit muito baixo', 'acoes': [...]})
← POST-MORTEM — INC-20240115-143200 ...
[RESULTADO] INC-20240115-143200: resolvido
Ações: ['aumentar_memory_limit (dry_run)', 'aumentar_memory_limit (live)']
Audit entries: 7Exemplo 2: Lendo o Audit Log
# Após execução, ler o audit log
with open('devops_audit.jsonl') as f:
entries = [json.loads(line) for line in f]
# Estatísticas
for entry in entries:
print(f'{entry["timestamp"]} | {entry["acao"]:<30} | {entry["duracao_ms"]}ms | {entry["status"]}')
# Análise de latência
import statistics
duracoes = [e['duracao_ms'] for e in entries]
print(f'Latência média: {statistics.mean(duracoes):.1f}ms')
print(f'Latência max: {max(duracoes)}ms — {max(entries, key=lambda e: e["duracao_ms"])["acao"]}')Padrões e Armadilhas
Padrões
Padrão 1: Loop com condição de saída dupla
for i in range(12):
response = client.messages.create(...)
# Saída 1: LLM decidiu terminar
if response.stop_reason == 'end_turn':
break
# Saída 2: incidente resolvido/escalado via tool
if incidente.status in (StatusIncidente.RESOLVIDO, StatusIncidente.ESCALADO):
breakPadrão 2: Audit de cada tool, não do loop
# Audit dentro de executar_tool_integrador, não fora
# Isso garante que TODA chamada de tool é registrada
duracao = int((datetime.utcnow() - inicio).total_seconds() * 1000)
registrar_audit(incidente, nome, args, resultado, duracao)Padrão 3: asdict() para serialização de dataclass
# dataclasses.asdict() converte Enum para valor string automaticamente
f.write(json.dumps(asdict(entrada), ensure_ascii=False) + '\n')
# ensure_ascii=False → preserva caracteres PT-BR no logArmadilhas
⚠️ Armadilha 1: Audit file truncado em vez de append
# ERRADO: sobrescreve o log a cada execução
with open(AUDIT_FILE, 'w') as f: ...
# CORRETO: append — preserva histórico de incidentes anteriores
with open(AUDIT_FILE, 'a') as f: ...⚠️ Armadilha 2: Loop sem saída por status
# Sem verificar incidente.status, o loop continua mesmo após RESOLVIDO
# O agente pode chamar mais tools desnecessárias (e custar mais tokens)
for i in range(12):
...
# FALTA: if incidente.status in (RESOLVIDO, ESCALADO): break⚠️ Armadilha 3: output_data sem truncamento
# Output de tools pode ser longo (logs, YAML, trace)
# Sem truncar, o audit log pode crescer rapidamente
output_data=output[:500], # truncar é importanteSe não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
Esta unidade não tem TODOs — o código está completo. O lab é exploratório:
Rode python starter.py e observe os 2 alertas sendo processados
Abra devops_audit.jsonl e analise o trail de auditoria
Modifique verificar_resolucao para sempre retornar resolvido=False — observe o agente escalar
Adicione um 3º alerta com tipo CertificateExpired e veja o agente buscar o runbook correto
Conecte funcionalidades reais: substitua o mock de diagnóstico por chamadas reais ao kubectl ou Prometheus
O pipeline integrador é a base para um agente DevOps de produção real. Cada substituição de mock por implementação real aproxima o lab da produção.
Agora você está pronto para o lab.