Why is regular deployment always a lottery?
\nEvery developer has experienced that moment of truth at least once: you press the "Deploy" button, and your heart sinks. What if the new version crashes the database? What if users see a 500 error instead of the main page?
\nRegular deployment is like skydiving without a reserve parachute. Either everything works perfectly, or you spend the night going through logs trying to figure out what went wrong. But there is a way to make this process safe — canary deployments.
\nIn this article, we will break down how to implement canary deployment in Kotlin using Ktor and a simple load balancer. You will learn to deliver new code to first 5% of users, then gradually increase traffic. And yes, we will write code that actually works.
\n\nWhat is Canary deployment and why do you need it?
\nImagine you are a ship captain. Instead of sailing straight through the reefs, you send a small boat (the canary) ahead. If it passes — the whole fleet sails. If it sinks — you lose only one boat.
\nIn the IT world, this looks like this: you deploy a new version of the service first to a small group of users. If metrics are fine — traffic increases. If not — you roll back in seconds.
\nThe advantages are obvious:
\n- \n
- Risk minimization — bugs affect only 1-5% of the audience. \n
- Fast feedback — you see errors before they become a disaster. \n
- A/B testing — you can compare the performance of old and new versions. \n
- Zero downtime — users don't notice the switch. \n
But there is a fly in the ointment: you need to be able to manage traffic, monitor metrics, and automate the process. Kotlin with its conciseness and coroutines is perfect for this task.
\n\nCanary deployment architecture on Kotlin
\nLet's build a simple but working architecture. We will have:
\n- \n
- Load balancer — decides what percentage of requests goes to the old version (stable) and what percentage to the new one (canary). \n
- Two service instances — stable and canary, both written in Kotlin with Ktor. \n
- Monitoring — error counters and response time metrics. \n
The load balancer will make decisions based on the X-Canary header or random distribution. We will go with the second approach — it's closer to reality.
Writing code: load balancer in Kotlin
\nLet's start with the load balancer. It will receive requests and forward them to one of the two services. For simplicity, we will use the built-in Ktor HTTP client.
\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 // Start with 5% traffic\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 for changing the canary percentage\n get("/canary/set/{perc")