Pular para conteúdo

Rollout canário do auth native — JVM + native lado-a-lado

Documento obsoleto

Status: Obsoleto · Responsável: Gustavo Madruga · Atualizado em: 2026-09-14

Obsoleto

O corte terminou: o central-backend é native-only e o pipeline.yml não builda mais o jar (decisão 0030). A página fica como registro do procedimento; rollback hoje é o redeploy da tag imutável <sha>-native (Deploy).

Pré-requisito: o e2e native (etc/tests/native/e2e-central-backend) verde e a decisão 0018. Este runbook é o corte em produção.

O auth é o SPOF do ecossistema — não se corta 100% pra native num passo. A estratégia: subir a imagem native ao lado do JVM atual, com o Traefik do Coolify dividindo o tráfego por peso (weighted round-robin); validar o native em produção com uma fração do tráfego; quando 100% ok, desligar o JVM e seguir native-only (rollbacks futuros passam a ser o rollback normal de container do Coolify).

⚠️ Validar na tua VM antes do corte real. Os nomes de serviço Traefik e o suporte a weighted entre dois recursos Coolify dependem da versão do Coolify/Traefik. Rode os passos de inspeção (§1) e teste os pesos com um recurso de baixo risco antes de aplicar no auth. Coolify não tem UI de canário — isto é config dinâmica do Traefik, aplicada por arquivo.

0. Topologia

  • Recurso A — auth (JVM): o que já existe, roteando auth.xadm.biz. Build Pack = Dockerfile (o Coolify builda o repo).
  • Recurso B — auth-native: novo recurso Coolify, mesma env do A (chaves, DB, cipher, firebase), Build Pack = Docker Image apontando pra imagem native pronta no registry (§Build da imagem).
  • Traefik: em vez de cada recurso dono do router de auth.xadm.biz (conflito), um router no arquivo dinâmico aponta pra um serviço weighted que balanceia entre A e B.

Build da imagem native (por que difere do JVM)

O native-image come ~6 GB de RAM + minutos de CPU — buildar no Coolify trava a VM de produção (o coolify-prod-vm-freeze que motivou o e2e native off-host). Então, ao contrário do JVM (que o Coolify builda do repo), a imagem native é buildada fora e o Coolify só puxa a imagem pronta.

O método é o Dockerfile.native hand-written na raiz do repo (builder native-image-community:25i2-ol8 → ./gradlew nativeCompile -PnativeCI → runtime ubuntu:24.04), buildado e publicado pela CI — não mais ./gradlew dockerBuildNative (plugin/wolfi). Quem builda e empurra é o workflow do GitHub .github/workflows/build-native.yml (dispara em tags v*, publish-only, não faz deploy):

# a CI (GitHub) builda o Dockerfile.native e publica no registry:
#   fonte.xadm.biz/xadm/central-backend:native-amd64   (+ :<sha>, e espelho ghcr.io)
# Coolify (Recurso B): Build Pack = Docker Image = fonte.xadm.biz/xadm/central-backend:native-amd64
#
# Para reproduzir/testar a MESMA imagem localmente (onde tiver Docker):
docker build -f Dockerfile.native -t central-backend:native-test .

Roda no runner GitHub (native-image é pesado, ~6 GB de builder; o runner hospedado aguenta, o Forgejo self-hosted não — o .forgejo/workflows/ci.yml segue como gate JVM). O Recurso B só troca qual tag puxa; o fluxo de deploy não muda.

1. Inspecionar os serviços Traefik (root da VM do Coolify)

# nomes dos serviços/routers que o Coolify gerou por container
docker exec coolify-proxy traefik version
ls -la /data/coolify/proxy/dynamic/
# lista os serviços conhecidos pelo Traefik (API interna, se habilitada)
docker exec coolify-proxy wget -qO- http://localhost:8080/api/http/services 2>/dev/null | tr ',' '\n' | grep -i auth
# ou pelos labels dos containers
docker inspect $(docker ps -q --filter name=auth) --format '{{.Name}} {{json .Config.Labels}}' | grep -i traefik

