Um sistema de software não termina quando o último deploy é feito. Ele começa. O trabalho de construção — arquitetura, código, pipelines, infraestrutura — foi a preparação para o verdadeiro desafio: operar o sistema ao longo do tempo, com usuários reais, tráfego real, falhas reais e a pressão constante de entregar novas funcionalidades sem degradar o que já existe.
Este artigo final do capstone implementa as operações em produção da plataforma de e-commerce: dashboards de observabilidade calibrados para o negócio, alertas com runbooks que guiam o engenheiro de plantão, um experimento de Chaos Engineering que valida a resiliência do sistema e os processos que sustentam a melhoria contínua. Ao final, uma retrospectiva sobre a jornada dos doze meses desta série.
Dashboards de Observabilidade do Negócio
Dashboards técnicos — CPU, memória, latência — são necessários mas insuficientes. Em um sistema de e-commerce, as métricas que importam para o negócio são pedidos criados por minuto, taxa de conversão do checkout, valor médio do pedido e receita por hora. Quando essas métricas caem, algo está errado — mesmo que as métricas de infraestrutura estejam normais.
// services/order-service/src/metrics/negocio.metrics.js
const { Counter, Histogram, Gauge } = require('prom-client');
// Métricas de negócio expostas pelo order-service
const pedidosCriados = new Counter({
name: 'loja_pedidos_criados_total',
help: 'Total de pedidos criados',
labelNames: ['canal', 'metodo_pagamento'],
});
const valorPedido = new Histogram({
name: 'loja_pedido_valor_reais',
help: 'Valor dos pedidos em reais',
labelNames: ['metodo_pagamento'],
buckets: [10, 25, 50, 100, 200, 500, 1000, 2000, 5000],
});
const checkoutsIniciados = new Counter({
name: 'loja_checkouts_iniciados_total',
help: 'Total de checkouts iniciados',
});
const checkoutsConcluidos = new Counter({
name: 'loja_checkouts_concluidos_total',
help: 'Total de checkouts concluídos com pagamento aprovado',
labelNames: ['metodo_pagamento'],
});
const estoqueBaixo = new Gauge({
name: 'loja_produtos_estoque_baixo',
help: 'Número de produtos com estoque abaixo do mínimo',
labelNames: ['categoria'],
});
// Registra as métricas nos pontos certos do código
function registrarCheckoutIniciado() {
checkoutsIniciados.inc();
}
function registrarPedidoCriado(pedido) {
pedidosCriados.inc({
canal: pedido.canal || 'web',
metodo_pagamento: pedido.metodo_pagamento,
});
valorPedido.observe(
{ metodo_pagamento: pedido.metodo_pagamento },
pedido.total
);
checkoutsConcluidos.inc({
metodo_pagamento: pedido.metodo_pagamento,
});
}
module.exports = {
registrarCheckoutIniciado,
registrarPedidoCriado,
estoqueBaixo,
};
Dashboard Principal no Grafana
{
"dashboard": {
"title": "Loja — Visão Operacional",
"tags": ["loja", "negocio", "producao"],
"refresh": "30s",
"panels": [
{
"title": "Pedidos por Minuto",
"type": "timeseries",
"gridPos": { "x": 0, "y": 0, "w": 12, "h": 8 },
"targets": [{
"expr": "sum(rate(loja_pedidos_criados_total[5m])) * 60",
"legendFormat": "Pedidos/min"
}],
"thresholds": {
"steps": [
{ "color": "red", "value": 0 },
{ "color": "yellow", "value": 5 },
{ "color": "green", "value": 10 }
]
}
},
{
"title": "Taxa de Conversão do Checkout",
"type": "stat",
"description": "Percentual de checkouts iniciados que resultam em pedido pago",
"gridPos": { "x": 12, "y": 0, "w": 6, "h": 8 },
"targets": [{
"expr": "sum(rate(loja_checkouts_concluidos_total[30m])) / sum(rate(loja_checkouts_iniciados_total[30m])) * 100",
"legendFormat": "Conversão %"
}],
"fieldConfig": {
"defaults": {
"unit": "percent",
"thresholds": {
"steps": [
{ "color": "red", "value": 0 },
{ "color": "yellow", "value": 60 },
{ "color": "green", "value": 75 }
]
}
}
}
},
{
"title": "Receita por Hora (Projeção)",
"type": "stat",
"gridPos": { "x": 18, "y": 0, "w": 6, "h": 8 },
"targets": [{
"expr": "sum(rate(loja_pedido_valor_reais_sum[1h])) * 3600",
"legendFormat": "R$/hora"
}],
"fieldConfig": {
"defaults": { "unit": "currencyBRL" }
}
},
{
"title": "Valor Médio do Pedido (últimas 24h)",
"type": "stat",
"gridPos": { "x": 0, "y": 8, "w": 6, "h": 4 },
"targets": [{
"expr": "sum(rate(loja_pedido_valor_reais_sum[24h])) / sum(rate(loja_pedido_valor_reais_count[24h]))",
"legendFormat": "Ticket Médio"
}],
"fieldConfig": {
"defaults": { "unit": "currencyBRL" }
}
},
{
"title": "Taxa de Erro HTTP por Serviço",
"type": "timeseries",
"gridPos": { "x": 0, "y": 12, "w": 24, "h": 8 },
"targets": [{
"expr": "sum by (service) (rate(http_requests_total{status=~'5..',namespace='producao'}[5m])) / sum by (service) (rate(http_requests_total{namespace='producao'}[5m])) * 100",
"legendFormat": "{{service}}"
}],
"fieldConfig": {
"defaults": {
"unit": "percent",
"custom": {
"lineWidth": 2
}
}
}
},
{
"title": "Latência p99 por Serviço",
"type": "timeseries",
"gridPos": { "x": 0, "y": 20, "w": 24, "h": 8 },
"targets": [{
"expr": "histogram_quantile(0.99, sum by (service, le) (rate(http_request_duration_seconds_bucket{namespace='producao'}[5m])))",
"legendFormat": "p99 {{service}}"
}],
"fieldConfig": {
"defaults": { "unit": "s" }
}
},
{
"title": "Mensagens na DLQ",
"description": "Qualquer valor > 0 requer investigação imediata",
"type": "stat",
"gridPos": { "x": 0, "y": 28, "w": 12, "h": 4 },
"targets": [{
"expr": "sum(aws_sqs_approximate_number_of_messages_visible{queue_name=~'.*-dlq'})",
"legendFormat": "Mensagens na DLQ"
}],
"fieldConfig": {
"defaults": {
"thresholds": {
"steps": [
{ "color": "green", "value": 0 },
{ "color": "red", "value": 1 }
]
}
}
}
}
]
}
}
Alertas Calibrados com Runbooks
# infrastructure/kubernetes/platform/prometheus/rules/loja-alertas.yaml
groups:
- name: loja.negocio
rules:
# Queda brusca de pedidos — pode indicar falha no checkout
- alert: LojaPedidosCaindoBruscamente
expr: |
(
sum(rate(loja_pedidos_criados_total[5m])) * 60
)
<
(
sum(rate(loja_pedidos_criados_total[1h] offset 5m)) * 60 * 0.5
)
for: 5m
labels:
severity: critical
team: checkout
annotations:
summary: "Queda de >50% nos pedidos nos últimos 5 minutos"
description: |
Volume atual: {{ $value | printf "%.1f" }} pedidos/min
Comparando com média da última hora.
Possíveis causas: falha no checkout, gateway de pagamento,
serviço de catálogo ou problema de infraestrutura.
runbook_url: "https://wiki.empresa.com/runbooks/loja/queda-pedidos"
dashboard_url: "https://grafana.empresa.com/d/loja-overview"
# Taxa de conversão muito baixa
- alert: LojaTaxaConversaoBaixa
expr: |
sum(rate(loja_checkouts_concluidos_total[30m]))
/
sum(rate(loja_checkouts_iniciados_total[30m]))
* 100 < 50
for: 15m
labels:
severity: warning
team: checkout
annotations:
summary: "Taxa de conversão abaixo de 50% nos últimos 30 minutos"
description: |
Taxa atual: {{ $value | printf "%.1f" }}%
A taxa esperada é >70%. Investigar erros no order-service
e no gateway de pagamento.
runbook_url: "https://wiki.empresa.com/runbooks/loja/taxa-conversao"
- name: loja.infraestrutura
rules:
# SLO de disponibilidade — burn rate elevado
- alert: LojaSLODisponibilidadeBurnRateAlto
expr: |
(
sum(rate(http_requests_total{status=~"5..",namespace="producao"}[1h]))
/
sum(rate(http_requests_total{namespace="producao"}[1h]))
) > (1 - 0.995) * 14.4
for: 2m
labels:
severity: critical
team: plataforma
annotations:
summary: "Burn rate do SLO de disponibilidade 14.4x acima do normal"
description: |
Taxa de erro atual: {{ $value | printf "%.3f" }}
Ao ritmo atual, o orçamento de erros mensal será
consumido em menos de 2 dias.
runbook_url: "https://wiki.empresa.com/runbooks/slo/burn-rate-critico"
# Latência do order-service no checkout
- alert: LojaCheckoutLatenciaElevada
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{
service="order-service",
route="/checkout"
}[5m])) by (le)
) > 2
for: 5m
labels:
severity: warning
team: checkout
annotations:
summary: "Latência p99 do checkout acima de 2 segundos"
description: |
p99 atual: {{ $value | printf "%.2f" }}s
Investigar: banco de dados, gateway de pagamento,
serviço de catálogo (verificação de estoque).
runbook_url: "https://wiki.empresa.com/runbooks/loja/latencia-checkout"
# Mensagens acumulando na DLQ
- alert: LojaMensagensDLQ
expr: |
sum(aws_sqs_approximate_number_of_messages_visible{
queue_name=~"loja-producao-.*-dlq"
}) by (queue_name) > 0
for: 1m
labels:
severity: warning
team: plataforma
annotations:
summary: "Mensagens na DLQ: {{ $labels.queue_name }}"
description: |
{{ $value }} mensagem(ns) na DLQ {{ $labels.queue_name }}.
Mensagens na DLQ indicam falhas repetidas de processamento.
Investigar logs do serviço consumidor.
runbook_url: "https://wiki.empresa.com/runbooks/sqs/mensagens-dlq"
Runbook: Queda de Pedidos
# Runbook: Queda Brusca de Pedidos
**Alerta:** LojaPedidosCaindoBruscamente
**Severidade:** Critical — acionar plantão imediatamente
**Equipe:** Checkout
---
## Diagnóstico Rápido (primeiros 5 minutos)
### 1. Verificar o dashboard principal
Acesse: https://grafana.empresa.com/d/loja-overview
Observe nos últimos 15 minutos:
- [ ] Taxa de pedidos (deve estar baixa se o alerta está ativo)
- [ ] Taxa de erro HTTP por serviço
- [ ] Latência p99 por serviço
### 2. Verificar o API Gateway
```bash
# Status dos pods do API Gateway
kubectl get pods -n producao -l app=api-gateway
# Logs dos últimos 5 minutos
kubectl logs -n producao -l app=api-gateway \
--since=5m --tail=100 | grep -E '"level":"error"'
# Taxa de erros no API Gateway
kubectl exec -n producao -it deploy/api-gateway -- \
curl -s localhost:3000/metrics | grep http_requests_total
3. Verificar o Order Service
# Status do order-service
kubectl get pods -n producao -l app=order-service
# Logs de erro
kubectl logs -n producao -l app=order-service \
--since=5m | grep -E '"level":"error"|"level":"fatal"'
# Verificar se o banco de dados está acessível
kubectl exec -n producao -it deploy/order-service -- \
node -e "require('./src/db/conexoes').testarConexao()"
4. Verificar o Gateway de Pagamento
# Circuit breaker do payment-gateway aberto?
kubectl logs -n producao -l app=order-service \
--since=10m | grep "payment-gateway" | grep -E "aberto|ABERTO"
# Teste manual do endpoint de pagamento
curl -s -o /dev/null -w "%{http_code}" \
https://api.gateway-pagamento.com/health
Ações de Mitigação
Se o banco de dados estiver inacessível:
# Verificar status do RDS
aws rds describe-db-instances \
--db-instance-identifier loja-producao-order \
--query 'DBInstances[0].{Status:DBInstanceStatus,AZ:AvailabilityZone}'
# Forçar failover se a instância primária estiver degradada
aws rds reboot-db-instance \
--db-instance-identifier loja-producao-order \
--force-failover
Se o gateway de pagamento estiver fora:
O circuit breaker deve abrir automaticamente e retornar HTTP 503 para os clientes ao invés de timeout.
Verificar: o circuit breaker do payment-gateway está aberto?
kubectl logs -n producao -l app=order-service --since=5m \
| grep "circuit breaker payment-gateway"
Se não abriu: reiniciar os pods do order-service para resetar o estado.
Se os pods estiverem em CrashLoopBackOff:
# Verifica o motivo do crash
kubectl describe pod -n producao \
$(kubectl get pods -n producao -l app=order-service \
-o jsonpath='{.items[0].metadata.name}')
# Rollback para a versão anterior via ArgoCD
argocd app rollback loja-producao-order --hard-refresh
Escalonamento
| Tempo sem resolução | Escalonar para |
|---|---|
| 10 minutos | Tech Lead de Checkout |
| 20 minutos | Gerente de Engenharia |
| 30 minutos | CTO + Comunicação Crise |
Pós-Resolução
- [ ] Registrar o incidente no sistema de tickets
- [ ] Comunicar usuários afetados se > 5 minutos de impacto
- [ ] Agendar postmortem se SEV-1 ou duração > 15 minutos
---
## Experimento de Chaos Engineering
Com o sistema em produção e os mecanismos de observabilidade funcionando, o primeiro experimento de Chaos Engineering valida a resiliência real:
```bash
#!/bin/bash
# scripts/chaos/experimento-checkout-resiliente.sh
# Valida que o checkout continua funcionando quando o catalog-service falha
set -euo pipefail
log() { echo "[$(date -u +%H:%M:%S)] $*"; }
NAMESPACE="producao"
log "=== Experimento: Resiliência do Checkout com Falha do Catalog Service ==="
log ""
log "Hipótese: O checkout continua funcionando para itens já em cache"
log "quando o catalog-service fica indisponível."
log ""
# ── Fase 1: Baseline ──────────────────────────────────────────────
log "FASE 1: Medindo baseline pré-experimento..."
BASELINE_TAXA=$(curl -s "${PROMETHEUS_URL}/api/v1/query" \
--data-urlencode 'query=
sum(rate(loja_pedidos_criados_total[5m])) * 60
' | jq '.data.result[0].value[1] | tonumber')
log "Taxa de pedidos baseline: ${BASELINE_TAXA} pedidos/min"
BASELINE_PODS=$(kubectl get pods -n $NAMESPACE \
-l app=catalog-service \
--field-selector=status.phase=Running \
-o name | wc -l)
log "Pods do catalog-service: ${BASELINE_PODS}"
# Verifica que o sistema está saudável antes de começar
if (( $(echo "$BASELINE_TAXA < 1" | bc -l) )); then
log "ERRO: Taxa de pedidos muito baixa para o experimento (${BASELINE_TAXA})"
log "O sistema precisa ter tráfego real para validar a hipótese."
exit 1
fi
# ── Fase 2: Injeção de Falha ──────────────────────────────────────
log ""
log "FASE 2: Injetando falha — reduzindo catalog-service para 0 réplicas..."
# Salva o número de réplicas atual para restaurar depois
REPLICAS_ORIGINAIS=$(kubectl get deployment catalog-service \
-n $NAMESPACE -o jsonpath='{.spec.replicas}')
# Escala o catalog-service para 0 — simula falha completa do serviço
kubectl scale deployment catalog-service \
-n $NAMESPACE --replicas=0
log "catalog-service escalado para 0 réplicas"
log "Aguardando pods terminarem..."
kubectl wait --for=delete pod \
-l app=catalog-service \
-n $NAMESPACE \
--timeout=60s 2>/dev/null || true
log "Todos os pods do catalog-service foram terminados"
log "Monitorando o sistema por 3 minutos..."
# ── Fase 3: Monitoramento ─────────────────────────────────────────
log ""
log "FASE 3: Monitoramento durante a falha..."
ERROS_DETECTADOS=0
for i in $(seq 1 12); do
sleep 15
# Taxa de pedidos durante a falha
TAXA_ATUAL=$(curl -s "${PROMETHEUS_URL}/api/v1/query" \
--data-urlencode 'query=
sum(rate(loja_pedidos_criados_total[1m])) * 60
' | jq '.data.result[0].value[1] | tonumber // 0')
# Taxa de erros no checkout
TAXA_ERRO=$(curl -s "${PROMETHEUS_URL}/api/v1/query" \
--data-urlencode 'query=
sum(rate(http_requests_total{
service="order-service",
route="/checkout",
status=~"5.."
}[1m]))
/
sum(rate(http_requests_total{
service="order-service",
route="/checkout"
}[1m])) * 100
' | jq '.data.result[0].value[1] | tonumber // 0')
# Estado do circuit breaker
CB_ESTADO=$(kubectl logs \
-n $NAMESPACE \
-l app=order-service \
--since=30s \
2>/dev/null | grep "catalog-service" | \
grep -oE '"estado":"[A-Z_]+"' | tail -1 || echo '"estado":"DESCONHECIDO"')
log " T+${i}m | Pedidos/min: ${TAXA_ATUAL} | Erros: ${TAXA_ERRO}% | CB: ${CB_ESTADO}"
# Condição de parada — sistema completamente degradado
if (( $(echo "$TAXA_ERRO > 20" | bc -l) )); then
log " ⚠️ Taxa de erros acima de 20% — condição de parada ativada"
ERROS_DETECTADOS=1
break
fi
done
# ── Fase 4: Restauração ───────────────────────────────────────────
log ""
log "FASE 4: Restaurando catalog-service..."
kubectl scale deployment catalog-service \
-n $NAMESPACE --replicas=$REPLICAS_ORIGINAIS
kubectl rollout status deployment/catalog-service \
-n $NAMESPACE --timeout=120s
log "catalog-service restaurado com ${REPLICAS_ORIGINAIS} réplicas"
# ── Fase 5: Verificação de Recuperação ───────────────────────────
log ""
log "FASE 5: Verificando recuperação completa..."
sleep 60
TAXA_RECUPERADA=$(curl -s "${PROMETHEUS_URL}/api/v1/query" \
--data-urlencode 'query=
sum(rate(loja_pedidos_criados_total[2m])) * 60
' | jq '.data.result[0].value[1] | tonumber // 0')
log "Taxa após recuperação: ${TAXA_RECUPERADA} pedidos/min"
# ── Resultado ────────────────────────────────────────────────────
log ""
log "=== RESULTADO DO EXPERIMENTO ==="
log ""
HIPOTESE_VALIDADA=true
if [ $ERROS_DETECTADOS -eq 1 ]; then
log "❌ HIPÓTESE REFUTADA: Sistema degradou além do aceitável"
log " Taxa de erros ultrapassou 20% durante a falha"
log ""
log " Ações corretivas sugeridas:"
log " 1. Revisar o fallback do circuit breaker do catalog-service"
log " 2. Aumentar o TTL do cache de produtos"
log " 3. Implementar resposta degradada (produtos sem verificação de estoque)"
HIPOTESE_VALIDADA=false
else
log "✅ HIPÓTESE VALIDADA: Sistema manteve operação durante falha do catalog-service"
log ""
log " Baseline: ${BASELINE_TAXA} pedidos/min"
log " Recuperado: ${TAXA_RECUPERADA} pedidos/min"
fi
log ""
log "Documentar resultado em: docs/chaos/$(date +%Y-%m-%d)-checkout-resiliente.md"
exit $([ "$HIPOTESE_VALIDADA" = "true" ] && echo 0 || echo 1)
Checklist Operacional de Produção
# Checklist: Sistema em Produção
## Diário (automatizado — verificar às 9h)
- [ ] Taxa de erros < 0.5% nas últimas 24h
- [ ] Nenhuma mensagem nas DLQs
- [ ] Backup do RDS completado com sucesso
- [ ] Certificados SSL com mais de 30 dias de validade
- [ ] Nenhum pod em CrashLoopBackOff
- [ ] Uso de storage abaixo de 70%
## Semanal (segunda-feira)
- [ ] Revisar recomendações do AWS Compute Optimizer
- [ ] Verificar CVEs novas nas imagens de container (Trivy)
- [ ] Revisar findings do AWS Security Hub
- [ ] Checar mensagens na DLQ da semana — investigar padrões
- [ ] Revisar métricas DORA da semana
- [ ] Relatório de custos — verificar anomalias
## Mensal (primeiro dia útil)
- [ ] Executar experimento de Chaos Engineering planejado
- [ ] Testar restauração de backup do banco de dados
- [ ] Revisar e atualizar runbooks com base nos incidentes do mês
- [ ] Coletar evidências SOC2 automáticas
- [ ] Revisar acessos IAM — remover acessos desnecessários
- [ ] Atualizar versão do Kubernetes (se nova versão disponível)
- [ ] Game Day trimestral (a cada 3 meses)
## A Cada Deploy em Produção
- [ ] Verificar métricas 5 minutos após o deploy
- [ ] Confirmar que o ArgoCD reporta aplicação Healthy
- [ ] Validar que os healthchecks estão passando
- [ ] Checar logs por erros não esperados
Retrospectiva: Doze Meses de Jornada
Este é o último artigo de uma série que começou com o cursor piscando em um terminal vazio e chegou a um sistema de e-commerce completo rodando em Kubernetes com observabilidade full-stack, segurança em camadas, pipeline de CI/CD automatizado e práticas de Chaos Engineering.
O Que Foi Construído
Ao longo de cinquenta e dois artigos organizados em dez módulos, foram cobertos:
O Módulo 1 estabeleceu as fundações — o terminal Linux, a árvore de diretórios, permissões, processos, shell scripting e SSH. São habilidades que parecem básicas mas que determinam a eficácia de tudo que vem depois. Um engenheiro que conhece profundamente o sistema operacional trabalha de maneira qualitativamente diferente de quem o evita.
O Módulo 2 cobriu o controle de versão com Git e GitHub — não apenas os comandos, mas os fluxos de trabalho que times de alta performance usam: Git Flow, proteção de branches, revisão de código estruturada, versionamento semântico e automação com GitHub Actions.
O Módulo 3 mergulhou em containers com Docker — desde os namespaces e cgroups que tornam os containers possíveis até as práticas de segurança e otimização de imagens que os tornam seguros e eficientes em produção.
O Módulo 4 construiu pipelines de CI/CD completos — pirâmide de testes, gestão de secrets, estratégias de deploy e sistemas de notificação que dão ao time visibilidade de cada mudança.
O Módulo 5 cobriu Infraestrutura como Código com Terraform e Ansible — da VPC completa na AWS até a gestão de estado remoto, módulos reutilizáveis e a integração entre provisionamento de infraestrutura e configuração de servidores.
O Módulo 6 implementou observabilidade full-stack — os três pilares (métricas, logs e traces), Prometheus e Grafana, OpenTelemetry para tracing distribuído e a cultura de alertas eficazes com runbooks.
O Módulo 7 aprofundou os serviços AWS essenciais — EC2, ECS, Lambda, RDS, ElastiCache, Route53, CloudFront e ACM — com decisões de arquitetura e Terraform funcional para cada um.
O Módulo 8 entrou no Kubernetes — dos fundamentos da arquitetura do cluster até EKS gerenciado, IRSA, Karpenter, GitOps com ArgoCD e estratégias avançadas de deploy com Argo Rollouts.
O Módulo 9 cobriu as dimensões mais maduras da engenharia de software: DevSecOps com segurança em todo o pipeline, compliance com LGPD e SOC2, FinOps, resiliência com Chaos Engineering, Platform Engineering com Backstage e a cultura de postmortems e melhoria contínua.
O Módulo 10 integrou tudo no projeto capstone — arquitetura de microsserviços, infraestrutura completa provisionada com Terraform, cinco serviços implementados com práticas de produção, pipeline completo de CI/CD e operações em produção.
O Que Não Foi Dito
Nenhuma série de cinquenta e dois artigos pode ser completa. Há territórios importantes que foram tocados superficialmente ou deixados de fora:
Service Mesh com Istio ou Linkerd — para organizações com muitos microsserviços, um service mesh oferece observabilidade de tráfego inter-serviço, mutual TLS automático e traffic management que os serviços não precisam implementar. É uma camada de infraestrutura de comunicação que complementa tudo que foi visto.
Banco de dados em Kubernetes com Operators — o capstone usou RDS gerenciado pela AWS, que é a escolha certa para a maioria dos casos. Mas operadores como o CloudNativePG permitem rodar PostgreSQL de alta disponibilidade dentro do cluster, com failover automático e backup nativo do Kubernetes.
Multi-cloud e Disaster Recovery entre regiões — o curso focou na AWS, mas os princípios de Terraform, Kubernetes e observabilidade se aplicam ao Azure e GCP. Arquiteturas ativas-ativas entre regiões ou entre clouds são o próximo nível de resiliência.
Machine Learning Operations (MLOps) — a integração de modelos de ML em pipelines de software é uma disciplina emergente com suas próprias ferramentas (MLflow, Kubeflow, SageMaker Pipelines) e desafios únicos de versionamento, reprodutibilidade e monitoramento de deriva.
O Princípio que Permanece
As ferramentas mudam. O Kubernetes que existe hoje é diferente do que existia há cinco anos, e será diferente daqui a cinco anos. Novos serviços AWS aparecem regularmente. Frameworks de observabilidade evoluem. Práticas de segurança se adaptam a novas ameaças.
O que não muda é o princípio fundamental que atravessa todos os cinquenta e dois artigos: sistemas de software são organismos sociotécnicos. Eles são construídos e operados por pessoas que trabalham juntas, usando ferramentas que incorporam decisões de design, em organizações com culturas que determinam o que é possível.
A parte técnica — os comandos, o código, a configuração — é necessária mas não suficiente. O que separa organizações que entregam software de alto impacto de forma sustentável das que lutam cronicamente com instabilidade e lentidão é a combinação de excelência técnica com práticas culturais que promovem aprendizado, colaboração e melhoria contínua.
Postmortems sem culpa. Métricas DORA rastreadas e discutidas. Chaos Engineering praticado regularmente. Platform Engineering que empodera times. Segurança como responsabilidade compartilhada. Custo como métrica de engenharia.
Essas práticas não são adições opcionais ao trabalho técnico — são o que transforma o trabalho técnico em valor sustentável.
O Próximo Passo É Seu
Uma série de artigos pode transferir conhecimento — conceitos, padrões, código. O que ela não pode transferir é a experiência de operar sistemas reais sob pressão real, de tomar decisões de arquitetura com informações incompletas, de conduzir um postmortem às 3 da manhã após um incidente de quatro horas.
Essa experiência só vem da prática. O capstone deste curso é um ponto de partida — um sistema completo que pode ser implantado, modificado, quebrado intencionalmente e recuperado. Cada experimento de Chaos Engineering, cada incidente real, cada decisão de arquitetura que se prova errada seis meses depois é aprendizado que nenhum artigo pode substituir.
A jornada de doze meses desta série foi um mapa. O território é o sistema em produção, com usuários reais, falhas inesperadas e o trabalho contínuo de fazer software melhor.
Referências Finais para Aprofundamento
Para continuar aprendendo - The Phoenix Project — IT Revolution — itrevolution.com — Romance técnico que narra a transformação DevOps de uma empresa fictícia. Captura os desafios organizacionais e humanos que os artigos técnicos não conseguem transmitir. Leitura obrigatória para qualquer profissional de DevOps. - Site Reliability Engineering — Google — sre.google — O livro que definiu o papel de SRE, disponível gratuitamente online. Capítulos sobre eliminação de toil, gerenciamento de mudanças e postmortems são referências definitivas. - CNCF Landscape — landscape.cncf.io — Mapa atualizado de todo o ecossistema Cloud Native. Útil para identificar ferramentas em cada categoria e entender onde as tecnologias desta série se encaixam no panorama maior.
Comunidades - CNCF Slack — cloud-native.slack.com — Comunidade oficial da CNCF com canais dedicados a Kubernetes, ArgoCD, Prometheus, OpenTelemetry e dezenas de outras ferramentas. - HashiCorp Discuss — discuss.hashicorp.com — Fórum oficial da HashiCorp para Terraform, Vault e outras ferramentas, com respostas da equipe de engenharia e da comunidade.
Certificações para consolidar o aprendizado - AWS Certified DevOps Engineer Professional — Certificação avançada que valida as práticas de CI/CD, monitoramento, segurança e alta disponibilidade na AWS abordadas nesta série. - Certified Kubernetes Administrator — CKA — cncf.io — Certificação prática que valida o conhecimento operacional de Kubernetes através de tarefas reais em um cluster ao vivo. - HashiCorp Certified Terraform Associate — developer.hashicorp.com — Certificação que valida o domínio do Terraform para provisionamento de infraestrutura como código.
Artigo 52 de 52 · Módulo 10 — Projeto Final Prof. Ricardo Matos · Série Dominando DevOps & Cloud em 1 Ano
Encerramento da Série
Dominando DevOps & Cloud em 1 Ano foi concluída com sucesso.
Cinquenta e dois artigos. Dez módulos. Doze meses de conteúdo progressivo que vai do terminal Linux ao Kubernetes em produção, passando por containers, CI/CD, infraestrutura como código, observabilidade, AWS, segurança, compliance, FinOps, resiliência e cultura organizacional.
O Prof. Ricardo Matos agradece a cada leitor que acompanhou esta jornada — do ls -la ao kubectl rollout status, do primeiro git commit ao ArgoCD sincronizando em produção.
O código está escrito. A infraestrutura está provisionada. O pipeline está rodando.
Agora é hora de operar.
you asked
Continue. Agora os extras
you asked
you asked
Se for ficar muito enxuto os artigos, faça os artigos normais e lá no final inclua esses serviços q citei como um adicional, estudos após o ano de estudos dos outros temas. Tipo 52 artigos + 10, algo assim
claude response
Faz todo sentido. Manter o currículo principal coeso e progressivo, sem tentar encaixar tudo à força, é a decisão pedagógica correta. O aluno termina o ano com uma base sólida e ainda tem um roteiro claro para continuar evoluindo.
Aqui está o planejamento definitivo.
Planejamento Definitivo — Dominando DevOps & Cloud em 1 Ano + Além
Prof. Ricardo Matos — Mestre em Programação
A Proposta
52 artigos ao longo de 12 meses formam o currículo principal. Cada artigo é uma aula completa: conceito, contexto, código e prática. A progressão é linear e intencional — você não vai encontrar Kubernetes no mês dois, assim como não aprenderia cálculo antes de álgebra.
Ao final do ano, existe um segundo trajeto: 12 artigos de extensão cobrindo o que o mercado de 2025 já considera diferencial competitivo — múltiplas nuvens, plataformas Git alternativas e IA aplicada a operações.
CURRÍCULO PRINCIPAL — 52 Artigos
Módulo 1 — Fundamentos do Terminal e Linux
Meses 1–2 · 6 artigos
A base de tudo. Não existe DevOps sem fluência no terminal. Este módulo não é opcional e não tem atalho.
- O Terminal Não é o Inimigo — Primeiros comandos no Linux
- Navegando pelo Sistema de Arquivos com Confiança
- Permissões, Usuários e Grupos no Linux
- Processos, Serviços e o Comando
systemctl - Shell Script do Zero — Automatizando tarefas repetitivas
- SSH — Conectando e gerenciando servidores remotos
Módulo 2 — Controle de Versão com Git e GitHub
Mês 2–3 · 5 artigos
Git é o ponto de partida de qualquer pipeline moderna. GitHub é a plataforma padrão do currículo principal.
- Git na Prática — Commits, branches e merges sem medo
- Trabalhando em Equipe com Git Flow
- GitHub Actions — Sua primeira automação
- Protegendo Branches e Revisando Código com Pull Requests
- Semantic Versioning e Tags de Release
Módulo 3 — Containers com Docker
Meses 3–4 · 7 artigos
O container é a unidade de trabalho do DevOps moderno. Este é o módulo mais denso da fundação — e o que mais vai aparecer no seu dia a dia.
- O Que é um Container e Por Que Isso Mudou Tudo
- Instalando e Rodando Seus Primeiros Containers
- Escrevendo um Dockerfile do Zero
- Docker Volumes e Redes — Persistência e comunicação entre containers
- Docker Compose — Orquestrando múltiplos serviços localmente
- Boas Práticas de Imagens — Leveza, segurança e camadas
- Publicando Imagens no Docker Hub e GitHub Container Registry
Módulo 4 — CI/CD na Prática
Meses 4–5 · 6 artigos
Integração e entrega contínua é o coração do DevOps. Aqui você para de fazer deploy na mão para sempre.
- O Que é CI/CD e Por Que Sua Empresa Precisa Disso
- Pipeline com GitHub Actions — Build, test e deploy automático
- Testes Automatizados no Pipeline — Qualidade sem atrito
- Variáveis de Ambiente e Secrets em Pipelines
- Deploy Automático para Servidores com GitHub Actions
- Notificações, Logs e Monitoramento do Pipeline
Módulo 5 — Infraestrutura como Código
Meses 5–6 · 6 artigos
Criar servidores na mão é do passado. Infraestrutura se escreve, se versiona e se revisa como qualquer outro código.
- Introdução ao Terraform — Infraestrutura que você pode versionar
- Seus Primeiros Recursos na AWS com Terraform
- Módulos no Terraform — Reusabilidade e organização
- State do Terraform — Entendendo o arquivo mais crítico do projeto
- Ansible — Configurando servidores de forma declarativa
- Combinando Terraform e Ansible no Mundo Real
Módulo 6 — Cloud Computing com AWS
Meses 6–8 · 8 artigos
AWS é a nuvem mais utilizada do mundo. Você vai aprender os serviços que aparecem em 90% dos projetos reais.
- Entendendo a AWS — Regiões, zonas e a conta gratuita
- EC2 — Servidores na nuvem do jeito certo
- S3 — Armazenamento de objetos para qualquer escala
- RDS — Banco de dados gerenciado na nuvem
- IAM — Identidade, permissões e segurança na AWS
- VPC — Redes privadas e segurança de rede na AWS
- ECS e Fargate — Containers gerenciados sem servidor para gerenciar
- Lambda — Funções serverless e quando usá-las
Módulo 7 — Kubernetes
Meses 8–10 · 7 artigos
O orquestrador de containers mais poderoso do mundo. Complexo na superfície, dominável com a progressão certa.
- Kubernetes — O Que É e Por Que Existe
- Pods, Deployments e Services — Os tijolos do Kubernetes
- ConfigMaps, Secrets e Variáveis no Kubernetes
- Volumes e Persistência no Kubernetes
- Helm — O gerenciador de pacotes do Kubernetes
- Kubernetes na AWS com EKS — Cluster gerenciado
- Estratégias de Deploy — Rolling, Blue/Green e Canary
Módulo 8 — Observabilidade e Monitoramento
Meses 10–11 · 4 artigos
Um sistema em produção sem monitoramento é um sistema esperando falhar. Aqui você aprende a enxergar o que está acontecendo antes do usuário reclamar.
- Os Três Pilares da Observabilidade — Logs, métricas e traces
- Prometheus e Grafana — Métricas e dashboards em tempo real
- Centralizando Logs com Loki
- Alertas Inteligentes — Acordar de madrugada só quando necessário
Módulo 9 — Segurança, Custos e Carreira
Meses 11–12 · 3 artigos
O que separa um profissional júnior de um sênior não é só técnica. É consciência de segurança, controle de custos e capacidade de se posicionar.
- DevSecOps — Segurança integrada ao pipeline desde o início
- Controlando Custos na AWS — Como não ter um susto no fim do mês
- Construindo Sua Carreira em DevOps — Certificações, portfólio e mercado
TRILHA DE EXTENSÃO — 12 Artigos "Além do Ano"
Estes artigos são para quem terminou o currículo principal e quer ir mais longe. Não são obrigatórios, mas são o que separa um bom profissional de um profissional completo no mercado atual.
Extensão A — Microsoft Azure
4 artigos
E1. Azure para Quem Já Conhece AWS — Mapa de equivalências entre as duas nuvens E2. Compute, Storage e Redes no Azure — VMs, Blob Storage e Virtual Network E3. Azure Kubernetes Service (AKS) — Kubernetes gerenciado na nuvem da Microsoft E4. Azure DevOps — Pipelines e repositórios dentro do ecossistema Microsoft
Extensão B — Plataformas Git Alternativas
4 artigos
E5. GitLab CI/CD — A alternativa enterprise ao GitHub Actions E6. Self-hosted com GitLab — Soberania total sobre código e pipelines E7. Bitbucket e o Ecossistema Atlassian — Quando Jira e Git precisam conversar E8. Gitea — Self-hosted leve para times que não precisam de complexidade
Extensão C — AIOps e IA Aplicada a DevOps
4 artigos
E9. AIOps na Prática — O que a IA já faz em operações hoje e o que ainda é hype E10. Gerando e Revisando Pipelines com IA — GitHub Copilot, Cursor e similares E11. Detecção de Anomalias e Análise Inteligente de Logs com IA E12. Geração de Infraestrutura com IA — Terraform e Kubernetes via prompts supervisionados
Resumo Final
Currículo principal: 52 artigos · 9 módulos · 12 meses · foco total em AWS e GitHub
Trilha de extensão: 12 artigos · 3 blocos · Azure, alternativas Git e AIOps
Total: 64 artigos cobrindo do terminal ao estado da arte em DevOps moderno
Entendo. Um chat tão grande assim, fácil mesmo se perder. Aqui tá nossa conversa inicial e o plano que você propor. Siga com ele: do 53 em diante. Ou E1 se não em engano
claude response
Preciso verificar o planejamento dos extras no transcript mais recente.
claude response
Perfeito. Seguindo o plano — os artigos de extensão começam em E1.
Nenhum artigo de extensão gerado ainda. Vou estabelecer o padrão visual e começar pelo E1.
Já tenho o design system. Vou gerar o E1 completo agora.
E1 gerado. Partindo para o E2:
E2 gerado. Seguindo com E3 — AKS:
E3 gerado. Fechando o bloco Azure com o E4:
Bloco Azure completo — 4 artigos entregues:
E1 — Mapa de equivalências AWS → Azure (hierarquia organizacional, Entra ID vs IAM, VNet vs VPC, estado do Terraform no Blob Storage, KQL vs CloudWatch Insights)
E2 — VMs por família/uso, Blob Storage com lifecycle policies e tiers, Application Gateway com WAF, Azure Functions nos três planos de hospedagem, Key Vault unificando secrets + chaves + certificados
E3 — Cluster AKS via Terraform com node pools sistema/app/spot, Workload Identity (equivalente ao IRSA), KEDA com Azure Service Bus, ACR integrado, GitOps com Flux como extensão gerenciada
E4 — Azure Pipelines com stages (Build → Staging → Produção), templates reutilizáveis entre repositórios, gates automáticos de qualidade, Service Connections com Workload Identity Federation, Branch Policies via Terraform, e a discussão sobre quando usar cada plataforma
Pronto para o bloco E5–E8 (Plataformas Git Alternativas) quando quiser.
you asked
Era em texto mesmo. Pode por eles aqui?
claude response
Claro. Os quatro artigos em texto, na sequência:
E1 — Azure para Quem Já Conhece AWS
Extensão A · Microsoft Azure · Artigo E1 de E4 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Que Muda e O Que Permanece
Ao longo dos doze meses do currículo principal desta série, a AWS funcionou como plataforma de referência. A escolha não foi arbitrária: a AWS detém a maior parcela do mercado global de nuvem e seu ecossistema de ferramentas, documentação e comunidade é o mais maduro disponível. Dominar a AWS primeiro é a decisão pedagógica correta.
Mas o mercado de trabalho não é monolítico. Grandes organizações frequentemente operam em múltiplas nuvens — seja por aquisições corporativas, requisitos regulatórios, estratégias de evitar dependência de fornecedor único ou porque determinados times já construíram expertise em uma plataforma específica. O Azure, nuvem da Microsoft, é o segundo maior provedor global e tem presença especialmente forte em empresas que já utilizam o ecossistema Microsoft: Active Directory, Office 365, SQL Server e Windows Server.
Este artigo é um mapa de tradução. Ele não recomeça do zero — parte do conhecimento já construído sobre AWS e mostra como os conceitos equivalem no Azure, onde as implementações são idênticas na prática, onde são semelhantes mas com nuances importantes, e onde o Azure segue caminhos fundamentalmente diferentes.
A Estrutura Organizacional: Contas vs. Subscrições
A primeira diferença que um engenheiro familiarizado com AWS encontra no Azure é a hierarquia organizacional. Na AWS, a unidade de isolamento de cobrança e segurança é a conta (account). Múltiplas contas são agrupadas em Organizations, com unidades organizacionais (OUs) para estruturar a hierarquia.
No Azure, a estrutura é ligeiramente diferente. O ponto de entrada é o Tenant do Azure Active Directory (agora chamado Microsoft Entra ID) — equivalente aproximado à organização como um todo. Dentro do tenant, existem Subscriptions (subscrições), que são a unidade de cobrança e limite de recursos, e funcionam de maneira análoga às contas AWS. As subscrições podem ser agrupadas em Management Groups, equivalentes às OUs no AWS Organizations.
O Resource Group merece atenção especial porque não tem equivalente direto na AWS. No Azure, todo recurso precisa pertencer a um Resource Group. Isso permite que uma aplicação completa — VMs, banco de dados, rede, storage — seja agrupada logicamente, e operações como aplicar tags, delegar permissões, monitorar custos ou deletar todos os recursos de uma vez operem sobre o grupo inteiro. É uma abstração útil que o Terraform implementa naturalmente ao provisionar recursos no Azure.
Mapa de Equivalências: AWS → Azure
A tabela a seguir cobre os serviços que aparecem em 90% dos projetos reais.
Compute: EC2 equivale a Azure Virtual Machines. A nomenclatura de tipos é diferente (B-series, D-series, E-series). Pricing por minuto em ambos. Azure Spot = EC2 Spot.
Object Storage: S3 equivale a Azure Blob Storage. A hierarquia é Storage Account → Container → Blob. Os tiers Hot/Cool/Cold/Archive equivalem aproximadamente a S3 Standard/IA/Glacier.
Containers gerenciados: ECS/Fargate equivale a Azure Container Instances (ACI). O ACI é mais simples para containers avulsos. Para orquestração, o caminho direto no Azure é o AKS.
Kubernetes gerenciado: EKS equivale ao AKS (Azure Kubernetes Service). O AKS integra-se nativamente com Entra ID para RBAC. Upgrade de versão do cluster é mais simples via CLI.
Serverless: Lambda equivale a Azure Functions. O Azure Functions suporta mais linguagens por padrão e tem modelo de hospedagem flexível (Consumption/Premium/Dedicated).
Banco relacional: RDS equivale a Azure Database for PostgreSQL/MySQL/Azure SQL. O Azure SQL é o serviço gerenciado do SQL Server — sem equivalente direto na AWS.
Cache gerenciado: ElastiCache equivale a Azure Cache for Redis. Ambos são Redis gerenciado. Configuração e tiers diferentes mas comportamento idêntico para a aplicação.
Filas de mensagens: SQS equivale a Azure Service Bus / Azure Queue Storage. O Azure Queue Storage é simples como SQS Standard. Service Bus é mais rico: tópicos, sessões, DLQ nativa — mais próximo do SQS + SNS juntos.
Event streaming: MSK/Kinesis equivale a Azure Event Hubs. O Event Hubs é compatível com protocolo Kafka — produtores/consumidores Kafka funcionam sem alteração de código.
CDN: CloudFront equivale a Azure Front Door / Azure CDN. O Front Door combina CDN + WAF + load balancing global em um produto.
DNS: Route 53 equivale a Azure DNS. O Azure DNS não tem registro de domínios nativamente.
Identidade e permissões: IAM equivale a Entra ID + Azure RBAC. Esta é a maior diferença conceitual — veja seção dedicada abaixo.
Segredos: Secrets Manager / Parameter Store equivale a Azure Key Vault. O Key Vault gerencia secrets, chaves de criptografia e certificados em um único serviço.
CI/CD: CodePipeline + CodeBuild equivale a Azure DevOps Pipelines. GitHub Actions (usado nesta série) funciona igualmente bem com Azure.
Observabilidade: CloudWatch equivale a Azure Monitor + Log Analytics. O Log Analytics Workspace tem uma linguagem de query muito mais poderosa chamada KQL.
Infraestrutura como código: CloudFormation equivale a ARM Templates / Bicep. Terraform funciona igualmente em ambas as nuvens.
Rede virtual: VPC equivale a Virtual Network (VNet). A VNet não tem subnets públicas/privadas por padrão como na AWS — o roteamento é mais flexível mas requer configuração explícita de NSGs e route tables.
Load Balancer: ALB/NLB equivale a Azure Load Balancer / Application Gateway. O Azure Load Balancer é L4 (equivalente ao NLB). O Application Gateway é L7 (equivalente ao ALB) com WAF integrado opcional.
Identidade: A Maior Diferença Conceitual
Em todas as equivalências anteriores, há uma que exige mais atenção do que as outras: identidade e permissões. Na AWS, o IAM é um serviço dentro da conta que gerencia tanto usuários humanos quanto identidades de serviços (roles). Políticas são documentos JSON que listam permissões explicitamente.
No Azure, a identidade é separada da autorização em dois sistemas distintos que trabalham juntos.
O Microsoft Entra ID (anteriormente Azure Active Directory) é o sistema de identidade — onde vivem usuários, grupos, service principals e managed identities. É um serviço de diretório completo, integrável com Active Directory on-premises via Azure AD Connect. Isso é fundamentalmente diferente do IAM da AWS, que é específico de nuvem.
O Azure RBAC (Role-Based Access Control) é o sistema de autorização — define quem pode fazer o quê em qual recurso. Em vez das políticas IAM da AWS (que listam permissões granulares), o Azure RBAC usa roles predefinidas e customizáveis que são atribuídas a identidades em um escopo específico (subscription, resource group ou recurso individual).
No Azure, uma role assignment é a combinação de: quem (identidade do Entra ID) + o que pode fazer (role com permissões) + onde (escopo: subscription, resource group ou recurso). Esse modelo é mais rígido mas mais fácil de auditar do que as políticas JSON do IAM.
Managed Identity: O Equivalente ao IAM Role para Serviços
Na AWS, quando um serviço precisa acessar outro, usa-se um IAM Role. O serviço assume o role automaticamente — sem credenciais hardcoded.
No Azure, o equivalente é a Managed Identity. A System-assigned Managed Identity é criada junto com o recurso e deletada quando o recurso é deletado — equivale a criar um IAM Role exclusivo para um recurso específico. A User-assigned Managed Identity é criada independentemente e pode ser atribuída a múltiplos recursos — equivale a um IAM Role compartilhado entre múltiplas instâncias.
Redes Virtuais: VPC vs. VNet
A Virtual Network (VNet) do Azure cumpre o mesmo papel da VPC da AWS: isola recursos em uma rede privada, controla o tráfego entre subnets e define conectividade com a internet e com redes on-premises.
A diferença mais importante é o conceito de subnets públicas e privadas. Na AWS, uma subnet é "pública" quando tem uma route table com rota para um Internet Gateway. No Azure, todas as subnets dentro de uma VNet podem ter acesso à internet por padrão — o que as torna "privadas" ou "públicas" é a combinação de Network Security Groups (NSGs), route tables e se os recursos têm Public IP Addresses associados.
O NSG do Azure equivale aos Security Groups da AWS, mas com uma diferença: ele pode ser associado tanto a subnets quanto a interfaces de rede individuais, e as regras têm prioridade numérica explícita em vez de serem permissivas por adição como nos Security Groups da AWS.
Uma nota de segurança importante: diferente da AWS onde uma subnet privada sem Internet Gateway é isolada por padrão, no Azure um recurso sem IP público ainda pode ter acesso de saída à internet via NAT implícito. Para isolar completamente, é necessário uma route table que force o tráfego de saída por um Azure Firewall.
Estado do Terraform no Azure
No currículo principal, o estado remoto do Terraform foi armazenado no S3 com lock no DynamoDB. No Azure, o backend equivalente usa Azure Blob Storage para o arquivo de estado e bloqueia via Azure Blob Storage leases — sem necessidade de um serviço separado como o DynamoDB. O backend é declarado no Terraform como backend "azurerm", apontando para a storage account, container e chave do arquivo de estado.
Regiões e Disponibilidade
Na AWS, as Regiões são localizações geográficas amplas e dentro delas existem Availability Zones — data centers fisicamente separados. No Azure, as Regions cumprem o mesmo papel. Dentro das regiões existem Availability Zones com a mesma função. O Azure adiciona um conceito intermediário relevante: Region Pairs. Cada região Azure tem um par designado geograficamente próximo para onde o Azure replica automaticamente alguns dados e prioriza recuperação em caso de interrupção regional.
A região Azure mais próxima do Brasil é a Brazil South, localizada em São Paulo — equivalente à região sa-east-1 da AWS. A região secundária para disaster recovery é a South Central US (Texas) — geograficamente mais distante que o par de AZs de São Paulo na AWS, ponto importante para arquiteturas com RTO/RPO muito baixos.
Próximos Passos na Trilha Azure
Este artigo estabeleceu o mapa de tradução entre AWS e Azure. Com os conceitos fundamentais alinhados — hierarquia organizacional, identidade, redes e estado do Terraform —, os próximos artigos aprofundam os serviços de compute, storage e rede (E2), o Kubernetes gerenciado com AKS (E3) e as pipelines de CI/CD com Azure DevOps (E4).
O investimento em aprender Azure sobre uma base sólida de AWS é menor do que aprender Azure do zero. Os princípios são os mesmos; a sintaxe, a nomenclatura e alguns padrões específicos da plataforma são o que muda.
Referências para Aprofundamento
— Azure para Profissionais AWS — Microsoft Learn: https://learn.microsoft.com/pt-br/azure/architecture/aws-professional/ — O Que é o Azure RBAC: https://learn.microsoft.com/pt-br/azure/role-based-access-control/overview — Terraform AzureRM Provider: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs — Visão Geral do Log Analytics: https://learn.microsoft.com/pt-br/azure/azure-monitor/logs/log-analytics-overview — Network Security Groups: https://learn.microsoft.com/pt-br/azure/virtual-network/network-security-groups-overview — Managed Identities para Recursos Azure: https://learn.microsoft.com/pt-br/entra/identity/managed-identities-azure-resources/overview
E2 — Compute, Storage e Redes no Azure
Extensão A · Microsoft Azure · Artigo E2 de E4 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Do Mapa para a Prática
O artigo anterior estabeleceu a visão geral: como o Azure se organiza, como a identidade funciona, como o Terraform autentica e as equivalências entre os principais serviços. Com esse mapa em mãos, é possível ir mais fundo nos serviços que formam a infraestrutura de qualquer aplicação: compute, storage e rede.
Este artigo cobre Azure Virtual Machines, Blob Storage, Virtual Network e Azure Functions com a profundidade necessária para colocá-los em produção — não apenas criar recursos, mas configurá-los com boas práticas de segurança, performance e custo.
Azure Virtual Machines em Produção
As Azure Virtual Machines são o equivalente direto das instâncias EC2. A seleção do tipo correto de VM segue a mesma lógica da AWS: para cada carga de trabalho existe uma família otimizada.
A nomenclatura do Azure usa letras para indicar a família e números para o tamanho. O padrão é Standard_[Família][vCPUs][Sufixo]. As principais famílias são a B-series (burstable, equivalente ao T3/T4g da AWS), a D-series (propósito geral, equivalente ao M5/M6i), a E-series (memória otimizada, equivalente ao R5/R6i), a F-series (compute otimizado, equivalente ao C5/C6i), a L-series (storage otimizado com NVMe, equivalente ao I3/I4i) e a N-series (GPU, equivalente ao P3/G4).
O sufixo no nome carrega informações importantes: s indica suporte a Premium Storage (SSD gerenciado de alta performance), d indica disco NVMe local temporário, _v5 é a geração do hardware. Para workloads de produção, sempre preferir as versões mais recentes pois oferecem melhor desempenho por custo.
Uma VM de produção no Azure é provisionada com Terraform declarando o resource group, a VNet e subnet, a network interface (ponte entre VM e subnet), opcionalmente um managed disk separado para dados (boa prática: separar disco de SO de disco de dados), e a VM em si com disable_password_authentication = true para usar apenas chave SSH. A Managed Identity do tipo SystemAssigned elimina qualquer necessidade de credenciais hardcoded para acessar outros serviços Azure. O campo custom_data com base64 é o equivalente ao User Data da AWS para scripts de inicialização.
Spot VMs
O equivalente ao EC2 Spot no Azure são as Azure Spot VMs — compute com desconto de 60–90% usando capacidade ociosa, que pode ser desalocada com 30 segundos de aviso. A configuração usa priority = "Spot" e eviction_policy. A diferença importante: na AWS, instâncias Spot sempre são terminadas quando interrompidas. No Azure, é possível escolher entre Deallocate (VM para mas o disco persiste, cobrança pelo disco continua) ou Delete (tudo é deletado, equivalente ao comportamento AWS). Para workers de processamento em batch, Delete é a escolha mais limpa.
Azure Blob Storage em Profundidade
O Azure Blob Storage é o equivalente do S3, mas com uma hierarquia de três níveis: Storage Account → Container → Blob. A Storage Account é a entidade de cobrança e o namespace — ela tem um nome único globalmente, assim como os buckets S3, mas esse nome define o endpoint DNS do serviço (nomeconta.blob.core.windows.net).
Tipos de Redundância
Diferente do S3 onde há um único tipo de serviço com múltiplas classes de armazenamento, no Azure a redundância é configurada no nível da Storage Account. O LRS (Locally Redundant Storage) mantém 3 cópias dentro de um único data center — o mais barato, não protege contra falha do data center. O ZRS (Zone-Redundant Storage) mantém 3 cópias em 3 availability zones — recomendado para produção. O GRS (Geo-Redundant Storage) mantém 6 cópias: 3 na região primária e 3 na região secundária — protege contra desastre regional. O GZRS combina ZRS na região primária com replicação geográfica — máxima durabilidade.
Access Tiers
O Azure Blob Storage tem quatro tiers de acesso. O Hot é para dados acessados com frequência — maior custo de armazenamento, menor custo de acesso, equivalente ao S3 Standard. O Cool é para dados acessados raramente, mínimo 30 dias de retenção — equivalente ao S3 Standard-IA. O Cold é para dados acessados muito raramente, mínimo 90 dias. O Archive é armazenamento offline — reidratação leva horas, equivalente ao S3 Glacier, mínimo 180 dias.
Uma diferença importante do S3: no Azure, o tier Archive opera no nível do blob individual, não do container. Para acessar um blob em Archive, é necessário "reidratá-lo" explicitamente, o que pode levar de 1 a 15 horas dependendo da prioridade escolhida.
A política de lifecycle no Azure é equivalente ao S3 Lifecycle Rules: é possível mover automaticamente blobs para Cool após N dias, para Archive após N dias, e deletar após N dias — tudo baseado em filtros por prefixo e tipo de blob.
Segurança no Blob Storage
Em produção, a Storage Account deve ter https_traffic_only_enabled = true, min_tls_version = "TLS1_2" e allow_nested_items_to_be_public = false. O soft delete (equivalente ao S3 Versioning com proteção contra deleção) mantém blobs deletados por N dias antes de removê-los permanentemente. O versionamento de blobs é habilitado com versioning_enabled = true nas blob properties.
Networking Avançado: Application Gateway
Na AWS, o ALB distribui tráfego HTTP/HTTPS baseado em regras de roteamento. No Azure, esse papel é do Application Gateway, que adiciona WAF integrado e terminação SSL.
O Application Gateway requer uma subnet dedicada (não pode compartilhar com outros recursos) e um IP público do SKU Standard. A configuração declara um backend address pool (onde vivem os servidores), backend HTTP settings (porta, protocolo, health probe), um frontend listener (porta, protocolo, certificado SSL) e routing rules que ligam listeners a backend pools. O WAF é configurado em modo Detection (apenas registra) ou Prevention (bloqueia) com os rule sets OWASP. O certificado SSL pode ser referenciado diretamente do Key Vault via key_vault_secret_id, sem precisar gerenciar o arquivo do certificado no Terraform.
Azure Functions em Profundidade
As Azure Functions têm três planos de hospedagem. O Consumption Plan é o equivalente direto ao Lambda: paga-se apenas pelo tempo de execução, escala automaticamente para zero quando não há invocações, e há cold start a considerar. O Premium Plan mantém instâncias pré-aquecidas eliminando cold starts, oferece conectividade com VNet e permite instâncias de maior capacidade — ideal para funções com resposta consistente exigida. O Dedicated (App Service) Plan roda as funções em VMs dedicadas, fazendo sentido quando já existe um App Service Plan compartilhado.
Uma Function App Linux requer uma Storage Account para armazenamento interno do runtime, um Service Plan (Y1 = Consumption), e o Application Insights para telemetria automática — um item que o AWS Lambda requer configuração manual para equivalente. A referência a secrets do Key Vault nas variáveis de ambiente usa a sintaxe @Microsoft.KeyVault(SecretUri=...), o que significa que o valor nunca aparece no Terraform ou nas configurações da função.
Os triggers das Azure Functions cobrem os mesmos casos de uso do Lambda: HTTP, Blob Storage (equivalente a S3 Event), Service Bus/Queue Storage (equivalente a SQS), Timer (equivalente ao EventBridge Scheduler) e muitos outros. O formato de CRON do Azure usa 6 campos com segundos no início (segundos minutos horas dias mês dia-semana), diferente dos 5 campos padrão do Linux — 0 0 2 * * * no Azure equivale a 0 2 * * ? * no EventBridge.
Azure Key Vault
O Azure Key Vault concentra em um único serviço o que na AWS é dividido entre Secrets Manager, KMS e ACM. Ele gerencia secrets, chaves de criptografia e certificados com uma API unificada e integração nativa com outros serviços Azure.
Em produção, o Key Vault deve ter soft_delete_retention_days = 90, purge_protection_enabled = true (proteção contra deleção permanente acidental) e enable_rbac_authorization = true (modelo mais moderno que Access Policies). As network ACLs devem restringir o acesso apenas a subnets autorizadas, com default_action = "Deny".
A permissão de uma Managed Identity para ler secrets usa o role Key Vault Secrets User via azurerm_role_assignment com escopo no Key Vault — sem precisar criar Access Policies manualmente.
Referências para Aprofundamento
— Tamanhos de VMs no Azure: https://learn.microsoft.com/pt-br/azure/virtual-machines/sizes/overview — Introdução ao Azure Blob Storage: https://learn.microsoft.com/pt-br/azure/storage/blobs/storage-blobs-introduction — Escala e Hospedagem do Azure Functions: https://learn.microsoft.com/pt-br/azure/azure-functions/functions-scale — O Que É o Azure Application Gateway: https://learn.microsoft.com/pt-br/azure/application-gateway/overview — Visão Geral do Azure Key Vault: https://learn.microsoft.com/pt-br/azure/key-vault/general/overview
E3 — Azure Kubernetes Service (AKS)
Extensão A · Microsoft Azure · Artigo E3 de E4 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Kubernetes é o Mesmo. A Nuvem é Diferente.
O Kubernetes em si é a mesma plataforma, seja no EKS da AWS ou no AKS do Azure. Os manifestos YAML que descrevem Deployments, Services, ConfigMaps e HPAs funcionam sem modificação entre os dois. O que muda são as integrações com a nuvem subjacente — e é aí que o AKS tem características próprias que valem conhecer.
Este artigo cobre a criação de um cluster AKS com Terraform, a integração com Microsoft Entra ID para autenticação, o Workload Identity como substituto do IRSA da AWS, o KEDA (Kubernetes Event-Driven Autoscaling) para escalonamento baseado em eventos do Azure Service Bus, e o GitOps com Flux — a alternativa nativa ao ArgoCD amplamente usada no ecossistema Azure.
AKS vs. EKS: As Diferenças que Importam
Ambos os serviços gerenciam o control plane do Kubernetes, mas com modelos ligeiramente diferentes.
No EKS, o control plane tem custo fixo por hora (aproximadamente USD 0,10/hora), independentemente do número de nós. Os nós são instâncias EC2 gerenciadas pelo usuário ou pelo EKS Managed Node Groups. O Karpenter é o provisionador de nós recomendado para escalonamento rápido.
No AKS, o control plane é gratuito no tier Free (sem SLA de uptime garantido) e tem custo no tier Standard (SLA de 99,95% para o API server). Os nós são Azure VMs em Node Pools. O escalonamento de nós é feito pelo Cluster Autoscaler nativo, e o KEDA cuida do escalonamento de pods baseado em eventos.
O AKS Free Tier é especialmente útil para ambientes de desenvolvimento e staging — o control plane gratuito representa uma economia significativa comparada ao EKS, que cobra pelo control plane independentemente do ambiente.
Provisionando um Cluster AKS com Terraform
O cluster AKS no Terraform é declarado com o recurso azurerm_kubernetes_cluster. Os pontos mais importantes são: o sku_tier = "Standard" para o SLA de produção; o automatic_channel_upgrade = "patch" para atualizações automáticas de segurança; o default_node_pool com only_critical_addons_enabled = true reservando esse pool apenas para componentes do sistema; e o oidc_issuer_enabled = true com workload_identity_enabled = true que habilita o equivalente ao OIDC do EKS para o Workload Identity.
A rede é configurada com o profile azure e network_plugin_mode = "overlay" — o Azure CNI Overlay é recomendado para clusters grandes pois não consome IPs da VNet para cada pod, diferente do CNI tradicional. A integração com Entra ID é declarada no bloco azure_active_directory_role_based_access_control com azure_rbac_enabled = true e um grupo de administradores.
Os node pools de aplicação são declarados separadamente com azurerm_kubernetes_cluster_node_pool. A boa prática no AKS é manter node pools distintos para o sistema e para as aplicações — o pool do sistema tem o taint implícito CriticalAddonsOnly, garantindo que workloads de aplicação não compitam com componentes do cluster. Um terceiro pool Spot com priority = "Spot" e min_count = 0 permite escalar para zero quando não há carga, zerando custos de compute em horários de baixa.
Para autenticar o kubectl no AKS, usa-se az aks get-credentials — equivalente ao aws eks update-kubeconfig do EKS. O kubelogin é o componente que faz a autenticação interativa via Entra ID, equivalente ao aws-iam-authenticator do EKS.
Workload Identity: O IRSA do Azure
No EKS, o IRSA (IAM Roles for Service Accounts) permitia que pods assumissem IAM Roles sem credenciais hardcoded — bastava anotar a ServiceAccount com o ARN do role. O AKS tem o equivalente chamado Workload Identity, que usa o mesmo mecanismo OIDC por baixo mas com a sintaxe do ecossistema Azure.
O fluxo é o mesmo: o cluster emite tokens OIDC para os pods, a Azure aceita esses tokens como prova de identidade e emite tokens de acesso aos recursos Azure solicitados.
Na AWS com IRSA, cria-se um IAM Role com trust policy apontando para o OIDC provider do EKS e um Condition restringindo ao namespace e ServiceAccount específicos. A ServiceAccount é anotada com o ARN do role.
No Azure com Workload Identity, cria-se uma User Assigned Managed Identity, depois um Federated Identity Credential que liga a ServiceAccount Kubernetes à identidade (declarando o issuer como o URL OIDC do cluster e o subject como system:serviceaccount:namespace:nome-da-sa). A ServiceAccount é anotada com o client-id da Managed Identity. O Deployment precisa do label azure.workload.identity/use: "true" para que os webhooks do Workload Identity injetem as variáveis de ambiente e o token no pod.
O SDK Azure para Node.js, Python, Java e outras linguagens detecta automaticamente o Workload Identity via variáveis de ambiente — new DefaultAzureCredential() funciona sem configuração adicional tanto em desenvolvimento (usando a CLI do Azure) quanto em produção (usando o Workload Identity).
Para montar secrets do Key Vault diretamente como variáveis de ambiente ou arquivos no pod, usa-se o recurso SecretProviderClass do Secrets Store CSI Driver — habilitado no AKS via o bloco key_vault_secrets_provider no Terraform.
KEDA: Escalonamento Baseado em Eventos
O KEDA (Kubernetes Event-Driven Autoscaling) é um componente open-source criado pela Microsoft e Red Hat que estende o HPA do Kubernetes para escalonar pods baseado em fontes de eventos externas — não apenas CPU e memória.
No EKS, o escalonamento baseado em tamanho de fila SQS requer configurar métricas customizadas no CloudWatch, expô-las via adapter e configurar o HPA — um processo relativamente complexo. No AKS com KEDA, a integração com Azure Service Bus é nativa e declarativa.
O KEDA é instalado via Helm com kedacore/keda no namespace keda. A autenticação com os serviços Azure é configurada via TriggerAuthentication com provider: azure-workload — sem connection strings estáticas. O ScaledObject declara o Deployment alvo, os limites de réplicas (minReplicaCount: 0 para scale to zero) e os triggers. Para Service Bus, o trigger especifica o namespace, o nome da fila e a proporção de mensagens por réplica — por exemplo, messageCount: "100" cria uma nova réplica para cada 100 mensagens na fila.
O KEDA suporta dezenas de scalers além do Service Bus: Azure Storage Queue, Azure Event Hubs, Redis, PostgreSQL, MySQL, HTTP requests, métricas do Prometheus e muitos outros. Essa flexibilidade é uma das razões pelas quais o KEDA se tornou padrão de facto em clusters Kubernetes independentemente da nuvem.
Azure Container Registry (ACR)
O Azure Container Registry é o equivalente do Amazon ECR. A integração com o AKS é nativa: basta uma azurerm_role_assignment atribuindo o role AcrPull ao kubelet_identity do cluster. Após isso, o Kubernetes faz pull de imagens do ACR sem image pull secrets configurados nos pods.
O SKU Premium do ACR oferece geo-replicação (disponibilidade global das imagens com baixa latência), scan de vulnerabilidades automático ao fazer push, e políticas de retenção de imagens. O az acr build permite fazer build da imagem diretamente no ACR sem precisar de Docker local — o build roda em infraestrutura gerenciada da Microsoft.
GitOps com Flux no AKS
No currículo principal, o GitOps foi implementado com ArgoCD. No ecossistema Azure, a alternativa nativa e igualmente popular é o Flux — o outro projeto de GitOps da CNCF. O AKS oferece Flux como extensão gerenciada, eliminando a necessidade de instalar e manter o Flux manualmente.
A extensão é instalada via azurerm_kubernetes_cluster_extension com extension_type = "microsoft.flux". A configuração do GitOps é declarada com azurerm_kubernetes_flux_configuration, apontando para um repositório Git (URL, branch, intervalo de sync) e definindo Kustomizations — subpaths do repositório para aplicar. É possível declarar dependências entre Kustomizations (apps dependem de infraestrutura estar sincronizada primeiro).
No repositório GitOps, o Flux usa dois recursos principais: HelmRelease para instalar charts Helm (equivalente ao ArgoCD Application com source do tipo Helm) e Kustomization para aplicar manifestos YAML organizados com Kustomize. O prune: true garante que recursos removidos do Git sejam deletados do cluster — equivalente ao syncPolicy.automated.prune do ArgoCD.
Monitoramento com Azure Monitor e KQL
O KQL (Kusto Query Language) é a linguagem de query do Log Analytics Workspace — mais poderosa que o CloudWatch Insights. Queries úteis para operação do AKS incluem: listar pods em estado não-Running nos últimos 30 minutos (KubePodInventory | where PodStatus != "Running"); calcular uso de CPU por namespace com Perf e agregação por bin de tempo; detectar evictions com KubeEvents | where Reason == "Evicted"; e identificar containers com muitas reinicializações com KubePodInventory | where RestartCount > 3.
Referências para Aprofundamento
— O Que É o Azure Kubernetes Service: https://learn.microsoft.com/pt-br/azure/aks/what-is-aks — Workload Identity no AKS: https://learn.microsoft.com/pt-br/azure/aks/workload-identity-overview — KEDA — Azure Service Bus Scaler: https://keda.sh/docs/2.14/scalers/azure-service-bus/ — Conceitos do Flux: https://fluxcd.io/flux/concepts/ — Introdução ao Azure Container Registry: https://learn.microsoft.com/pt-br/azure/container-registry/container-registry-intro — azurerm_kubernetes_cluster no Terraform Registry: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster
E4 — Azure DevOps: Pipelines e Repositórios
Extensão A · Microsoft Azure · Artigo E4 de E4 · Conclusão do Bloco Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Além do GitHub Actions
O GitHub Actions foi a plataforma de CI/CD do currículo principal desta série — uma escolha natural para projetos que hospedam código no GitHub. Mas no mercado corporativo, especialmente em empresas que já adotaram o ecossistema Microsoft, o Azure DevOps é frequentemente a plataforma consolidada de desenvolvimento de software. Entender o Azure DevOps é, portanto, uma habilidade prática para quem vai trabalhar nesse ambiente.
Este artigo cobre o Azure DevOps de forma completa: o que é cada componente da plataforma, como os pipelines funcionam com YAML, como integrar com o AKS para deploy, e a discussão sobre quando usar Azure DevOps versus manter o GitHub Actions mesmo em projetos Azure.
O Que É o Azure DevOps
O Azure DevOps não é apenas uma ferramenta de CI/CD — é uma plataforma integrada de gestão do ciclo de vida de software que a Microsoft oferece como serviço (azure.microsoft.com/products/devops) ou instalável on-premises (Azure DevOps Server). Ele tem cinco componentes principais.
O Azure Boards é rastreamento de trabalho com Kanban, Scrum e épicos — equivalente ao GitHub Issues + Projects ou ao Jira, com integração nativa com PRs e commits.
O Azure Repos são repositórios Git (ou TFVC legado) — equivalente ao GitHub Repositories, com pull requests, code review e branch policies.
O Azure Pipelines é o CI/CD declarativo em YAML — o coração do Azure DevOps, equivalente ao GitHub Actions mas com conceitos próprios (stages, jobs, tasks).
O Azure Artifacts é o registro de pacotes (npm, NuGet, Maven, PyPI, Docker) — equivalente ao GitHub Packages ou ao AWS CodeArtifact.
O Azure Test Plans é a gestão de testes manuais e exploratórios — sem equivalente direto no GitHub, mais relevante para times com QA dedicado.
A orientação atual da própria Microsoft é usar GitHub Actions para novos projetos que hospedam código no GitHub, e Azure DevOps Pipelines quando há necessidade de integração profunda com Azure Boards, Azure Artifacts, ou quando o código está no Azure Repos. Muitas organizações usam os dois: código no GitHub, rastreamento de trabalho no Azure Boards, pipeline no GitHub Actions conectado ao Azure.
Estrutura de um Pipeline YAML
O pipeline YAML do Azure DevOps usa conceitos similares ao GitHub Actions mas com nomenclatura diferente. O mapeamento é: GitHub Actions usa workflow → job → step; Azure DevOps usa pipeline → stage → job → step (task ou script).
O Azure DevOps adiciona o conceito de stage como agrupamento de jobs que representam fases do processo (Build, Test, Deploy Staging, Deploy Production). Isso que no GitHub Actions exige conventions e dependências explícitas entre jobs, no Azure DevOps é um conceito de primeira classe.
O arquivo azure-pipelines.yml na raiz do repositório é equivalente aos arquivos em .github/workflows/. O trigger define branches e paths que disparam o pipeline — o pr define o gatilho para pull requests. Variáveis globais são declaradas com variables e podem referenciar grupos de variáveis do Azure DevOps Library (equivalente aos Secrets do GitHub). O pool.vmImage equivale ao runs-on do GitHub Actions.
Um stage de Build contém jobs paralelos para testes unitários, lint e análise de segurança. A task PublishTestResults@2 publica os resultados dos testes no painel do Azure DevOps — um recurso que o GitHub Actions não tem nativamente. A task PublishCodeCoverageResults@1 equivale ao upload para Codecov.
O stage de BuildImagem usa a task Docker@2 com command: buildAndPush para fazer build e push para o ACR em uma única operação. O stage de DeployStaging usa o tipo especial deployment (em vez de job), que habilita rastreamento de deploys no Environment. A task AzureCLI@2 é o equivalente ao step com uses: azure/login@v2 seguido de comandos kubectl.
O stage de DeployProducao referencia um Environment configurado no portal com aprovação manual — equivalente aos Environments do GitHub com required reviewers. A estratégia runOnce com os blocos preDeploy, deploy, routeTraffic, postRouteTraffic, on.failure e on.success oferece um controle granular do processo de deploy que o GitHub Actions não tem nativamente.
Templates Reutilizáveis
O recurso mais poderoso dos Azure Pipelines para times com múltiplos repositórios são os templates. Ao contrário dos GitHub Actions reutilizáveis, os templates do Azure DevOps podem ser compostos de maneira muito mais granular: templates de steps individuais, de jobs inteiros ou de stages completos.
Um repositório central de templates é referenciado nos pipelines com o bloco resources.repositories, apontando para o nome da organização, repositório e branch. O template é chamado com template: caminho/arquivo.yml@nomeDoRepositorioRemoto, passando parâmetros tipados. Os parâmetros do template declaram tipo (string, number, boolean), nome e valor padrão — o Terraform YAML valida os tipos em tempo de parse, antes de executar.
Service Connections: Autenticando com Azure Resources
As Service Connections do Azure DevOps são o equivalente dos secrets de credenciais no GitHub Actions, mas gerenciadas centralmente e reutilizáveis em múltiplos projetos e pipelines. Para conectar ao Azure, a recomendação é Workload Identity Federation — sem client secrets com prazo de expiração. A criação pode ser feita pelo portal do Azure DevOps ou via az devops service-endpoint azurerm create com --azure-rm-service-principal-authentication-scheme WorkloadIdentityFederation.
Ambientes e Gates Automáticos
Os Environments do Azure DevOps fornecem controle de aprovação para deploys em ambientes críticos. Mas o Azure DevOps vai além do GitHub Actions: suporta múltiplos aprovadores com lógica (qualquer um aprova vs. todos precisam aprovar), timeouts de aprovação, e gates automáticos.
Gates automáticos permitem que o pipeline verifique condições externas antes de prosseguir — por exemplo, verificar que a taxa de erro em staging está abaixo de 1% antes de permitir o deploy em produção, ou que não há alertas críticos ativos no Azure Monitor. Os gates são configurados no portal do Azure DevOps no painel de Approvals and Checks do Environment, e executam periodicamente até a condição ser satisfeita ou o timeout ser atingido.
O tipo deployment com strategy runOnce oferece os blocos preDeploy (antes do deploy), deploy (o deploy em si), routeTraffic (roteamento de tráfego), postRouteTraffic (verificações pós-roteamento), on.failure (rollback automático) e on.success (notificações). Esse ciclo de vida estruturado é exclusivo do Azure DevOps — no GitHub Actions, toda essa lógica precisa ser implementada manualmente com steps condicionais.
Azure Artifacts: Registro Privado de Pacotes
Times que desenvolvem bibliotecas internas compartilhadas entre múltiplos projetos precisam de um registro privado de pacotes. O Azure Artifacts oferece feeds para npm, NuGet, Maven, PyPI e Universal Packages.
A publicação de um pacote npm no pipeline usa a task npmAuthenticate@0 para configurar o arquivo .npmrc com as credenciais do feed do Azure Artifacts, seguida de npm publish. O arquivo .npmrc do projeto aponta para a URL do feed no formato pkgs.dev.azure.com/organização/projeto/_packaging/feed/npm/registry/. Não é necessário configurar tokens manualmente — a task de autenticação cuida disso usando a Service Connection configurada.
Branch Policies via Terraform
As Branch Policies do Azure Repos são o equivalente às Branch Protection Rules do GitHub. O provider Terraform microsoft/azuredevops permite gerenciá-las como código. As políticas mais comuns são: azurerm_branch_policy_min_reviewers (mínimo de revisores, com opção de impedir que o próprio autor aprove), azurerm_branch_policy_build_validation (exige que um pipeline específico passe antes de mergear) e azurerm_branch_policy_comment_resolution (todos os comentários de revisão devem ser resolvidos antes do merge).
Quando Usar o Quê
A decisão entre GitHub Actions e Azure DevOps raramente é binária em organizações de médio e grande porte. O cenário mais comum é uma combinação.
Código no GitHub + Pipeline no GitHub Actions + Azure como infraestrutura é a combinação mais simples para novos projetos. O GitHub Actions autentica no Azure via Workload Identity Federation e faz deploy para o AKS. Toda a gestão de código e pipeline fica em um único lugar.
Código no Azure Repos + Pipeline no Azure Pipelines + AKS é o padrão em organizações que já adotaram o Azure DevOps como plataforma corporativa, especialmente aquelas com controles de compliance que exigem que o código não saia do ambiente Microsoft.
Código no GitHub + Rastreamento no Azure Boards + Pipeline misto é uma combinação comum em organizações que usam o GitHub pela experiência do desenvolvedor mas mantêm o Azure Boards para gestão de projetos por causa da integração com Power BI e Microsoft Teams.
Com os quatro artigos desta extensão, a transição de AWS para Azure está coberta nos pontos mais importantes: conceitos e equivalências (E1), compute, storage e redes (E2), Kubernetes com AKS (E3) e CI/CD com Azure DevOps (E4). O engenheiro que domina ambas as plataformas tem uma versatilidade de mercado significativa — e percebe, na prática, que os fundamentos são sempre os mesmos: automação, observabilidade, segurança por padrão e infraestrutura como código.
Referências para Aprofundamento
— O Que São os Azure Pipelines: https://learn.microsoft.com/pt-br/azure/devops/pipelines/get-started/what-is-azure-pipelines — Service Connections — Azure DevOps Docs: https://learn.microsoft.com/pt-br/azure/devops/pipelines/library/service-endpoints — Templates de Pipeline — Azure DevOps Docs: https://learn.microsoft.com/pt-br/azure/devops/pipelines/process/templates — Introdução ao Azure Artifacts: https://learn.microsoft.com/pt-br/azure/devops/artifacts/start-using-azure-artifacts — AzureDevOps Provider — Terraform Registry: https://registry.terraform.io/providers/microsoft/azuredevops/latest/docs
Bloco Azure concluído — E1 a E4. Pronto para o bloco E5–E8 (Plataformas Git Alternativas) quando quiser.
you asked
Continue
claude response
E5 — GitLab CI/CD: A Alternativa Enterprise ao GitHub Actions
Extensão B · Plataformas Git Alternativas · Artigo E5 de E8 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Por Que o GitLab Existe no Mercado
O GitHub é a plataforma padrão para projetos open-source e para a maioria dos times que trabalham com código público ou semi-público. Mas o GitLab ocupa um espaço diferente no mercado: é a escolha preferida de organizações que precisam de uma plataforma DevOps completa, auto-suficiente e, frequentemente, instalada em infraestrutura própria.
A diferença filosófica é importante. O GitHub nasceu como plataforma de hospedagem de código e foi adicionando funcionalidades ao longo do tempo — Actions, Packages, Security, Projects. O GitLab nasceu com a proposta de ser uma plataforma DevOps completa desde o início: repositório, CI/CD, registro de containers, gerenciamento de issues, wiki, monitoramento, segurança e muito mais, tudo integrado em um único produto.
Para o engenheiro que já domina o GitHub Actions, o GitLab CI/CD é a segunda plataforma mais importante para conhecer — não porque seja "melhor", mas porque ela aparece com frequência em contextos enterprise e em organizações que valorizam soberania sobre seus dados e pipelines.
A Estrutura do GitLab CI/CD
O arquivo de pipeline no GitLab é o .gitlab-ci.yml, que fica na raiz do repositório — equivalente ao .github/workflows/nome.yml do GitHub Actions. Mas há uma diferença estrutural importante: no GitHub Actions, cada arquivo de workflow é independente. No GitLab, há um único arquivo de pipeline por projeto (embora ele possa incluir outros arquivos via include).
O mapeamento de conceitos é o seguinte. No GitHub Actions, um workflow contém jobs que contêm steps. No GitLab CI/CD, um pipeline contém stages que agrupam jobs, e cada job contém um script. Os stages definem a ordem de execução — todos os jobs de um stage executam em paralelo, e o próximo stage só começa quando todos os jobs do stage anterior terminam.
# .gitlab-ci.yml
# Stages definem a ordem de execução
stages:
- verificacao
- build
- testes
- seguranca
- deploy-staging
- deploy-producao
# Variáveis globais (equivalente ao env: global do GitHub Actions)
variables:
NODE_VERSION: "20"
DOCKER_DRIVER: overlay2
# Variáveis sensíveis vêm das CI/CD Variables do projeto (Settings → CI/CD)
# ── Job: Lint ────────────────────────────────────────────────────
lint:
stage: verificacao
image: node:20-alpine
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
script:
- npm ci
- npm run lint
rules:
# Executar em qualquer branch ou merge request
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
# ── Job: Testes Unitários ────────────────────────────────────────
testes-unitarios:
stage: testes
image: node:20-alpine
services:
# Services são containers auxiliares — equivalente ao services: do GitHub Actions
- name: postgres:16-alpine
alias: postgres
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
- name: redis:7-alpine
alias: redis
variables:
DATABASE_URL: "postgresql://testuser:testpass@postgres:5432/testdb"
REDIS_URL: "redis://redis:6379"
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
script:
- npm ci
- npm test -- --coverage
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/' # Regex para extrair cobertura do output
artifacts:
when: always
reports:
# Equivalente ao PublishTestResults do Azure DevOps
junit: test-results.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
expire_in: 7 days
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
Cache e Artifacts: Conceitos Distintos
O GitLab separa explicitamente dois conceitos que o GitHub Actions trata de maneira mais unificada com actions/cache e actions/upload-artifact.
O cache no GitLab é para acelerar execuções — node_modules, dependências Maven, cache do pip. Ele é armazenado no runner e reutilizado entre jobs e pipelines. A chave do cache pode ser baseada em arquivos (como o package-lock.json) para invalidar automaticamente quando as dependências mudam.
Os artifacts são saídas do job que precisam ser passadas para jobs subsequentes ou preservadas para download. Um job de build gera o binário como artifact; um job de deploy consome esse artifact. A propriedade expire_in define por quanto tempo o artifact é mantido — sem isso, o GitLab usa a configuração padrão da instância.
# Job de build que gera artifact para o job de deploy
build-imagem:
stage: build
image: docker:24
services:
- docker:24-dind # Docker-in-Docker
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
before_script:
# Login no GitLab Container Registry (equivalente ao ECR/ACR)
# CI_REGISTRY, CI_REGISTRY_USER e CI_REGISTRY_PASSWORD são variáveis automáticas
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
# Também taggear como latest na branch main
- |
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
docker push $CI_REGISTRY_IMAGE:latest
fi
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
Variáveis Automáticas do GitLab
O GitLab injeta dezenas de variáveis de ambiente automaticamente em cada job. Para quem vem do GitHub Actions (onde as variáveis equivalentes ficam no contexto github.*), é útil conhecer as mais usadas:
CI_COMMIT_SHA é o SHA completo do commit — equivalente ao ${{ github.sha }}. CI_COMMIT_SHORT_SHA são os primeiros 8 caracteres — muito usado para taggear imagens Docker. CI_COMMIT_BRANCH é o nome da branch atual — equivalente ao ${{ github.ref_name }}. CI_COMMIT_TAG é a tag do commit, se existir — equivalente ao ${{ github.ref }} quando é uma tag. CI_PIPELINE_SOURCE indica o que disparou o pipeline: push, merge_request_event, schedule, web e outros — equivalente ao ${{ github.event_name }}. CI_REGISTRY_IMAGE é a URL da imagem no GitLab Container Registry deste projeto — sem equivalente direto no GitHub, que precisa construir a URL manualmente. CI_PROJECT_PATH é o caminho do projeto no formato grupo/subgrupo/projeto. CI_ENVIRONMENT_URL é a URL do environment de deploy, configurada no job.
Segurança no Pipeline: SAST, DAST e Dependency Scanning
Um diferencial significativo do GitLab em relação ao GitHub Actions é a integração nativa de ferramentas de segurança. O GitLab oferece SAST (Static Application Security Testing), DAST (Dynamic Application Security Testing), Dependency Scanning e Container Scanning como templates prontos para incluir no pipeline — sem precisar configurar actions de terceiros ou criar scripts customizados.
# Incluir templates de segurança do GitLab
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
# Os jobs de segurança são adicionados automaticamente pelo include
# e executam no stage 'test' por padrão
# Configuração do Container Scanning (scan da imagem Docker)
container_scanning:
stage: seguranca
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
CS_SEVERITY_THRESHOLD: HIGH # Falhar apenas em HIGH ou CRITICAL
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# Configuração do SAST
sast:
stage: seguranca
variables:
SAST_EXCLUDED_PATHS: "spec,test,tests,tmp,node_modules"
Os resultados de todas as ferramentas de segurança aparecem na interface do GitLab no painel de Security do projeto e nos Merge Requests — o revisor vê os findings de segurança diretamente no MR, sem precisar navegar para outro sistema.
Deploy com Environments e Revisão Manual
O GitLab tem um sistema de Environments robusto que rastreia o histórico de deploys, permite rollback visual e suporta revisão manual antes de deploys em ambientes críticos.
# ── Deploy em Staging ────────────────────────────────────────────
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
environment:
name: staging
url: https://staging.minha-app.com
variables:
KUBECONFIG: $KUBECONFIG_STAGING # Secret file configurada nas CI/CD Variables
script:
- kubectl set image deployment/minha-app
app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
-n staging
- kubectl rollout status deployment/minha-app -n staging --timeout=5m
- |
# Smoke test
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.minha-app.com/health)
if [ "$HTTP_STATUS" != "200" ]; then
echo "Smoke test falhou: $HTTP_STATUS"
exit 1
fi
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# ── Deploy em Produção com Revisão Manual ─────────────────────────
deploy-producao:
stage: deploy-producao
image: bitnami/kubectl:latest
environment:
name: producao
url: https://minha-app.com
variables:
KUBECONFIG: $KUBECONFIG_PRODUCAO
# when: manual — o job só executa quando alguém clicar no botão na interface
# Equivalente ao environment com required reviewers do GitHub Actions
when: manual
# allow_failure: false garante que o pipeline fica em pausa esperando a aprovação
allow_failure: false
script:
- kubectl set image deployment/minha-app
app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
-n producao
- kubectl rollout status deployment/minha-app -n producao --timeout=10m
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
# ── Rollback Manual ───────────────────────────────────────────────
rollback-producao:
stage: deploy-producao
image: bitnami/kubectl:latest
environment:
name: producao
action: stop # Marca o environment como parado no histórico
when: manual
script:
- kubectl rollout undo deployment/minha-app -n producao
- kubectl rollout status deployment/minha-app -n producao
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
Pipelines Agendados e Multi-projeto
O GitLab suporta pipelines agendados (equivalente ao schedule trigger do GitHub Actions) configurados via interface ou API — úteis para testes noturnos, relatórios periódicos e rotações de credenciais.
Para projetos que precisam disparar pipelines em outros projetos (por exemplo, um repositório de infraestrutura que, ao mudar, deve redesenhar aplicações dependentes), o GitLab oferece o trigger:
# Pipeline que dispara outro pipeline em outro projeto
notificar-downstream:
stage: deploy-producao
trigger:
project: minha-empresa/infraestrutura-kubernetes
branch: main
strategy: depend # Aguardar o pipeline downstream terminar
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
GitLab Runners: Onde o Pipeline Executa
Os GitLab Runners são os agentes que executam os jobs — equivalente aos runners hospedados do GitHub (ubuntu-latest, windows-latest) e aos self-hosted runners. O GitLab.com oferece runners compartilhados gratuitos para projetos públicos e com limites para projetos privados. Para ambientes on-premises ou com requisitos específicos de segurança e performance, é comum instalar runners próprios.
Um runner próprio é instalado em qualquer máquina Linux, Windows ou macOS com o gitlab-runner e registrado no projeto ou grupo GitLab com um token. O executor (como o job executa) pode ser Shell (diretamente na máquina), Docker (em containers isolados — o mais comum), Kubernetes (em pods do cluster — excelente para escalonamento automático) ou VirtualBox/Parallels (em VMs).
Para registrar um runner Kubernetes no cluster:
# helm values para o GitLab Runner no Kubernetes
# helm install gitlab-runner gitlab/gitlab-runner -f values.yml
gitlabUrl: https://gitlab.com/ # Ou a URL do GitLab self-hosted
runnerToken: "TOKEN-DO-RUNNER" # Obtido em Settings → CI/CD → Runners
rbac:
create: true
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "gitlab-runners"
image = "ubuntu:22.04"
privileged = false
[[runners.kubernetes.volumes.empty_dir]]
name = "docker-certs"
mount_path = "/certs/client"
medium = "Memory"
O Que Vem a Seguir
Este artigo cobriu o GitLab CI/CD como plataforma SaaS e o padrão de pipelines para times que hospedam código no gitlab.com. O próximo artigo vai mais fundo: o GitLab instalado em infraestrutura própria (self-hosted), que é onde a plataforma mostra todo o seu potencial para organizações com requisitos de soberania de dados, compliance rigoroso ou simplesmente custos de licença mais previsíveis.
Referências para Aprofundamento
— Documentação do GitLab CI/CD: https://docs.gitlab.com/ee/ci/ — Variáveis Predefinidas do GitLab CI/CD: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html — GitLab Security Dashboard: https://docs.gitlab.com/ee/user/application_security/security_dashboard/ — GitLab Runner — Executor Kubernetes: https://docs.gitlab.com/runner/executors/kubernetes/ — Templates de Segurança do GitLab: https://docs.gitlab.com/ee/user/application_security/
E6 — GitLab Self-Hosted: Soberania Total sobre Código e Pipelines
Extensão B · Plataformas Git Alternativas · Artigo E6 de E8 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Por Que Instalar o Próprio GitLab
Há contextos em que usar um serviço SaaS para hospedar código e pipelines não é uma opção. Organizações financeiras reguladas, defesa, saúde com dados sensíveis de pacientes, governos e empresas com políticas rígidas de residência de dados frequentemente precisam que toda a cadeia de desenvolvimento de software opere dentro de sua própria infraestrutura. Nenhum código, nenhuma credencial, nenhum log de pipeline deve sair do ambiente controlado.
O GitLab foi desenhado desde o início para ser instalado em infraestrutura própria — não como um produto secundário, mas como o modelo principal de distribuição por anos. O GitLab.com (SaaS) só veio depois. Isso significa que a experiência de self-hosting é madura, bem documentada e com suporte de longo prazo.
Há também uma razão financeira. O GitLab.com cobra por usuário por mês nos planos pagos. Para organizações com centenas ou milhares de desenvolvedores, o custo de uma instância self-hosted com licença Enterprise Edition (ou mesmo a Community Edition gratuita) pode ser significativamente menor do que o SaaS a longo prazo.
Arquitetura do GitLab Self-Hosted
O GitLab é uma aplicação complexa composta de múltiplos componentes. Compreender a arquitetura é essencial antes de instalar, porque as decisões de infraestrutura dependem de quais componentes serão gerenciados internamente e quais serão delegados a serviços gerenciados.
Os componentes principais são: Puma (servidor web Rails que serve a interface e a API), Sidekiq (processamento de jobs em background — notificações, mirrors, CI triggers), GitLab Workhorse (proxy reverso leve que lida com uploads e operações Git pesadas), Gitaly (serviço gRPC que gerencia todos os acessos ao repositório Git — isolado por razões de segurança e performance), PostgreSQL (banco de dados principal), Redis (cache e filas do Sidekiq), e Object Storage (para artifacts, LFS, uploads e backups — S3, Azure Blob ou GCS).
Para instalações pequenas (até 500 usuários), todos esses componentes podem rodar em uma única VM. Para instalações médias e grandes, a arquitetura de referência do GitLab recomenda separar os componentes: banco de dados gerenciado (RDS ou equivalente), Redis gerenciado (ElastiCache), object storage separado, e múltiplos nós Puma/Sidekiq atrás de um load balancer.
Instalação com Docker Compose (Ambientes Menores)
Para ambientes de desenvolvimento, staging e instalações com até algumas dezenas de usuários, o Docker Compose oferece a forma mais rápida de ter um GitLab funcional:
# docker-compose.yml
version: '3.8'
services:
gitlab:
image: gitlab/gitlab-ee:latest # EE para funcionalidades enterprise; CE para community
container_name: gitlab
restart: always
hostname: 'gitlab.empresa.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.empresa.com'
# PostgreSQL externo (recomendado para produção)
gitlab_rails['db_adapter'] = 'postgresql'
gitlab_rails['db_host'] = 'postgres'
gitlab_rails['db_port'] = 5432
gitlab_rails['db_database'] = 'gitlabhq_production'
gitlab_rails['db_username'] = 'gitlab'
gitlab_rails['db_password'] = ENV['DB_PASSWORD']
# Redis externo
gitlab_rails['redis_host'] = 'redis'
gitlab_rails['redis_port'] = 6379
# Object storage (S3 ou compatível — MinIO no self-hosted)
gitlab_rails['object_store']['enabled'] = true
gitlab_rails['object_store']['connection'] = {
'provider' => 'AWS',
'region' => 'us-east-1',
'aws_access_key_id' => ENV['MINIO_ACCESS_KEY'],
'aws_secret_access_key' => ENV['MINIO_SECRET_KEY'],
'endpoint' => 'http://minio:9000',
'path_style' => true
}
gitlab_rails['object_store']['objects']['artifacts']['bucket'] = 'gitlab-artifacts'
gitlab_rails['object_store']['objects']['lfs']['bucket'] = 'gitlab-lfs'
gitlab_rails['object_store']['objects']['uploads']['bucket'] = 'gitlab-uploads'
gitlab_rails['object_store']['objects']['packages']['bucket'] = 'gitlab-packages'
# SSL — Let's Encrypt automático (precisa de porta 80 pública)
# letsencrypt['enable'] = true
# Para certificado próprio:
nginx['ssl_certificate'] = '/etc/gitlab/ssl/gitlab.empresa.com.crt'
nginx['ssl_certificate_key'] = '/etc/gitlab/ssl/gitlab.empresa.com.key'
# SMTP para notificações
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = 'smtp.empresa.com'
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = 'gitlab@empresa.com'
gitlab_rails['smtp_password'] = ENV['SMTP_PASSWORD']
gitlab_rails['smtp_domain'] = 'empresa.com'
gitlab_rails['smtp_authentication'] = 'login'
gitlab_rails['smtp_enable_starttls_auto'] = true
# Integração com Active Directory / LDAP (muito comum em enterprise)
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-EOS
main:
label: 'Active Directory'
host: 'ad.empresa.com'
port: 636
uid: 'sAMAccountName'
encryption: 'simple_tls'
bind_dn: 'CN=gitlab-svc,OU=ServiceAccounts,DC=empresa,DC=com'
password: '#{ENV["LDAP_PASSWORD"]}'
base: 'OU=Users,DC=empresa,DC=com'
group_base: 'OU=Groups,DC=empresa,DC=com'
EOS
# Limites de performance
puma['worker_processes'] = 4
sidekiq['concurrency'] = 20
ports:
- '80:80'
- '443:443'
- '22:22' # SSH para Git
volumes:
- gitlab-config:/etc/gitlab
- gitlab-logs:/var/log/gitlab
- gitlab-data:/var/opt/gitlab
- ./ssl:/etc/gitlab/ssl:ro
env_file:
- .env
postgres:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_DB: gitlabhq_production
POSTGRES_USER: gitlab
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
restart: always
volumes:
- redis-data:/data
minio:
image: minio/minio:latest
restart: always
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
volumes:
- minio-data:/data
ports:
- '9001:9001' # Console MinIO (apenas interno)
volumes:
gitlab-config:
gitlab-logs:
gitlab-data:
postgres-data:
redis-data:
minio-data:
Instalação no Kubernetes com Helm (Produção)
Para instalações de produção com alta disponibilidade, o GitLab oferece um chart Helm oficial que distribui os componentes em pods separados, com escalonamento independente e alta disponibilidade:
# values.yml para o GitLab Helm Chart
# helm repo add gitlab https://charts.gitlab.io/
# helm install gitlab gitlab/gitlab -f values.yml -n gitlab --create-namespace
global:
hosts:
domain: empresa.com
# gitlab.empresa.com, registry.empresa.com, minio.empresa.com
ingress:
configureCertmanager: true
class: nginx
# PostgreSQL externo — usar RDS ou Azure Database em produção
psql:
host: rds-gitlab.xxxxxx.sa-east-1.rds.amazonaws.com
port: 5432
database: gitlabhq_production
username: gitlab
password:
secret: gitlab-postgres-secret
key: password
# Redis externo
redis:
host: gitlab-redis.xxxxxx.cache.amazonaws.com
auth:
secret: gitlab-redis-secret
key: password
# Object storage — S3
minio:
enabled: false # Usar S3 real em produção
appConfig:
object_store:
enabled: true
proxy_download: true
connection:
secret: gitlab-s3-secret
key: connection
# LDAP / Active Directory
appConfig:
ldap:
enabled: true
servers:
main:
label: 'Active Directory'
host: 'ad.empresa.com'
port: 636
uid: 'sAMAccountName'
encryption: 'simple_tls'
bind_dn: 'CN=gitlab-svc,OU=ServiceAccounts,DC=empresa,DC=com'
base: 'OU=Users,DC=empresa,DC=com'
# Componentes gerenciados pelo chart
gitlab:
webservice:
replicaCount: 3
resources:
requests:
cpu: "500m"
memory: "1.5Gi"
limits:
cpu: "2"
memory: "3Gi"
sidekiq:
replicaCount: 2
resources:
requests:
cpu: "250m"
memory: "1Gi"
gitaly:
persistence:
size: 500Gi
storageClass: gp3
# Nginx Ingress Controller
nginx-ingress:
enabled: true
# Cert-Manager para SSL automático
certmanager-issuer:
email: ops@empresa.com
Backups e Recuperação
O backup do GitLab self-hosted é uma responsabilidade que não existe no SaaS — e é onde muitas instalações falham. O GitLab fornece um rake task oficial para backups que cobre todos os dados da aplicação:
#!/bin/bash
# scripts/backup-gitlab.sh
# Executado diariamente via cron ou pipeline agendado
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BUCKET="s3://empresa-gitlab-backups"
echo "[$TIMESTAMP] Iniciando backup do GitLab..."
# Para instalação Docker:
docker exec gitlab gitlab-backup create \
BACKUP=backup_$TIMESTAMP \
SKIP=registry # Registry tem volume próprio — backup separado
# Para instalação Kubernetes:
# kubectl exec -n gitlab deploy/gitlab-webservice -- \
# gitlab-backup create BACKUP=backup_$TIMESTAMP
echo "Backup criado. Enviando para S3..."
# O GitLab já envia para object storage se configurado
# Mas para garantia extra, copiar também para um bucket de backup:
aws s3 cp \
/var/opt/gitlab/backups/backup_${TIMESTAMP}_gitlab_backup.tar \
$BUCKET/gitlab/ \
--storage-class STANDARD_IA
# Backup das configurações (não incluídas no backup padrão)
# ATENÇÃO: contém chaves secretas — criptografar antes de armazenar
tar -czf /tmp/gitlab-config-$TIMESTAMP.tar.gz /etc/gitlab/
aws kms encrypt \
--key-id alias/gitlab-backup-key \
--plaintext fileb:///tmp/gitlab-config-$TIMESTAMP.tar.gz \
--output text \
--query CiphertextBlob | \
aws s3 cp - $BUCKET/config/gitlab-config-$TIMESTAMP.tar.gz.enc
# Limpar arquivos temporários
rm -f /tmp/gitlab-config-$TIMESTAMP.tar.gz
echo "[$TIMESTAMP] Backup concluído."
# Verificar que backups antigos estão sendo removidos (retenção 30 dias)
aws s3 ls $BUCKET/gitlab/ | \
awk '{print $4}' | \
sort | \
head -n -30 | \
xargs -I{} aws s3 rm $BUCKET/gitlab/{}
Atualizações: O Maior Desafio do Self-Hosted
Manter um GitLab self-hosted atualizado é o aspecto operacional mais trabalhoso. O GitLab lança uma nova versão minor todo mês (no dia 22) e patches de segurança conforme necessário. Saltar versões sem seguir o caminho de upgrade pode corromper o banco de dados.
A regra fundamental é: nunca pular mais de uma versão minor por vez, e sempre passar pelas versões marcadas como "upgrade stops" pelo GitLab. O path de upgrade de 16.0 para 17.2, por exemplo, precisa passar por versões intermediárias específicas.
Para instalações Docker, o processo de atualização é:
# Verificar a versão atual
docker exec gitlab gitlab-rake gitlab:env:info | grep "GitLab version"
# Verificar o caminho de upgrade recomendado em:
# https://gitlab-com.gitlab.io/support/toolbox/upgrade-path/
# Fazer backup antes de qualquer upgrade
docker exec gitlab gitlab-backup create
# Atualizar a imagem (substituir pela versão específica do upgrade path)
docker pull gitlab/gitlab-ee:17.1.0-ee.0
# Atualizar o docker-compose.yml e recriar o container
docker compose up -d gitlab
# Aguardar o GitLab reconfigure e as migrações de banco
docker logs -f gitlab | grep -E "gitlab Reconfigured|Running reconfigure"
O Que Vem a Seguir
Este artigo cobriu a instalação e operação do GitLab self-hosted — do Docker Compose para ambientes menores até o Helm Chart para produção em Kubernetes, passando por backups e estratégia de atualizações. O próximo artigo sai do ecossistema GitLab e cobre o Bitbucket, a plataforma Git do ecossistema Atlassian, relevante para organizações que já usam Jira e Confluence como ferramentas centrais de gestão.
Referências para Aprofundamento
— GitLab Helm Chart — Documentação Oficial: https://docs.gitlab.com/charts/ — GitLab Backup e Restore: https://docs.gitlab.com/ee/administration/backup_restore/ — GitLab Upgrade Path Tool: https://gitlab-com.gitlab.io/support/toolbox/upgrade-path/ — GitLab Reference Architectures: https://docs.gitlab.com/ee/administration/reference_architectures/ — GitLab LDAP Integration: https://docs.gitlab.com/ee/administration/auth/ldap/
E7 — Bitbucket e o Ecossistema Atlassian
Extensão B · Plataformas Git Alternativas · Artigo E7 de E8 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Quando o Jira Já Está no Centro
Há uma situação muito específica que justifica o uso do Bitbucket: a organização já tem o Jira como sistema central de rastreamento de trabalho, e a integração nativa entre repositório, pipeline e tickets é um requisito, não um diferencial. Nesse contexto, o Bitbucket não compete com o GitHub ou o GitLab pelo melhor repositório Git — ele compete sendo o repositório Git que melhor conversa com o restante do ecossistema Atlassian.
O Jira, o Confluence e o Bitbucket são os três produtos centrais do ecossistema Atlassian. A integração entre eles é profunda: um commit com a mensagem [PROJ-123] Fix null pointer exception in checkout automaticamente referencia e atualiza o ticket Jira PROJ-123. Um branch criado diretamente do Jira cria o branch no Bitbucket com o nome correto. O Confluence documenta os projetos e os links para repositórios e pull requests aparecem automaticamente nos espaços relevantes.
Para quem trabalha em uma organização onde o Jira é a fonte de verdade para o trabalho, ignorar essa integração é perder produtividade real.
Bitbucket Pipelines: CI/CD Nativo
O Bitbucket Pipelines é o sistema de CI/CD integrado ao Bitbucket Cloud — equivalente ao GitHub Actions e ao GitLab CI/CD. O arquivo de configuração é o bitbucket-pipelines.yml na raiz do repositório.
A sintaxe é mais simples do que o GitHub Actions ou o GitLab CI/CD, o que é tanto um ponto positivo (curva de aprendizado menor) quanto uma limitação (menos flexibilidade em cenários complexos):
# bitbucket-pipelines.yml
image: node:20-alpine # Imagem padrão para todos os steps
definitions:
# Serviços auxiliares (equivalente ao services: do GitLab)
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
redis:
image: redis:7-alpine
# Steps reutilizáveis (equivalente aos templates do GitLab)
steps:
- step: &instalar-dependencias
name: Instalar dependências
caches:
- node # Cache nativo do Bitbucket para node_modules
script:
- npm ci
- step: &testes
name: Testes unitários
caches:
- node
services:
- postgres
- redis
script:
- npm ci
- npm test -- --coverage
artifacts:
- coverage/**
pipelines:
# Pipeline para pull requests
pull-requests:
'**': # Qualquer branch com PR aberto
- step: *instalar-dependencias
- step: *testes
- step:
name: Lint e verificações de qualidade
caches:
- node
script:
- npm ci
- npm run lint
- npm run type-check
# Pipeline para branches específicas
branches:
main:
- step: *instalar-dependencias
- step: *testes
- step:
name: Build da imagem Docker
services:
- docker
script:
# Bitbucket tem variáveis automáticas similares ao GitLab
# $BITBUCKET_COMMIT, $BITBUCKET_BRANCH, $BITBUCKET_REPO_SLUG
- IMAGE_TAG=$BITBUCKET_DOCKER_REGISTRY/$BITBUCKET_REPO_SLUG:$BITBUCKET_COMMIT
- docker build -t $IMAGE_TAG .
- echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
- docker push $IMAGE_TAG
- step:
name: Deploy em Staging
deployment: staging # Liga ao Deployment Environment do Bitbucket
script:
- pipe: atlassian/aws-eks-kubectl-run:2.2.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
CLUSTER_NAME: 'meu-cluster-eks'
KUBECTL_COMMAND: >-
set image deployment/minha-app
app=$BITBUCKET_DOCKER_REGISTRY/$BITBUCKET_REPO_SLUG:$BITBUCKET_COMMIT
-n staging
- step:
name: Deploy em Produção
deployment: production
trigger: manual # Equivalente ao when: manual do GitLab
script:
- pipe: atlassian/aws-eks-kubectl-run:2.2.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
CLUSTER_NAME: 'meu-cluster-eks'
KUBECTL_COMMAND: >-
set image deployment/minha-app
app=$BITBUCKET_DOCKER_REGISTRY/$BITBUCKET_REPO_SLUG:$BITBUCKET_COMMIT
-n producao
# Pipelines agendados (cron)
custom:
testes-nocturnos:
- variables:
- name: AMBIENTE
default: staging
- step:
name: Testes de integração completos
caches:
- node
script:
- npm ci
- npm run test:integration -- --env=$AMBIENTE
Pipes: Ações Prontas do Bitbucket
Uma característica diferenciada do Bitbucket Pipelines são os Pipes — blocos de funcionalidade empacotados que simplificam integrações com serviços externos. É o equivalente das Actions do GitHub Marketplace, mas com foco no ecossistema Atlassian e parceiros cloud.
A Atlassian mantém pipes oficiais para AWS (deploy no ECS, EKS, S3, Lambda), Azure, GCP, Kubernetes e ferramentas de qualidade como Snyk e Sonarqube. Usar um pipe é mais simples do que escrever os comandos equivalentes:
# Usando o pipe da AWS para deploy no S3 (site estático)
- step:
name: Deploy no S3
script:
- npm run build
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
S3_BUCKET: 'minha-app-frontend'
LOCAL_PATH: 'dist'
DELETE_FLAG: 'true' # Remover arquivos deletados do bucket
CACHE_CONTROL: 'max-age=31536000'
# Pipe para invalidar CloudFront após o deploy
- step:
name: Invalidar cache do CloudFront
script:
- pipe: atlassian/aws-cloudfront-invalidate:0.6.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
DISTRIBUTION_ID: $CLOUDFRONT_DISTRIBUTION_ID
A Integração Jira-Bitbucket na Prática
A integração Jira-Bitbucket é automática quando os dois produtos pertencem à mesma organização no Atlassian Cloud. Não há configuração manual de webhooks ou tokens — a Atlassian gerencia a conexão internamente.
O que acontece na prática: quando um desenvolvedor cria um branch com o nome contendo o código do ticket Jira (por exemplo, feature/PROJ-456-autenticacao-oauth), o Jira automaticamente aparece no ticket PROJ-456 mostrando que há um branch ativo. Quando um commit é feito com PROJ-456 na mensagem, o Jira registra o commit no ticket. Quando o pull request é aberto, o ticket mostra o PR. Quando o PR é mergeado e o pipeline faz o deploy, o Jira pode ser configurado para transicionar automaticamente o ticket para "Em Produção".
Essa rastreabilidade bidirecional — do ticket ao código, do código ao deploy — sem configuração adicional é o principal argumento para o Bitbucket em organizações Jira-cêntricas.
Branch Permissions e Code Insights
O Bitbucket Cloud suporta Branch Permissions equivalentes às Branch Protection Rules do GitHub: exigir pull requests, número mínimo de aprovadores, impedir push direto na main, e exigir que o pipeline passe antes do merge.
O Code Insights é um recurso que permite que ferramentas externas de análise publiquem resultados diretamente no pull request — cobertura de testes, findings de segurança, análise estática. Sonarqube, Snyk e outras ferramentas têm integração nativa com o Code Insights do Bitbucket.
Bitbucket Data Center: Self-Hosted Enterprise
Assim como o GitLab tem a opção self-hosted, o Bitbucket tem o Bitbucket Data Center — a versão instalável em infraestrutura própria, com suporte a alta disponibilidade e clustering. É a escolha para organizações que precisam do ecossistema Atlassian mas não podem usar o cloud por razões regulatórias.
O Bitbucket Data Center pode ser instalado em servidores Linux próprios ou no Kubernetes via Helm Chart oficial da Atlassian. A configuração requer PostgreSQL externo, sistema de arquivos compartilhado (NFS ou equivalente para repositórios Git), Elasticsearch para busca e, opcionalmente, o Bamboo (CI/CD self-hosted da Atlassian) como alternativa ao Bitbucket Pipelines em ambientes on-premises.
Quando Não Usar o Bitbucket
O Bitbucket faz sentido em um contexto bem definido: organização que já usa Jira como ferramenta central e valoriza a integração nativa. Fora desse contexto, tanto o GitHub quanto o GitLab oferecem mais funcionalidades, comunidade maior e ecossistema de integrações mais rico.
O Bitbucket Pipelines, em particular, é menos poderoso do que o GitHub Actions (em termos de ecossistema de actions) e do que o GitLab CI/CD (em termos de funcionalidades enterprise como SAST integrado e templates avançados). Para times que não usam o Jira, a escolha entre GitHub e GitLab raramente passa pelo Bitbucket.
Referências para Aprofundamento
— Bitbucket Pipelines — Documentação Oficial: https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/ — Pipes do Bitbucket: https://bitbucket.org/product/features/pipelines/integrations — Integração Jira e Bitbucket: https://support.atlassian.com/jira-software-cloud/docs/use-jira-and-bitbucket-together/ — Bitbucket Data Center — Instalação: https://confluence.atlassian.com/bitbucketserver/install-bitbucket-data-center-776640391.html
E8 — Gitea: Self-Hosted Leve para Times Menores
Extensão B · Plataformas Git Alternativas · Artigo E8 de E8 · Conclusão do Bloco Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
A Alternativa Minimalista
O GitLab self-hosted é poderoso — e pesado. Uma instalação completa consume vários gigabytes de RAM mesmo em repouso, exige PostgreSQL, Redis, MinIO ou S3, e requer atenção operacional constante com atualizações mensais. Para o problema que resolve — uma plataforma DevOps completa para centenas ou milhares de usuários — esse custo é justificado.
Mas há um conjunto de casos de uso onde esse custo não é justificado: times pequenos (2 a 30 pessoas), projetos internos que não precisam de CI/CD sofisticado, laboratórios de desenvolvimento, ambientes air-gapped com recursos limitados, ou simplesmente a necessidade de um mirror privado de repositórios com autenticação básica.
Para esses casos, o Gitea é a resposta. É um servidor Git self-hosted escrito em Go, distribuído como um único binário, que roda com menos de 100MB de RAM e pode usar SQLite como banco de dados. Toda a interface, API compatível com GitHub e funcionalidades básicas em um arquivo executável.
Instalação: Um Binário, Uma Máquina
A instalação mais simples do Gitea não requer Docker, Kubernetes, banco de dados externo ou qualquer dependência além do próprio binário:
# Instalar o Gitea em um servidor Linux
# Criar usuário dedicado (boa prática de segurança)
sudo adduser \
--system \
--shell /bin/bash \
--gecos 'Git Version Control' \
--group \
--disabled-password \
--home /home/git \
git
# Criar diretórios necessários
sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo chown -R git:git /var/lib/gitea/
sudo chmod -R 750 /var/lib/gitea/
sudo mkdir /etc/gitea
sudo chown root:git /etc/gitea
sudo chmod 770 /etc/gitea
# Baixar o binário (verificar versão atual em gitea.io)
wget -O /usr/local/bin/gitea \
https://dl.gitea.com/gitea/1.22.0/gitea-1.22.0-linux-amd64
chmod +x /usr/local/bin/gitea
# Criar serviço systemd para inicialização automática
sudo tee /etc/systemd/system/gitea.service > /dev/null <<EOF
[Unit]
Description=Gitea (Git with a cup of tea)
After=network.target
Wants=postgresql.service
[Service]
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable gitea
sudo systemctl start gitea
# Acessar http://servidor:3000 para completar a instalação via interface
Instalação com Docker Compose
Para ambientes onde o Docker já está disponível, o Docker Compose oferece mais portabilidade e facilidade de backup:
# docker-compose.yml
version: '3.8'
services:
gitea:
image: gitea/gitea:1.22.0
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=postgres:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=${DB_PASSWORD}
- GITEA__server__DOMAIN=git.empresa.com
- GITEA__server__SSH_DOMAIN=git.empresa.com
- GITEA__server__ROOT_URL=https://git.empresa.com/
- GITEA__server__HTTP_PORT=3000
- GITEA__mailer__ENABLED=true
- GITEA__mailer__SMTP_ADDR=smtp.empresa.com
- GITEA__mailer__SMTP_PORT=587
- GITEA__mailer__FROM=gitea@empresa.com
- GITEA__mailer__USER=gitea@empresa.com
- GITEA__mailer__PASSWD=${SMTP_PASSWORD}
# Desabilitar registro público — apenas admin cria usuários
- GITEA__service__DISABLE_REGISTRATION=true
- GITEA__service__REQUIRE_SIGNIN_VIEW=true
ports:
- "3000:3000"
- "222:22" # SSH em porta alternativa (host 222 → container 22)
volumes:
- gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- postgres
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=gitea
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
gitea-data:
postgres-data:
Gitea Actions: CI/CD Compatível com GitHub Actions
A partir da versão 1.19, o Gitea implementou o Gitea Actions — um sistema de CI/CD que usa a mesma sintaxe do GitHub Actions. Workflows .github/workflows/*.yml do GitHub podem ser executados no Gitea com pouca ou nenhuma modificação, usando o act_runner (o agente de execução).
Essa compatibilidade é um diferencial significativo: times que migram do GitHub para Gitea por razões de custo ou soberania de dados não precisam reescrever seus workflows. A grande maioria das actions do GitHub Marketplace também funciona no Gitea, desde que não dependam de serviços específicos do GitHub.
# .github/workflows/ci.yml — funciona tanto no GitHub quanto no Gitea
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
testes:
runs-on: ubuntu-latest # O act_runner provisiona o ambiente
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Instalar dependências
run: npm ci
- name: Testes
run: npm test
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
- name: Build
run: npm run build
Para habilitar o Gitea Actions, é necessário instalar o act_runner — o agente que executa os workflows:
# Instalar o act_runner na mesma máquina ou em outra dedicada
wget -O act_runner \
https://dl.gitea.com/act_runner/0.2.10/act_runner-0.2.10-linux-amd64
chmod +x act_runner
# Registrar o runner no Gitea
# O token de registro está em: Admin → Site Administration → Runners
./act_runner register \
--instance https://git.empresa.com \
--token TOKEN-DE-REGISTRO \
--name "runner-principal" \
--labels "ubuntu-latest:docker://node:20" \
--no-interactive
# Iniciar o runner como serviço systemd
sudo tee /etc/systemd/system/act_runner.service > /dev/null <<EOF
[Unit]
Description=Gitea Act Runner
After=network.target
[Service]
ExecStart=/usr/local/bin/act_runner daemon
WorkingDirectory=/var/lib/act_runner
Restart=always
User=git
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now act_runner
Mirroring: Gitea como Espelho Privado
Um caso de uso valioso do Gitea é como espelho privado de repositórios públicos. Times que dependem de repositórios open-source externos podem criar mirrors internos para garantir disponibilidade mesmo se o GitHub estiver fora do ar, ou para conformidade com políticas que proíbem downloads diretos da internet durante builds:
# Via API REST do Gitea (compatível com a API do GitHub)
curl -X POST https://git.empresa.com/api/v1/repos/migrate \
-H "Content-Type: application/json" \
-H "Authorization: token $GITEA_TOKEN" \
-d '{
"clone_addr": "https://github.com/expressjs/express",
"repo_name": "express",
"mirror": true,
"mirror_interval": "8h",
"private": true,
"description": "Mirror do Express.js — sincronizado a cada 8h"
}'
O Gitea sincroniza automaticamente o mirror no intervalo configurado. Builds internos podem referenciar git.empresa.com/mirrors/express em vez de github.com/expressjs/express.
Limitações do Gitea
A leveza do Gitea tem um custo: funcionalidades enterprise que o GitLab oferece nativamente precisam ser compostas com outras ferramentas ou simplesmente não estão disponíveis. Não há SAST integrado, não há Container Registry nativo (embora haja suporte experimental), não há gestão avançada de projetos, não há pages para documentação estática.
Para times pequenos com necessidades simples — repositório Git privado, CI/CD básico com Gitea Actions, mirroring e controle de acesso por organização — o Gitea resolve o problema com uma fração da complexidade operacional do GitLab. Para times que crescem e precisam de mais, a migração de Gitea para GitLab é bem definida: ambos suportam a API REST do GitLab em graus diferentes, e o GitLab tem um importer nativo para Gitea.
Conclusão do Bloco: Plataformas Git Alternativas
Os quatro artigos desta extensão cobriram o panorama completo das principais alternativas ao GitHub para hospedagem de código e CI/CD. O GitLab CI/CD (E5) como plataforma SaaS com funcionalidades enterprise integradas de segurança. O GitLab self-hosted (E6) para organizações que precisam de soberania total sobre código e pipelines. O Bitbucket (E7) para o contexto específico do ecossistema Atlassian com Jira no centro. E o Gitea (E8) para times pequenos que precisam de simplicidade e leveza.
O denominador comum entre todas essas plataformas é que o conhecimento central — Git, pipelines YAML, containers, Kubernetes, observabilidade — é transferível. A sintaxe muda. Os princípios permanecem.
Referências para Aprofundamento
— Gitea — Documentação Oficial: https://docs.gitea.com/ — Gitea Actions: https://docs.gitea.com/usage/actions/overview — act_runner — Gitea Act Runner: https://gitea.com/gitea/act_runner — Gitea API v1: https://gitea.com/api/swagger — Comparação de Plataformas Git Self-Hosted: https://alternativeto.net/software/gitlab/
Bloco E (Plataformas Git Alternativas) concluído — E5 a E8. Pronto para o bloco final E9–E12 (AIOps e IA aplicada a DevOps) quando quiser.
you asked
Continue
claude response
E9 — AIOps na Prática: O Que a IA Já Faz em Operações Hoje
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E9 de E12 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Separando o Real do Hype
Poucas áreas da tecnologia acumularam tanto ruído em tão pouco tempo quanto a aplicação de inteligência artificial a operações de software. Vendors prometem plataformas que "eliminam incidentes antes que aconteçam", "auto-remediam qualquer falha" e "reduzem o trabalho operacional em 90%". A realidade é mais modesta — e, ainda assim, genuinamente útil.
AIOps é um termo cunhado pelo Gartner em 2016 para descrever a aplicação de machine learning e big data a operações de TI. Na prática, em 2025, significa um conjunto de capacidades concretas que já estão em produção em organizações de médio e grande porte: detecção de anomalias em séries temporais de métricas, correlação automática de alertas, análise inteligente de logs, e assistência na resolução de incidentes.
Este artigo é deliberadamente honesto sobre o que funciona, o que está maduro o suficiente para produção, e o que ainda é promessa de roadmap.
O Que Realmente Funciona Hoje
Detecção de Anomalias em Métricas
O problema clássico de alertas baseados em threshold estático é bem conhecido: um threshold fixo de "latência > 500ms = alerta" gera falsos positivos às sextas-feiras à noite (quando o tráfego cai e latências ocasionalmente sobem) e falsos negativos em degradações graduais que nunca cruzam o threshold mas representam um problema real.
Detecção de anomalias baseada em machine learning resolve esse problema modelando o comportamento normal de uma métrica ao longo do tempo — incluindo padrões sazonais (hora do dia, dia da semana) — e alertando quando o desvio do comportamento esperado é estatisticamente significativo.
O AWS CloudWatch Anomaly Detection, o Azure Monitor com detecção de anomalias, e o Prometheus com o operador predict_linear são implementações práticas dessa ideia. O Datadog e o Dynatrace oferecem versões mais sofisticadas com modelos de machine learning específicos para cada métrica.
# Exemplo: Detecção de anomalias com Prophet (biblioteca da Meta)
# Útil para construir alertas customizados em métricas do Prometheus
from prophet import Prophet
import pandas as pd
import requests
from datetime import datetime, timedelta
def consultar_prometheus(query: str, horas: int = 168) -> pd.DataFrame:
"""Consulta dados históricos do Prometheus para treinar o modelo."""
agora = datetime.now()
inicio = agora - timedelta(hours=horas)
resposta = requests.get(
"http://prometheus:9090/api/v1/query_range",
params={
"query": query,
"start": inicio.timestamp(),
"end": agora.timestamp(),
"step": "5m",
}
)
dados = resposta.json()["data"]["result"][0]["values"]
df = pd.DataFrame(dados, columns=["ds", "y"])
df["ds"] = pd.to_datetime(df["ds"], unit="s")
df["y"] = df["y"].astype(float)
return df
def detectar_anomalia(metrica: str, valor_atual: float) -> dict:
"""
Treina um modelo Prophet com dados históricos e verifica se
o valor atual é uma anomalia.
"""
# Obter 7 dias de histórico para treinar o modelo
historico = consultar_prometheus(metrica, horas=168)
# Treinar o modelo — Prophet detecta sazonalidade diária e semanal
modelo = Prophet(
interval_width=0.95, # Intervalo de confiança de 95%
daily_seasonality=True, # Padrão hora do dia
weekly_seasonality=True, # Padrão dia da semana
changepoint_prior_scale=0.05 # Sensibilidade a mudanças de tendência
)
modelo.fit(historico)
# Prever o valor esperado para agora
futuro = modelo.make_future_dataframe(periods=1, freq="5min")
previsao = modelo.predict(futuro)
# Último ponto da previsão é "agora"
ultimo = previsao.iloc[-1]
limite_inferior = ultimo["yhat_lower"]
limite_superior = ultimo["yhat_upper"]
esperado = ultimo["yhat"]
anomalia = valor_atual < limite_inferior or valor_atual > limite_superior
return {
"anomalia": anomalia,
"valor_atual": valor_atual,
"esperado": esperado,
"intervalo": [limite_inferior, limite_superior],
"desvio_sigma": abs(valor_atual - esperado) / (
(limite_superior - limite_inferior) / 3.92
)
}
# Uso em um script de monitoramento
if __name__ == "__main__":
# Verificar se a taxa de pedidos está anômala
resultado = detectar_anomalia(
metrica='sum(rate(loja_pedidos_criados_total[5m])) * 60',
valor_atual=8.5 # pedidos por minuto agora
)
if resultado["anomalia"]:
print(f"ANOMALIA DETECTADA!")
print(f"Valor atual: {resultado['valor_atual']:.1f}")
print(f"Esperado: {resultado['esperado']:.1f}")
print(f"Intervalo normal: {resultado['intervalo'][0]:.1f} - {resultado['intervalo'][1]:.1f}")
print(f"Desvio: {resultado['desvio_sigma']:.1f}σ")
Correlação Automática de Alertas
Em sistemas complexos, uma única falha pode gerar dezenas de alertas. Um banco de dados sobrecarregado dispara alerta de latência no serviço A, no serviço B, no serviço C, e em cada endpoint que depende de qualquer um deles. O engenheiro de plantão recebe 40 notificações que são, na verdade, um único problema.
A correlação de alertas — agrupar alertas relacionados em um único incidente com uma causa raiz provável — é uma das aplicações mais maduras de ML em operações. Ferramentas como PagerDuty AIOps, Moogsoft e Dynatrace Davis fazem isso usando grafos de dependência combinados com análise temporal: alertas que chegam juntos e em serviços relacionados provavelmente têm a mesma causa.
O Alertmanager do Prometheus oferece uma versão mais simples dessa ideia com o group_by e group_wait — agrupa alertas com labels similares antes de notificar. Não é ML, mas resolve 80% do problema de alert storm sem adicionar complexidade:
# alertmanager.yml — agrupamento inteligente sem ML
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s # Aguardar 30s para agrupar alertas relacionados
group_interval: 5m # Reagrupar a cada 5 minutos
repeat_interval: 4h # Não repetir o alerta por 4 horas
routes:
# Alertas de banco de dados — agrupar por instância
- matchers:
- alertname =~ "DB.*"
group_by: ['db_instance']
group_wait: 10s
# Alertas de Kubernetes — agrupar por namespace
- matchers:
- alertname =~ "Kube.*"
group_by: ['namespace', 'alertname']
group_wait: 1m
Análise Inteligente de Logs com IA
A análise de logs é uma das tarefas mais tediosas em operações: vasculhar centenas de megabytes de logs estruturados em busca do evento relevante após um incidente. Duas abordagens de IA estão maduras o suficiente para uso em produção.
Clustering de Logs para Detecção de Padrões Novos
O clustering agrupa mensagens de log por similaridade semântica e alerta quando um novo padrão aparece — sem precisar definir regras manualmente. Isso é especialmente útil para detectar erros que nunca ocorreram antes:
# log_clustering.py
# Detecta padrões novos em logs usando clustering semântico
import re
import hashlib
from collections import defaultdict, Counter
from dataclasses import dataclass
from typing import Optional
import json
@dataclass
class PadraoLog:
template: str # Padrão com variáveis removidas
contagem: int
primeiro_visto: str
ultimo_visto: str
exemplos: list[str]
hash: str
def normalizar_mensagem(mensagem: str) -> str:
"""
Remove valores variáveis da mensagem para extrair o padrão.
Ex: "User 12345 logged in from 192.168.1.1"
→ "User <NUM> logged in from <IP>"
"""
# Remover UUIDs
resultado = re.sub(
r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
'<UUID>', mensagem, flags=re.IGNORECASE
)
# Remover IPs
resultado = re.sub(
r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', '<IP>', resultado
)
# Remover números (preservar contexto)
resultado = re.sub(r'\b\d+\b', '<NUM>', resultado)
# Remover timestamps em vários formatos
resultado = re.sub(
r'\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}', '<TIMESTAMP>', resultado
)
# Remover paths de arquivo
resultado = re.sub(r'(/[\w.-]+)+', '<PATH>', resultado)
# Normalizar espaços
resultado = re.sub(r'\s+', ' ', resultado).strip()
return resultado
class AnalisadorLogs:
def __init__(self, janela_baseline_horas: int = 24):
self.padroes_conhecidos: dict[str, PadraoLog] = {}
self.padroes_novos: list[PadraoLog] = []
self.janela_baseline = janela_baseline_horas
def processar_linha(self, linha: str, timestamp: str) -> Optional[PadraoLog]:
"""
Processa uma linha de log. Retorna o padrão se for novo.
"""
try:
# Parsear log JSON estruturado
log = json.loads(linha)
mensagem = log.get("message", log.get("msg", linha))
except json.JSONDecodeError:
mensagem = linha
template = normalizar_mensagem(mensagem)
hash_template = hashlib.md5(template.encode()).hexdigest()[:8]
if hash_template not in self.padroes_conhecidos:
# Novo padrão — registrar e alertar
padrao = PadraoLog(
template=template,
contagem=1,
primeiro_visto=timestamp,
ultimo_visto=timestamp,
exemplos=[mensagem[:200]],
hash=hash_template
)
self.padroes_conhecidos[hash_template] = padrao
self.padroes_novos.append(padrao)
return padrao
else:
# Padrão conhecido — apenas incrementar contagem
self.padroes_conhecidos[hash_template].contagem += 1
self.padroes_conhecidos[hash_template].ultimo_visto = timestamp
return None
def relatorio_padroes_novos(self) -> str:
if not self.padroes_novos:
return "Nenhum padrão novo detectado."
linhas = [f"=== {len(self.padroes_novos)} Padrão(ões) Novo(s) Detectado(s) ===\n"]
for p in self.padroes_novos[:10]: # Mostrar até 10
linhas.append(f"Hash: {p.hash}")
linhas.append(f"Padrão: {p.template[:120]}")
linhas.append(f"Exemplo: {p.exemplos[0][:120]}")
linhas.append(f"Primeiro visto: {p.primeiro_visto}")
linhas.append("")
return "\n".join(linhas)
Resumo de Incidentes com LLM
A aplicação de Large Language Models à análise de logs pós-incidente é uma das mais promissoras e já está em produção em várias ferramentas. O fluxo é simples: coletar logs relevantes do período do incidente, enviar para um LLM com um prompt estruturado, receber um resumo em linguagem natural com causa raiz provável e timeline.
# incident_analyzer.py
# Usa Claude via API Anthropic para analisar logs de incidente
import anthropic
import json
from datetime import datetime
def analisar_incidente(
logs: list[dict],
metricas: dict,
inicio_incidente: datetime,
fim_incidente: datetime
) -> str:
"""
Envia logs e métricas do período do incidente para análise via LLM.
Retorna um resumo estruturado com causa raiz provável e timeline.
"""
cliente = anthropic.Anthropic()
# Preparar contexto do incidente
duracao = (fim_incidente - inicio_incidente).seconds // 60
# Filtrar logs do período do incidente (máximo 200 linhas para caber no contexto)
logs_relevantes = [
log for log in logs
if log.get("level") in ("error", "fatal", "warn")
][:200]
prompt = f"""Você é um especialista em SRE analisando um incidente de produção.
PERÍODO DO INCIDENTE:
- Início: {inicio_incidente.isoformat()}
- Fim: {fim_incidente.isoformat()}
- Duração: {duracao} minutos
MÉTRICAS DURANTE O INCIDENTE:
{json.dumps(metricas, indent=2, ensure_ascii=False)}
LOGS DO PERÍODO (erros e avisos):
{json.dumps(logs_relevantes, indent=2, ensure_ascii=False)}
Por favor, forneça:
1. RESUMO EXECUTIVO (2-3 frases): O que aconteceu e qual foi o impacto.
2. CAUSA RAIZ PROVÁVEL: O que mais provavelmente causou o incidente, baseado nos logs e métricas.
3. TIMELINE DOS EVENTOS: Ordem cronológica dos eventos relevantes.
4. SERVIÇOS AFETADOS: Quais serviços foram impactados e como.
5. AÇÕES IMEDIATAS QUE RESOLVERAM: O que fez o sistema se recuperar.
6. RECOMENDAÇÕES PREVENTIVAS: 3 ações concretas para evitar recorrência.
Seja específico e técnico. Cite as mensagens de log relevantes quando pertinente."""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
return resposta.content[0].text
# Integração com Grafana via webhook
def handler_alerta_grafana(payload: dict) -> dict:
"""
Recebe um alerta do Grafana, coleta contexto e gera análise.
Pode ser usado como endpoint de uma Cloud Function/Lambda.
"""
alerta = payload.get("alerts", [{}])[0]
labels = alerta.get("labels", {})
servico = labels.get("service", "desconhecido")
# Em produção: coletar logs reais do Loki/CloudWatch/Log Analytics
logs_exemplo = [
{"timestamp": "2025-10-15T14:23:01Z", "level": "error",
"service": servico, "message": "Connection pool exhausted: timeout after 30s"},
{"timestamp": "2025-10-15T14:23:03Z", "level": "error",
"service": servico, "message": "Failed to acquire DB connection: pool size 20/20"},
{"timestamp": "2025-10-15T14:23:15Z", "level": "warn",
"service": "api-gateway", "message": f"Upstream {servico} returning 503"},
]
metricas_exemplo = {
"db_connection_pool_usage": "100%",
"taxa_erro_http": "34%",
"latencia_p99": "8200ms",
"pods_running": 3,
"pods_desired": 3
}
analise = analisar_incidente(
logs=logs_exemplo,
metricas=metricas_exemplo,
inicio_incidente=datetime.fromisoformat("2025-10-15T14:22:00"),
fim_incidente=datetime.fromisoformat("2025-10-15T14:38:00")
)
return {
"servico": servico,
"analise": analise,
"gerado_em": datetime.now().isoformat()
}
O Que Ainda É Hype
É importante ser honesto sobre o que ainda não funciona de maneira confiável em produção.
Auto-remediação autônoma — a ideia de que o sistema detecta um problema e o corrige sozinho sem intervenção humana — existe em casos muito específicos e bem definidos: reiniciar um pod em CrashLoopBackOff, escalar horizontalmente quando a fila está crescendo, rotacionar uma credencial expirada. Para esses casos específicos, a automação já existia antes do "AIOps" como runbooks automatizados. Para problemas complexos e não-antecipados, a auto-remediação autônoma ainda produz mais riscos do que benefícios.
Predição de falhas com antecedência significativa (horas ou dias) continua sendo um problema em aberto para a maioria dos sistemas de software. Infraestrutura de hardware (discos, memória ECC errors) tem sinais preditivos confiáveis. Software de aplicação distribuído é muito mais caótico e menos previsível.
Root cause analysis totalmente automática funciona bem em sistemas simples com topologias bem mapeadas. Em sistemas com centenas de microsserviços e dependências complexas, a IA pode sugerir causas prováveis mas raramente determina a causa raiz com confiança suficiente para agir sem supervisão humana.
Ferramentas Prontas para Começar
Para quem quer implementar AIOps sem construir tudo do zero, as opções mais maduras são as seguintes.
O Grafana ML (parte do Grafana Cloud) oferece detecção de anomalias diretamente nas queries do Grafana — sem código, apenas configuração. Útil para times que já usam Grafana.
O AWS DevOps Guru analisa métricas e logs da AWS e identifica comportamentos anômalos em aplicações rodando na AWS. Integrado nativamente com CloudWatch, sem configuração adicional para stacks AWS.
O Azure Monitor Intelligent Alerting usa ML para ajustar thresholds de alerta dinamicamente com base no histórico da métrica.
O Datadog Watchdog monitora automaticamente todas as métricas ingeridas no Datadog e abre "histórias" quando detecta anomalias — sem precisar configurar alertas manualmente para cada métrica.
Referências para Aprofundamento
— Prophet — Forecasting at Scale: https://facebook.github.io/prophet/ — AWS DevOps Guru — Documentação: https://docs.aws.amazon.com/devops-guru/ — Grafana Machine Learning: https://grafana.com/docs/grafana-cloud/alerting-and-irm/machine-learning/ — Drain3 — Log Clustering: https://github.com/logpai/Drain3 — Azure Monitor Anomaly Detection: https://learn.microsoft.com/pt-br/azure/azure-monitor/alerts/alerts-dynamic-thresholds
E10 — Gerando e Revisando Pipelines com IA
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E10 de E12 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
A IA como Par de Programação em DevOps
O uso de assistentes de IA para gerar e revisar código de aplicação está amplamente difundido. O mesmo padrão se aplica com vantagens claras ao código de infraestrutura: arquivos Dockerfile, YAML de pipelines, manifestos Kubernetes e configurações Terraform têm estrutura repetitiva, convenções estabelecidas e erros comuns conhecidos — exatamente o tipo de problema onde assistentes de IA são mais úteis.
Este artigo não é sobre usar IA como uma caixa mágica que produz infraestrutura perfeita sem esforço. É sobre usar IA como um par de trabalho competente: rápido para gerar o rascunço inicial, útil para identificar problemas óbvios, mas que precisa de supervisão humana para decisões de arquitetura, segurança e adequação ao contexto específico.
GitHub Copilot em Arquivos de Infraestrutura
O GitHub Copilot, com o contexto do repositório disponível, é especialmente útil para completar blocos repetitivos em arquivos Terraform e YAML. Ao escrever o começo de um recurso, ele sugere a configuração completa baseada nos padrões já presentes no repositório:
# Digitar isto no Terraform:
resource "aws_security_group" "rds" {
name = "rds-${var.ambiente}"
description = "Security group para RDS PostgreSQL"
vpc_id = var.vpc_id
# O Copilot sugere as regras baseado no contexto do projeto:
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
description = "PostgreSQL da camada de aplicação"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Saída irrestrita"
}
tags = merge(var.tags_comuns, {
Name = "sg-rds-${var.ambiente}"
})
}
O Copilot também é útil para gerar documentação inline — ao comentar # Verifica se o certificado SSL expira em menos de 30 dias acima de um bloco vazio em um pipeline YAML, ele sugere os steps correspondentes.
Engenharia de Prompts para Geração de Pipelines
Usar um LLM via chat (Claude, GPT-4, Gemini) para gerar pipelines completos funciona melhor com prompts estruturados que fornecem contexto suficiente. Prompts vagos produzem resultados genéricos que precisam de retrabalho extenso. Prompts específicos produzem resultados próximos do utilizável:
Um prompt eficaz para gerar um pipeline deve incluir: a plataforma de CI/CD (GitHub Actions, GitLab CI, Azure Pipelines), a linguagem e versão, os comandos de build e teste específicos do projeto, a plataforma de deploy (EKS, AKS, Cloud Run), os requisitos de segurança (scan de imagem, análise estática), e restrições importantes (não usar dependências externas, secrets via Vault, etc.).
Por exemplo, em vez de pedir "crie um pipeline para minha aplicação Node.js", um prompt útil seria: "Crie um workflow GitHub Actions para uma API Node.js 20 que usa npm, roda testes com Jest (cobertura mínima 80%), faz build de imagem Docker com BuildKit e multi-stage, faz push para o ECR da AWS usando OIDC, e faz deploy no EKS via kubectl rollout. O deploy em produção deve exigir aprovação manual. Usar cache de dependências npm e de camadas Docker."
O resultado desse prompt específico é um ponto de partida sólido que cobre 80% do trabalho. Os 20% restantes — ajustar os nomes dos recursos específicos do projeto, adicionar steps de notificação, configurar os branches corretos — são trabalho de customização que o engenheiro precisa fazer com conhecimento do contexto real.
Usando IA para Revisar Infraestrutura como Código
Além de gerar, a IA é útil para revisar código de infraestrutura existente — identificando problemas de segurança, antipadrões e oportunidades de melhoria. Um fluxo de revisão automatizado pode ser integrado ao pipeline de CI:
# infra_reviewer.py
# Revisa Terraform e YAML de CI/CD buscando problemas conhecidos
import anthropic
import sys
from pathlib import Path
PROMPT_REVISAO_TERRAFORM = """Você é um especialista em segurança e infraestrutura como código.
Revise o código Terraform abaixo e identifique:
1. PROBLEMAS DE SEGURANÇA (críticos e altos)
- Recursos expostos publicamente sem necessidade
- Permissões excessivamente permissivas (wildcards no IAM)
- Dados sensíveis hardcoded
- Criptografia ausente onde deveria existir
- Portas desnecessariamente abertas em security groups
2. ANTIPADRÕES DE IaC
- Recursos sem tags obrigatórias
- Ausência de lifecycle rules importantes
- Nomes de recursos sem convenção
- Dependências implícitas que deveriam ser explícitas
3. CUSTOS E PERFORMANCE
- Tipos de instância superprovisioned para o caso de uso
- Recursos Multi-AZ desnecessários em ambientes de dev/staging
- Oportunidades óbvias de usar Spot/Preemptible
Para cada problema, informe:
- Severidade: CRÍTICO | ALTO | MÉDIO | BAIXO | INFORMATIVO
- Linha aproximada no código (se identificável)
- Descrição clara do problema
- Correção recomendada com exemplo de código quando pertinente
Código para revisar:
```hcl
{codigo}
```"""
PROMPT_REVISAO_PIPELINE = """Você é um especialista em CI/CD e DevSecOps.
Revise o pipeline YAML abaixo e identifique:
1. PROBLEMAS DE SEGURANÇA
- Secrets expostos em logs ou variáveis de ambiente não protegidas
- Permissões excessivas (GITHUB_TOKEN com write em tudo)
- Pinning de actions por tag em vez de SHA (risco de supply chain)
- Ausência de verificação de integridade de dependências
2. PROBLEMAS DE CONFIABILIDADE
- Ausência de timeouts em steps que podem travar
- Falta de estratégia de retry para operações flaky
- Dependências entre jobs não declaradas explicitamente
- Ausência de rollback automático em falhas de deploy
3. EFICIÊNCIA
- Cache não configurado para dependências
- Jobs que poderiam ser paralelos mas são sequenciais
- Steps redundantes
Código para revisar:
```yaml
{codigo}
```"""
def revisar_arquivo(caminho: str) -> dict:
cliente = anthropic.Anthropic()
arquivo = Path(caminho)
conteudo = arquivo.read_text()
# Selecionar o prompt correto baseado na extensão
if arquivo.suffix == ".tf" or arquivo.suffix == ".tfvars":
prompt = PROMPT_REVISAO_TERRAFORM.format(codigo=conteudo)
tipo = "Terraform"
elif arquivo.suffix in (".yml", ".yaml"):
prompt = PROMPT_REVISAO_PIPELINE.format(codigo=conteudo)
tipo = "Pipeline YAML"
else:
return {"erro": f"Tipo de arquivo não suportado: {arquivo.suffix}"}
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=3000,
messages=[{"role": "user", "content": prompt}]
)
return {
"arquivo": caminho,
"tipo": tipo,
"revisao": resposta.content[0].text,
"tokens_usados": resposta.usage.input_tokens + resposta.usage.output_tokens
}
def main():
if len(sys.argv) < 2:
print("Uso: python infra_reviewer.py arquivo1.tf arquivo2.yml ...")
sys.exit(1)
total_problemas = 0
for caminho in sys.argv[1:]:
resultado = revisar_arquivo(caminho)
print(f"\n{'='*60}")
print(f"REVISÃO: {resultado['arquivo']} ({resultado['tipo']})")
print('='*60)
print(resultado.get("revisao", resultado.get("erro")))
# Contar problemas críticos e altos para o exit code do CI
revisao = resultado.get("revisao", "")
total_problemas += revisao.count("CRÍTICO") + revisao.count("ALTO")
print(f"\n{'='*60}")
print(f"Total de problemas CRÍTICO/ALTO encontrados: {total_problemas}")
# Retornar exit code diferente de zero se houver críticos/altos
# (fará o pipeline de CI falhar se integrado como step de revisão)
sys.exit(1 if total_problemas > 0 else 0)
if __name__ == "__main__":
main()
Integração no Pipeline de CI
O script de revisão pode ser integrado como step opcional no pipeline de pull requests — não bloqueante para agilidade, mas gerando um comentário no PR com os findings:
# .github/workflows/revisao-infra.yml
name: Revisão de Infraestrutura com IA
on:
pull_request:
paths:
- '**/*.tf'
- '**/*.yml'
- '**/*.yaml'
- '**/Dockerfile'
jobs:
revisar:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Identificar arquivos de infra modificados
id: arquivos
run: |
ARQUIVOS=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \
| grep -E '\.(tf|yml|yaml)$' \
| grep -v node_modules \
| head -20) # Limitar para não exceder contexto
echo "lista=$ARQUIVOS" >> $GITHUB_OUTPUT
echo "Arquivos a revisar: $ARQUIVOS"
- name: Configurar Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Instalar dependências
run: pip install anthropic
- name: Executar revisão com IA
id: revisao
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python infra_reviewer.py ${{ steps.arquivos.outputs.lista }} \
> revisao_output.txt 2>&1 || true
echo "saida<<EOF" >> $GITHUB_OUTPUT
cat revisao_output.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comentar no PR com findings
uses: actions/github-script@v7
with:
script: |
const output = `${{ steps.revisao.outputs.saida }}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🤖 Revisão de Infraestrutura com IA\n\n\`\`\`\n${output}\n\`\`\``
});
Limites da Revisão por IA
A revisão de infraestrutura por IA é útil para encontrar problemas conhecidos e documentados — os erros que aparecem em listas de "top 10 security mistakes" e que qualquer ferramenta de linting estático também encontraria. Ela não substitui a revisão humana especializada para problemas de negócio, conformidade regulatória específica ou decisões de arquitetura que requerem contexto organizacional profundo.
Também é importante notar que modelos de linguagem alucinam — podem identificar "problemas" em código correto ou sugerir "correções" que introduzem novos problemas. Toda sugestão de revisão por IA deve ser validada pelo engenheiro antes de aplicar. Usada como triagem inicial e gerador de checklist, a IA acelera o processo de revisão. Usada como árbitro final, é um risco.
Referências para Aprofundamento
— GitHub Copilot para Infrastructure as Code: https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line — Anthropic API — claude.ai/docs: https://docs.anthropic.com/pt/api/getting-started — Checkov — Static Analysis para IaC: https://www.checkov.io/ — tfsec — Segurança em Terraform: https://aquasecurity.github.io/tfsec/
E11 — Detecção de Anomalias e Análise Inteligente de Logs com IA
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E11 de E12 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Problema de Escala em Observabilidade
Um sistema em produção de médio porte — vinte microsserviços, dois clusters Kubernetes, três bancos de dados — produz facilmente 50 GB de logs por dia e centenas de milhares de pontos de métricas por hora. Nenhum engenheiro lê 50 GB de logs. Nenhuma equipe consegue criar manualmente regras de alerta para cada padrão relevante em centenas de milhares de séries temporais.
A observabilidade sem inteligência é um arquivo morto: os dados estão todos lá, mas só se tornam úteis após um incidente, quando alguém sabe o que procurar. A proposta do AIOps nesta camada é tornar esses dados proativamente úteis — surfaçar o relevante antes que o problema se manifeste para os usuários.
Detecção de Anomalias em Séries Temporais com ADTK
O ADTK (Anomaly Detection Toolkit) é uma biblioteca Python projetada especificamente para séries temporais de monitoramento. Diferente do Prophet (focado em previsão), o ADTK oferece detectores compostos que combinam múltiplas estratégias:
# anomaly_pipeline.py
# Pipeline completo de detecção de anomalias para métricas de produção
import pandas as pd
import numpy as np
from adtk.detector import (
ThresholdAD,
QuantileAD,
InterQuartileRangeAD,
LevelShiftAD,
SeasonalAD,
PersistAD,
)
from adtk.pipeline import Pipeline
from adtk.aggregator import OrAggregator
import requests
from datetime import datetime, timedelta
def buscar_metrica_prometheus(query: str, horas: int = 168) -> pd.Series:
"""Busca dados do Prometheus e retorna como pandas Series."""
fim = datetime.now()
inicio = fim - timedelta(hours=horas)
r = requests.get(
"http://prometheus:9090/api/v1/query_range",
params={
"query": query,
"start": inicio.timestamp(),
"end": fim.timestamp(),
"step": "1m",
}
)
r.raise_for_status()
resultado = r.json()
if not resultado["data"]["result"]:
return pd.Series(dtype=float)
valores = resultado["data"]["result"][0]["values"]
serie = pd.Series(
[float(v[1]) for v in valores],
index=pd.to_datetime([v[0] for v in valores], unit="s", utc=True),
name=query
)
return serie
class PipelineDeteccaoAnomalias:
"""
Pipeline de detecção de anomalias que combina múltiplos detectores.
Cada detector captura um tipo diferente de anomalia.
"""
def __init__(self):
# ThresholdAD: valores absolutos fora de limites
self.detector_threshold = ThresholdAD(high=None, low=0)
# QuantileAD: valores nos extremos da distribuição histórica
self.detector_quantil = QuantileAD(high=0.995, low=0.005)
# LevelShiftAD: mudanças súbitas no nível médio da métrica
# (ex: taxa de erro que sobe de 0.1% para 2% de uma vez)
self.detector_mudanca_nivel = LevelShiftAD(
c=6.0, # Sensibilidade (múltiplo do desvio padrão)
side="both", # Detectar tanto subidas quanto descidas
window=10 # Janela de comparação em pontos
)
# SeasonalAD: anomalias considerando padrão sazonal
# (ex: tráfego muito baixo numa sexta à tarde)
self.detector_sazonal = SeasonalAD(
c=3.0,
side="both"
)
# PersistAD: valores que persistem em nível anômalo por tempo
# (evita alertar sobre spikes momentâneos)
self.detector_persistencia = PersistAD(
c=3.0,
side="both",
window=5 # Persistir por 5 períodos = 5 minutos
)
def analisar(self, serie: pd.Series) -> dict:
"""
Roda todos os detectores na série e retorna análise consolidada.
"""
if len(serie) < 60:
return {"suficiente_historico": False}
anomalias_por_detector = {}
# Executar cada detector
detectores = {
"quantil": self.detector_quantil,
"mudanca_nivel": self.detector_mudanca_nivel,
"sazonal": self.detector_sazonal,
"persistencia": self.detector_persistencia,
}
for nome, detector in detectores.items():
try:
resultado = detector.fit_detect(serie)
if resultado is not None:
anomalias_por_detector[nome] = resultado[resultado == True]
except Exception as e:
anomalias_por_detector[nome] = pd.Series(dtype=bool)
# Consolidar: anomalia confirmada se 2+ detectores concordam
todos_timestamps = set()
for anomalias in anomalias_por_detector.values():
todos_timestamps.update(anomalias.index)
confirmadas = []
for ts in todos_timestamps:
votos = sum(
1 for anomalias in anomalias_por_detector.values()
if ts in anomalias.index and anomalias.get(ts, False)
)
if votos >= 2:
confirmadas.append({
"timestamp": ts.isoformat(),
"valor": float(serie.get(ts, np.nan)),
"detectores": votos,
"confirmada": True
})
# Calcular estatísticas
media = float(serie.mean())
desvio = float(serie.std())
atual = float(serie.iloc[-1])
zscore_atual = (atual - media) / desvio if desvio > 0 else 0
return {
"suficiente_historico": True,
"anomalias_recentes": [
a for a in confirmadas
if pd.Timestamp(a["timestamp"]) > serie.index[-1] - timedelta(hours=1)
],
"total_anomalias_detectadas": len(confirmadas),
"valor_atual": atual,
"media_historica": media,
"zscore_atual": zscore_atual,
"nivel_alerta": (
"CRITICO" if abs(zscore_atual) > 4 else
"ALTO" if abs(zscore_atual) > 3 else
"MEDIO" if abs(zscore_atual) > 2 else
"NORMAL"
)
}
def monitorar_metricas_criticas():
"""
Monitora as métricas de negócio e infraestrutura mais críticas.
Retorna relatório consolidado para envio ao canal de alertas.
"""
pipeline = PipelineDeteccaoAnomalias()
metricas = {
"pedidos_por_min": 'sum(rate(loja_pedidos_criados_total[5m])) * 60',
"taxa_erro_http": 'sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) * 100',
"latencia_p99": 'histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))',
"uso_conexoes_db": 'pg_stat_activity_count / pg_settings_max_connections * 100',
}
alertas = []
for nome, query in metricas.items():
try:
serie = buscar_metrica_prometheus(query)
if serie.empty:
continue
analise = pipeline.analisar(serie)
if analise.get("nivel_alerta") in ("CRITICO", "ALTO"):
alertas.append({
"metrica": nome,
"nivel": analise["nivel_alerta"],
"valor_atual": analise["valor_atual"],
"media_historica": analise["media_historica"],
"zscore": analise["zscore_atual"],
"anomalias_recentes": len(analise.get("anomalias_recentes", []))
})
except Exception as e:
print(f"Erro ao analisar {nome}: {e}")
return alertas
Análise Semântica de Logs com Embeddings
O clustering por normalização de texto (como no artigo E9) funciona para agrupar mensagens estruturalmente similares. Mas às vezes mensagens com estrutura diferente têm o mesmo significado operacional: "Connection refused to database" e "FATAL: database unreachable" descrevem o mesmo problema. Para agrupar por significado, e não apenas por estrutura, embeddings são mais eficazes:
# log_semantic_search.py
# Busca semântica em logs usando embeddings — encontra logs relevantes
# mesmo quando a terminologia exata é desconhecida
import anthropic
import numpy as np
import json
from pathlib import Path
from typing import Optional
def gerar_embedding(texto: str, cliente: anthropic.Anthropic) -> list[float]:
"""
Gera embedding para um texto usando o modelo de embeddings da Anthropic.
Embeddings capturam significado semântico — textos similares têm
embeddings próximos no espaço vetorial.
"""
# Truncar para limitar tokens
texto_truncado = texto[:500]
resposta = cliente.beta.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1,
messages=[{"role": "user", "content": texto_truncado}],
betas=["embeddings-2025-03-01"],
embedding=True # Modo de embedding — retorna vetor em vez de texto
)
return resposta.embedding
def similaridade_cosseno(a: list[float], b: list[float]) -> float:
"""Calcula similaridade entre dois embeddings. Vai de -1 a 1."""
va, vb = np.array(a), np.array(b)
return float(np.dot(va, vb) / (np.linalg.norm(va) * np.linalg.norm(vb)))
class IndiceSemanticoLogs:
"""
Índice de logs com busca semântica.
Permite encontrar logs relevantes por significado,
não apenas por palavras-chave exatas.
"""
def __init__(self):
self.cliente = anthropic.Anthropic()
self.entradas: list[dict] = []
self.cache_path = Path(".log_index_cache.json")
self._carregar_cache()
def _carregar_cache(self):
if self.cache_path.exists():
with open(self.cache_path) as f:
self.entradas = json.load(f)
def _salvar_cache(self):
with open(self.cache_path, "w") as f:
json.dump(self.entradas, f)
def indexar_logs(self, logs: list[dict], campo_mensagem: str = "message"):
"""
Gera embeddings para uma lista de logs e indexa para busca.
Em produção: processar em batches e usar banco vetorial (pgvector, Qdrant).
"""
novos = 0
for log in logs:
mensagem = log.get(campo_mensagem, "")
if not mensagem or any(e["mensagem"] == mensagem for e in self.entradas):
continue # Pular duplicatas
embedding = gerar_embedding(mensagem, self.cliente)
self.entradas.append({
"mensagem": mensagem,
"timestamp": log.get("timestamp", ""),
"nivel": log.get("level", ""),
"servico": log.get("service", ""),
"embedding": embedding
})
novos += 1
if novos > 0:
self._salvar_cache()
return novos
def buscar(self, consulta: str, top_k: int = 10, nivel_minimo: Optional[str] = None) -> list[dict]:
"""
Busca logs semanticamente similares à consulta.
Exemplo: buscar("database connection problem") encontra logs
mesmo que usem "FATAL: DB unreachable" ou "connection pool exhausted".
"""
if not self.entradas:
return []
embedding_consulta = gerar_embedding(consulta, self.cliente)
resultados = []
for entrada in self.entradas:
if nivel_minimo and entrada.get("nivel", "") < nivel_minimo:
continue
sim = similaridade_cosseno(embedding_consulta, entrada["embedding"])
resultados.append({**entrada, "similaridade": sim})
resultados.sort(key=lambda x: x["similaridade"], reverse=True)
return resultados[:top_k]
def agrupar_por_significado(self, threshold: float = 0.92) -> list[list[dict]]:
"""
Agrupa logs com significado semântico similar.
threshold: similaridade mínima para considerar logs do mesmo grupo.
"""
grupos = []
usados = set()
for i, entrada in enumerate(self.entradas):
if i in usados:
continue
grupo = [entrada]
usados.add(i)
for j, outra in enumerate(self.entradas[i+1:], start=i+1):
if j in usados:
continue
sim = similaridade_cosseno(entrada["embedding"], outra["embedding"])
if sim >= threshold:
grupo.append(outra)
usados.add(j)
if len(grupo) > 1:
grupos.append(grupo)
# Ordenar grupos por tamanho (grupos maiores = padrão mais frequente)
grupos.sort(key=len, reverse=True)
return grupos
Dashboard de Saúde com Análise Narrativa
Um padrão emergente em AIOps é o "health narrative" — em vez de apenas mostrar métricas em gráficos, gerar uma descrição em linguagem natural do estado atual do sistema, destacando o que mudou e o que requer atenção:
# health_narrative.py
# Gera resumo narrativo do estado do sistema para o standup diário
import anthropic
from datetime import datetime
def gerar_narrativa_saude(metricas: dict, anomalias: list, incidentes_24h: list) -> str:
"""
Gera um resumo em linguagem natural do estado do sistema.
Útil para standups, relatórios de turno e dashboards executivos.
"""
cliente = anthropic.Anthropic()
hora_atual = datetime.now().strftime("%H:%M de %d/%m/%Y")
prompt = f"""Você é um SRE descrevendo o estado atual do sistema para o standup da equipe.
Baseado nos dados abaixo, escreva um parágrafo conciso (5-8 frases) descrevendo:
- O estado geral do sistema agora
- O que mudou nas últimas 24 horas em relação ao normal
- Qualquer ponto de atenção que a equipe deve estar ciente
- Se há algo que requer ação imediata
Use linguagem direta e técnica. Seja específico com números quando relevante.
HORA ATUAL: {hora_atual}
MÉTRICAS ATUAIS (vs. média das últimas 24h):
{_formatar_metricas(metricas)}
ANOMALIAS DETECTADAS NAS ÚLTIMAS 2 HORAS:
{_formatar_anomalias(anomalias)}
INCIDENTES NAS ÚLTIMAS 24H:
{_formatar_incidentes(incidentes_24h)}"""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=500,
messages=[{"role": "user", "content": prompt}]
)
return resposta.content[0].text
def _formatar_metricas(metricas: dict) -> str:
linhas = []
for nome, dados in metricas.items():
atual = dados.get("atual", "N/A")
media = dados.get("media_24h", "N/A")
delta = dados.get("delta_pct", 0)
sinal = "↑" if delta > 0 else "↓" if delta < 0 else "→"
linhas.append(f" {nome}: {atual} {sinal} {abs(delta):.1f}% (média: {media})")
return "\n".join(linhas) if linhas else " Nenhuma métrica disponível"
def _formatar_anomalias(anomalias: list) -> str:
if not anomalias:
return " Nenhuma anomalia detectada"
linhas = []
for a in anomalias[:5]:
linhas.append(f" [{a.get('nivel', '?')}] {a.get('metrica', '?')}: {a.get('descricao', '')}")
return "\n".join(linhas)
def _formatar_incidentes(incidentes: list) -> str:
if not incidentes:
return " Nenhum incidente nas últimas 24h"
linhas = []
for inc in incidentes:
status = "RESOLVIDO" if inc.get("resolvido") else "EM ABERTO"
linhas.append(
f" [{status}] {inc.get('titulo', '?')} "
f"({inc.get('duracao_min', '?')}min, {inc.get('servico', '?')})"
)
return "\n".join(linhas)
Referências para Aprofundamento
— ADTK — Anomaly Detection Toolkit: https://adtk.readthedocs.io/ — Drain3 — Log Parsing com Template Mining: https://github.com/logpai/Drain3 — pgvector — Extensão PostgreSQL para Embeddings: https://github.com/pgvector/pgvector — Anthropic API — Embeddings: https://docs.anthropic.com/pt/docs/build-with-claude/embeddings — Amazon DevOps Guru — ML para detecção de anomalias AWS: https://docs.aws.amazon.com/devops-guru/
E12 — Geração de Infraestrutura com IA: Terraform e Kubernetes via Prompts
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E12 de E12 · Conclusão da Série Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Estado da Arte em 2025
A geração de infraestrutura via prompts de linguagem natural avançou significativamente nos últimos dois anos. Em 2023, a ideia de "descrever em português o que você quer e receber Terraform funcional" era mais experimento do que prática. Em 2025, é uma ferramenta usada regularmente por engenheiros — não como substituto do conhecimento de infraestrutura, mas como acelerador.
A distinção é fundamental. Um engenheiro que não entende Terraform, VPCs, IAM e subnets não consegue usar geração por IA de forma produtiva — não tem capacidade de avaliar se o código gerado está correto, seguro e adequado. Um engenheiro que domina esses conceitos consegue usar a IA para ir de zero a rascunho funcional em minutos em vez de horas, e então aplicar seu julgamento para ajustar, melhorar e validar.
Todo o currículo principal desta série existia, entre outras razões, para construir o conhecimento necessário para usar esta ferramenta de forma responsável.
Padrões de Prompts para Infraestrutura
Prompts eficazes para gerar infraestrutura têm quatro componentes: contexto (o que já existe), objetivo (o que precisa ser criado), restrições (o que não pode ser feito) e formato esperado (módulo, recurso avulso, variáveis separadas, etc.).
Um exemplo de prompt completo para gerar um módulo Terraform:
Contexto: Tenho uma VPC existente com ID referenciado via data source,
subnets privadas em 3 AZs, e um cluster EKS já provisionado.
Tenho o módulo terraform-aws-modules/rds/aws disponível.
Objetivo: Criar um módulo Terraform reutilizável para RDS PostgreSQL 16
que siga as melhores práticas de produção.
Restrições:
- Usar manage_master_user_password = true (sem senha no state)
- Multi-AZ obrigatório em produção, opcional em staging via variável
- Performance Insights habilitado com retenção de 7 dias
- Backup com retenção mínima de 7 dias
- Sem acesso público
- Tags obrigatórias: ambiente, projeto, gerenciado_por
- Variável de tipo de instância com default razoável para produção
Formato: Módulo com main.tf, variables.tf, outputs.tf.
Incluir comentários explicando decisões não óbvias.
Esse nível de especificidade produz código que precisa de ajustes mínimos. Prompts vagos ("crie um RDS PostgreSQL") produzem código genérico que pode funcionar mas provavelmente não segue os padrões do projeto.
Ferramenta de Geração com Validação
O fluxo mais produtivo combina geração via LLM com validação automática:
# infra_generator.py
# Gera e valida infraestrutura como código via LLM
import anthropic
import subprocess
import tempfile
import os
import re
from pathlib import Path
from dataclasses import dataclass
@dataclass
class ResultadoGeracao:
codigo: str
valido: bool
erros_validacao: list[str]
avisos_seguranca: list[str]
arquivo_gerado: str
SISTEMA_TERRAFORM = """Você é um especialista em Terraform e AWS com foco em produção.
Ao gerar código Terraform:
1. SEMPRE use versões explícitas nos required_providers
2. SEMPRE adicione tags nos recursos (pelo menos: ambiente, projeto, gerenciado_por="terraform")
3. NUNCA hardcode credenciais, passwords ou secrets — use Secrets Manager ou variables sensíveis
4. SEMPRE use backend remoto (não gere o bloco backend mas mencione que é necessário)
5. Prefira data sources a recursos hardcoded quando o recurso já existe
6. Use locals para evitar repetição de valores
7. Adicione outputs para valores que outros módulos possam precisar
8. Inclua comentários para decisões não óbvias
Retorne APENAS o código Terraform, sem explicações antes ou depois.
O código deve estar pronto para ser salvo em arquivo .tf e executado."""
def gerar_terraform(descricao: str, contexto: str = "") -> str:
"""Gera código Terraform baseado em descrição em linguagem natural."""
cliente = anthropic.Anthropic()
prompt_completo = descricao
if contexto:
prompt_completo = f"Contexto existente:\n{contexto}\n\nObjetivo:\n{descricao}"
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
system=SISTEMA_TERRAFORM,
messages=[{"role": "user", "content": prompt_completo}]
)
codigo = resposta.content[0].text
# Remover blocos de código markdown se presentes
codigo = re.sub(r'^```(?:hcl|terraform)?\n', '', codigo, flags=re.MULTILINE)
codigo = re.sub(r'\n```$', '', codigo, flags=re.MULTILINE)
return codigo.strip()
def validar_terraform(codigo: str) -> tuple[bool, list[str]]:
"""
Valida sintaxe do Terraform usando terraform validate.
Retorna (válido, lista_de_erros).
"""
with tempfile.TemporaryDirectory() as tmpdir:
arquivo = Path(tmpdir) / "main.tf"
arquivo.write_text(codigo)
# Inicializar sem backend para validação local
init = subprocess.run(
["terraform", "init", "-backend=false", "-no-color"],
cwd=tmpdir,
capture_output=True,
text=True
)
if init.returncode != 0:
return False, [f"terraform init falhou: {init.stderr}"]
validate = subprocess.run(
["terraform", "validate", "-no-color", "-json"],
cwd=tmpdir,
capture_output=True,
text=True
)
if validate.returncode == 0:
return True, []
import json
try:
resultado = json.loads(validate.stdout)
erros = [
f"Linha {d.get('range', {}).get('start', {}).get('line', '?')}: {d.get('summary', '')}"
for d in resultado.get("diagnostics", [])
if d.get("severity") == "error"
]
return False, erros
except Exception:
return False, [validate.stdout or validate.stderr]
def verificar_seguranca_basica(codigo: str) -> list[str]:
"""
Verificações de segurança básicas sem dependências externas.
Para produção, usar Checkov ou tfsec.
"""
avisos = []
linhas = codigo.split('\n')
padroes_problematicos = [
(r'0\.0\.0\.0/0.*ingress', "Security group com entrada de qualquer IP (0.0.0.0/0)"),
(r'publicly_accessible\s*=\s*true', "RDS com acesso público habilitado"),
(r'password\s*=\s*"[^"]{3,}"', "Possível senha hardcoded no código"),
(r'secret_key\s*=\s*"[A-Za-z0-9+/]{20,}"', "Possível chave secreta hardcoded"),
(r'encrypted\s*=\s*false', "Recurso com criptografia explicitamente desabilitada"),
(r'skip_final_snapshot\s*=\s*true', "RDS sem snapshot final ao deletar"),
(r'deletion_protection\s*=\s*false', "Recurso sem proteção contra deleção"),
]
for i, linha in enumerate(linhas, 1):
for padrao, descricao in padroes_problematicos:
if re.search(padrao, linha, re.IGNORECASE):
avisos.append(f"Linha {i}: {descricao}")
return avisos
def gerar_e_validar(
descricao: str,
contexto: str = "",
arquivo_saida: str = "gerado.tf",
max_tentativas: int = 3
) -> ResultadoGeracao:
"""
Gera Terraform, valida e tenta corrigir automaticamente se inválido.
"""
codigo = gerar_terraform(descricao, contexto)
for tentativa in range(max_tentativas):
valido, erros = validar_terraform(codigo)
if valido:
break
if tentativa < max_tentativas - 1:
print(f"Tentativa {tentativa + 1}: código inválido. Solicitando correção...")
# Pedir ao LLM que corrija os erros específicos
cliente = anthropic.Anthropic()
correcao = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
system=SISTEMA_TERRAFORM,
messages=[
{"role": "user", "content": descricao},
{"role": "assistant", "content": codigo},
{
"role": "user",
"content": f"O código tem erros de validação. Corrija:\n" +
"\n".join(erros) +
"\n\nRetorne apenas o código corrigido."
}
]
)
codigo = correcao.content[0].text
codigo = re.sub(r'^```(?:hcl|terraform)?\n', '', codigo, flags=re.MULTILINE)
codigo = re.sub(r'\n```$', '', codigo, flags=re.MULTILINE)
codigo = codigo.strip()
avisos = verificar_seguranca_basica(codigo)
Path(arquivo_saida).write_text(codigo)
return ResultadoGeracao(
codigo=codigo,
valido=valido,
erros_validacao=erros if not valido else [],
avisos_seguranca=avisos,
arquivo_gerado=arquivo_saida
)
Geração de Manifestos Kubernetes
O mesmo padrão se aplica a manifestos Kubernetes. A vantagem aqui é que o Kubernetes tem um schema bem definido e ferramentas de validação (kubeval, kubeconform) que podem verificar o YAML gerado antes de aplicar no cluster:
# k8s_generator.py
# Gera e valida manifestos Kubernetes
import anthropic
import subprocess
import tempfile
import re
import yaml
from pathlib import Path
SISTEMA_KUBERNETES = """Você é um especialista em Kubernetes com foco em segurança e produção.
Ao gerar manifestos Kubernetes:
1. SEMPRE defina resource requests e limits
2. SEMPRE configure liveness e readiness probes
3. SEMPRE use securityContext com runAsNonRoot: true e readOnlyRootFilesystem: true
4. SEMPRE configure PodDisruptionBudget para Deployments críticos
5. SEMPRE use topologySpreadConstraints para distribuir pods entre zonas
6. NUNCA use a imagem com tag 'latest' — use SHA ou versão específica
7. Use ConfigMaps para configuração não-sensível e ExternalSecrets/SecretProviderClass para sensível
8. Adicione preStop hook com sleep para graceful shutdown
Retorne APENAS o YAML, sem explicações. Múltiplos recursos separados por ---."""
def gerar_manifesto_k8s(descricao: str, namespace: str = "default") -> str:
"""Gera manifesto Kubernetes baseado em descrição."""
cliente = anthropic.Anthropic()
prompt = f"Namespace: {namespace}\n\n{descricao}"
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
system=SISTEMA_KUBERNETES,
messages=[{"role": "user", "content": prompt}]
)
manifesto = resposta.content[0].text
manifesto = re.sub(r'^```yaml\n', '', manifesto, flags=re.MULTILINE)
manifesto = re.sub(r'\n```$', '', manifesto, flags=re.MULTILINE)
return manifesto.strip()
def validar_manifesto_k8s(manifesto: str) -> tuple[bool, list[str]]:
"""
Valida manifesto Kubernetes com kubeconform.
Requer: pip install kubeconform ou instalação do binário.
"""
with tempfile.NamedTemporaryFile(
mode='w', suffix='.yaml', delete=False
) as f:
f.write(manifesto)
arquivo_tmp = f.name
try:
resultado = subprocess.run(
[
"kubeconform",
"-strict",
"-summary",
"-kubernetes-version", "1.29.0",
arquivo_tmp
],
capture_output=True,
text=True
)
if resultado.returncode == 0:
return True, []
else:
erros = [
linha for linha in resultado.stdout.split('\n')
if 'error' in linha.lower() or 'invalid' in linha.lower()
]
return False, erros or [resultado.stdout]
except FileNotFoundError:
# kubeconform não instalado — validação básica com yaml.safe_load
try:
list(yaml.safe_load_all(manifesto))
return True, ["kubeconform não disponível — apenas sintaxe YAML validada"]
except yaml.YAMLError as e:
return False, [str(e)]
finally:
Path(arquivo_tmp).unlink(missing_ok=True)
def dry_run_kubectl(manifesto: str, namespace: str = "default") -> tuple[bool, str]:
"""
Executa kubectl apply --dry-run=server para validar contra o cluster real.
Requer kubectl configurado e acesso ao cluster.
"""
with tempfile.NamedTemporaryFile(
mode='w', suffix='.yaml', delete=False
) as f:
f.write(manifesto)
arquivo_tmp = f.name
try:
resultado = subprocess.run(
[
"kubectl", "apply",
"--dry-run=server",
"--namespace", namespace,
"-f", arquivo_tmp,
],
capture_output=True,
text=True
)
return resultado.returncode == 0, resultado.stdout + resultado.stderr
finally:
Path(arquivo_tmp).unlink(missing_ok=True)
O Fluxo Completo: Prompt → Validação → Revisão Humana
O fluxo de trabalho mais produtivo combina geração, validação automática e revisão humana supervisionada:
# cli_infra.py
# Interface de linha de comando para geração supervisionada de infraestrutura
import click
from infra_generator import gerar_e_validar
from k8s_generator import gerar_manifesto_k8s, validar_manifesto_k8s
@click.group()
def cli():
"""Gerador de infraestrutura como código assistido por IA."""
pass
@cli.command()
@click.argument('descricao')
@click.option('--contexto', '-c', default='', help='Arquivo com contexto existente')
@click.option('--output', '-o', default='gerado.tf', help='Arquivo de saída')
@click.option('--auto-aplicar', is_flag=True, help='Aplicar sem confirmação humana')
def terraform(descricao: str, contexto: str, output: str, auto_aplicar: bool):
"""Gera código Terraform a partir de descrição em linguagem natural."""
contexto_str = ""
if contexto:
from pathlib import Path
contexto_str = Path(contexto).read_text()
click.echo(f"⏳ Gerando Terraform para: {descricao[:60]}...")
resultado = gerar_e_validar(descricao, contexto_str, output)
if resultado.avisos_seguranca:
click.echo(f"\n⚠️ {len(resultado.avisos_seguranca)} aviso(s) de segurança:")
for aviso in resultado.avisos_seguranca:
click.echo(f" • {aviso}")
if resultado.valido:
click.echo(f"\n✅ Código gerado e validado: {resultado.arquivo_gerado}")
click.echo("\n--- PRÉVIA ---")
click.echo(resultado.codigo[:1000] + ("..." if len(resultado.codigo) > 1000 else ""))
click.echo("--- FIM DA PRÉVIA ---\n")
if not auto_aplicar:
click.echo("⚠️ REVISÃO HUMANA OBRIGATÓRIA antes de aplicar.")
click.echo(" Execute: terraform plan")
click.echo(" Verifique: terraform plan -out=plano.tfplan")
click.echo(" Aplique: terraform apply plano.tfplan")
else:
click.echo("🚀 Iniciando terraform plan (auto-aplicar habilitado)...")
import subprocess
subprocess.run(["terraform", "plan", f"-out={output}.plan"])
else:
click.echo(f"\n❌ Código inválido após {3} tentativas:")
for erro in resultado.erros_validacao:
click.echo(f" • {erro}")
if __name__ == '__main__':
cli()
Conclusão: IA como Ferramenta, Não como Substituto
Ao longo dos quatro artigos desta extensão sobre AIOps e IA aplicada a DevOps, um padrão consistente emergiu: a IA é mais útil onde o domínio é estruturado, onde há convenções estabelecidas e onde os erros podem ser verificados automaticamente.
Detecção de anomalias em métricas funciona porque há décadas de literatura em estatística aplicada a séries temporais. Análise de logs funciona porque os padrões linguísticos em mensagens de erro são estruturados e repetitivos. Geração de Terraform funciona porque a linguagem tem schema, as melhores práticas são documentadas e terraform validate captura erros de sintaxe imediatamente.
O que não funciona sem supervisão humana cuidadosa: decisões de arquitetura que envolvem trade-offs de negócio, configurações de segurança em contextos regulatórios específicos, e qualquer coisa que afete produção sem validação prévia.
O engenheiro que termina esta série tem o conhecimento necessário para usar essas ferramentas produtivamente — e, mais importante, para reconhecer seus limites. A IA acelera o trabalho de quem sabe o que está fazendo. Para quem não sabe, ela acelera a chegada aos problemas.
Referências para Aprofundamento
— Anthropic API — Documentação Oficial: https://docs.anthropic.com/pt/ — Terraform — Documentação Oficial: https://developer.hashicorp.com/terraform/docs — kubeconform — Validação de Manifestos: https://github.com/yannh/kubeconform — Checkov — Static Analysis para IaC: https://www.checkov.io/ — GitHub Copilot para DevOps: https://docs.github.com/en/copilot
Dominando DevOps & Cloud em 1 Ano — Concluída.
52 artigos do currículo principal. 12 artigos de extensão. 64 artigos no total cobrindo do terminal Linux ao AIOps, passando por Docker, Kubernetes, AWS, Azure, Terraform, CI/CD, observabilidade, segurança, FinOps e muito mais.
Prof. Ricardo Matos — Mestre em Programação
claude response
E9 — AIOps na Prática: O Que a IA Já Faz em Operações Hoje
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E9 de E12 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Separando o Real do Hype
Poucas áreas da tecnologia acumulam tanto entusiasmo e tanta confusão simultâneos quanto a aplicação de inteligência artificial a operações de software. Vendors prometem sistemas que "resolvem incidentes automaticamente", "preveem falhas antes que aconteçam" e "eliminam o trabalho manual de operações". Parte disso já existe e funciona. Parte é aspiracional. Parte é marketing reembalado de funcionalidades que existem há anos sob outro nome.
Este artigo é uma avaliação honesta do estado atual: o que a IA já faz em operações com resultado comprovado, onde ainda é promessa, e como um engenheiro pode começar a aplicar essas ferramentas sem se perder em hype.
AIOps é o termo consolidado para a aplicação de machine learning e análise de dados a operações de TI. O conceito foi cunhado pelo Gartner em 2017 e desde então expandiu para cobrir desde correlação de alertas até geração automática de código de infraestrutura. Para este artigo, é útil dividir o território em quatro categorias por maturidade.
Categoria 1: Consolidado e Funcionando
Detecção de Anomalias em Métricas
A detecção de anomalias em séries temporais é o caso de uso mais maduro do AIOps. O problema é bem definido: dado o comportamento histórico de uma métrica (taxa de requisições, latência, uso de CPU), identificar quando o valor atual desvia significativamente do esperado — considerando sazonalidade, tendências e padrões de dia da semana.
O AWS CloudWatch Anomaly Detection e o Azure Monitor com Dynamic Thresholds resolvem exatamente esse problema. Em vez de configurar manualmente um alerta "dispare quando a latência ultrapassar 500ms", é possível configurar "dispare quando a latência estiver fora do intervalo esperado para este horário e dia da semana". O modelo aprende com 14 dias de histórico e ajusta os limites automaticamente.
# boto3 — configurar Anomaly Detector no CloudWatch
import boto3
cloudwatch = boto3.client('cloudwatch', region_name='sa-east-1')
# Criar um anomaly detector para a métrica de latência
cloudwatch.put_anomaly_detector(
Namespace='MinhaApp',
MetricName='Latencia_p99',
Dimensions=[
{'Name': 'Servico', 'Value': 'order-service'},
],
Stat='p99',
Configuration={
# Excluir fins de semana do modelo (comportamento diferente)
'ExcludedTimeRanges': [],
'MetricTimezone': 'America/Sao_Paulo'
}
)
# Criar alerta baseado no anomaly detector
cloudwatch.put_metric_alarm(
AlarmName='order-service-latencia-anomalia',
AlarmDescription='Latência p99 fora do padrão esperado',
# ANOMALY_DETECTION_BAND retorna o intervalo esperado pelo modelo
Metrics=[
{
'Id': 'latencia',
'MetricStat': {
'Metric': {
'Namespace': 'MinhaApp',
'MetricName': 'Latencia_p99',
'Dimensions': [{'Name': 'Servico', 'Value': 'order-service'}]
},
'Period': 300,
'Stat': 'p99'
}
},
{
'Id': 'banda_esperada',
'Expression': 'ANOMALY_DETECTION_BAND(latencia, 2)',
# 2 = desvios padrão acima/abaixo do esperado
}
],
ComparisonOperator': 'GreaterThanUpperThreshold',
ThresholdMetricId': 'banda_esperada',
EvaluationPeriods': 3,
TreatMissingData': 'notBreaching',
AlarmActions': ['arn:aws:sns:sa-east-1:123456789012:alertas-criticos']
)
Correlação de Alertas e Redução de Ruído
Sistemas de observabilidade modernos geram centenas ou milhares de alertas durante um incidente. Um serviço cai, e imediatamente disparam alertas de latência, de taxa de erro, de health check, de filas acumulando, de dependentes que começam a falhar em cascata. Sem correlação, o engenheiro de plantão recebe 50 notificações sobre o mesmo problema raiz.
A correlação de alertas — agrupar alertas relacionados em um único incidente — é a funcionalidade central do PagerDuty, Opsgenie e ferramentas similares. O que o machine learning adiciona é a capacidade de aprender quais alertas tendem a aparecer juntos, qual deles geralmente é a causa raiz, e qual a sequência temporal típica — sem configuração manual de regras de correlação.
O Prometheus AlertManager sozinho não faz isso (tem agrupamento por labels, mas não correlação inteligente). Ferramentas como o AWS DevOps Guru e o Dynatrace Davis aplicam ML para reduzir dezenas de alertas a um único evento de "problema detectado" com causa raiz inferida.
Análise Inteligente de Logs
A busca em logs com expressões regulares e filtros manuais existe há décadas. O que o ML adiciona é a capacidade de classificar logs sem configuração prévia: identificar quais mensagens de log são "normais para este horário", quais são padrões novos que nunca apareceram antes, e quais correlacionam com degradações de performance.
O CloudWatch Log Insights com ML e o Elastic Observability com ML Jobs fazem isso nativamente. O exemplo mais útil na prática é a detecção de padrões novos: em vez de procurar por uma string específica de erro, o sistema alerta quando um tipo de mensagem de log aparece pela primeira vez ou com frequência anormalmente alta.
# Exemplo: usar o AWS DevOps Guru para análise automática de anomalias
# O DevOps Guru analisa CloudWatch métricas e logs automaticamente
# Configuração via Terraform:
resource "aws_devopsguru_resource_collection" "minha_app" {
type = "AWS_CLOUD_FORMATION"
cloud_formation {
# DevOps Guru monitora todos os recursos desta stack
stack_names = [
"minha-app-producao",
"minha-app-infraestrutura"
]
}
}
# O DevOps Guru gera insights automaticamente quando detecta anomalias.
# Não requer configuração de alertas individuais — ele aprende o baseline
# e notifica quando algo está fora do padrão, com causa raiz inferida.
Categoria 2: Emergindo com Resultados Reais
Assistentes de Código para Infraestrutura
O GitHub Copilot, o Cursor e ferramentas similares têm impacto mensurável na escrita de Terraform, scripts Bash, manifestos Kubernetes e pipelines YAML. O benefício não é que a IA escreve código perfeito — é que ela elimina o tempo gasto consultando documentação para lembrar a sintaxe de um recurso que se usa raramente.
Um engenheiro que sabe o que quer fazer mas precisa consultar a documentação do azurerm_kubernetes_cluster para lembrar o nome exato do campo de Workload Identity economiza 5 minutos com o Copilot. Multiplicado por dezenas de interações por dia, o ganho de produtividade é real e já documentado em estudos do GitHub: desenvolvedores completam tarefas com assistência de IA entre 35% e 55% mais rápido em benchmarks controlados.
O próximo artigo (E10) cobre isso em profundidade com exemplos práticos de geração e revisão de pipelines.
Root Cause Analysis Assistida
Ferramentas como o Datadog Watchdog e o Dynatrace Davis vão além da detecção de anomalias: elas tentam identificar a causa raiz automaticamente, correlacionando eventos de infraestrutura, deploys recentes, mudanças de configuração e métricas de aplicação.
O resultado prático não é "a IA resolve o incidente sozinha" — ainda é o engenheiro que toma as decisões e executa as ações. O que a ferramenta faz é apresentar um diagnóstico estruturado: "Anomalia detectada às 14:23. Correlacionada com deploy realizado às 14:20. Serviço order-service mostrando latência 3x acima do normal. Possível causa: nova versão da query de banco de dados em /src/orders/repository.js linha 47."
Isso reduz o tempo de diagnóstico — o MTTD (Mean Time to Detect) e o MTTR (Mean Time to Recover) — que são as métricas que realmente importam em operações.
Categoria 3: Promissora mas Ainda Imatura
Remediação Automática
A ideia de um sistema que detecta um problema e automaticamente o corrige — reinicia o pod com crash, faz rollback do deploy problemático, aumenta a capacidade — existe em implementações limitadas e confiáveis. O Kubernetes já faz isso em nível básico com liveness probes e restarts automáticos. O HPA escala automaticamente baseado em métricas.
O que ainda é imaturo é a remediação autônoma de problemas mais complexos: "a query está lenta porque o índice está fragmentado — reconstruir o índice automaticamente em produção". Esse tipo de ação requer confiança muito alta na decisão da IA, porque uma ação errada pode piorar o problema. A maioria das organizações com cultura de engenharia madura ainda prefere "IA sugere, humano aprova e executa" para ações de remediação não-triviais.
Previsão de Capacidade
Machine learning aplicado a previsão de capacidade — "quantas instâncias vou precisar semana que vem para a campanha de Black Friday?" — funciona razoavelmente bem em problemas com padrões históricos claros. Mas para eventos sem precedente, crescimento acelerado de usuários ou mudanças de comportamento causadas por features novas, a previsão tem limitações inerentes: não é possível prever o que nunca aconteceu antes.
Como Começar: Um Roteiro Prático
Para times que querem começar a aplicar AIOps sem grandes investimentos em ferramentas especializadas, o roteiro mais pragmático segue três passos.
O primeiro é ativar as funcionalidades de ML que já estão nos serviços em uso. O CloudWatch Anomaly Detection, o Azure Monitor com Dynamic Thresholds, o AWS DevOps Guru e o Datadog Watchdog são ativados com poucos cliques ou linhas de Terraform. Não requerem dados de treinamento customizados nem expertise em ML.
O segundo é integrar um assistente de código ao workflow de desenvolvimento. GitHub Copilot ou Cursor para escrita de Terraform, pipelines e scripts. O ROI é imediato e mensurável em horas economizadas.
O terceiro é instrumentar as métricas de negócio que alimentarão os modelos. Detecção de anomalias em métricas técnicas (CPU, latência) é útil, mas detecção de anomalias em métricas de negócio (taxa de conversão, pedidos por minuto) é o que gera valor direto. Isso requer que as métricas de negócio estejam sendo coletadas com consistência — o que foi construído no capstone desta série.
Referências para Aprofundamento
— AWS DevOps Guru — Documentação: https://docs.aws.amazon.com/devops-guru/latest/userguide/what-is-devops-guru.html — CloudWatch Anomaly Detection: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Anomaly_Detection.html — Azure Monitor — Dynamic Thresholds: https://learn.microsoft.com/pt-br/azure/azure-monitor/alerts/alerts-dynamic-thresholds — Datadog Watchdog: https://docs.datadoghq.com/watchdog/ — GitHub Copilot — Pesquisa de Produtividade: https://github.blog/2022-09-07-research-quantifying-github-copilots-impact-on-developer-productivity/
E10 — Gerando e Revisando Pipelines com IA
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E10 de E12 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
A Mudança no Workflow de Engenharia
O GitHub Copilot foi lançado em 2021. O ChatGPT em novembro de 2022. O Cursor em 2023. Em poucos anos, assistentes de código baseados em LLMs passaram de curiosidade a ferramenta padrão no workflow de uma fração crescente de engenheiros de software e infraestrutura.
A questão não é mais "isso vai ser útil?" — já é útil, e os dados de produtividade são consistentes. A questão é como usar essas ferramentas de maneira eficaz, especificamente para as tarefas de DevOps: escrita de pipelines CI/CD, Terraform, manifestos Kubernetes, scripts de operações e revisão de infraestrutura como código.
Este artigo cobre técnicas práticas de prompting para tarefas de infraestrutura, geração de pipelines completos, revisão automatizada de código de infraestrutura, e os limites que o engenheiro precisa conhecer para não confiar cegamente nas sugestões da IA.
Princípios de Prompting para Infraestrutura
A diferença entre um prompt que gera código útil e um que gera código genérico está no contexto fornecido. Para tarefas de infraestrutura, quatro elementos de contexto fazem a maior diferença: a plataforma de destino (AWS/Azure/GCP, versão), a restrição de segurança aplicável, o padrão que já existe no projeto, e o resultado esperado.
Um prompt fraco para geração de Terraform seria: "Crie um bucket S3". O resultado será um recurso mínimo sem nenhuma das configurações de segurança ou compliance que um bucket de produção precisa.
Um prompt eficaz seria:
Crie um recurso Terraform
aws_s3_bucketpara armazenar artifacts de CI/CD. Requisitos: versioning habilitado, server-side encryption com KMS (a chave KMS já existe comodata.aws_kms_key.ci), block public access em todas as configurações, lifecycle rule para mover objetos para Glacier após 90 dias e deletar após 365, e bucket policy que permita acesso apenas da rolearn:aws:iam::ACCOUNT:role/ci-pipeline-role. Provider AWS versão ~> 5.0. Não use o recurso depreciadoaws_s3_bucket_acl.
A diferença no resultado é substancial. O segundo prompt gera código que se aproxima do que iria para produção. O primeiro gera um ponto de partida que precisaria de 80% de retrabalho.
Gerando um Pipeline GitHub Actions do Zero
O caso de uso mais comum de assistência de IA em DevOps é gerar o esqueleto de um pipeline para um projeto novo. O processo mais eficaz não é pedir o pipeline completo em um único prompt — é construir em camadas, revisando cada parte antes de prosseguir.
Primeiro prompt — contexto e estrutura:
Preciso de um workflow GitHub Actions para uma API Node.js 20 usando Express, TypeScript e Jest para testes. O repositório usa npm workspaces com três pacotes:
packages/api,packages/sharedepackages/worker. O workflow deve rodar em push paramaine em pull requests. Crie apenas a estrutura do workflow com os jobs definidos (sem os steps internos ainda): lint, testes unitários, build da imagem Docker e deploy em staging. Os jobs de testes e lint devem rodar em paralelo. O build e deploy apenas na branch main.
A resposta vai gerar a estrutura com needs: e if: corretos. Então se aprofunda em cada job:
Agora detalhe o job de testes unitários. Ele precisa de serviços PostgreSQL 16 e Redis 7. Os testes rodam com
npm test --workspace=packages/api. A cobertura mínima é 80% e deve ser verificada via script. O resultado dos testes deve ser publicado como artifact. Useubuntu-22.04para compatibilidade com bibliotecas nativas.
Esse processo iterativo produz resultados mais confiáveis do que tentar gerar tudo de uma vez, porque permite revisar e corrigir antes de acumular erros em prompts subsequentes.
Revisão Automatizada de Terraform
Além de gerar código, os assistentes de IA são úteis para revisar infraestrutura existente em busca de problemas de segurança, custo e conformidade com boas práticas. O processo funciona bem quando o contexto é fornecido junto com o código:
Revise este módulo Terraform que cria um cluster EKS. Identifique: (1) problemas de segurança, (2) configurações que violam o princípio de menor privilégio, (3) recursos sem tags obrigatórias, (4) ausência de configurações de alta disponibilidade. Para cada problema, explique o risco e sugira a correção específica.
Um exemplo de revisão assistida que encontra problemas reais:
# Código submetido para revisão — com problemas intencionais
resource "aws_eks_cluster" "principal" {
name = "meu-cluster"
role_arn = aws_iam_role.eks.arn
vpc_config {
subnet_ids = var.subnet_ids
# endpoint_public_access não configurado — padrão é true
# endpoint_private_access não configurado — padrão é false
}
# Sem encryption_config — etcd não criptografado
# Sem enabled_cluster_log_types — sem logs do control plane
# kubernetes_version não especificado — vai usar a versão padrão atual
# e pode mudar inesperadamente em apply futuro
}
resource "aws_iam_role_policy_attachment" "eks_worker" {
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" # PROBLEMA: acesso total
role = aws_iam_role.eks_nodes.name
}
Um assistente bem instruído identificará: o endpoint público habilitado sem restrição de CIDR (qualquer IP pode tentar acessar o API server), a ausência de criptografia do etcd (dados dos Secrets do Kubernetes ficam sem criptografia em repouso), a falta de logs do control plane (sem auditoria de quem fez o quê), a política AdministratorAccess nos nós do cluster (qualquer pod com acesso à metadata do EC2 pode assumir permissão total na conta AWS), e a ausência da versão do Kubernetes (risco de drift de configuração).
Geração de Runbooks a Partir de Alertas
Um uso menos óbvio mas muito prático: gerar runbooks estruturados a partir da descrição de um alerta ou de um incidente já resolvido. O prompt precisa incluir o sistema, o alerta, o que foi investigado e o que resolveu:
Crie um runbook para o alerta "HighMemoryPressureInPod" no Kubernetes. O alerta dispara quando um pod usa mais de 90% do memory limit por mais de 10 minutos. O runbook deve cobrir: diagnóstico inicial (comandos kubectl específicos), causas mais comuns com como identificar cada uma, ações de mitigação imediata (sem downtime), ações de resolução definitiva, e quando escalar. Formato: markdown com seções numeradas e blocos de código copiáveis.
O resultado é um rascunho de runbook que precisa de revisão por alguém que conhece o sistema específico, mas economiza 60–70% do tempo de criação do zero.
Análise de Custos com IA
Uma aplicação prática em FinOps: submeter a saída do AWS Cost Explorer ou do infracost para análise e recomendações de otimização.
O infracost é uma ferramenta open-source que calcula o custo de recursos Terraform antes de aplicá-los. Integrado ao pipeline, ele comenta no PR com a diferença de custo estimada. Combinado com análise por LLM, pode gerar recomendações:
# .github/workflows/infracost.yml
name: Análise de Custo
on:
pull_request:
paths:
- 'infrastructure/**'
jobs:
infracost:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup Infracost
uses: infracost/actions/setup@v3
with:
api-key: ${{ secrets.INFRACOST_API_KEY }}
- name: Gerar estimativa de custo
run: |
infracost breakdown \
--path infrastructure/ \
--format json \
--out-file /tmp/infracost.json
- name: Analisar com IA e comentar no PR
uses: actions/github-script@v7
env:
INFRACOST_JSON: /tmp/infracost.json
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
with:
script: |
const fs = require('fs');
const infracostData = JSON.parse(
fs.readFileSync(process.env.INFRACOST_JSON, 'utf8')
);
// Chamar API para análise contextual
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 1000,
messages: [{
role: 'user',
content: `Analise este relatório de custo do Infracost e forneça:
1. Resumo do impacto de custo desta mudança
2. Os 3 recursos mais caros adicionados
3. Sugestões específicas de otimização de custo
4. Alertas para recursos que possam ter custo inesperadamente alto
Relatório: ${JSON.stringify(infracostData.projects, null, 2).slice(0, 3000)}
Responda em português, de forma concisa, em markdown.`
}]
})
});
const analysis = await response.json();
const analysisText = analysis.content[0].text;
const monthlyDiff = infracostData.diffTotalMonthlyCost;
const emoji = parseFloat(monthlyDiff) > 50 ? '⚠️' :
parseFloat(monthlyDiff) > 0 ? '💰' : '✅';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## ${emoji} Análise de Custo — Infracost\n\n` +
`**Variação mensal estimada:** $${monthlyDiff}/mês\n\n` +
`---\n\n${analysisText}`
});
Os Limites que o Engenheiro Precisa Conhecer
A produtividade que os assistentes de IA proporcionam vem com riscos que precisam ser gerenciados conscientemente.
O primeiro risco é a confiança excessiva em código gerado sem revisão. LLMs geram código plausível — código que parece correto, compila e até funciona em cenários simples. Mas podem gerar configurações de segurança incorretas, versões depreciadas de recursos, ou lógica sutil que falha em edge cases. Todo código gerado por IA para infraestrutura deve ser revisado com o mesmo rigor aplicado a código escrito por um engenheiro júnior.
O segundo risco é a alucinação de APIs e recursos. Modelos de linguagem às vezes inventam argumentos de funções, nomes de campos em recursos Terraform, ou opções de configuração que não existem. Para infraestrutura, isso significa terraform plan vai falhar — o que é controlável. O risco maior é quando a alucinação é de um recurso que existe mas com comportamento diferente do descrito.
O terceiro risco é o contexto de segurança ausente. Um LLM sem contexto de segurança específico da organização não sabe que determinados CIDRs são proibidos, que certos tipos de instância estão bloqueados por política, ou que secrets precisam passar pelo cofre corporativo. Prompts eficazes incluem essas restrições explicitamente.
A conclusão pragmática é que assistentes de IA são amplificadores de capacidade, não substitutos de julgamento. O engenheiro que sabe o que quer construir e conhece as boas práticas usa a IA para chegar lá mais rápido. O engenheiro que não sabe o que quer construir vai chegar mais rápido ao lugar errado.
Referências para Aprofundamento
— GitHub Copilot — Documentação: https://docs.github.com/en/copilot — Cursor Editor: https://www.cursor.com/ — Infracost — Cloud Cost Estimates: https://www.infracost.io/docs/ — Prompt Engineering para Desenvolvedores: https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview — Anthropic API — Documentação: https://docs.anthropic.com/
E11 — Detecção de Anomalias e Análise Inteligente de Logs com IA
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E11 de E12 Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Problema do Volume
Uma aplicação moderna em produção gera dezenas de milhares de linhas de log por minuto. Um cluster Kubernetes com dez serviços pode gerar gigabytes de logs por hora. Ler logs manualmente durante um incidente é como procurar uma agulha em um palheiro que cresce enquanto você procura.
A análise tradicional de logs funciona assim: o engenheiro sabe o que procurar, escreve uma query no Kibana ou CloudWatch Insights, filtra por nível de erro, e percorre os resultados. Isso pressupõe que o engenheiro já tem uma hipótese — o que é frequentemente falso nos primeiros minutos de um incidente.
A análise inteligente de logs com ML inverte a lógica: em vez de procurar por padrões conhecidos, o sistema identifica o que é incomum em relação ao comportamento histórico. Novos padrões de erro que nunca apareceram. Mensagens que passaram a aparecer com frequência anormalmente alta. Correlações entre eventos em serviços diferentes que nunca co-ocorreram antes.
Detecção de Anomalias com Prometheus e Python
Para times que já têm Prometheus como sistema de métricas, é possível construir detecção de anomalias sem ferramentas adicionais usando a API HTTP do Prometheus e análise em Python. O exemplo a seguir implementa um detector simples baseado em Z-score:
#!/usr/bin/env python3
# detector_anomalias.py
# Detecta anomalias em métricas do Prometheus usando Z-score
import requests
import numpy as np
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import Optional
import json
import os
PROMETHEUS_URL = os.getenv('PROMETHEUS_URL', 'http://localhost:9090')
SLACK_WEBHOOK = os.getenv('SLACK_WEBHOOK_URL')
ZSCORE_THRESHOLD = 3.0 # Desvios padrão para considerar anomalia
@dataclass
class Anomalia:
metrica: str
labels: dict
valor_atual: float
media_historica: float
desvio_padrao: float
zscore: float
timestamp: datetime
def buscar_historico(query: str, horas: int = 24) -> list[tuple[float, float]]:
"""Busca o histórico de uma métrica no Prometheus."""
fim = datetime.utcnow()
inicio = fim - timedelta(hours=horas)
response = requests.get(
f'{PROMETHEUS_URL}/api/v1/query_range',
params={
'query': query,
'start': inicio.timestamp(),
'end': fim.timestamp(),
'step': '5m'
},
timeout=30
)
response.raise_for_status()
data = response.json()
if data['status'] != 'success' or not data['data']['result']:
return []
# Retorna apenas os valores (timestamp, valor)
return [(float(ts), float(val))
for ts, val in data['data']['result'][0]['values']]
def detectar_anomalia(
query: str,
nome_metrica: str,
janela_historico_horas: int = 168 # 7 dias
) -> Optional[Anomalia]:
"""
Detecta se o valor atual de uma métrica é anômalo
comparado com o histórico recente.
"""
# Buscar histórico completo
historico = buscar_historico(query, janela_historico_horas)
if len(historico) < 10:
return None # Histórico insuficiente
valores = np.array([v for _, v in historico])
valor_atual = valores[-1]
# Excluir o valor atual do cálculo de baseline
baseline = valores[:-1]
media = np.mean(baseline)
desvio = np.std(baseline)
if desvio == 0:
return None # Métrica constante — sem anomalia possível
zscore = abs((valor_atual - media) / desvio)
if zscore >= ZSCORE_THRESHOLD:
return Anomalia(
metrica=nome_metrica,
labels={},
valor_atual=valor_atual,
media_historica=media,
desvio_padrao=desvio,
zscore=zscore,
timestamp=datetime.utcnow()
)
return None
def notificar_slack(anomalia: Anomalia):
"""Envia notificação de anomalia para o Slack."""
if not SLACK_WEBHOOK:
print(f"ANOMALIA: {anomalia}")
return
direcao = "↑" if anomalia.valor_atual > anomalia.media_historica else "↓"
variacao_pct = abs(
(anomalia.valor_atual - anomalia.media_historica)
/ anomalia.media_historica * 100
)
payload = {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"⚠️ Anomalia Detectada: {anomalia.metrica}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*Valor atual:*\n{anomalia.valor_atual:.2f} {direcao}"
},
{
"type": "mrkdwn",
"text": f"*Média histórica:*\n{anomalia.media_historica:.2f}"
},
{
"type": "mrkdwn",
"text": f"*Desvio (Z-score):*\n{anomalia.zscore:.1f}σ"
},
{
"type": "mrkdwn",
"text": f"*Variação:*\n{variacao_pct:.0f}% acima/abaixo do normal"
}
]
}
]
}
requests.post(SLACK_WEBHOOK, json=payload, timeout=10)
def executar_verificacoes():
"""Executa todas as verificações de anomalia configuradas."""
verificacoes = [
(
'sum(rate(http_requests_total{status=~"5..",namespace="producao"}[5m])) / '
'sum(rate(http_requests_total{namespace="producao"}[5m])) * 100',
'Taxa de Erro HTTP (%)'
),
(
'histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket'
'{namespace="producao"}[5m])) by (le))',
'Latência p99 (segundos)'
),
(
'sum(rate(loja_pedidos_criados_total[5m])) * 60',
'Pedidos por Minuto'
),
(
'sum(rate(loja_checkouts_concluidos_total[30m])) / '
'sum(rate(loja_checkouts_iniciados_total[30m])) * 100',
'Taxa de Conversão (%)'
),
]
anomalias_detectadas = []
for query, nome in verificacoes:
anomalia = detectar_anomalia(query, nome)
if anomalia:
anomalias_detectadas.append(anomalia)
notificar_slack(anomalia)
print(f"Verificação concluída: {len(anomalias_detectadas)} anomalia(s) detectada(s)")
return anomalias_detectadas
if __name__ == '__main__':
executar_verificacoes()
Análise de Logs com LLM Durante Incidentes
O uso mais imediato de LLMs em operações é a análise de logs durante incidentes. O processo é simples: coletar os logs relevantes do período do incidente, enviá-los ao modelo com contexto sobre o sistema, e perguntar o que está errado.
O desafio é o volume. Logs de 30 minutos de um sistema em produção podem ter milhares de linhas — mais do que cabe no contexto de um LLM. A solução é filtrar e sumarizar antes de enviar:
#!/usr/bin/env python3
# analise_incidente.py
# Analisa logs de um incidente usando LLM
import anthropic
import subprocess
import sys
from datetime import datetime, timedelta
def coletar_logs_kubernetes(
namespace: str,
inicio: datetime,
fim: datetime,
max_linhas: int = 500
) -> dict[str, str]:
"""Coleta logs dos pods em um namespace no período do incidente."""
logs = {}
# Listar pods no namespace
resultado = subprocess.run(
['kubectl', 'get', 'pods', '-n', namespace, '-o', 'name'],
capture_output=True, text=True
)
pods = resultado.stdout.strip().split('\n')
for pod in pods:
pod_nome = pod.replace('pod/', '')
resultado = subprocess.run(
[
'kubectl', 'logs', pod_nome,
'-n', namespace,
'--since-time', inicio.isoformat() + 'Z',
'--tail', str(max_linhas)
],
capture_output=True, text=True
)
if resultado.stdout:
# Filtrar apenas linhas de erro e warning para reduzir volume
linhas_relevantes = [
linha for linha in resultado.stdout.split('\n')
if any(nivel in linha.upper()
for nivel in ['ERROR', 'WARN', 'FATAL', 'EXCEPTION', 'PANIC'])
]
if linhas_relevantes:
logs[pod_nome] = '\n'.join(linhas_relevantes[:100])
return logs
def analisar_incidente_com_llm(
logs: dict[str, str],
descricao_incidente: str,
arquitetura_sistema: str
) -> str:
"""Envia logs para análise pelo LLM e retorna o diagnóstico."""
cliente = anthropic.Anthropic()
# Formatar os logs para o contexto
logs_formatados = ""
for servico, conteudo in logs.items():
if conteudo.strip():
logs_formatados += f"\n=== {servico} ===\n{conteudo}\n"
prompt = f"""Você é um engenheiro sênior de SRE analisando um incidente de produção.
DESCRIÇÃO DO INCIDENTE:
{descricao_incidente}
ARQUITETURA DO SISTEMA:
{arquitetura_sistema}
LOGS COLETADOS (apenas erros e warnings do período do incidente):
{logs_formatados[:8000]} # Limitar para não exceder o contexto
Analise os logs e forneça:
1. **Causa raiz provável**: O que causou o incidente, com evidências dos logs.
2. **Linha do tempo**: Reconstrua a sequência de eventos com base nos timestamps.
3. **Serviços afetados**: Quais serviços foram impactados e em que ordem.
4. **Ações imediatas recomendadas**: O que fazer agora para resolver ou mitigar.
5. **Prevenção**: O que pode ser feito para evitar que isso se repita.
Seja específico e referencie as mensagens de log relevantes quando possível.
Responda em português."""
mensagem = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
return mensagem.content[0].text
def main():
# Exemplo de uso durante um incidente real
NAMESPACE = "producao"
INICIO_INCIDENTE = datetime.utcnow() - timedelta(minutes=30)
FIM_INCIDENTE = datetime.utcnow()
DESCRICAO = """
Às 14:23 os alertas começaram a disparar: queda de 60% nos pedidos por minuto
e aumento da taxa de erro HTTP para 15%. O time foi acionado às 14:25.
O deploy mais recente foi às 14:20 (versão v2.3.1 do order-service).
"""
ARQUITETURA = """
Sistema de e-commerce com microsserviços:
- api-gateway: recebe requisições e roteia para os serviços
- catalog-service: gerencia produtos e estoque (PostgreSQL + Redis cache)
- order-service: cria e gerencia pedidos (PostgreSQL, publica no SQS)
- notification-service: consome SQS e envia emails/SMS
Todos rodando no Kubernetes (EKS) com HPA configurado.
"""
print("Coletando logs do período do incidente...")
logs = coletar_logs_kubernetes(NAMESPACE, INICIO_INCIDENTE, FIM_INCIDENTE)
print(f"Logs coletados de {len(logs)} serviço(s)")
if not logs:
print("Nenhum log de erro encontrado no período")
sys.exit(0)
print("\nAnalisando com LLM...")
diagnostico = analisar_incidente_com_llm(logs, DESCRICAO, ARQUITETURA)
print("\n" + "="*60)
print("DIAGNÓSTICO DO INCIDENTE")
print("="*60)
print(diagnostico)
# Salvar o diagnóstico para o postmortem
with open(f'diagnostico-{datetime.now().strftime("%Y%m%d-%H%M")}.md', 'w') as f:
f.write(f"# Diagnóstico do Incidente — {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n")
f.write(f"## Descrição\n{DESCRICAO}\n\n")
f.write(f"## Análise\n{diagnostico}\n")
if __name__ == '__main__':
main()
Classificação Automática de Alertas
Outro caso de uso prático: usar um LLM para classificar e priorizar alertas que chegam em volume, reduzindo o ruído antes de acordar alguém:
# classificador_alertas.py
# Recebe alertas do Alertmanager via webhook e classifica com LLM
from flask import Flask, request, jsonify
import anthropic
import json
app = Flask(__name__)
cliente = anthropic.Anthropic()
RUNBOOKS_BASE_URL = "https://wiki.empresa.com/runbooks"
def classificar_alerta(alerta: dict) -> dict:
"""Classifica um alerta e sugere ação usando LLM."""
nome = alerta.get('labels', {}).get('alertname', 'Desconhecido')
severidade = alerta.get('labels', {}).get('severity', 'unknown')
descricao = alerta.get('annotations', {}).get('description', '')
sumario = alerta.get('annotations', {}).get('summary', '')
prompt = f"""Você é um sistema de triagem de alertas de infraestrutura.
ALERTA RECEBIDO:
- Nome: {nome}
- Severidade declarada: {severidade}
- Resumo: {sumario}
- Descrição: {descricao}
Com base nas informações acima, forneça uma análise em JSON com exatamente estes campos:
{{
"urgencia": "imediata|alta|media|baixa",
"acordar_plantao": true/false,
"provavel_causa": "descrição em uma frase",
"primeira_acao": "o que fazer primeiro",
"pode_ser_falso_positivo": true/false,
"razao_falso_positivo": "se aplicável, por quê poderia ser falso positivo"
}}
Responda APENAS com o JSON, sem texto adicional."""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=400,
messages=[{"role": "user", "content": prompt}]
)
try:
classificacao = json.loads(resposta.content[0].text)
except json.JSONDecodeError:
classificacao = {
"urgencia": "alta",
"acordar_plantao": True,
"provavel_causa": "Erro na classificação automática",
"primeira_acao": "Investigar manualmente",
"pode_ser_falso_positivo": False,
"razao_falso_positivo": ""
}
return classificacao
@app.route('/webhook/alertmanager', methods=['POST'])
def receber_alertas():
"""Endpoint que recebe alertas do Alertmanager."""
payload = request.json
alertas = payload.get('alerts', [])
resultados = []
for alerta in alertas:
if alerta.get('status') != 'firing':
continue
classificacao = classificar_alerta(alerta)
nome = alerta['labels'].get('alertname', 'N/A')
# Logar para auditoria
print(f"Alerta: {nome} | Urgência: {classificacao['urgencia']} | "
f"Acordar plantão: {classificacao['acordar_plantao']}")
resultados.append({
'alerta': nome,
'classificacao': classificacao
})
# Notificar apenas se necessário acordar o plantão
if classificacao['acordar_plantao']:
enviar_para_pagerduty(alerta, classificacao)
else:
# Apenas registrar no canal de alertas sem notificação urgente
enviar_para_slack_informativo(alerta, classificacao)
return jsonify({'processados': len(resultados), 'resultados': resultados})
def enviar_para_pagerduty(alerta, classificacao):
# Implementação da integração com PagerDuty
pass
def enviar_para_slack_informativo(alerta, classificacao):
# Implementação da notificação Slack sem urgência
pass
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
Referências para Aprofundamento
— Prometheus HTTP API: https://prometheus.io/docs/prometheus/latest/querying/api/ — Anthropic API — Messages: https://docs.anthropic.com/en/api/messages — Elastic ML — Anomaly Detection: https://www.elastic.co/guide/en/machine-learning/current/ml-ad-overview.html — AWS CloudWatch Anomaly Detection: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Anomaly_Detection.html
E12 — Geração de Infraestrutura com IA
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E12 de E12 · Conclusão da Série Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Estado Atual da Geração de Infraestrutura
Gerar Terraform, manifestos Kubernetes e pipelines CI/CD a partir de descrições em linguagem natural é tecnicamente possível hoje. Não é ficção científica. Ferramentas como o GitHub Copilot, o Cursor e modelos de linguagem via API já fazem isso com qualidade suficiente para ser útil — especialmente para engenheiros que sabem o que querem e usam a IA para chegar lá mais rápido.
O que ainda é ficção científica é o sistema que recebe "preciso de uma plataforma de e-commerce escalável na AWS" e entrega infraestrutura de produção completamente configurada, segura e otimizada para custo sem supervisão humana. Esse nível de autonomia não existe com confiabilidade suficiente para produção em 2025.
Este último artigo da série cobre o que já funciona, como construir um workflow prático de geração supervisionada de infraestrutura com IA, e deixa ao leitor uma visão honesta de onde a área está indo.
Geração Supervisionada: O Modelo que Funciona
O modelo que produz resultados confiáveis não é "IA gera, humano aplica". É "IA gera, humano revisa e valida, então aplica". A IA funciona como um engenheiro júnior muito rápido que escreve um primeiro rascunho — útil, mas que precisa de revisão antes de ir para produção.
O workflow prático tem quatro etapas. Na primeira, o engenheiro descreve o que precisa em linguagem natural, com restrições explícitas de segurança, custo e conformidade. Na segunda, a IA gera o código Terraform, YAML ou script. Na terceira, ferramentas automatizadas validam o código gerado: terraform validate, tflint, checkov para segurança, kubeval para Kubernetes. Na quarta, o engenheiro revisa o código validado, ajusta o que for necessário, e aplica.
Construindo um Gerador de Módulos Terraform
O exemplo a seguir implementa um gerador de módulos Terraform que usa um LLM para criar o código e ferramentas estáticas para validá-lo:
#!/usr/bin/env python3
# gerador_terraform.py
# Gera módulos Terraform a partir de descrições em linguagem natural
# com validação automática do código gerado
import anthropic
import subprocess
import tempfile
import os
import sys
from pathlib import Path
SYSTEM_PROMPT = """Você é um especialista em Terraform e AWS com foco em segurança e boas práticas.
Quando solicitado a gerar código Terraform, você SEMPRE:
1. Usa o provider AWS versão ~> 5.0
2. Inclui tags obrigatórias: environment, managed-by = "terraform", project
3. Habilita criptografia em repouso para todos os recursos que suportam
4. Configura logs de auditoria quando disponível
5. Aplica o princípio de menor privilégio em políticas IAM
6. Inclui o recurso `aws_resourcegroups_group` para agrupamento
7. Adiciona comentários explicando decisões de configuração não óbvias
8. NÃO usa recursos depreciados
9. NÃO hardcoda valores que deveriam ser variáveis
Estrutura do módulo gerado:
- main.tf (recursos principais)
- variables.tf (todas as variáveis com descrições e validações)
- outputs.tf (outputs relevantes)
- versions.tf (required_providers)
Responda APENAS com os arquivos em blocos de código marcados com o nome do arquivo:
```main.tf
... conteúdo ...
... conteúdo ...
```"""
def gerar_modulo_terraform(descricao: str, contexto_adicional: str = "") -> dict[str, str]:
"""Gera um módulo Terraform a partir de uma descrição."""
cliente = anthropic.Anthropic()
prompt = f"""{descricao}
{f"Contexto adicional: {contexto_adicional}" if contexto_adicional else ""}
Gere um módulo Terraform completo seguindo as diretrizes estabelecidas."""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": prompt}]
)
# Extrair os arquivos do código gerado
conteudo = resposta.content[0].text
arquivos = {}
import re
padrao = r'```(\w+\.tf)\n(.*?)```'
matches = re.findall(padrao, conteudo, re.DOTALL)
for nome_arquivo, conteudo_arquivo in matches:
arquivos[nome_arquivo] = conteudo_arquivo.strip()
return arquivos
def validar_terraform(arquivos: dict[str, str]) -> tuple[bool, list[str]]:
"""Valida o código Terraform gerado com ferramentas estáticas."""
erros = []
with tempfile.TemporaryDirectory() as tmpdir:
# Escrever os arquivos no diretório temporário
for nome, conteudo in arquivos.items():
(Path(tmpdir) / nome).write_text(conteudo)
# 1. terraform fmt --check (formatação)
resultado = subprocess.run(
['terraform', 'fmt', '-check', '-diff', tmpdir],
capture_output=True, text=True
)
if resultado.returncode != 0:
erros.append(f"Formatação: {resultado.stdout}")
# 2. terraform init + validate (sintaxe)
subprocess.run(
['terraform', 'init', '-backend=false'],
capture_output=True, cwd=tmpdir
)
resultado = subprocess.run(
['terraform', 'validate'],
capture_output=True, text=True, cwd=tmpdir
)
if resultado.returncode != 0:
erros.append(f"Validação: {resultado.stderr}")
# 3. tflint (boas práticas e recursos depreciados)
if subprocess.run(['which', 'tflint'], capture_output=True).returncode == 0:
resultado = subprocess.run(
['tflint', '--format', 'compact'],
capture_output=True, text=True, cwd=tmpdir
)
if resultado.returncode != 0:
erros.append(f"TFLint: {resultado.stdout}")
# 4. checkov (segurança)
if subprocess.run(['which', 'checkov'], capture_output=True).returncode == 0:
resultado = subprocess.run(
['checkov', '-d', tmpdir, '--quiet', '--compact',
'--framework', 'terraform'],
capture_output=True, text=True
)
if resultado.returncode != 0:
# Extrair apenas os checks que falharam
linhas_falha = [l for l in resultado.stdout.split('\n')
if 'FAILED' in l][:10]
if linhas_falha:
erros.append(f"Segurança (Checkov):\n" + '\n'.join(linhas_falha))
return len(erros) == 0, erros
def salvar_modulo(nome_modulo: str, arquivos: dict[str, str]):
"""Salva o módulo gerado no sistema de arquivos."""
diretorio = Path(f'modules/{nome_modulo}')
diretorio.mkdir(parents=True, exist_ok=True)
for nome_arquivo, conteudo in arquivos.items():
(diretorio / nome_arquivo).write_text(conteudo)
print(f" Salvo: {diretorio / nome_arquivo}")
def main():
# Exemplo de uso
descricao = """
Crie um módulo Terraform para um RDS PostgreSQL 16 para ambiente de produção na AWS.
Requisitos:
- Instância db.t4g.medium com possibilidade de escalar via variável
- Multi-AZ habilitado em produção (variável para controlar)
- Backup automático com retenção de 7 dias
- Janela de manutenção configurável
- Performance Insights habilitado
- Monitoramento Enhanced habilitado
- Parâmetro group customizado com shared_buffers e max_connections configuráveis
- Subnet group que recebe a lista de subnet IDs como variável
- Security group que permite acesso apenas de um security group de origem (variável)
- Senha gerenciada pelo AWS Secrets Manager (manage_master_user_password)
- Outputs: endpoint, porta, nome do banco, ARN do secret da senha
"""
print("Gerando módulo Terraform com IA...")
arquivos = gerar_modulo_terraform(descricao)
if not arquivos:
print("Erro: nenhum arquivo gerado")
sys.exit(1)
print(f"\nArquivos gerados: {list(arquivos.keys())}")
print("\nValidando código gerado...")
valido, erros = validar_terraform(arquivos)
if valido:
print("✅ Validação passou — sem erros")
salvar_modulo('rds-postgresql', arquivos)
print("\nMódulo salvo em modules/rds-postgresql/")
print("Próximos passos:")
print(" 1. Revisar o código gerado")
print(" 2. Ajustar variáveis para o projeto específico")
print(" 3. Testar em ambiente de desenvolvimento")
print(" 4. Submeter para revisão via pull request")
else:
print("⚠️ Validação encontrou problemas:")
for erro in erros:
print(f"\n {erro}")
print("\nO código foi gerado mas requer correções antes do uso.")
# Salvar mesmo com erros para revisão manual
salvar_modulo('rds-postgresql-revisar', arquivos)
if __name__ == '__main__':
main()
Geração de Manifestos Kubernetes
O mesmo padrão se aplica à geração de manifestos Kubernetes. O kubeval e o kube-score validam estrutura e boas práticas:
def gerar_manifesto_kubernetes(descricao: str) -> str:
"""Gera um manifesto Kubernetes a partir de uma descrição."""
cliente = anthropic.Anthropic()
system = """Você é especialista em Kubernetes e segurança de containers.
Ao gerar manifestos, você SEMPRE inclui:
- securityContext com runAsNonRoot: true, readOnlyRootFilesystem: true,
allowPrivilegeEscalation: false, capabilities.drop: [ALL]
- resources.requests e resources.limits para CPU e memória
- livenessProbe e readinessProbe
- terminationGracePeriodSeconds adequado à aplicação
- topologySpreadConstraints para distribuição entre zonas
- labels padronizados: app, version, component
NUNCA use:
- hostNetwork: true
- privileged: true
- latest como tag de imagem
- recursos sem limits definidos"""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=3000,
system=system,
messages=[{"role": "user", "content": descricao}]
)
return resposta.content[0].text
def validar_kubernetes(manifesto_yaml: str) -> tuple[bool, list[str]]:
"""Valida um manifesto Kubernetes."""
erros = []
with tempfile.NamedTemporaryFile(suffix='.yaml', mode='w',
delete=False) as f:
f.write(manifesto_yaml)
tmpfile = f.name
try:
# kubeval — valida contra os schemas da API do Kubernetes
if subprocess.run(['which', 'kubeval'], capture_output=True).returncode == 0:
resultado = subprocess.run(
['kubeval', tmpfile],
capture_output=True, text=True
)
if resultado.returncode != 0:
erros.append(f"kubeval: {resultado.stdout}")
# kube-score — boas práticas de segurança e confiabilidade
if subprocess.run(['which', 'kube-score'],
capture_output=True).returncode == 0:
resultado = subprocess.run(
['kube-score', 'score', tmpfile],
capture_output=True, text=True
)
# kube-score retorna 0 mesmo com warnings; filtrar apenas críticos
linhas_criticas = [l for l in resultado.stdout.split('\n')
if '✗' in l or 'CRITICAL' in l]
if linhas_criticas:
erros.append("kube-score:\n" + '\n'.join(linhas_criticas[:10]))
finally:
os.unlink(tmpfile)
return len(erros) == 0, erros
Para Onde a Área Está Indo
O ritmo de evolução das ferramentas de IA aplicadas a DevOps é rápido o suficiente para que qualquer previsão específica fique desatualizada em meses. O que é possível afirmar com confiança sobre a direção geral:
A geração de infraestrutura vai se tornar mais confiável à medida que os modelos sejam treinados com mais exemplos de código de infraestrutura de alta qualidade e as ferramentas de validação fiquem mais sofisticadas. O gap entre "IA gera" e "pode
you asked
Continue. O último ficou corrompido
claude response
E12 — Geração de Infraestrutura com IA
Extensão C · AIOps e IA Aplicada a DevOps · Artigo E12 de E12 · Conclusão da Série Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Estado Atual da Geração de Infraestrutura
Gerar Terraform, manifestos Kubernetes e pipelines CI/CD a partir de descrições em linguagem natural é tecnicamente possível hoje. Não é ficção científica. Ferramentas como o GitHub Copilot, o Cursor e modelos de linguagem via API já fazem isso com qualidade suficiente para ser útil — especialmente para engenheiros que sabem o que querem e usam a IA para chegar lá mais rápido.
O que ainda é ficção científica é o sistema que recebe "preciso de uma plataforma de e-commerce escalável na AWS" e entrega infraestrutura de produção completamente configurada, segura e otimizada para custo sem supervisão humana. Esse nível de autonomia não existe com confiabilidade suficiente para produção em 2025. Este artigo cobre o que já funciona, como construir um workflow prático de geração supervisionada de infraestrutura com IA, e termina com uma visão honesta de onde a área está indo.
Geração Supervisionada: O Modelo que Funciona
O modelo que produz resultados confiáveis não é "IA gera, humano aplica". É "IA gera, humano revisa e valida, então aplica". A IA funciona como um engenheiro júnior muito rápido que escreve um primeiro rascunho — útil, mas que precisa de revisão antes de ir para produção.
O workflow prático tem quatro etapas. Na primeira, o engenheiro descreve o que precisa em linguagem natural, com restrições explícitas de segurança, custo e conformidade. Na segunda, a IA gera o código Terraform, YAML ou script. Na terceira, ferramentas automatizadas validam o código gerado: terraform validate, tflint, checkov para segurança, kubeval para Kubernetes. Na quarta, o engenheiro revisa o código validado, ajusta o que for necessário, e aplica.
Construindo um Gerador de Módulos Terraform
O exemplo a seguir implementa um gerador de módulos Terraform que usa um LLM para criar o código e ferramentas estáticas para validá-lo antes de entregar para revisão humana:
#!/usr/bin/env python3
# gerador_terraform.py
# Gera módulos Terraform a partir de descrições em linguagem natural
# com validação automática do código gerado
import anthropic
import subprocess
import tempfile
import re
import os
import sys
from pathlib import Path
SYSTEM_PROMPT = """Você é um especialista em Terraform e AWS com foco em segurança
e boas práticas.
Quando solicitado a gerar código Terraform, você SEMPRE:
1. Usa o provider AWS versão ~> 5.0
2. Inclui tags obrigatórias: environment, managed-by = "terraform", project
3. Habilita criptografia em repouso para todos os recursos que suportam
4. Configura logs de auditoria quando disponível
5. Aplica o princípio de menor privilégio em políticas IAM
6. Adiciona comentários explicando decisões de configuração não óbvias
7. NÃO usa recursos depreciados
8. NÃO hardcoda valores que deveriam ser variáveis
Estrutura do módulo gerado:
- main.tf (recursos principais)
- variables.tf (todas as variáveis com descrições e validações)
- outputs.tf (outputs relevantes)
- versions.tf (required_providers)
Responda APENAS com os arquivos em blocos de código marcados com o nome do arquivo.
Exemplo:
```main.tf
... conteúdo ...
... conteúdo ...
"""
def gerar_modulo_terraform(descricao: str, contexto: str = "") -> dict[str, str]: """Gera um módulo Terraform a partir de uma descrição em linguagem natural.""" cliente = anthropic.Anthropic()
prompt = descricao
if contexto:
prompt += f"\n\nContexto adicional: {contexto}"
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": prompt}]
)
# Extrair os arquivos do código gerado usando regex
conteudo = resposta.content[0].text
arquivos = {}
padrao = r'```(\w+\.tf)\n(.*?)```'
matches = re.findall(padrao, conteudo, re.DOTALL)
for nome_arquivo, conteudo_arquivo in matches:
arquivos[nome_arquivo] = conteudo_arquivo.strip()
return arquivos
def validar_terraform(arquivos: dict[str, str]) -> tuple[bool, list[str]]: """ Valida o código Terraform gerado com ferramentas estáticas. Retorna (passou, lista_de_erros). """ erros = []
with tempfile.TemporaryDirectory() as tmpdir:
# Escrever os arquivos no diretório temporário
for nome, conteudo in arquivos.items():
(Path(tmpdir) / nome).write_text(conteudo)
# 1. terraform fmt --check (formatação)
resultado = subprocess.run(
['terraform', 'fmt', '-check', '-diff', tmpdir],
capture_output=True, text=True
)
if resultado.returncode != 0:
erros.append(f"Formatação incorreta:\n{resultado.stdout}")
# 2. terraform init + validate (sintaxe e referências)
subprocess.run(
['terraform', 'init', '-backend=false'],
capture_output=True, cwd=tmpdir
)
resultado = subprocess.run(
['terraform', 'validate'],
capture_output=True, text=True, cwd=tmpdir
)
if resultado.returncode != 0:
erros.append(f"Erro de validação:\n{resultado.stderr}")
# 3. tflint — boas práticas e recursos depreciados (se instalado)
tem_tflint = subprocess.run(
['which', 'tflint'], capture_output=True
).returncode == 0
if tem_tflint:
resultado = subprocess.run(
['tflint', '--format', 'compact'],
capture_output=True, text=True, cwd=tmpdir
)
if resultado.returncode != 0:
erros.append(f"TFLint:\n{resultado.stdout}")
# 4. checkov — verificações de segurança (se instalado)
tem_checkov = subprocess.run(
['which', 'checkov'], capture_output=True
).returncode == 0
if tem_checkov:
resultado = subprocess.run(
[
'checkov', '-d', tmpdir,
'--quiet', '--compact',
'--framework', 'terraform',
# Ignorar checks que exigem conta AWS real
'--skip-check', 'CKV_AWS_116,CKV_AWS_117'
],
capture_output=True, text=True
)
if resultado.returncode != 0:
# Extrair apenas as linhas de falha para não poluir o output
linhas_falha = [
l for l in resultado.stdout.split('\n')
if 'FAILED' in l
][:10]
if linhas_falha:
erros.append(
"Checkov (segurança):\n" + '\n'.join(linhas_falha)
)
return len(erros) == 0, erros
def salvar_modulo(nome_modulo: str, arquivos: dict[str, str]): """Salva os arquivos do módulo gerado no sistema de arquivos.""" diretorio = Path(f'modules/{nome_modulo}') diretorio.mkdir(parents=True, exist_ok=True)
for nome_arquivo, conteudo in arquivos.items():
(diretorio / nome_arquivo).write_text(conteudo)
print(f" Salvo: {diretorio / nome_arquivo}")
def main(): descricao = """ Crie um módulo Terraform para um RDS PostgreSQL 16 de produção na AWS.
Requisitos:
- Instância db.t4g.medium com tipo configurável via variável
- Multi-AZ habilitado em produção (variável booleana para controlar)
- Backup automático com retenção de 7 dias
- Performance Insights habilitado com retenção de 7 dias
- Enhanced Monitoring habilitado (intervalo de 60 segundos)
- Parameter group customizado com shared_buffers e max_connections configuráveis
- Subnet group que recebe a lista de subnet IDs como variável
- Security group que permite acesso apenas de um SG de origem (variável)
- Senha gerenciada pelo AWS Secrets Manager via manage_master_user_password
- Outputs: endpoint, porta, identificador, ARN do secret da senha
"""
print("Gerando módulo Terraform com IA...")
arquivos = gerar_modulo_terraform(descricao)
if not arquivos:
print("Erro: nenhum arquivo foi extraído da resposta.")
sys.exit(1)
print(f"Arquivos gerados: {list(arquivos.keys())}")
print("\nValidando código gerado...")
valido, erros = validar_terraform(arquivos)
if valido:
print("✅ Validação passou sem erros.")
salvar_modulo('rds-postgresql', arquivos)
print("\nMódulo salvo em modules/rds-postgresql/")
print("\nPróximos passos:")
print(" 1. Revise o código gerado antes de usar em produção")
print(" 2. Ajuste as variáveis para o contexto do projeto")
print(" 3. Teste em ambiente de desenvolvimento")
print(" 4. Submeta para revisão via pull request")
else:
print("⚠️ Validação encontrou problemas que precisam de correção:")
for erro in erros:
print(f"\n {erro}")
# Salvar mesmo com erros para revisão manual
salvar_modulo('rds-postgresql-revisar', arquivos)
print("\nArquivos salvos em modules/rds-postgresql-revisar/ para revisão manual.")
if name == 'main': main()
## Geração de Manifestos Kubernetes
O mesmo padrão se aplica à geração de manifestos Kubernetes. O prompt precisa incluir as restrições de segurança explicitamente, porque um LLM sem esse contexto tende a gerar manifestos mínimos sem `securityContext`, sem `resources.limits` e sem probes:
```python
def gerar_e_validar_manifesto_kubernetes(descricao: str) -> tuple[str, list[str]]:
"""Gera e valida um manifesto Kubernetes."""
cliente = anthropic.Anthropic()
system = """Você é especialista em Kubernetes e segurança de containers.
Ao gerar manifestos, você SEMPRE inclui em cada container:
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
E em cada Deployment:
- resources.requests e resources.limits para CPU e memória
- livenessProbe e readinessProbe com httpGet ou exec
- terminationGracePeriodSeconds adequado à aplicação
- topologySpreadConstraints distribuindo entre zonas de disponibilidade
- labels padronizados: app, version, component
NUNCA use: hostNetwork: true, privileged: true, tag 'latest' em imagens,
ou recursos sem limits definidos.
Responda apenas com o YAML, sem explicações antes ou depois."""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=3000,
system=system,
messages=[{"role": "user", "content": descricao}]
)
manifesto = resposta.content[0].text
# Remover marcadores de bloco de código se presentes
manifesto = re.sub(r'^```ya?ml\n', '', manifesto, flags=re.MULTILINE)
manifesto = re.sub(r'^```\n?$', '', manifesto, flags=re.MULTILINE)
erros = []
with tempfile.NamedTemporaryFile(
suffix='.yaml', mode='w', delete=False
) as f:
f.write(manifesto)
tmpfile = f.name
try:
# kubeval — valida contra os schemas da API do Kubernetes
tem_kubeval = subprocess.run(
['which', 'kubeval'], capture_output=True
).returncode == 0
if tem_kubeval:
resultado = subprocess.run(
['kubeval', '--strict', tmpfile],
capture_output=True, text=True
)
if resultado.returncode != 0:
erros.append(f"kubeval: {resultado.stdout}")
# kube-score — boas práticas de segurança e confiabilidade
tem_kubescore = subprocess.run(
['which', 'kube-score'], capture_output=True
).returncode == 0
if tem_kubescore:
resultado = subprocess.run(
['kube-score', 'score', tmpfile],
capture_output=True, text=True
)
# Filtrar apenas falhas críticas
linhas_criticas = [
l for l in resultado.stdout.split('\n')
if '✗' in l
]
if linhas_criticas:
erros.append("kube-score:\n" + '\n'.join(linhas_criticas[:10]))
finally:
os.unlink(tmpfile)
return manifesto, erros
Geração de Pipeline GitHub Actions
Para geração de pipelines, o contexto mais importante a fornecer é o stack tecnológico, as ferramentas de segurança já adotadas na organização e os ambientes de deploy existentes. Um pipeline gerado sem esse contexto será genérico demais para ser útil diretamente:
def gerar_pipeline_github_actions(
stack: str,
servicos_deploy: list[str],
ferramentas_seguranca: list[str],
cobertura_minima: int = 80
) -> str:
"""Gera um workflow GitHub Actions completo para o stack informado."""
cliente = anthropic.Anthropic()
descricao = f"""
Gere um workflow GitHub Actions completo para um projeto com as seguintes características:
Stack: {stack}
Ambientes de deploy: {', '.join(servicos_deploy)}
Ferramentas de segurança a integrar: {', '.join(ferramentas_seguranca)}
Cobertura de testes mínima: {cobertura_minima}%
O workflow deve:
1. Disparar em push para main e em pull requests
2. Jobs de lint e testes unitários rodando em paralelo
3. Build da imagem Docker apenas na branch main após os testes passarem
4. Scan de segurança da imagem com Trivy (falhar em CRITICAL e HIGH)
5. Deploy em staging automaticamente após o build
6. Deploy em produção com aprovação manual (environment protection)
7. Verificação de métricas pós-deploy via curl no endpoint de health
8. Rollback automático se o health check falhar após o deploy
Use OIDC para autenticação com AWS (sem access keys estáticas).
Inclua comentários explicando as decisões não óbvias do pipeline.
"""
resposta = cliente.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
messages=[{"role": "user", "content": descricao}]
)
return resposta.content[0].text
Integração Completa: IA no Pull Request
O ponto de maior alavancagem é integrar a geração e revisão assistida por IA diretamente no fluxo de pull requests. Quando um PR modifica arquivos de infraestrutura, um job automatizado pode gerar um comentário com análise de custo, verificações de segurança e sugestões — tudo antes que qualquer revisor humano olhe para o código:
# .github/workflows/revisao-infraestrutura.yml
name: Revisão de Infraestrutura com IA
on:
pull_request:
paths:
- 'infrastructure/**'
- 'kubernetes/**'
- '.github/workflows/**'
permissions:
contents: read
pull-requests: write
jobs:
revisao-ia:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Histórico completo para comparar com base
- name: Coletar arquivos modificados
id: diff
run: |
# Listar arquivos de infraestrutura modificados neste PR
ARQUIVOS=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \
| grep -E '\.(tf|yml|yaml)$' | head -20)
echo "arquivos<<EOF" >> $GITHUB_OUTPUT
echo "$ARQUIVOS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Executar Checkov
id: checkov
continue-on-error: true
run: |
pip install checkov --quiet
checkov -d infrastructure/ \
--output json \
--quiet > /tmp/checkov-result.json || true
- name: Análise com IA e comentário no PR
uses: actions/github-script@v7
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
with:
script: |
const fs = require('fs');
const { execSync } = require('child_process');
// Coletar o diff completo dos arquivos de infraestrutura
const diff = execSync(
`git diff origin/${{ github.base_ref }}...HEAD \
-- 'infrastructure/**' 'kubernetes/**'`
).toString().slice(0, 6000); // Limitar tamanho
// Ler resultado do Checkov se disponível
let checkovSummary = 'Checkov não executado';
try {
const checkovData = JSON.parse(
fs.readFileSync('/tmp/checkov-result.json', 'utf8')
);
const passed = checkovData.summary?.passed || 0;
const failed = checkovData.summary?.failed || 0;
checkovSummary = `${passed} checks passaram, ${failed} falharam`;
} catch (e) {}
// Solicitar análise ao LLM
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 1500,
messages: [{
role: 'user',
content: `Revise este diff de infraestrutura como código e forneça:
1. **Resumo da mudança**: O que está sendo alterado em uma frase.
2. **Riscos de segurança**: Qualquer configuração que reduza a postura de segurança.
3. **Riscos operacionais**: Mudanças que podem causar downtime ou comportamento inesperado.
4. **Melhorias sugeridas**: Até 3 sugestões concretas e acionáveis.
5. **Aprovação recomendada**: Sim/Não/Com ressalvas.
Resultado do Checkov: ${checkovSummary}
Diff:
\`\`\`diff
${diff}
\`\`\`
Responda em português, em markdown conciso. Seja direto e específico.`
}]
})
});
const data = await response.json();
const analise = data.content[0].text;
// Publicar o comentário no PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## 🤖 Revisão Automática de Infraestrutura\n\n` +
`> *Análise gerada automaticamente — não substitui revisão humana*\n\n` +
`${analise}\n\n` +
`---\n` +
`*Checkov: ${checkovSummary}*`
});
Para Onde a Área Está Indo
O ritmo de evolução das ferramentas de IA aplicadas a DevOps é rápido o suficiente para que qualquer previsão específica fique desatualizada em meses. O que é possível afirmar com confiança sobre a direção geral:
A geração de infraestrutura vai se tornar mais confiável à medida que modelos sejam treinados com mais exemplos de código de infraestrutura de alta qualidade e as ferramentas de validação fiquem mais sofisticadas. O gap entre "IA gera" e "pode ir para produção sem revisão" vai diminuir — mas provavelmente nunca chegar a zero para infraestrutura crítica, porque o contexto organizacional (políticas de segurança, decisões de arquitetura passadas, restrições de compliance) não está nos dados de treinamento do modelo.
A remediação autônoma de incidentes vai avançar em ações de baixo risco e alta frequência — reiniciar um pod, escalar um deployment, adicionar capacidade — onde o custo de uma ação errada é baixo e reversível. Para ações de alto impacto, o padrão "IA sugere, humano aprova" vai persistir por razões de responsabilidade organizacional além das técnicas.
A interface entre engenheiro e infraestrutura vai mudar. Hoje se escreve Terraform. Amanhã talvez se descreva a intenção ("preciso de um banco de dados para este serviço com essas características") e as ferramentas geram e gerenciam a implementação, com o engenheiro revisando e aprovando. Não é uma mudança de quem é responsável — é uma mudança no nível de abstração em que o trabalho acontece.
O que não vai mudar é a necessidade de compreender os fundamentos. Um engenheiro que não entende como uma VPC funciona, o que um Security Group faz, ou por que a separação de responsabilidades importa em IAM não consegue revisar código de infraestrutura gerado por IA de maneira eficaz — vai aprovar problemas que não consegue identificar. A IA amplifica a capacidade de quem tem os fundamentos. Para quem não tem, pode amplificar os erros na mesma proporção.
É por isso que esta série começou com o terminal Linux e chegou aqui: os fundamentos não são a parte chata antes do conteúdo interessante. Os fundamentos são o que torna possível usar bem tudo que veio depois — incluindo as ferramentas de IA que, cada vez mais, fazem parte do dia a dia de quem opera sistemas em produção.
Encerramento da Trilha de Extensão
Esta série cobriu do terminal Linux ao estado da arte em DevOps moderno: infraestrutura em múltiplas nuvens, pipelines em qualquer plataforma Git relevante no mercado, e as ferramentas de IA que estão mudando como engenheiros escrevem e operam sistemas.
O conhecimento não expira. As ferramentas mudam. Os princípios de automação, observabilidade, segurança por padrão e entrega contínua que atravessam todos os artigos vistos são os mesmos que vão estar no centro do trabalho daqui a dez anos — em ferramentas que hoje não existem, em plataformas que ainda não foram inventadas.
O meu profundo agradecimento a cada leitor(a), visitante e estudante que percorreu esta jornada.
Agora vá construir!
Referências para Aprofundamento
— Anthropic API — Messages: https://docs.anthropic.com/en/api/messages — Infracost — Estimativas de Custo para Terraform: https://www.infracost.io/docs/ — Checkov — Segurança em IaC: https://www.checkov.io/ — TFLint — Linter para Terraform: https://github.com/terraform-linters/tflint — kube-score — Análise de Manifestos Kubernetes: https://kube-score.com/ — kubeval — Validação de Manifestos Kubernetes: https://www.kubeval.com/