Containers revolucionaram como deployamos aplicações, mas essa abstração tem custo. Configurações inadequadas podem transformar uma aplicação performática em um gargalo. Este artigo explora como otimizar performance em ambientes containerizados.
Container não é máquina virtual. Otimizar como se fosse é o primeiro erro.
O Impacto dos Containers na Performance
Overhead real
Aplicação nativa: 100 RPS baseline
Aplicação em Docker: 95-98 RPS (2-5% overhead)
Aplicação em K8s: 90-95 RPS (5-10% overhead)
O overhead é pequeno, mas erros de configuração amplificam drasticamente:
Container mal configurado: 50-70 RPS
→ Problema não é o container, é a configuração
Fontes de overhead
- Rede: overlay networks, NAT, iptables
- Storage: copy-on-write, volumes
- CPU: cgroups, scheduling
- Memória: limites, OOM killer
Configurando Recursos Corretamente
CPU: requests vs limits
resources:
requests:
cpu: "500m" # Garantido pelo scheduler
limits:
cpu: "1000m" # Máximo permitido
Erros comuns:
# ❌ Limite muito baixo - throttling constante
limits:
cpu: "100m"
# ❌ Sem limite - pode monopolizar nó
limits:
cpu: null
# ❌ Request = Limit alto - desperdício
requests:
cpu: "2000m"
limits:
cpu: "2000m"
Configuração recomendada:
# ✅ Request baseado em uso médio, limit em picos
resources:
requests:
cpu: "250m" # Uso médio observado
limits:
cpu: "1000m" # Headroom para picos
CPU Throttling
Quando container atinge limite de CPU, sofre throttling:
Sem throttling: latência p99 = 50ms
Com throttling: latência p99 = 500ms (10x pior)
Como detectar:
# Métricas do container
cat /sys/fs/cgroup/cpu/cpu.stat
# nr_throttled: número de vezes throttled
# throttled_time: tempo total em nanosegundos
Prometheus query:
rate(container_cpu_cfs_throttled_seconds_total[5m])
Memória: o equilíbrio delicado
resources:
requests:
memory: "512Mi"
limits:
memory: "1Gi"
O problema do OOM Killer:
Memória usada > Limit → OOM Kill → Pod reinicia
→ Conexões perdidas, requisições em voo falham
Configuração segura:
# ✅ Headroom de 20-30% acima do uso normal
resources:
requests:
memory: "512Mi" # Uso médio
limits:
memory: "768Mi" # +50% headroom
JVM em Containers
JVMs antigas não respeitam limites de container:
# Container com 1GB de limit
# JVM antiga vê 64GB da máquina host
# Aloca heap de 16GB → OOM Kill instantâneo
Solução:
# Use JVM 11+ que respeita cgroups
FROM eclipse-temurin:17-jre
# Ou configure explicitamente
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0"
Configuração recomendada para JVM:
env:
- name: JAVA_OPTS
value: >-
-XX:MaxRAMPercentage=75.0
-XX:InitialRAMPercentage=50.0
-XX:+UseG1GC
-XX:+UseContainerSupport
Otimização de Rede
Service mesh overhead
Sem service mesh: latência = 5ms
Com Istio sidecar: latência = 8-12ms (+60-140%)
Quando vale a pena:
- Observabilidade distribuída
- mTLS obrigatório
- Traffic management complexo
Quando evitar:
- Latência ultra-baixa crítica
- Alto volume de requests internos
- Simplicidade é prioridade
DNS lookup
Cada request pode fazer DNS lookup:
Request → DNS lookup (2-5ms) → Connection → Response
Otimização:
# Configurar dnsPolicy
spec:
dnsPolicy: ClusterFirst
dnsConfig:
options:
- name: ndots
value: "2" # Reduz lookups desnecessários
Connection pooling
# ❌ Nova conexão por request
# TCP handshake + TLS = 50-100ms por request
# ✅ Pool de conexões
# Reutiliza conexões estabelecidas
Configuração de pool:
// Node.js com pool de conexões
const pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
Otimização de Storage
Tipos de storage e performance
emptyDir (memory): ~500MB/s
emptyDir (disk): ~100MB/s
hostPath: ~100MB/s
PersistentVolume: ~50-100MB/s (depende do provider)
Copy-on-Write overhead
Layers de imagem usam CoW:
# ❌ Muitos layers = muito CoW
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# ✅ Menos layers
RUN apt-get update && \
apt-get install -y package1 package2 && \
rm -rf /var/lib/apt/lists/*
Logs e performance
# ❌ Logs para stdout sem limite
# Disk I/O cresce indefinidamente
# ✅ Limitar logs do container
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
# Configurar log rotation no runtime
Docker daemon config:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Kubernetes-Specific Optimizations
Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-app
Topology Spread
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
Readiness vs Liveness
# Liveness: app está viva?
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
# Readiness: app pode receber tráfego?
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Erro comum:
# ❌ Readiness probe muito pesado
readinessProbe:
httpGet:
path: /health # Checa DB, cache, APIs externas
periodSeconds: 1 # A cada segundo!
# = DDoS em si mesmo
# ✅ Readiness leve e frequência adequada
readinessProbe:
httpGet:
path: /health/ready # Check básico
periodSeconds: 5
Graceful Shutdown
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
// Aplicação deve tratar SIGTERM
process.on('SIGTERM', async () => {
console.log('Received SIGTERM, shutting down gracefully');
await server.close();
await db.close();
process.exit(0);
});
Imagem Docker Otimizada
Multi-stage build
# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Runtime stage
FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "server.js"]
Imagem base
# ❌ Imagem pesada
FROM ubuntu:22.04 # ~77MB
# ✅ Imagem otimizada
FROM alpine:3.18 # ~7MB
# ✅ Distroless (ainda menor, mais seguro)
FROM gcr.io/distroless/nodejs:18 # ~40MB, sem shell
Startup time
Imagem grande (500MB): pull = 30-60s
Imagem otimizada (50MB): pull = 3-6s
Monitoramento de Performance
Métricas essenciais
# Container-level
- container_cpu_usage_seconds_total
- container_memory_usage_bytes
- container_network_receive_bytes_total
- container_fs_reads_bytes_total
# Application-level
- http_request_duration_seconds
- http_requests_total
- process_resident_memory_bytes
Dashboard mínimo
1. CPU Usage vs Request vs Limit
2. Memory Usage vs Request vs Limit
3. CPU Throttling
4. Pod Restarts
5. Network I/O
6. Disk I/O
Conclusão
Performance em containers depende de:
- Recursos corretos: requests e limits baseados em dados reais
- Evitar throttling: CPU throttling destrói latência
- Memória com headroom: OOM kills causam instabilidade
- Rede otimizada: DNS, connection pools, service mesh consciente
- Imagens enxutas: menos layers, menos tamanho, startup rápido
Antes de culpar o container:
- Verifique se há CPU throttling
- Confirme que não há OOM kills
- Analise métricas de rede
- Compare com baseline fora do container
Container é ferramenta, não vilão. Performance ruim em container geralmente é performance ruim amplificada.