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 também 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 com 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, 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 vira uma entrada num arquivo, não um procedimento.

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ã. 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. 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