tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP

Deploy & DevOps · Por · Publicado em · 8 min de leitura

Mapa de papel com alfinetes e fios ligando pontos, mostrando conexões entre locais
Representa redes e roteamento de tráfego entre serviços Foto: TheAndrasBarta / Pixabay

Tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP. Você verá como rotear por header/cookie para forçar a versão canary, balancear o restante por pesos e ajustar o rollout com segurança, tudo sem Kubernetes.

O que vamos construir e quando usar Canary

Você vai rodar duas versões do app PHP (stable e canary) atrás do Traefik. Pedidos com header/cookie irão 100% para a versão canary; o restante seguirá um balanceamento ponderado (weights). Isso permite validar em produção com baixo risco.

Use Canary quando precisa testar mudanças reais com parte do tráfego. Para troca instantânea entre versões idênticas de infraestrutura, veja o deploy blue‑green.

Se ainda está escolhendo reverse proxy para Docker, compare em Traefik vs Nginx.

  • Direcionar tráfego por header ou cookie para canary
  • Balancear o restante do tráfego com pesos (ex.: 95/5)
  • Uma única rota (Host) para o consumidor

Pré‑requisitos

Você precisa de um host Linux com Docker e Docker Compose instalados, acesso root para editar /etc/hosts e a porta 80 liberada. Conhecer redes Docker e labels do Traefik ajuda.

Não usaremos TLS para simplificar. Em produção, adicione HTTPS e hardening. Compare opções em /comparativo-traefik-vs-caddy-https-docker.

  • Docker e Docker Compose instalados
  • Porta 80 disponível no host
  • Editor de texto e terminal
  • Acesso sudo para editar /etc/hosts

Criar o app PHP de exemplo (stable e canary)

Faremos um app PHP simples, servido pelo servidor embutido do PHP, para demonstração. Não use o servidor embutido em produção. O código expõe / e /health e diferencia versões por variável de ambiente. O comando abaixo adiciona uma linha ao /etc/hosts.

mkdir -p app

cat > app/index.php <<'PHP'
<?php
header('Content-Type: application/json');
echo json_encode([
  'version' => getenv('VERSION') ?: 'unknown',
  'hostname' => gethostname(),
  'time' => date('c'),
  'path' => $_SERVER['REQUEST_URI'] ?? '/'
]);
PHP

cat > app/health.php <<'PHP'
<?php
http_response_code(200);
echo "ok";
PHP

# Mapear domínio local para testes (requer sudo)
echo '127.0.0.1 app.local' | sudo tee -a /etc/hosts

Definir docker-compose.yml com Traefik, headers e weights

Criaremos o Traefik como reverse proxy, dois serviços do app (stable e canary) e as regras: um router de alta prioridade para header/cookie e um router padrão com weighted routing.

Este arranjo implementa, na prática, o tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP.

version: '3.9'

services:
  traefik:
    image: traefik:v2.10
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --accesslog=true
      - --log.level=INFO
    ports:
      - '80:80'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - web
    labels:
      - traefik.enable=true

      # Router canary por header/cookie (prioridade alta)
      - traefik.http.routers.app-canary.rule=Host(`app.local`) && (Headers(`X-Canary`,`1`) || Cookie(`canary`,`1`))
      - traefik.http.routers.app-canary.entrypoints=web
      - traefik.http.routers.app-canary.priority=1000
      - traefik.http.routers.app-canary.service=app-canary@docker

      # Router padrão (Host) usando weighted routing
      - traefik.http.routers.app.rule=Host(`app.local`)
      - traefik.http.routers.app.entrypoints=web
      - traefik.http.routers.app.service=app-weighted

      # Services ponderados: comece 100% stable, 0% canary
      - traefik.http.services.app-weighted.weighted.services.stable.name=app-stable@docker
      - traefik.http.services.app-weighted.weighted.services.stable.weight=100
      - traefik.http.services.app-weighted.weighted.services.canary.name=app-canary@docker
      - traefik.http.services.app-weighted.weighted.services.canary.weight=0

  app-stable:
    image: php:8.2-cli-alpine
    working_dir: /app
    command: sh -c 'php -S 0.0.0.0:8000 -t /app'
    environment:
      - VERSION=stable
    volumes:
      - ./app:/app:ro
    networks:
      - web
    labels:
      - traefik.enable=true
      - traefik.http.services.app-stable.loadbalancer.server.port=8000
      - traefik.http.services.app-stable.loadbalancer.healthcheck.path=/health
      - traefik.http.services.app-stable.loadbalancer.healthcheck.interval=5s

  app-canary:
    image: php:8.2-cli-alpine
    working_dir: /app
    command: sh -c 'php -S 0.0.0.0:8000 -t /app'
    environment:
      - VERSION=canary
    volumes:
      - ./app:/app:ro
    networks:
      - web
    labels:
      - traefik.enable=true
      - traefik.http.services.app-canary.loadbalancer.server.port=8000
      - traefik.http.services.app-canary.loadbalancer.healthcheck.path=/health
      - traefik.http.services.app-canary.loadbalancer.healthcheck.interval=5s

