Pourquoi un déploiement classique est-il toujours une loterie ?
\nChaque développeur a vécu au moins une fois dans sa vie le moment de vérité : vous appuyez sur le bouton "Deploy", et votre cœur s'emballe. Et si la nouvelle version faisait planter la base de données ? Et si les utilisateurs voyaient une erreur 500 au lieu de la page d'accueil ?
\nUn déploiement classique ressemble à un saut en parachute sans parachute de secours. Soit tout fonctionne parfaitement, soit vous passez la nuit à fouiller les logs en essayant de comprendre ce qui a mal tourné. Mais il existe un moyen de rendre ce processus sûr : les déploiements canari (canary deployments).
\nDans cet article, nous allons voir comment implémenter un déploiement canari avec Kotlin en utilisant Ktor et un simple répartiteur de charge. Vous apprendrez à déployer du nouveau code d'abord pour 5 % des utilisateurs, puis à augmenter progressivement le trafic. Et oui, nous écrirons du code qui fonctionne réellement.
\n\nQu'est-ce qu'un déploiement Canary et pourquoi en avez-vous besoin ?
\nImaginez que vous êtes le capitaine d'un navire. Au lieu de naviguer directement à travers les récifs, vous envoyez un petit bateau (le canari) en éclaireur. S'il passe, toute la flotte le suit. S'il coule, vous ne perdez qu'un seul bateau.
\nDans le monde de l'informatique, cela ressemble à ceci : vous déployez une nouvelle version de votre service d'abord pour un petit groupe d'utilisateurs. Si les métriques sont bonnes, le trafic augmente. Sinon, vous effectuez un rollback en quelques secondes.
\nLes avantages sont évidents :
\n- \n
- Minimisation des risques — les bugs n'affectent que 1 à 5 % de l'audience. \n
- Retour d'information rapide — vous voyez les erreurs avant qu'elles ne deviennent une catastrophe. \n
- Tests A/B — vous pouvez comparer les performances des anciennes et nouvelles versions. \n
- Zero downtime — les utilisateurs ne remarquent pas la transition. \n
Mais il y a un revers de la médaille : il faut savoir gérer le trafic, surveiller les métriques et automatiser le processus. Kotlin, avec sa concision et ses coroutines, est parfaitement adapté à cette tâche.
\n\nArchitecture d'un déploiement Canary avec Kotlin
\nConstruisons une architecture simple mais fonctionnelle. Nous aurons :
\n- \n
- Un répartiteur de charge — il décide quel pourcentage de requêtes va vers l'ancienne version (stable) et quel pourcentage vers la nouvelle (canary). \n
- Deux instances du service — stable et canary, toutes deux écrites en Kotlin avec Ktor. \n
- Un monitoring — des compteurs d'erreurs et de temps de réponse. \n
Le répartiteur de charge prendra sa décision en fonction de l'en-tête X-Canary ou d'une distribution aléatoire. Nous suivrons la deuxième voie, plus proche de la réalité.
Écrire le code : le répartiteur de charge en Kotlin
\nCommençons par le répartiteur de charge. Il recevra les requêtes et les redirigera vers l'un des deux services. Pour simplifier, nous utiliserons le client HTTP intégré 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// Configuration\nconst val STABLE_URL = "http://localhost:8081"\nconst val CANARY_URL = "http://localhost:8082"\nvar canaryPercent = 5 // On commence avec 5% du trafic\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 pour modifier le pourcentage du canari\n get("/canary/set/{perc