RAFAEL REIS case study
platform engineering · alta disponibilidade

Um time de plataforma de uma pessoa só

TL;DR

Stack: Proxmox · Docker · control-plane GitOps · PostgreSQL/Patroni · MongoDB replica sets · Redis Sentinel · Traefik · Cloudflare · GitHub Actions

O contexto: quando o time de plataforma é você

Num SaaS bootstrapped não existe "abre um ticket pra infra". Eu sou a infra — e o SRE, o DBA, o time de segurança e o plantão. Isso muda o critério de toda decisão técnica: cada componente que entra na arquitetura é um componente que eu vou fazer patch, monitorar, e pelo qual vou ser acordado.

A disciplina que emerge disso tem nome: superfície mínima. Não é minimalismo estético — é sobrevivência. As seções abaixo são a mesma decisão aplicada em camadas diferentes.

A forma do problema: 60+ tenants, ≈200 containers

Cada tenant — uma revenda com seu próprio domínio — é materializado como um conjunto próprio de containers (site, API, backoffice), todos gerados do mesmo template versionado, sobre um plano de dados compartilhado (PostgreSQL + MongoDB) com isolamento por tenant na camada de aplicação. O roteamento é por domínio, no reverse proxy, e o DNS da frota inteira é reconciliado automaticamente contra um registro-âncora — tenant novo entra num manifesto, e o resto acontece.

a forma da frotainterativo: passe o mouse por um tenant
TRAEFIK · roteamento por domínio dominio-da-revenda.com.br → containers do tenant tenant 1 site · api backoffice tenant 2 site · api backoffice tenant 3 site · api backoffice ×60+ PostgreSQL · MongoDB · Redis plano de dados compartilhado · isolamento por tenant na aplicação HA cross-DC (ver o case da migração)
o template é um só, versionado · tenant novo = 1 entrada no manifesto

O trade-off é consciente: containers por tenant custam mais memória que um monolito multi-tenant puro, mas compram isolamento de falha (um tenant enlouquecido não derruba os vizinhos), deploy e rollback por tenant, uma frota que um humano consegue raciocinar a respeito — e simplicidade no edge: cada revenda usa o próprio domínio comercial apontando direto para o seu conjunto de containers, sem uma camada complexa de proxy e rewrite para multiplexar um app único entre dezenas de domínios de terceiros.

Alta disponibilidade que não depende de mim acordado

A produção roda em dois data centers geo-separados em hot-standby: PostgreSQL com Patroni (auto-failover), MongoDB replica sets com árbitro num terceiro ponto neutro, Redis Sentinel, réplicas assíncronas — RPO de segundos. Na frente, um load balancer cujo health check só responde 200 no data center onde o banco é o líder gravável: o tráfego segue o failover do banco sozinho, com detecção em ~1–2 minutos, sem intervenção humana.

A história completa de como a produção chegou nesses dois DCs — com zero downtime — é o case da migração. Aqui, o ponto é outro: failover automático é o que permite um time de um dormir.

A plataforma interna: GitOps sem cerimônia

O deploy da frota é governado por GitOps, via um control-plane leve — ferramenta open-source adotada de propósito (manager central + agentes por VM, rede zero-trust). Superfície mínima vale também pra isso: não reinvento commodity; a engenharia própria vai onde ferramenta pronta não resolve — o manifesto que materializa a frota, o reconciliador de DNS, o pré-aquecedor de failover e a observabilidade sob medida:

Antes disso havia uma UI de gestão de containers apontando e clicando. Substituí porque UI não tem diff, não tem history, não tem revert — git tem os três de graça.

CI no GitHub Actions (builds multi-arch), imagens em registry próprio, e a frota de tenants provisionada por manifesto + scripts versionados com timer — tenant novo não é um procedimento, é uma entrada num arquivo.

Por que NÃO Kubernetes (ainda)

A pergunta que todo mundo faz. A resposta curta: Kubernetes resolve problemas de escala de organização que eu não tenho, a um custo de operação que eu pagaria sozinho.

A versão longa: o que eu preciso de um orquestrador é desired state, reconcile, drift detection, rollback e roteamento — e isso o control-plane GitOps + Docker + reverse proxy entregam com uma fração das partes móveis. O plano de controle do Kubernetes é, ele próprio, um sistema distribuído que alguém precisa operar, atualizar e entender às 3h da manhã — e aqui esse alguém tem nome e sobrenome. Minha frota é estável e previsível (tenants não têm elasticidade de Black Friday), então scheduling dinâmico e autoscaling — o coração do valor do K8s — ficariam ociosos.

E os problemas realmente difíceis dessa operação — failover de banco cross-DC, replicação, cutover de DNS/LB, backup imutável — vivem abaixo e acima do orquestrador. Kubernetes não resolveria nenhum deles por mim; eu só passaria a operá-los e ao Kubernetes.

O "(ainda)" é honesto: em escala maior, ou com um time, a conta muda — e conhecer exatamente qual problema o K8s me resolveria (porque resolvi cada um na unha) é justamente o que me faz saber quando adotá-lo.

Pelo mesmo princípio, sem Terraform e sem Ansible: as VMs nascem de template cloud-init + scripts versionados no repo, direto na API do hypervisor. Para 2 hypervisors e uma dúzia de VMs, uma camada declarativa extra é mais superfície pra manter — a decisão registrada em ADR foi "uma forma só de gerenciar, menos superfície". IaC é o princípio; a ferramenta se escolhe pelo tamanho do problema.

Observabilidade: os olhos que substituem um time

Aqui foi o inverso do control-plane: construída sob medida, porque observabilidade genérica não sabe o que é um tenant. Se uma pessoa opera a frota, a frota precisa se reportar sozinha:

E a lista de probes se gera sozinha do manifesto de tenants: tenant novo entra no monitoramento sem ninguém lembrar de adicioná-lo. Automação que depende de memória humana não é automação.

a frota, materializada do manifesto
tenants: 60+ · containers: ≈200 · probes externos: 180+
cada chip = 1 revenda (site · api · backoffice) do mesmo template versionado · tenant novo = 1 entrada no manifesto

Backup em camadas (porque réplica não é backup)

Réplica assíncrona protege contra perda de hardware — e replica fielmente o seu DELETE errado. Por isso, camadas independentes:

backup em camadaspasse o mouse: o que cada camada cobre
réplica viva cross-DC RPO segundos snapshots ZFS locais, a cada 15 min RPO 15 min backup diário cifrado das VMs · restore medido em ~20 min RPO 24h off-site em 2 destinos · object-lock imutável + dump lógico do banco de receita, de hora em hora RPO catástrofe 1h
cada camada cobre a falha que a de cima não cobre · réplica não é backup

A conta no fim do mês

Tudo isso — dois data centers, HA real, observabilidade, backups imutáveis — roda em dois servidores bare-metal e um punhado de serviços gerenciados baratos, por uma fração do que a mesma pegada custaria em cloud gerenciada. Hot-standby em vez de ativo-ativo foi decisão de custo consciente: metade do hardware "parado" é o prêmio do seguro — e ainda assim sobrou headroom pra frota dobrar.

Cost optimization aqui não é FinOps de dashboard: é arquitetura que cabe no negócio.

Lições

Outros cases

Migrando 50+ tenants em produção entre data centers, com zero downtime Réplica quente, cutover atômico, e prova por captura de pacotes — não por otimismo. 12 anos de homelab: meu stack de IA self-hosted LLM local em GPU, RAG híbrido, e um agente que pentesta o meu próprio SaaS.

Sempre aberto a trocar ideia com quem opera infraestrutura de verdade.

LinkedIn