networks:
  web: {}

Subir ambiente e validar rotas por header/cookie

Suba os containers e faça requisições de teste. Primeiro, valide a rota padrão (Host). Depois, force o tráfego para canary com header e com cookie. Se usa firewall local, libere a porta 80.

docker compose up -d

# Rota padrão (deve ir para stable)
curl -s http://app.local/

# Forçar canary pelo header X-Canary=1
curl -s -H 'X-Canary: 1' http://app.local/

# Forçar canary por cookie canary=1
curl -s --cookie 'canary=1' http://app.local/

# Conferir health dos serviços
curl -s -I http://app.local/health

Rollout gradual: ajustar weights e aplicar

Edite o docker-compose.yml e ajuste os pesos. Uma sequência comum: 99/1, 95/5, 90/10, 75/25 e 50/50, monitorando métricas e erros entre passos. Sempre garanta compatibilidade de banco e schema.

  • Aumente o weight do canary gradualmente
  • Monitore erros, latência e saturação
  • Reduza para zero em caso de falhas (rollback)
# Exemplo: mudar para 90% stable, 10% canary
# No serviço traefik, ajuste as labels:
# - traefik.http.services.app-weighted.weighted.services.stable.weight=90
# - traefik.http.services.app-weighted.weighted.services.canary.weight=10

# Aplique sem recriar os apps
docker compose up -d --no-deps traefik

# Gere amostras e observe distribuição
for i in $(seq 1 20); do curl -s http://app.local/ | grep -o '"version":"[a-z]*"'; done | sort | uniq -c

Sessões, healthchecks e observabilidade

Sessões de usuário: o balanceamento ponderado pode alternar entre stable e canary. Se seu app exige afinidade, use roteamento por cookie para usuários específicos ou armazene sessões externamente, como em Redis.

Healthchecks HTTP foram configurados em /health. Só containers saudáveis recebem tráfego. Para acompanhar, verifique os logs de acesso do Traefik e métricas da aplicação.

Centralize logs estruturados para depurar mais rápido. Veja logs JSON e correlação e, para dashboards, confira Prometheus e Grafana com Docker Compose.

  • Afinidade por cookie para grupos piloto
  • Healthchecks bloqueiam instâncias doentes
  • Logs e métricas para feedback rápido
# Acompanhar acessos direcionados pelo Traefik
docker logs -f traefik | grep app.local

Confirmações finais, próximos passos e erros comuns

Confirme que: 1) requisições com header/cookie caem sempre na versão canary; 2) a rota padrão respeita os weights; 3) o rollback zera o weight do canary. Automatize a troca de pesos na sua pipeline de CI, validando métricas entre etapas.

Para funcionalidades arriscadas, combine Canary com flags no código. Veja ideias em feature flags em monólitos PHP.

Host não resolve? Verifique se app.local está em /etc/hosts. Porta 80 ocupada? Pare o serviço que a usa. Sem efeito dos weights? Confirme que as labels estão no serviço traefik e reexecute o up. Header/cookie ignorados? Ajuste a prioridade do router canary.

Sessões perdidas entre versões: use cookie para rotear sempre para canary durante o teste, ou armazene sessões externamente. Erros 5xx em alta? Faça rollback imediato (weight=0) e investigue logs e healthchecks.

  • /etc/hosts e portas corretas
  • Labels no serviço traefik, não nos apps
  • Prioridade alta no router canary (priority)
  • Rollback rápido se aparecerem erros 5xx

Perguntas frequentes

Posso fazer Canary sem Kubernetes?

Sim. Traefik com Docker Compose oferece roteamento por headers/cookies e weighted routing. É suficiente para muitos cenários. Você controla o fluxo entre stable e canary por labels e rede Docker, sem a complexidade do Kubernetes.

Como rotear por cookie ou header no Traefik?

Crie um router com prioridade alta e uma regra combinando Host com Headers ou Cookie. Direcione esse router para o serviço canary. O restante do tráfego cai no router padrão, que usa weighted routing para distribuir entre stable e canary.

E as sessões de usuário durante o canary?

Como o tráfego pode alternar entre versões, sessões locais podem se perder. Use cookie para forçar canary para usuários selecionados, ou armazene sessões em um backend compartilhado, como Redis, permitindo alternância sem perda de estado.

Como automatizar o rollout gradual?

No pipeline de CI, altere os valores de weight nas labels do Traefik e faça um redeploy apenas do serviço traefik. Entre cada incremento, valide métricas e erros. Se algo sair do esperado, reduza o weight do canary para zero.

Como lidar com mudanças de banco de dados?

Mantenha compatibilidade entre versões. Primeiro, evolua o schema de forma compatível (expandir), depois o código; só então remova campos antigos (contrair). Para mudanças arriscadas de comportamento, combine com feature flags no app.

Quer adaptar Canary ao seu ambiente?

Se seu cenário envolve múltiplos serviços, TLS, sessões e validações automatizadas, podemos desenhar um rollout canário com observabilidade e rollback seguro.

Falar sobre Canary com Traefik

Veja também