¿Por qué un despliegue normal es siempre una lotería?
\nCada desarrollador ha vivido al menos una vez el momento de la verdad: presionas el botón "Deploy" y el corazón se te va a los pies. ¿Y si la nueva versión tumba la base de datos? ¿Y si los usuarios ven un error 500 en lugar de la página principal?
\nUn despliegue normal es como saltar en paracaídas sin uno de repuesto. O todo funciona perfectamente, o pasas la noche revisando logs tratando de entender qué salió mal. Pero hay una forma de hacer este proceso seguro: canary deployments (despliegue canario).
\nEn este artículo, analizaremos cómo implementar un canary deployment en Kotlin usando Ktor y un balanceador simple. Aprenderá a entregar código nuevo primero al 5% de los usuarios y luego aumentar gradualmente el tráfico. Y sí, escribiremos código que realmente funciona.
\n\n¿Qué es un Canary deployment y por qué lo necesita?
\nImagínese que es el capitán de un barco. En lugar de navegar directamente entre los arrecifes, envía un pequeño bote (canario) por delante. Si pasa, navega toda la flota. Si se hunde, solo pierde un bote.
\nEn el mundo de TI, esto se ve así: despliega la nueva versión del servicio primero para un pequeño grupo de usuarios. Si las métricas están bien, el tráfico aumenta. Si no, revierte en segundos.
\nLas ventajas son obvias:
\n- \n
- Minimización de riesgos — los errores afectan solo al 1-5% de la audiencia. \n
- Retroalimentación rápida — ve los errores antes de que se conviertan en una catástrofe. \n
- Pruebas A/B — puede comparar el rendimiento de las versiones antigua y nueva. \n
- Zero downtime — los usuarios no notan el cambio. \n
Pero hay una pega: hay que saber gestionar el tráfico, monitorear métricas y automatizar el proceso. Kotlin, con su concisión y corrutinas, es ideal para esta tarea.
\n\nArquitectura de Canary deployment en Kotlin
\nConstruyamos una arquitectura simple pero funcional. Tendremos:
\n- \n
- Balanceador — decide qué porcentaje de solicitudes va a la versión antigua (stable) y cuál a la nueva (canary). \n
- Dos instancias del servicio — stable y canary, ambas escritas en Kotlin con Ktor. \n
- Monitoreo — contadores de errores y tiempo de respuesta. \n
El balanceador tomará la decisión basándose en la cabecera X-Canary o en una distribución aleatoria. Optaremos por el segundo camino, que es más cercano a la realidad.
Escribiendo el código: balanceador en Kotlin
\nEmpecemos con el balanceador. Recibirá solicitudes y las reenviará a uno de los dos servicios. Para simplificar, usaremos el cliente HTTP integrado de 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// Configuración\nconst val STABLE_URL = "http://localhost:8081"\nconst val CANARY_URL = "http://localhost:8082"\nvar canaryPercent = 5 // Empezamos con el 5% del tráfico\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 cambiar el porcentaje del canario\n get("/canary/set/{perc")