Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Implementar circuit_breaker_remediacao() que para automações após falhas repetidas
Implementar canary_rollback() com redirecionamento gradual de tráfego
Usar o catálogo de remediações com modos dry_run e live
Projetar o loop diagnóstico → remediação → verificação → escalação
Completar os 2 TODOs do starter
Por que isso importa
Auto-remediação sem circuit breaker é perigosa: um agente que tenta a mesma remediação 10x sem sucesso pode agravar um incidente. Um pod com OOMKilled que é reiniciado 10x sem aumentar o memory limit é um loop sem resolução — e cada restart causa downtime adicional.
O circuit breaker para auto-remediação é o mesmo padrão que microserviços usam para dependências: após N falhas, para de tentar e escala para humano. Canary rollback é a alternativa ao rollback imediato total — reduz risco gradualmente.
Conceitos Fundamentais
Catálogo de Remediações
ACOES_REMEDIACAO = {
'oom_pod': AcaoRemediacao(
tipo='aumentar_memory_limit',
descricao='Aumentar memory limit do deployment em 50%',
risco='baixo',
reversivel=True,
comando_dry_run='kubectl patch deployment {servico} ... --dry-run=client',
comando_live='kubectl patch deployment {servico} ...',
),
'cpu_alta': AcaoRemediacao(
tipo='scale_out',
descricao='Scale out: adicionar 2 replicas',
risco='baixo',
reversivel=True,
...
),
}Cada remediação tem risco (baixo/médio/alto) e reversivel. O agente pode usar esse metadado para decidir se executa automaticamente ou escala para humano.
TODO 1: circuit_breaker_remediacao(servico, historico_falhas)
def circuit_breaker_remediacao(servico: str, historico_falhas: dict) -> bool:
"""Retorna True se o circuit breaker deve ser ativado (parar remediações)."""
from datetime import datetime, timedelta
falhas_do_servico = historico_falhas.get(servico, [])
# Critério 1: mais de 3 falhas na última hora
uma_hora_atras = datetime.utcnow() - timedelta(hours=1)
falhas_recentes = [
f for f in falhas_do_servico
if datetime.fromisoformat(f['timestamp']) > uma_hora_atras
]
if len(falhas_recentes) >= 3:
print(f'[CIRCUIT BREAKER] ABERTO: {servico} teve {len(falhas_recentes)} falhas na última hora')
return True
# Critério 2: remediação revertida 2+ vezes
reversoes = [f for f in falhas_do_servico if f.get('tipo') == 'revertido']
if len(reversoes) >= 2:
print(f'[CIRCUIT BREAKER] ABERTO: {servico} teve {len(reversoes)} reversões')
return True
return False # circuit breaker fechado — pode continuar remediandoUso no pipeline:
historico_falhas = {} # dict: servico → lista de {timestamp, tipo, motivo}
# Antes de cada remediação
if circuit_breaker_remediacao(alerta.servico, historico_falhas):
return escalar_para_humano(motivo='Circuit breaker ativado após múltiplas falhas')
# Após remediação falhar
historico_falhas.setdefault(alerta.servico, []).append({
'timestamp': datetime.utcnow().isoformat(),
'tipo': 'falha',
'motivo': 'verificar_saude retornou False',
})TODO 2: canary_rollback(servico, versao_anterior, taxa_erro_atual)
def canary_rollback(servico: str, versao_anterior: str, taxa_erro_atual: float) -> str:
etapas = []
# Condição de gatilho: taxa de erro > 5% após deploy
if taxa_erro_atual < 5.0:
return f'Taxa de erro {taxa_erro_atual}% está abaixo do threshold. Canary rollback não necessário.'
# Etapa 1: Redirecionar 50% do tráfego para versão anterior
etapas.append(f'[{datetime.utcnow().strftime("%H:%M:%S")}] Redirecionando 50% do tráfego para {servico}:{versao_anterior}')
etapas.append(f' [MOCK] kubectl patch service {servico} -p \'{"canary-weight": "50"}\'')
# Etapa 2: Aguardar 2 minutos (simulado)
etapas.append(f' Aguardando 2 minutos para estabilização...')
# time.sleep(120) — comentado para não bloquear no lab
# Etapa 3: Verificar se erro caiu
import random
erro_apos_canary = taxa_erro_atual * random.uniform(0.3, 0.7) # simula melhora
if erro_apos_canary < taxa_erro_atual * 0.5:
# Erro caiu mais de 50% → rollback total
etapas.append(f' Taxa de erro caiu: {taxa_erro_atual:.1f}% → {erro_apos_canary:.1f}%')
etapas.append(f'[{datetime.utcnow().strftime("%H:%M:%S")}] Redirecionando 100% para {versao_anterior}')
etapas.append(f' [MOCK] kubectl set image deployment/{servico} {servico}={servico}:{versao_anterior}')
etapas.append(f' ✅ Rollback completo para {versao_anterior}')
else:
# Erro não caiu → investigar versão anterior também
etapas.append(f' Taxa de erro após canary: {erro_apos_canary:.1f}% (esperado < {taxa_erro_atual * 0.5:.1f}%)')
etapas.append(f' ⚠️ Versão anterior pode ter o mesmo problema. Escalar para SRE.')
return '\n'.join(etapas)Aprofundamento Técnico
Modos Dry-Run e Live
def executar_acao(acao: AcaoRemediacao, servico: str, modo: ModoExecucao) -> dict:
if modo == ModoExecucao.DRY_RUN:
cmd = acao.comando_dry_run.format(servico=servico)
print(f' [DRY-RUN] {cmd}')
return {'status': 'simulado', 'comando': cmd, 'resultado': f'[DRY-RUN] simulado'}
else:
cmd = acao.comando_live.format(servico=servico)
print(f' [LIVE] {cmd}')
return {'status': 'executado', 'resultado': 'sucesso (mock)'}Sequência mandatória: sempre dry-run primeiro, live só após confirmação:
1. executar_remediacao(tipo='oom_pod', modo='dry_run') → simular
2. Verificar que dry-run está correto
3. executar_remediacao(tipo='oom_pod', modo='live') → aplicarO system prompt reforça: “Sempre dry-run antes de live.”
Loop de Remediação
alerta recebido
↓
circuit_breaker_remediacao → aberto? → escalar_para_humano
↓ fechado
diagnosticar_alerta → mapear tipo → acao_recomendada
↓
executar_acao(dry_run) → verificar resultado esperado
↓
executar_acao(live)
↓
verificar_saude → resolvido?
↓ sim ↓ não
registrar_audit registrar_falha → circuit_breaker?
end ↓ não → tentar outra ação
↓ sim → escalar_para_humanoPor que Canary e não Rollback Total Imediato?
Rollback imediato move 100% do tráfego de volta para a versão anterior em segundos. O risco: se a versão anterior também tem o problema (bug na dependência, por exemplo), você não ganhou nada — mas perdeu tempo.
Canary rollback é gradual: 50% → observar → 100% se melhorou. Isso detecta o caso raro onde a versão anterior também está problemática antes de comprometer todo o tráfego com ela.
Exemplos Anotados
Exemplo 1: Agente com Circuit Breaker
# Simular histórico de falhas
historico_falhas = {
'checkout-service': [
{'timestamp': (datetime.utcnow() - timedelta(minutes=30)).isoformat(), 'tipo': 'falha', 'motivo': 'memory ainda alta'},
{'timestamp': (datetime.utcnow() - timedelta(minutes=20)).isoformat(), 'tipo': 'falha', 'motivo': 'memory ainda alta'},
{'timestamp': (datetime.utcnow() - timedelta(minutes=10)).isoformat(), 'tipo': 'revertido', 'motivo': 'timeout'},
]
}
# Circuit breaker decide
bloqueado = circuit_breaker_remediacao('checkout-service', historico_falhas)
# → [CIRCUIT BREAKER] ABERTO: checkout-service teve 3 falhas na última hora
# → bloqueado = True
# Agente escala ao invés de remediar
if bloqueado:
escalar_para_humano('Circuit breaker ativo após 3 falhas/1h', urgencia='P1')Exemplo 2: Canary Rollback
resultado = canary_rollback('api-gateway', 'v2.0.5', taxa_erro_atual=12.5)
print(resultado)
# [14:35:01] Redirecionando 50% do tráfego para api-gateway:v2.0.5
# [MOCK] kubectl patch service api-gateway -p '{"canary-weight": "50"}'
# Aguardando 2 minutos para estabilização...
# Taxa de erro caiu: 12.5% → 4.2%
# [14:35:01] Redirecionando 100% para v2.0.5
# [MOCK] kubectl set image deployment/api-gateway api-gateway=api-gateway:v2.0.5
# ✅ Rollback completo para v2.0.5Padrões e Armadilhas
Padrões
Padrão 1: Circuit breaker com janela de tempo
# Verificar falhas NA ÚLTIMA HORA — não falhas históricas totais
uma_hora_atras = datetime.utcnow() - timedelta(hours=1)
falhas_recentes = [f for f in falhas if datetime.fromisoformat(f['timestamp']) > uma_hora_atras]Padrão 2: Dry-run obrigatório no catálogo
@dataclass
class AcaoRemediacao:
comando_dry_run: str # sempre presente
comando_live: str # sempre presente
# ambos são obrigatórios — não há ação sem dry-runPadrão 3: Escalação com contexto completo
escalar_para_humano(
motivo='3 remediações de OOMKilled falharam — memory continua aumentando',
acoes_tentadas=['aumentar_memory_limit x3', 'escalar_hpa'],
urgencia='P1',
)Armadilhas
⚠️ Armadilha 1: Circuit breaker sem janela de tempo
# RUIM: conta falhas de todos os tempos
if len(historico_falhas[servico]) >= 3:
return True # pode bloquear por uma falha de semanas atrás
# BOM: só conta falhas recentes
falhas_recentes = [f for f in historico if datetime.fromisoformat(f['timestamp']) > uma_hora_atras]⚠️ Armadilha 2: Canary sem timeout
# Sem timeout no canary, o agente fica esperando indefinidamente
# Simular timeout:
import asyncio
async def canary_com_timeout(servico, versao, erro, timeout_s=300):
try:
return await asyncio.wait_for(canary_rollback_async(servico, versao, erro), timeout=timeout_s)
except asyncio.TimeoutError:
return 'Canary timeout — escalar para humano'⚠️ Armadilha 3: Executar live sem verificar dry-run primeiro
# Sistema com policy que exige dry-run antes de live
def executar_acao(acao, servico, modo):
if modo == ModoExecucao.LIVE:
# Verificar que dry-run foi executado recentemente
if not HISTORICO_DRY_RUN.get(f'{acao.tipo}_{servico}'):
raise ValueError('Dry-run obrigatório antes de live')Se não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
2 TODOs no starter.py:
TODO 1 — circuit_breaker_remediacao(servico, historico_falhas):
def circuit_breaker_remediacao(servico: str, historico_falhas: dict) -> bool:
from datetime import datetime, timedelta
falhas = historico_falhas.get(servico, [])
uma_hora_atras = datetime.utcnow() - timedelta(hours=1)
recentes = [f for f in falhas if datetime.fromisoformat(f['timestamp']) > uma_hora_atras]
if len(recentes) >= 3:
print(f'[CB] ABERTO: {len(recentes)} falhas recentes em {servico}')
return True
reversoes = [f for f in falhas if f.get('tipo') == 'revertido']
if len(reversoes) >= 2:
print(f'[CB] ABERTO: {len(reversoes)} reversões em {servico}')
return True
return FalseTODO 2 — canary_rollback(servico, versao_anterior, taxa_erro_atual): Implemente o fluxo com as 3 etapas: redirecionar 50% → aguardar → verificar → 100% ou escalar.
Rode com python starter.py e observe o agente processando o alerta OOMKilled em dry_run.
Agora você está pronto para o lab.