Multi-instance e roteamento de tráfego

O Runner permite que a mesma app rode em múltiplas instâncias independentes — cada uma com domínio próprio, estado próprio e distribuição de tráfego controlada. Esta página cobre o modelo de instâncias, os comandos de peso e o canary automático.

Pré-requisitos

  • Runner v2.30.10+
  • App registrada com pelo menos uma instância configurada no .deploy.yml
  • traefik.mode: local (default) no config.yml do servidor — obrigatório para weighted routing/canary

O que é uma instância

Uma instância é um ambiente independente dentro da mesma app. Cada instância tem:

  • Domínio próprio (ex: app.exemplo.com.br, staging.exemplo.com.br)
  • Estado próprio (/opt/runner/state/<app>.yml separado)
  • Histórico de versões independente
  • O mesmo .deploy.yml como fonte de configuração

Exemplo de app com duas instâncias:

# .deploy.yml
instances:
  production:
    domain: app.exemplo.com.br
    source:
      type: dist
    keep_versions: 3

  staging:
    domain: staging.exemplo.com.br
    source:
      type: branch
      branch: dev
    keep_versions: 2

A instância production e a staging rodam de forma completamente independente. Um deploy em staging não afeta production.

Variáveis de ambiente por instância

Cada instância pode sobrescrever variáveis do bloco environment: global via environment_overrides:. A prioridade, da mais fraca pra mais forte, é:

.env.template (base do repositório)
    ↓
environment:            (global, no .deploy.yml)
    ↓
environment_overrides:  (por instância — maior prioridade)
# .deploy.yml
environment:
  DB_HOST: mysql
  LOG_LEVEL: info

instances:
  production:
    domain: app.exemplo.com.br
    environment_overrides:
      DB_HOST: mysql-prod
      LOG_LEVEL: warn

  staging:
    domain: staging.exemplo.com.br
    environment_overrides:
      DB_HOST: mysql-staging
      LOG_LEVEL: debug

Cada instância recebe seu próprio .envproduction fica com DB_HOST=mysql-prod, staging com DB_HOST=mysql-staging. Secrets seguem a mesma lógica de resolução, mas veja Secrets por instância para o isolamento adicional de ckey/bundle quando cada instância roda num servidor diferente.

Branch por instância

Cada instância pode acompanhar uma branch (ou tipo de source) diferente:

instances:
  production:
    source:
      type: dist       # consome artefatos compilados
  staging:
    source:
      type: branch
      branch: dev      # acompanha a branch dev diretamente

Registrando cada máquina numa instância

Numa topologia multi-ambiente (produção, staging, homolog em servidores separados), cada servidor registra a app apontando pra sua própria instância:

# Na máquina de produção
runner add --repo usuario/meu-app --branch main --instance production

# Na máquina de staging
runner add --repo usuario/meu-app --branch dev --instance staging

O cron runner fetch --deploy (igual em todas as máquinas) resolve qual instância deployar seguindo esta ordem de prioridade:

  1. state.instance (definido via --instance no add/edit)
  2. default_instance_name() do .deploy.yml (primeira instância declarada)
  3. "production" (fallback)

Pra trocar a instância de uma app já registrada:

runner edit meu-app --instance homolog

O comando valida que a instância existe no .deploy.yml antes de salvar.

Weight-based routing

Dentro de uma instância, o Runner pode distribuir tráfego entre duas versões ao mesmo tempo. Isso é feito via Traefik weighted services — sem reinicialização de containers.

Ver distribuição atual

runner weights <project> -i <instance>

Exemplo:

runner weights meu-app -i production

Saída típica durante canary:

Distribuicao de trafego (production):
  v1.0.0: 90%
  v1.1.0-rc1: 10%

Ajustar peso de uma versão

runner weight <project> <instance> <version> <pct>

O parâmetro <pct> é um inteiro de 0 a 100. O Runner recalcula o peso da outra versão automaticamente.

# Aumentar canary para 50%
runner weight meu-app production v1.1.0-rc1 50

# Saída:
# Peso atualizado:
#   v1.0.0: 50%
#   v1.1.0-rc1: 50%

Promover uma versão para 100%

