Por que o deploy comum é sempre uma loteria?
\nTodo desenvolvedor já passou pelo momento da verdade pelo menos uma vez na vida: você aperta o botão "Deploy" e o coração vai à boca. E se a nova versão derrubar o banco? E se os usuários virem o erro 500 em vez da página inicial?
\nO deploy comum é como pular de paraquedas sem reserva. Ou tudo funciona perfeitamente, ou você passa a noite analisando logs, tentando entender o que deu errado. Mas existe uma maneira de tornar esse processo seguro — canary deployments (deploy canário).
\nNeste artigo, vamos explorar como implementar um canary deployment em Kotlin usando Ktor e um balanceador simples. Você aprenderá a entregar código novo primeiro para 5% dos usuários e, em seguida, aumentar gradualmente o tráfego. E sim, escreveremos código que realmente funciona.
\n\nO que é Canary deployment e por que você precisa dele?
\nImagine que você é o capitão de um navio. Em vez de navegar diretamente pelos recifes, você envia um pequeno barco (canário) na frente. Se ele passar, toda a frota navega. Se ele afundar, você perde apenas um barco.
\nNo mundo da TI, isso funciona assim: você implanta a nova versão do serviço primeiro para um pequeno grupo de usuários. Se as métricas estiverem boas, o tráfego aumenta. Se não, você reverte em segundos.
\nAs vantagens são óbvias:
\n- \n
- Minimização de riscos — os bugs afetam apenas 1-5% do público. \n
- Feedback rápido — você vê os erros antes que se tornem uma catástrofe. \n
- Teste A/B — é possível comparar o desempenho das versões antiga e nova. \n
- Zero downtime — os usuários não percebem a troca. \n
Mas há um porém: é preciso saber gerenciar o tráfego, monitorar métricas e automatizar o processo. Kotlin, com sua concisão e corrotinas, é excelente para essa tarefa.
\n\nArquitetura do Canary deployment em Kotlin
\nVamos construir uma arquitetura simples, mas funcional. Teremos:
\n- \n
- Balanceador — decide qual porcentagem das requisições vai para a versão antiga (stable) e qual para a nova (canary). \n
- Duas instâncias do serviço — stable e canary, ambas escritas em Kotlin com Ktor. \n
- Monitoramento — contadores de erros e tempo de resposta. \n
O balanceador tomará a decisão com base no cabeçalho X-Canary ou em uma distribuição aleatória. Seguiremos o segundo caminho — é mais próximo da realidade.
Escrevendo o código: balanceador em Kotlin
\nComeçaremos pelo balanceador. Ele receberá as requisições e as encaminhará para um dos dois serviços. Para simplificar, usaremos o cliente HTTP embutido do Ktor.
\n\nimport io.ktor.client.*\nimport io.ktor.client.engine.cio.*\nimport io.ktor.client.request.*\nimport io.ktor.client.statement.*\nimport io.ktor.server.application.*\nimport io.ktor.server.engine.*\nimport io.ktor.server.netty.*\nimport io.ktor.server.response.*\nimport io.ktor.server.routing.*\nimport kotlinx.coroutines.runBlocking\nimport kotlin.random.Random\n\n// Configuração\nconst val STABLE_URL = "http://localhost:8081"\nconst val CANARY_URL = "http://localhost:8082"\nvar canaryPercent = 5 // Começamos com 5% do tráfego\n\nfun main() {\n val client = HttpClient(CIO)\n\n embeddedServer(Netty, port = 8080) {\n routing {\n get("/api/data") {\n val useCanary = Random.nextInt(100) < canaryPercent\n val targetUrl = if (useCanary) CANARY_URL else STABLE_URL\n\n val response = runBlocking {\n client.get("$targetUrl/api/data")\n }\n\n call.respondText(\n response.bodyAsText(),\n contentType = response.contentType()\n )\n }\n\n // Endpoint para alterar a porcentagem do canário\n get("/canary/set/{perc")