! O Problema
Plataformas AI multi-tenant têm características únicas que complicam infraestrutura tradicional: workloads altamente variáveis (um prompt pode levar 100ms ou 30s), dependência crítica de serviços externos (LLMs, WhatsApp), e a necessidade de isolar tenants enquanto compartilha recursos eficientemente.
Desafios Específicos
- •Spiky traffic: Campanhas de marketing podem 10x o tráfego em minutos — precisa escalar rápido e voltar sem desperdiçar recursos
- •Stateful services: Memory Engine e pgvector precisam de connection pools controlados — não é só "spawn more containers" e pronto
- •Redis as SPOF: Cache, sessions, queues, rate limiting — tudo passa pelo Redis. Precisa de HA real
- •12+ microservices: Orquestrar deploys coordenados sem breaking changes entre serviços de versões diferentes
- •Secrets sprawl: 40+ API keys de LLMs, webhooks, databases — precisam de rotação sem downtime
A solução foi construída iterativamente: começou com docker-compose monolítico, evoluiu para overlays por ambiente, depois scaling horizontal com nginx LB, e finalmente Redis Sentinel para HA. Cada passo foi motivado por necessidades reais de produção.
📦 Arquitetura de Containers
Dockerfiles seguem padrões production-grade: multi-stage builds para imagens menores, usuários non-root para segurança, BuildKit cache mounts para builds rápidos, e health checks que realmente testam a aplicação (não só se o processo existe).
# Multi-stage build with Python 3.12-slim
FROM python:3.12-slim as base
# Non-root user for security (CVE mitigation)
RUN useradd --create-home --shell /bin/bash memory
USER memory
# BuildKit cache mount - doesn't re-download wheels on each build
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-cache-dir -r requirements.txt
# Health check that tests real endpoint, not just process
HEALTHCHECK --interval=30s --timeout=15s --start-period=60s --retries=5 \
CMD curl -f http://localhost:8050/health || exit 1
# Startup script (not direct CMD) for init logic
CMD ["/app/scripts/start-memory-engine.sh"]Docker Compose Overlays
Em vez de um docker-compose.yml monolítico, usamos overlays que compõem configurações. Isso permite que desenvolvimento, staging e produção compartilhem a base mas customizem o que precisam — sem duplicação e sem drift acidental.
docker-compose.yml
Base: todos os serviços, volumes, networks. Configuração comum que funciona em qualquer ambiente.
docker-compose.scale.yml
Overlay: nginx-lb, job de migrations separado, portas removidas (tudo via LB), WEB_CONCURRENCY configurável.
docker-compose.sentinel.yml
Overlay: topologia Redis master/replica/sentinel para HA. Serviços recebem vars REDIS_SENTINEL_*.
docker-compose.dev.yml
Overlay: volumes de código montados, debug habilitado, hot reload, portas expostas diretamente.
# Local development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production with scaling
docker compose -f docker-compose.yml -f docker-compose.scale.yml up \
--scale ai-engine=3 \
--scale backend-orchestrator=2
# Production with full HA
docker compose \
-f docker-compose.yml \
-f docker-compose.scale.yml \
-f docker-compose.sentinel.yml \
up -dResource Limits & Reservations
redis:
image: redis:8.0-alpine
command:
- redis-server
- --maxmemory 2gb
- --maxmemory-policy allkeys-lru # Automatic eviction
- --activedefrag yes # Background defrag
- --latency-monitor-threshold 100
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
labels:
- "com.optimus.service=redis"
- "com.optimus.environment=production"📈 Scaling Horizontal
Sistema de scaling que vai de 1 a N instâncias sem mudança de código. Load balancer nginx distribui tráfego, session affinity via Redis, e health checks determinam routing.
Arquitetura de Scaling
Nginx Load Balancer
# Docker internal DNS resolver - CRITICAL for --scale
resolver 127.0.0.11 ipv6=off;
upstream ai_engine {
zone ai_engine 64k;
least_conn; # Request goes to instance with fewest connections
# 'resolve' enables dynamic DNS - new replicas are discovered
server ai-engine:8010 resolve;
keepalive 32; # Connection pooling for backends
}
upstream backend_orchestrator {
zone backend_orchestrator 64k;
least_conn;
server backend-orchestrator:8020 resolve;
keepalive 32;
}
# Backend Orchestrator needs WebSocket for real-time dashboard
server {
listen 8020;
location / {
proxy_pass http://backend_orchestrator;
# WebSocket upgrade headers
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Longer timeouts for WebSocket
proxy_read_timeout 300s;
}
}
# Mapping for connection upgrade
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}Migration Job Isolado
Quando você escala serviços, todos tentam rodar migrations ao iniciar — race condition garantida. A solução foi um job dedicado que roda ANTES das replicas subirem, usando depends_on: condition: service_completed_successfully.
# Single migrations job (runs once, exits)
migrations:
extends:
file: docker-compose.yml
service: memory-engine
restart: "no"
ports: [] # No exposure
environment:
- RUN_MIGRATIONS_ONLY=true
command: >
bash -c "
python /app/scripts/wait-for-postgres.py &&
python -m alembic upgrade head &&
echo '✅ Migrations completed'
"
# Replicas only come up after migrations finish
memory-engine:
extends:
file: docker-compose.yml
service: memory-engine
environment:
- SKIP_STARTUP_BOOTSTRAP=true # Don't attempt migrations
- RUN_MIGRATIONS=false
depends_on:
migrations:
condition: service_completed_successfullyScale-Up Script
./scripts/scale-up.sh \
--scale ai-engine=3 \
--scale backend-orchestrator=2 \
--scale memory-engine=2 \
--build ai-engine \ # Rebuild only what changed
--run-migrations \ # Force migrations
--observability \ # Enable Tempo/Loki/Alloy
--logs on # Tail instance logs
# Scale down (keeps volumes)
./scripts/scale-up.sh --down
# Scale down + cleanup network
./scripts/scale-up.sh --down --prune-network⚠️ Considerações Stateful
Memory Engine é stateful — mantém connection pools com PostgreSQL. Escalar de 2 para 6 replicas cria 6x mais conexões ao banco. Por isso configuramos DB_POOL_SIZE e DB_MAX_OVERFLOW via env vars, permitindo ajuste dinâmico: DB_POOL_SIZE=5 DB_MAX_OVERFLOW=10 = máx 15 conexões/replica. DB_POOL_SIZE=5 DB_MAX_OVERFLOW=10 = max 15 connections/replica.
🛡️ Alta Disponibilidade
Redis Sentinel para failover automático. Três sentinels monitoram o master e promovem replica automaticamente em caso de falha.
Topologia Sentinel
# Redis Master with replication enabled
redis:
image: redis:8.0-alpine
hostname: redis-master
command:
- redis-server
- --appendonly yes # Durability (AOF)
- --lazyfree-lazy-eviction yes
- --replica-announce-ip redis-master
# Replicas point to master
redis-replica-1:
command:
- redis-server
- --replicaof redis-master 6379
- --replica-announce-ip redis-replica-1
# Sentinels monitor and perform failover
redis-sentinel-1:
command:
- redis-sentinel
- /etc/redis/sentinel.conf
- --port 26379
# Services use Sentinel mode
ai-engine:
environment:
- REDIS_SENTINEL_ENABLED=true
- REDIS_SENTINEL_MASTER=mymaster
- REDIS_SENTINEL_HOSTS=sentinel-1:26379,sentinel-2:26379,sentinel-3:26379Failover Automático
- 1Master fica inacessível (crash, network partition, ou qualquer falha)
- 2Sentinels detectam (configurable timeout, default 30s)
- 3Quorum de 2 sentinels concorda que master está down
- 4Eleição de novo master entre replicas (baseia-se em replication offset)
- 5Sentinels atualizam configuração; clientes reconectam automaticamente
✓ Por que 3 Sentinels?
Com quorum de 2, você precisa de no mínimo 3 sentinels para tolerar 1 falha. Se tiver só 2 sentinels e 1 falhar, não atinge quorum e failover não acontece. Número ímpar evita split-brain (empate de votos).
⚙️ CI/CD Pipeline
GitHub Actions com 12 workflows especializados. Cada PR passa por lint, type check, testes unitários, e validação de arquitetura antes de merge.
ci.yml
Ruff lint, mypy type-check, pytest, Docker build. Roda em todo push/PR.
guardrails.yml
Validação de arquitetura. Bloqueia PRs que violam patterns definidos em policy.yaml.
e2e-tests.yml
Testes end-to-end com pgvector + Redis reais. Valida fluxos multi-ERP.
llm-eval.yml
Avaliação de qualidade de outputs do AI Engine. Detecta regressões em prompts.
Architecture Guardrails
O sistema de guardrails usa um MCP server que analisa diffs e valida contra regras. Se um PR viola um pattern (ex: import direto entre camadas, SQL raw em controllers), o workflow falha e posta um comentário detalhado no PR.
# Runs only on PRs that touch Python code
on:
pull_request:
paths:
- 'ai-engine/**/*.py'
- 'backend-orchestrator/**/*.py'
- 'memory-engine/**/*.py'
jobs:
validate:
steps:
- name: Generate diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr.diff
- name: Validate guardrails
run: |
python mcp_servers/optimus_project_mcp/cli_validate_diff.py < pr.diff
# If fails, post comment on PR with violations
- name: Comment on PR (if violations)
if: failure()
uses: actions/github-script@v7
with:
script: |
const violations = JSON.parse(fs.readFileSync('guardrails-result.json'));
github.rest.issues.createComment({
issue_number: context.issue.number,
body: formatViolations(violations)
});E2E Tests com Services
jobs:
e2e-tests:
runs-on: ubuntu-latest
timeout-minutes: 15
# Real containers as GitHub Actions services
services:
postgres:
image: pgvector/pgvector:0.8.1-pg16
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-retries 5
redis:
image: redis/redis-stack:latest
options: >-
--health-cmd "redis-cli ping"
steps:
- name: Run migrations
run: python -m alembic upgrade head
- name: Run E2E tests
run: |
pytest tests/e2e/ -v --tb=short \
--cov=ai_engine --cov-report=xml🔐 Segurança
Múltiplas camadas de segurança: secrets management, network isolation, e scanning automatizado.
./secrets-manager.sh init # Create .env.secrets from example
./secrets-manager.sh validate # Verify all are filled
./secrets-manager.sh backup # Encrypted backup (GPG)
./secrets-manager.sh restore # Restore from backup
./secrets-manager.sh rotate # Interactive key rotation
./secrets-manager.sh check-security # Audit for issuesSeparação de Secrets
.env.secrets
API keys, passwords, tokens. Nunca commitado. Gitignore'd.
.env.models
Configurações de modelos LLM. Pode ser commitado (sem keys).
.env.example
Template com placeholders. Commitado para documentação.
Container Security
- ✓Non-root users: Todos os containers rodam como usuário não-root (appuser:1001 ou similar). Mitiga privilege escalation.
- ✓Slim base images: python:3.12-slim em vez de full. Menos pacotes = menos superfície de ataque.
- ✓Read-only configs: Arquivos de config montados como :ro (read-only). Containers não podem modificar.
- ✓Network isolation: Rede bridge dedicada (optimus). Services só acessíveis via LB.
- ✓Internal Memory Engine: Porta 8050 não exposta ao host — só acessível via nginx-lb internamente.
🔧 Operações
Ferramentas de operação para gerenciar o ciclo de vida dos serviços.
scale-up.sh
Scaling completo com build, migrations, e tail de logs.
--scale, --build, --observability, --attachvalidate_redis.sh
Health check de Redis: ping, memory, connected clients.
redis-cli info, dbsize, latencyrun_load_tests.sh
Load testing com k6 ou locust. Gera relatório.
--users, --duration, --endpointtrace_smoke_test.sh
Valida tracing E2E: gera trace, verifica no Tempo.
opentelemetry, tempo, span verificationHealth Checks Úteis
# Via load balancer
curl http://localhost:8010/health # AI Engine
curl http://localhost:8020/health # Backend Orchestrator
# Container status
docker compose -f docker-compose.yml -f docker-compose.scale.yml ps
# Check LB routing (DNS resolution)
docker exec nginx-lb nslookup ai-engine
docker exec nginx-lb nslookup backend-orchestrator
# Redis health
docker exec optimus_redis_8_production redis-cli ping
docker exec optimus_redis_8_production redis-cli info memory📊 Resultados
Lições Aprendidas
- →Compose overlays > monolítico: Manutenção muito mais fácil quando cada concern está isolado
- →Migrations job separado: Evita race conditions e permite escalar replicas livremente
- →Health checks reais: Testar o endpoint /health, não só se o processo existe
- →DNS dinâmico no LB: Sem isso, nginx não descobre novas replicas
- →Sentinel quorum ímpar: 3 sentinels com quorum 2 é o mínimo para HA real
Stack Técnico
Explore Outros Case Studies
Veja como outros componentes do Optimus foram construídos