tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP
Deploy & DevOps · Por Kevin Marcondes · Publicado em · 8 min de leitura
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 TraefikVeja também
- Tutorial: configurar HAProxy com Let's Encrypt e healthchecks para balanceamento de tráfego entre containers Docker em VPS (passo a passo)
- Tutorial prático: implementar mTLS entre Nginx e microserviços usando smallstep CA em Docker Compose
- Tutorial: configurar cgroup v2 e systemd ResourceControl para limitar CPU, memória e I/O de containers Docker no Debian
- Tutorial: automatizar snapshots LVM e uploads incrementais para S3 usando systemd timers e Restic no Debian