runner promote <project> -i <instance> <version>

O promote faz três ações em sequência:

  1. Define peso 100% para a versão especificada
  2. Remove as outras versões do roteamento
  3. Limpa containers antigos (respeitando keep_versions)
runner promote meu-app -i production v1.1.0-rc1

Depois do promote, runner weights mostra apenas uma versão com 100%.

Canary auto-promotion (v1.2.0+)

O canary automático faz a progressão de pesos sem intervenção manual. O scheduler avança a cada tick configurado, desde que o healthcheck passe.

Configurar no .deploy.yml

instances:
  production:
    domain: app.exemplo.com.br
    canary:
      enabled: true
      auto_promote:
        enabled: true
        initial_weight: 10
        increment: 10
        interval: 5m
        final_weight: 100
        health_check_before: true
      abort_on:
        - health_check_failed
        - error_rate_above:5%

Ver status do scheduler

runner canary status meu-app -i production

Saída típica:

Canary Status: meu-app (production)
  Stable:  v1.0.0
  Canary:  v1.1.0-rc1
  Weight:  30%
  State:   running
  Next:    +5m -> 40%
  ETA:     35m para 100%

Controlar a progressão

# Pausar no peso atual (sem rollback)
runner canary pause meu-app -i production

# Retomar de onde parou
runner canary resume meu-app -i production

# Abortar e voltar 100% para a versão estável
runner canary abort meu-app -i production

O abort zera o peso do canary, restaura 100% para a versão estável e envia notificação (se configurado).

Integrar com CI/CD via sinais

O Runner aceita sinais externos de pipelines de CI para pausar ou abortar um canary antes de avançar o próximo step.

# Testes falharam — abortar canary
runner signal send meu-app --status fail --message "3 testes de integracao falharam"

# Testes passaram — liberar progressão
runner signal send meu-app --status ok --message "smoke tests passaram"

Ver runner signal para a referência completa de status e actions.

Padrão típico de canary manual

# 1. Verificar versão atual
runner versions meu-app -i production

# 2. Fazer deploy da nova versão (começa com 0% do tráfego)
runner deploy meu-app --version v1.1.0-rc1

# 3. Direcionar 10% do tráfego para o canary
runner weight meu-app production v1.1.0-rc1 10

# 4. Observar logs e métricas por ~15 minutos
runner logs tail meu-app
docker logs meu-app_production_v1.1.0-rc1 --tail 100

# 5. Se OK, aumentar para 50%
runner weight meu-app production v1.1.0-rc1 50

# 6. Aguardar mais ~30 minutos para produção crítica
# 7. Promover para 100% quando confiável
runner promote meu-app -i production v1.1.0-rc1

O que pode dar errado

Canary com `traefik.mode: docker_labels`

Labels Traefik por container não suportam weighted routing (elas reagem a start/stop do container, não a mudança de peso). Se o servidor estiver em traefik.mode: docker_labels (usado quando Traefik roda com network_mode: host), runner weight/runner promote continuam funcionando, mas sem distribuição gradual — o tráfego vai 100% pra última versão iniciada.

Solução: usar traefik.mode: local (default) no config.yml do servidor onde o canary roda.

Promote antes de validar

Usar promote muito cedo leva o tráfego completo para uma versão não validada. Não há desfazer automático — é necessário um novo deploy da versão anterior.

Prática recomendada: esperar pelo menos 15 minutos em 10% e 30 minutos em 50% antes de promover em produção crítica.

Canary em mudança de schema de banco

Canary não é adequado quando a nova versão exige schema diferente do banco. As duas versões compartilham o mesmo banco — se a nova migrou a tabela, a versão antiga quebra ao receber tráfego.

Alternativa: migração em dois passos — primeiro adicione a coluna como nullable (retrocompatível), depois faça o canary, depois remova o campo antigo.

Canary em breaking change de API

Se a nova versão muda contratos de API de forma incompatível, 10% dos usuários recebem a API nova enquanto 90% recebem a antiga. Isso quebra clientes que fazem múltiplas requests na mesma sessão.

Alternativa: versionamento de API (/v1/ e /v2/) ou instância staging separada para validação.

By Borlot.com.br on 27/05/2026