Introdução: Por que você ainda tem medo de clicar em "Deploy"?
\n\nVocê escreveu o código. Testou na máquina local. Os testes passaram. Tudo perfeito. Você clica no tão esperado botão Deploy... e após 5 minutos vê o erro 500 aparecer, enquanto os usuários em pânico escrevem para o suporte. Familiar?
\n\nEstratégias tradicionais de deploy (blue-green, rolling update) são boas, mas têm uma desvantagem: ou incluem todo o código novo de uma vez, ou não o incluem. E se você pudesse enviar uma nova funcionalidade para apenas 1% dos usuários, observar as métricas, e só então, após confirmar a estabilidade, liberá-la para 100%?
\n\nÉ exatamente assim que funcionam os canary deployments (lançamentos canários). O nome vem de uma antiga prática de mineração — levar um canário para a mina para detectar vazamentos de gás a tempo. Se o pássaro silenciasse, os mineiros sabiam: era hora de correr para cima. No mundo da TI, o "canário" é um pequeno grupo de usuários que testa o novo código. Se tudo estiver bem — o lançamento segue adiante. Se não — você reverte mais rápido do que consegue dizer "hotfix".
\n\nNeste artigo, vamos escrever um sistema completo de canary deployment em TypeScript. Você aprenderá como configurar o balanceamento de tráfego, como automatizar o processo via CI/CD e como monitorar a "saúde" do seu canário. Vamos lá!
\n\nO que é Canary Deployment e por que você precisa dele?
\n\nCanary deployment é uma estratégia de lançamento de novo código onde a versão atualizada do aplicativo é primeiro enviada para uma pequena porcentagem de usuários (geralmente 1-10%). Os demais continuam usando a versão antiga e estável. Se a nova versão não apresentar problemas (erros, queda de desempenho, aumento de latência), você aumenta gradualmente a porcentagem de tráfego para ela, até que substitua completamente a versão antiga.
\n\nPrincipais vantagens:
\n- \n
- Minimização de riscos — bugs afetam apenas uma pequena parte do público. \n
- Reversão rápida — basta redirecionar o tráfego de volta para a versão estável. \n
- Teste A/B em condições reais — você pode comparar métricas da versão antiga e da nova. \n
- Rampa de lançamento suave — você não sobrecarrega o servidor com um aumento repentino de carga. \n
Agora, vamos passar da teoria à prática. Vamos construir um sistema composto por três partes:
\n- \n
- Backend em TypeScript (Express.js). \n
- Balanceador de tráfego com Nginx. \n
- Automação com GitHub Actions. \n
Passo 1. Escrevendo o aplicativo em TypeScript com suporte a versionamento
\n\nPrimeiro, vamos criar um aplicativo Express simples que será capaz de operar em dois modos: stable e canary. A diferença será apenas na resposta do endpoint raiz, mas em um projeto real, isso pode ser qualquer nova funcionalidade.
\n\n// src/app.ts\nimport express, { Request, Response } from 'express';\nimport dotenv from 'dotenv';\n\ndotenv.config();\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\nconst VERSION = process.env.VERSION || 'stable'; // 'stable' ou 'canary'\n\napp.get('/', (req: Request, res: Response) => {\n res.json({\n message: 'Hello from API!',\n version: VERSION,\n timestamp: new Date().toISOString(),\n });\n});\n\n// Endpoint para health check\napp.get('/health', (req: Request, res: Response) => {\n res.status(200).send('OK');\n});\n\napp.listen(PORT, () => {\n console.log(`Server running on port ${PORT} in ${VERSION} mode`);\n});\n\n\nAgora, vamos criar o Dockerfile para empacotar o aplicativo em um contêiner. Isso simplificará o deploy e o balanceamento.
\n\n# Dockerfile\nFROM node:18-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci\nCOPY . .\nRUN npm run build\n\nFROM node:18-alpine AS runner\nWORK