لماذا النشر العادي هو دائمًا يانصيب؟
\nكل مطور مر بلحظة الحقيقة مرة واحدة على الأقل في حياته: تضغط على زر "Deploy"، ويذهب قلبك إلى كعبيك. ماذا لو أسقط الإصدار الجديد قاعدة البيانات؟ ماذا لو رأى المستخدمون خطأ 500 بدلاً من الصفحة الرئيسية؟
\nالنشر العادي يشبه القفز بالمظلة بدون مظلة احتياطية. إما أن يعمل كل شيء بشكل مثالي، أو تقضي الليل في تصفح السجلات محاولاً فهم ما حدث خطأ. ولكن هناك طريقة لجعل هذه العملية آمنة — نشر الكناري (Canary deployments).
\nفي هذه المقالة، سنشرح كيفية تنفيذ نشر الكناري على Kotlin باستخدام Ktor وموازن بسيط. ستتعلم كيفية تقديم كود جديد أولاً لـ 5% من المستخدمين، ثم زيادة حركة المرور تدريجياً. ونعم، سنكتب كودًا يعمل بالفعل.
\n\nما هو نشر الكناري ولماذا تحتاجه تحديدًا؟
\nتخيل أنك قبطان سفينة. بدلاً من الإبحار مباشرة عبر الشعاب المرجانية، ترسل قاربًا صغيرًا (كناري) للأمام. إذا نجح، يبحر الأسطول بأكمله. إذا غرق، تخسر قاربًا واحدًا فقط.
\nفي عالم تكنولوجيا المعلومات، يبدو الأمر كالتالي: تقوم بنشر الإصدار الجديد من الخدمة أولاً لمجموعة صغيرة من المستخدمين. إذا كانت المقاييس جيدة، تزداد حركة المرور. إذا لم تكن كذلك، تتراجع في ثوانٍ.
\nالمزايا واضحة:
\n- \n
- تقليل المخاطر — تؤثر الأخطاء على 1-5% فقط من الجمهور. \n
- ردود فعل سريعة — ترى الأخطاء قبل أن تتحول إلى كارثة. \n
- اختبار A/B — يمكن مقارنة أداء الإصدارين القديم والجديد. \n
- Zero downtime — لا يلاحظ المستخدمون التبديل. \n
ولكن هناك جانب سلبي: يجب أن تكون قادرًا على إدارة حركة المرور، ومراقبة المقاييس، وأتمتة العملية. Kotlin مع اختصاره و coroutines مناسب تمامًا لهذه المهمة.
\n\nهندسة نشر الكناري على Kotlin
\nدعونا نبني بنية بسيطة ولكنها عملية. سيكون لدينا:
\n- \n
- موازن — يقرر النسبة المئوية للطلبات التي تذهب إلى الإصدار القديم (stable) وأيها إلى الإصدار الجديد (canary). \n
- مثيلان للخدمة — stable و canary، كلاهما مكتوبان بلغة Kotlin مع Ktor. \n
- مراقبة — عدادات الأخطاء ووقت الاستجابة. \n
سيتخذ الموازن القرار بناءً على رأس X-Canary أو التوزيع العشوائي. سنسلك الطريق الثاني — فهو أقرب إلى الواقع.
كتابة الكود: الموازن بلغة Kotlin
\nلنبدأ بالموازن. سيتلقى الطلبات ويعيد توجيهها إلى إحدى الخدمتين. للتبسيط، سنستخدم عميل HTTP المدمج في 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// الإعدادات\nconst val STABLE_URL = "http://localhost:8081"\nconst val CANARY_URL = "http://localhost:8082"\nvar canaryPercent = 5 // نبدأ بـ 5% من حركة المرور\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 // نقطة نهاية لتغيير نسبة الكناري\n get("/canary/set/{perc