Anote os nomes de serviço dos dois containers (algo como auth-<id> e auth-native-<id>, com sufixo @docker).

2. Definir o serviço weighted + router (arquivo dinâmico)

Crie /data/coolify/proxy/dynamic/auth-canary.yml (ajuste os name: aos anotados no §1):

http:
  routers:
    auth-canary:
      rule: "Host(`auth.xadm.biz`)"
      entryPoints: ["https"]
      tls:
        certResolver: letsencrypt      # o mesmo resolver que os outros recursos usam
      service: auth-canary

  services:
    auth-canary:
      weighted:
        services:
          - name: "auth-<id>@docker"        # Recurso A (JVM)
            weight: 90
          - name: "auth-native-<id>@docker" # Recurso B (native)
            weight: 10

Importante: desabilite o router de domínio que o Coolify cria automaticamente em CADA recurso (senão dois routers disputam auth.xadm.biz). No Coolify, em cada recurso, use um domínio interno/placeholder (ex.: auth-a.internal, auth-b.internal) para o Traefik ainda criar o serviço de cada um, mas o router público de auth.xadm.biz ser só o do arquivo acima. Confirme que só um router casa o Host: docker exec coolify-proxy wget -qO- http://localhost:8080/api/http/routers | tr ',' '\n' | grep auth.xadm.biz.

O Traefik recarrega o arquivo dinâmico sozinho (watch). Sem restart.

3. Escalar o peso (validação progressiva)

Comece 90/10, observe, suba. Editar o weight: e salvar basta (hot-reload):

# ex.: 50/50 — editar auth-canary.yml e trocar os weights, depois:
docker exec coolify-proxy wget -qO- http://localhost:8080/api/http/services | tr ',' '\n' | grep -A2 auth-canary

A cada degrau (10 → 50 → 100%), monitorar (§4). Sinal ruim em qualquer degrau → rollback (§5).

4. Monitoração (o que olhar durante o canário)

  • Emissão/validação de JWT: logs do auth-native — Login pAbast OK, ausência de 500/INTERNAL_SERVER_ERROR em /api/auth/**, /.well-known/jwks.json e /api/apps/**. O PowerSync cacheia o JWKS — a chave é a mesma (mesmo kid=auth-1), então não há invalidação.
  • GlitchTip (bug.xadm.biz): picos de erro do recurso auth-native.
  • RSS/boot: docker stats do container native (esperado ~43 MiB, boot ~1 s).
  • Comparar A vs B: as respostas dos dois recursos devem ser idênticas (mesmo contrato).
docker logs -f $(docker ps -q --filter name=auth-native) | grep -iE "login|error|exception|500"

5. Rollback imediato

Voltar o peso do native a 0 (ou remover o serviço B do weighted) — hot-reload, sem deploy:

# editar auth-canary.yml: weight do auth-native -> 0  (ou weight do JVM -> 100 e remover B)
# Traefik recarrega sozinho; todo o tráfego volta pro JVM em segundos.

Se o arquivo dinâmico em si for o problema, rm /data/coolify/proxy/dynamic/auth-canary.yml e reabilitar o router de domínio no Recurso A (JVM) pela UI do Coolify.

6. Collapse (native-only)

Native em 100% e estável por uma janela combinada:

  1. Reabilitar auth.xadm.biz direto no Recurso B (native) pela UI do Coolify (router próprio).
  2. Remover /data/coolify/proxy/dynamic/auth-canary.yml.
  3. Desligar/remover o Recurso A (JVM).
  4. A partir daqui, rollback = o rollback normal de container/deploy do Coolify no Recurso B.

Notas

  • Build da imagem native: off-host + push pro registry do Forgejo (ver §Build da imagem). O Coolify não compila native no deploy (build-spike travaria a VM).
  • Env idêntica: o Recurso B precisa das MESMAS AUTH_PRIVATE_KEY/AUTH_PUBLIC_KEY, AUTH_CIPHER_KEY, DATASOURCES_*, AUTH_FIREBASE_PROJECT_ID. AUTH_FIREBASE_JWKS_URL fica no default (Google) — o override só existe pro e2e.