Todo engenheiro que opera sistemas em produção por tempo suficiente aprende uma lição inevitável: sistemas complexos falham. Não é uma questão de se, mas de quando e como. A questão relevante não é construir sistemas que nunca falhem — isso é impossível em qualquer escala significativa — mas construir sistemas que falhem de maneira previsível, controlada e recuperável.
A diferença entre um sistema resiliente e um frágil não está na ausência de falhas — está em como o sistema se comporta quando as falhas ocorrem. Um sistema resiliente degrada graciosamente: quando um componente falha, os demais continuam funcionando com capacidade reduzida, sem colapso em cascata. Um sistema frágil, ao contrário, amplifica falhas locais em interrupções sistêmicas.
O Chaos Engineering é a disciplina de experimentar intencionalmente falhas em sistemas de produção para descobrir fraquezas antes que elas causem incidentes reais. A premissa é contraintuitiva mas poderosa: se o sistema vai falhar de qualquer forma, é melhor que a primeira falha aconteça em condições controladas, com o time de plantão disponível, do que às 3 da manhã durante um pico de tráfego.
Os Padrões de Resiliência Fundamentais
Antes de experimentar caos, é necessário implementar os padrões que tornam um sistema capaz de tolerar falhas. Esses padrões são os blocos de construção de qualquer arquitetura resiliente.
Circuit Breaker
O Circuit Breaker protege um serviço de continuar tentando chamar um serviço dependente que está falhando. Funciona como um disjuntor elétrico: quando o número de falhas ultrapassa um limiar, o circuito "abre" e todas as chamadas subsequentes são rejeitadas imediatamente sem tentar o serviço downstream. Após um período de espera, o circuito entra em estado "half-open" e permite uma chamada de teste — se bem-sucedida, o circuito fecha; se não, continua aberto.
// src/resilience/circuit-breaker.js
class CircuitBreaker {
constructor(options = {}) {
this.nome = options.nome || 'circuit-breaker';
this.limiarFalhas = options.limiarFalhas || 5;
this.timeoutAbertura = options.timeoutAbertura || 60000; // 60s
this.timeoutRequisicao = options.timeoutRequisicao || 5000; // 5s
// Estados: FECHADO (normal) | ABERTO (rejeitando) | HALF_OPEN (testando)
this.estado = 'FECHADO';
this.contagemFalhas = 0;
this.ultimaFalha = null;
this.contagemSuccessos = 0;
// Métricas para o Prometheus
this._inicializarMetricas();
}
_inicializarMetricas() {
const { Counter, Gauge } = require('prom-client');
this.metricaEstado = new Gauge({
name: `circuit_breaker_state`,
help: 'Estado do circuit breaker (0=fechado, 1=half-open, 2=aberto)',
labelNames: ['nome'],
});
this.metricaChamadas = new Counter({
name: `circuit_breaker_calls_total`,
help: 'Total de chamadas pelo circuit breaker',
labelNames: ['nome', 'resultado'],
});
}
async executar(funcao, fallback = null) {
// Circuito aberto — rejeita imediatamente
if (this.estado === 'ABERTO') {
const agora = Date.now();
if (agora - this.ultimaFalha < this.timeoutAbertura) {
this.metricaChamadas.inc({ nome: this.nome, resultado: 'rejeitado' });
if (fallback) return fallback();
throw new Error(
`Circuit breaker ${this.nome} aberto — aguardando recuperação`
);
}
// Timeout expirou — tenta half-open
this._transicionarPara('HALF_OPEN');
}
try {
// Executa com timeout
const resultado = await Promise.race([
funcao(),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error('Timeout da requisição')),
this.timeoutRequisicao
)
),
]);
this._registrarSuccesso();
this.metricaChamadas.inc({ nome: this.nome, resultado: 'sucesso' });
return resultado;
} catch (erro) {
this._registrarFalha(erro);
this.metricaChamadas.inc({ nome: this.nome, resultado: 'falha' });
if (fallback) return fallback();
throw erro;
}
}
_registrarSuccesso() {
if (this.estado === 'HALF_OPEN') {
this.contagemSuccessos++;
// Requer 2 sucessos consecutivos para fechar
if (this.contagemSuccessos >= 2) {
this._transicionarPara('FECHADO');
}
}
this.contagemFalhas = 0;
}
_registrarFalha(erro) {
this.ultimaFalha = Date.now();
this.contagemFalhas++;
this.contagemSuccessos = 0;
console.warn(JSON.stringify({
level: 'warn',
msg: `Circuit breaker ${this.nome}: falha registrada`,
contagemFalhas: this.contagemFalhas,
limiar: this.limiarFalhas,
erro: erro.message,
}));
if (
this.estado !== 'ABERTO' &&
this.contagemFalhas >= this.limiarFalhas
) {
this._transicionarPara('ABERTO');
}
}
_transicionarPara(novoEstado) {
const estadoAnterior = this.estado;
this.estado = novoEstado;
const estadoNumerico = { FECHADO: 0, HALF_OPEN: 1, ABERTO: 2 };
this.metricaEstado.set(
{ nome: this.nome },
estadoNumerico[novoEstado]
);
if (novoEstado === 'FECHADO') {
this.contagemFalhas = 0;
this.contagemSuccessos = 0;
}
console.info(JSON.stringify({
level: 'info',
msg: `Circuit breaker ${this.nome}: transição de estado`,
de: estadoAnterior,
para: novoEstado,
}));
}
obterEstado() {
return {
nome: this.nome,
estado: this.estado,
contagemFalhas: this.contagemFalhas,
ultimaFalha: this.ultimaFalha
? new Date(this.ultimaFalha).toISOString()
: null,
};
}
}
module.exports = { CircuitBreaker };
Retry com Backoff Exponencial e Jitter
Tentar novamente uma operação que falhou é razoável — mas tentar novamente imediatamente, com todos os clientes ao mesmo tempo, pode transformar uma falha temporária em uma cascata de sobrecarga. O backoff exponencial aumenta o intervalo entre tentativas exponencialmente. O jitter adiciona aleatoriedade para evitar que múltiplos clientes tentem ao mesmo tempo (problema do "thundering herd"):
// src/resilience/retry.js
async function comRetry(funcao, opcoes = {}) {
const {
tentativasMaximas = 3,
delayBase = 1000, // 1s
delayMaximo = 30000, // 30s
multiplicador = 2,
jitter = true,
retryableErros = null, // null = tenta qualquer erro
onRetry = null, // callback antes de cada retry
} = opcoes;
let tentativa = 0;
while (true) {
try {
return await funcao();
} catch (erro) {
tentativa++;
// Verifica se o erro é recuperável
if (retryableErros && !retryableErros.some(e => erro instanceof e)) {
throw erro;
}
// Verifica se deve tentar status HTTP específicos
if (erro.status && ![429, 500, 502, 503, 504].includes(erro.status)) {
throw erro;
}
if (tentativa >= tentativasMaximas) {
console.error(JSON.stringify({
level: 'error',
msg: 'Todas as tentativas esgotadas',
tentativas: tentativa,
erro: erro.message,
}));
throw erro;
}
// Calcula delay com backoff exponencial
const delayExponencial = Math.min(
delayBase * Math.pow(multiplicador, tentativa - 1),
delayMaximo
);
// Adiciona jitter para evitar thundering herd
// Estratégia "full jitter": delay aleatório entre 0 e o delay calculado
const delay = jitter
? Math.random() * delayExponencial
: delayExponencial;
console.warn(JSON.stringify({
level: 'warn',
msg: 'Tentativa falhou, tentando novamente',
tentativa,
tentativasMaximas,
proximaTentativaMs: Math.round(delay),
erro: erro.message,
}));
if (onRetry) {
await onRetry({ tentativa, erro, delay });
}
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Uso combinado com circuit breaker
class ClienteHTTPResilient {
constructor(baseUrl, opcoes = {}) {
this.baseUrl = baseUrl;
this.circuitBreaker = new CircuitBreaker({
nome: opcoes.nome || new URL(baseUrl).hostname,
limiarFalhas: opcoes.limiarFalhas || 5,
timeoutAbertura: opcoes.timeoutAbertura || 30000,
});
}
async get(caminho, opcoes = {}) {
return this.circuitBreaker.executar(
() => comRetry(
() => fetch(`${this.baseUrl}${caminho}`, {
method: 'GET',
signal: AbortSignal.timeout(5000),
...opcoes,
}).then(async res => {
if (!res.ok) {
const erro = new Error(`HTTP ${res.status}`);
erro.status = res.status;
throw erro;
}
return res.json();
}),
{ tentativasMaximas: 3, delayBase: 500 }
),
opcoes.fallback
);
}
}
module.exports = { comRetry, ClienteHTTPResilient };
Bulkhead: Isolamento de Recursos
O padrão Bulkhead — referência às divisórias estanques de um navio — isola recursos entre diferentes partes do sistema para que a sobrecarga em uma parte não afete as demais. Na prática, significa ter pools de threads, conexões ou workers separados por funcionalidade:
// src/resilience/bulkhead.js
class Bulkhead {
constructor(opcoes = {}) {
this.nome = opcoes.nome;
this.concorrenciaMaxima = opcoes.concorrenciaMaxima || 10;
this.tamanhoFila = opcoes.tamanhoFila || 20;
this.timeoutFila = opcoes.timeoutFila || 5000;
this.executando = 0;
this.fila = [];
const { Gauge, Counter } = require('prom-client');
this.metricaExecutando = new Gauge({
name: 'bulkhead_executing',
help: 'Chamadas em execução no bulkhead',
labelNames: ['nome'],
});
this.metricaRejeitadas = new Counter({
name: 'bulkhead_rejected_total',
help: 'Chamadas rejeitadas pelo bulkhead',
labelNames: ['nome'],
});
}
async executar(funcao) {
// Verifica se pode executar imediatamente
if (this.executando < this.concorrenciaMaxima) {
return this._executarAgora(funcao);
}
// Verifica se a fila tem espaço
if (this.fila.length >= this.tamanhoFila) {
this.metricaRejeitadas.inc({ nome: this.nome });
throw new Error(
`Bulkhead ${this.nome} saturado: fila cheia (${this.tamanhoFila})`
);
}
// Adiciona à fila e aguarda
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
const index = this.fila.findIndex(item => item.resolve === resolve);
if (index !== -1) this.fila.splice(index, 1);
reject(new Error(`Timeout de fila no bulkhead ${this.nome}`));
}, this.timeoutFila);
this.fila.push({
funcao,
resolve,
reject,
timeout,
});
});
}
async _executarAgora(funcao) {
this.executando++;
this.metricaExecutando.set({ nome: this.nome }, this.executando);
try {
return await funcao();
} finally {
this.executando--;
this.metricaExecutando.set({ nome: this.nome }, this.executando);
this._processarFila();
}
}
_processarFila() {
if (this.fila.length === 0) return;
if (this.executando >= this.concorrenciaMaxima) return;
const proximo = this.fila.shift();
clearTimeout(proximo.timeout);
this._executarAgora(proximo.funcao)
.then(proximo.resolve)
.catch(proximo.reject);
}
}
module.exports = { Bulkhead };
AWS Fault Injection Simulator
O AWS FIS — Fault Injection Simulator — é o serviço gerenciado da AWS para Chaos Engineering. Permite injetar falhas reais em recursos AWS — terminar instâncias EC2, introduzir latência em chamadas de rede, falhar chamadas de API do SSM, degradar bancos de dados RDS — de forma controlada e com mecanismos de parada de emergência.
Experimento: Terminação de Instâncias EC2
# fis.tf
# IAM Role para o FIS executar ações nos recursos
resource "aws_iam_role" "fis" {
name = "${var.project_name}-${var.environment}-fis-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "fis.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "fis" {
role = aws_iam_role.fis.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ec2:TerminateInstances",
"ec2:StopInstances",
"ec2:DescribeInstances",
"ecs:StopTask",
"ecs:DescribeTasks",
"rds:RebootDBInstance",
"rds:FailoverDBCluster",
"elasticache:RebootCacheCluster",
"ssm:SendCommand",
"ssm:GetCommandInvocation",
]
Resource = "*"
Condition = {
StringEquals = {
"aws:ResourceTag/Environment" = var.environment
"aws:ResourceTag/ChaosElegible" = "true"
}
}
},
{
Effect = "Allow"
Action = ["cloudwatch:DescribeAlarms"]
Resource = "*"
}
]
})
}
# Experimento 1: Termina 33% das instâncias do Auto Scaling Group
resource "aws_fis_experiment_template" "terminar_instancias" {
description = "Termina 1/3 das instâncias EC2 para testar recuperação do ASG"
role_arn = aws_iam_role.fis.arn
# Stop condition — aborta o experimento se o alarme disparar
stop_condition {
source = "aws:cloudwatch:alarm"
value = aws_cloudwatch_alarm.disponibilidade_critica.arn
}
# Alvo: instâncias EC2 com tag Environment e ChaosElegible
target {
name = "instancias-app"
resource_type = "aws:ec2:instance"
selection_mode = "PERCENT(33)" # Termina 33%
resource_tag {
key = "Environment"
value = var.environment
}
resource_tag {
key = "ChaosElegible"
value = "true"
}
}
action {
name = "terminar-instancias"
action_id = "aws:ec2:terminate-instances"
target {
key = "Instances"
value = "instancias-app"
}
}
tags = local.tags_comuns
}
# Experimento 2: Introduz latência de rede via SSM
resource "aws_fis_experiment_template" "latencia_rede" {
description = "Introduz 200ms de latência nas chamadas de rede para testar timeouts"
role_arn = aws_iam_role.fis.arn
stop_condition {
source = "aws:cloudwatch:alarm"
value = aws_cloudwatch_alarm.latencia_critica.arn
}
target {
name = "instancias-app"
resource_type = "aws:ec2:instance"
selection_mode = "PERCENT(50)"
resource_tag {
key = "ChaosElegible"
value = "true"
}
}
action {
name = "injetar-latencia"
action_id = "aws:ssm:send-command"
parameter {
key = "documentArn"
value = "arn:aws:ssm:us-east-1::document/AWSFIS-Run-Network-Latency"
}
parameter {
key = "documentParameters"
value = jsonencode({
Interface = "eth0"
DelayMilliseconds = "200"
JitterMilliseconds = "50"
DurationSeconds = "120"
InstallDependencies = "True"
})
}
parameter {
key = "duration"
value = "PT3M" # Duração máxima do experimento: 3 minutos
}
target {
key = "Instances"
value = "instancias-app"
}
}
tags = local.tags_comuns
}
# Experimento 3: Falha no banco de dados RDS Multi-AZ
resource "aws_fis_experiment_template" "failover_rds" {
description = "Força failover do RDS Multi-AZ para testar tempo de recuperação"
role_arn = aws_iam_role.fis.arn
stop_condition {
source = "none"
}
target {
name = "banco-dados"
resource_type = "aws:rds:db"
selection_mode = "ALL"
resource_tag {
key = "Environment"
value = var.environment
}
resource_tag {
key = "ChaosElegible"
value = "true"
}
}
action {
name = "failover-rds"
action_id = "aws:rds:reboot-db-instances"
parameter {
key = "forceFailover"
value = "true"
}
target {
key = "DBInstances"
value = "banco-dados"
}
}
tags = local.tags_comuns
}
# Alarme de parada de emergência
resource "aws_cloudwatch_alarm" "disponibilidade_critica" {
alarm_name = "${var.project_name}-${var.environment}-disponibilidade-critica"
comparison_operator = "LessThanThreshold"
evaluation_periods = 2
metric_name = "HealthyHostCount"
namespace = "AWS/ApplicationELB"
period = 60
statistic = "Average"
threshold = 1
dimensions = {
LoadBalancer = aws_lb.aplicacao.arn_suffix
TargetGroup = aws_lb_target_group.aplicacao.arn_suffix
}
alarm_description = "Para o experimento FIS se não houver hosts saudáveis"
tags = local.tags_comuns
}
Executando Experimentos com a CLI
#!/bin/bash
# scripts/executar-experimento-chaos.sh
# Executa um experimento de Chaos Engineering com observação em tempo real
set -euo pipefail
TEMPLATE_ID="${1:?Uso: $0 <experiment-template-id>}"
REGIAO="us-east-1"
log() { echo "[$(date -u +%H:%M:%S)] $*"; }
erro() { echo "[$(date -u +%H:%M:%S)] ERRO: $*" >&2; exit 1; }
log "=== Iniciando Experimento de Chaos Engineering ==="
log "Template: $TEMPLATE_ID"
# Verifica que os sistemas estão saudáveis antes de começar
log "Verificando saúde dos sistemas antes do experimento..."
HOSTS_SAUDAVEIS=$(aws elbv2 describe-target-health \
--target-group-arn "$TARGET_GROUP_ARN" \
--query 'TargetHealthDescriptions[?TargetHealth.State==`healthy`] | length(@)' \
--output text)
if [ "$HOSTS_SAUDAVEIS" -lt 2 ]; then
erro "Sistema não está saudável o suficiente para chaos (${HOSTS_SAUDAVEIS} hosts). Abortando."
fi
log "Sistema saudável: $HOSTS_SAUDAVEIS hosts antes do experimento"
# Registra o início para correlacionar com métricas
INICIO_EXPERIMENTO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# Inicia o experimento
log "Iniciando experimento..."
EXPERIMENTO_ID=$(aws fis start-experiment \
--experiment-template-id "$TEMPLATE_ID" \
--region "$REGIAO" \
--query 'experiment.id' \
--output text)
log "Experimento iniciado: $EXPERIMENTO_ID"
# Monitora o experimento
log "Monitorando estado do experimento..."
while true; do
STATUS=$(aws fis get-experiment \
--id "$EXPERIMENTO_ID" \
--region "$REGIAO" \
--query 'experiment.state.status' \
--output text)
case "$STATUS" in
"running")
HOSTS_AGORA=$(aws elbv2 describe-target-health \
--target-group-arn "$TARGET_GROUP_ARN" \
--query 'TargetHealthDescriptions[?TargetHealth.State==`healthy`] | length(@)' \
--output text 2>/dev/null || echo "N/A")
log "Status: $STATUS | Hosts saudáveis: $HOSTS_AGORA"
sleep 15
;;
"completed")
log "Experimento concluído com sucesso!"
break
;;
"stopped")
log "AVISO: Experimento parado (stop condition ativada)"
break
;;
"failed")
erro "Experimento falhou. Verificar logs do FIS."
;;
*)
log "Status inesperado: $STATUS"
sleep 5
;;
esac
done
# Aguarda recuperação e verifica estado final
log "Aguardando recuperação completa do sistema..."
sleep 60
HOSTS_FINAL=$(aws elbv2 describe-target-health \
--target-group-arn "$TARGET_GROUP_ARN" \
--query 'TargetHealthDescriptions[?TargetHealth.State==`healthy`] | length(@)' \
--output text)
log "Estado final: $HOSTS_FINAL hosts saudáveis"
log "Início: $INICIO_EXPERIMENTO"
log "Fim: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
if [ "$HOSTS_FINAL" -ge "$HOSTS_SAUDAVEIS" ]; then
log "✅ Sistema se recuperou completamente"
else
log "⚠️ Sistema recuperou parcialmente: ${HOSTS_FINAL}/${HOSTS_SAUDAVEIS} hosts"
fi
Game Days: Praticando a Falha
O Game Day é a prática de simular falhas e incidentes em ambiente controlado com o time completo presente — uma forma de treino operacional que combina o Chaos Engineering técnico com o treinamento de resposta a incidentes.
Um Game Day típico segue a estrutura:
Pré-Game Day — definição do cenário, hipóteses a testar, métricas de sucesso e plano de rollback. O time documenta o comportamento esperado do sistema.
Execução — um facilitador conduz os experimentos enquanto o time observa métricas, toma decisões e responde como faria em um incidente real.
Revisão — análise do que aconteceu versus o que foi esperado, identificação de fraquezas e definição de ações corretivas.
## Game Day — Plano de Execução
**Data:** 2025-03-15
**Duração:** 4 horas (09h–13h)
**Facilitador:** Ana Costa
**Participantes:** Time de Plataforma + On-call
### Cenário 1: Falha de uma AZ inteira (09h00–10h00)
**Hipótese:** O sistema continua funcionando com latência < 500ms
se a AZ us-east-1b ficar indisponível.
**Ação:** Adicionar regra de NACL bloqueando todo o tráfego para a
subnet privada em us-east-1b.
**Métricas observadas:**
- Taxa de erro HTTP 5xx (alvo: < 0.1%)
- Latência p99 (alvo: < 500ms)
- Tempo de detecção do health check
- Tempo até rebalanceamento do ASG
**Rollback:** Remover a regra de NACL
---
### Cenário 2: Esgotamento do pool de conexões RDS (10h15–11h15)
**Hipótese:** A aplicação degrada graciosamente e retorna HTTP 503
ao invés de travar quando o pool de conexões é esgotado.
**Ação:** Injetar carga de 10x via k6 apontando para endpoint
que abre transações longas no banco.
**Métricas observadas:**
- Comportamento do circuit breaker
- Resposta HTTP da aplicação (503 vs travamento)
- DatabaseConnections no CloudWatch RDS
- Tempo de recuperação após a carga diminuir
---
### Cenário 3: Rollback de deploy com bug crítico (11h30–12h30)
**Hipótese:** O time consegue detectar e reverter um deploy
problemático em menos de 5 minutos.
**Ação:** Deploy de versão com bug que aumenta latência p99 em 10x.
Time precisa detectar, decidir reverter e executar.
**Métricas observadas:**
- MTTD (tempo até detecção via alerta)
- MTTR (tempo até rollback completo)
- Eficácia dos runbooks
Healthchecks e Graceful Shutdown
Resiliência não é apenas sobre sobreviver a falhas externas — é também sobre falhar graciosamente quando o próprio processo precisa ser encerrado:
// src/server.js — servidor com healthchecks e graceful shutdown completos
const express = require('express');
const app = express();
// Estado de prontidão do servidor
const estado = {
pronto: false, // readiness: aceita tráfego?
vivo: true, // liveness: processo está OK?
encerrandoGrace: false,
};
// Endpoint de liveness — monitora se o processo está vivo
// Falha apenas em casos extremos: deadlock, memória corrompida
app.get('/health/live', (req, res) => {
if (!estado.vivo) {
return res.status(503).json({ status: 'unhealthy', motivo: 'processo-degradado' });
}
res.json({ status: 'healthy', uptime: process.uptime() });
});
// Endpoint de readiness — monitora se está pronto para receber tráfego
// Falha durante inicialização, encerramento ou quando dependências estão indisponíveis
app.get('/health/ready', async (req, res) => {
if (estado.encerrandoGrace) {
return res.status(503).json({
status: 'not-ready',
motivo: 'encerrando',
});
}
if (!estado.pronto) {
return res.status(503).json({
status: 'not-ready',
motivo: 'inicializando',
});
}
// Verifica dependências críticas
try {
await Promise.all([
verificarBancoDados(),
verificarRedis(),
]);
res.json({ status: 'ready' });
} catch (erro) {
res.status(503).json({
status: 'not-ready',
motivo: 'dependencia-indisponivel',
detalhe: erro.message,
});
}
});
// Graceful shutdown — garante que requisições em andamento completam
function configurarGracefulShutdown(servidor) {
const TIMEOUT_GRACE = 30000; // 30 segundos
async function encerrar(sinal) {
console.info(JSON.stringify({
level: 'info',
msg: `Recebido sinal ${sinal} — iniciando encerramento gracioso`,
}));
// 1. Marca como não-pronto — load balancer para de enviar tráfego
estado.encerrandoGrace = true;
// 2. Aguarda o load balancer detectar o unhealthy (geralmente 10-30s)
await new Promise(resolve => setTimeout(resolve, 10000));
// 3. Para de aceitar novas conexões
servidor.close(async () => {
console.info(JSON.stringify({
level: 'info',
msg: 'Servidor HTTP fechado — encerrando conexões com banco',
}));
// 4. Fecha conexões com banco e cache graciosamente
try {
await pool.end();
await redisClient.quit();
console.info(JSON.stringify({
level: 'info',
msg: 'Encerramento gracioso concluído',
}));
process.exit(0);
} catch (erro) {
console.error(JSON.stringify({
level: 'error',
msg: 'Erro no encerramento gracioso',
erro: erro.message,
}));
process.exit(1);
}
});
// Timeout forçado — se demorar mais que TIMEOUT_GRACE, força encerramento
setTimeout(() => {
console.error(JSON.stringify({
level: 'error',
msg: 'Timeout de encerramento gracioso — forçando saída',
}));
process.exit(1);
}, TIMEOUT_GRACE);
}
process.on('SIGTERM', () => encerrar('SIGTERM'));
process.on('SIGINT', () => encerrar('SIGINT'));
}
// Inicialização com verificação de dependências
async function inicializar() {
console.info('Inicializando servidor...');
// Aguarda dependências ficarem disponíveis
await aguardarBancoDados();
await aguardarRedis();
// Executa migrations pendentes
await executarMigrations();
// Marca como pronto para receber tráfego
estado.pronto = true;
console.info('Servidor pronto para receber tráfego');
}
const servidor = app.listen(process.env.PORT || 3000, async () => {
await inicializar();
});
configurarGracefulShutdown(servidor);
O Que Vem a Seguir
O próximo artigo aborda Platform Engineering — a disciplina emergente que consolida as práticas de DevOps em plataformas internas que permitem que times de desenvolvimento sejam autônomos sem precisar ser especialistas em infraestrutura. Serão cobertos o conceito de Internal Developer Platform, o Backstage da Spotify como portal de desenvolvedores e os princípios de "paved roads" que equilibram autonomia e padronização.
Referências para Aprofundamento
Chaos Engineering - Principles of Chaos Engineering — principlesofchaos.org — Manifesto original dos princípios de Chaos Engineering, definindo o método científico aplicado a sistemas distribuídos. - AWS Fault Injection Simulator — docs.aws.amazon.com — Documentação completa do AWS FIS com guia de criação de experimentos, ações disponíveis e integração com CloudWatch. - Chaos Engineering — O'Reilly — oreilly.com — Livro referência sobre Chaos Engineering por engenheiros da Netflix, cobrindo desde os fundamentos até implementações avançadas.
Resiliência - Release It! — Pragmatic Bookshelf — pragprog.com — Livro fundamental sobre padrões de resiliência em sistemas de produção, incluindo Circuit Breaker, Bulkhead, Timeout e Back Pressure. - AWS Resilience Hub — docs.aws.amazon.com — Documentação do AWS Resilience Hub, serviço que avalia automaticamente a resiliência de aplicações AWS contra RTO e RPO definidos.