هذا المقال يتطلب مستوى: متوسط. الافتراض إنك بتشغّل خدمة HTTP طويلة العمر خلف Service في Kubernetes، والـ rollout بيستخدم استراتيجية RollingUpdate الافتراضية.
لو كل rolling update بيرجّعلك نسبة من الطلبات في صورة أخطاء 502، المشكلة مش في التطبيق ولا في السيرفر. المشكلة إن الـ Pod بيموت وهو لسه ماسك اتصالات شغّالة. هنا هتعرف تصلّحها في 3 إضافات صغيرة على الـ Deployment.
Graceful Shutdown: ليه الـ deploy بيقطع اتصالات المستخدمين وإزاي توقفه
المشكلة باختصار
وقت الـ rolling update، Kubernetes بيشيل الـ Pods القديمة ويطلع جديدة. لو الـ Pod القديم اتقفل فجأة وهو بينفّذ طلبات، الطلبات دي بتتقطع. النتيجة: أخطاء 502 و503 عند المستخدم من غير أي سبب واضح في الـ logs.
يعني إيه Graceful Shutdown؟
تخيّل كاشير في سوبر ماركت، والمدير قاله "اقفل حالًا". لو قام سايب زبون في نص عملية الدفع، الزبون بيطلع متضايق من غير فاتورة. الكاشير المحترم بيعمل حاجة أذكى: بيوقف استقبال زباين جداد، بس يخلّص اللي واقفين قدامه بالفعل، وبعدها يقفل. ده بالظبط الـ Graceful Shutdown.
علميًا: لما Kubernetes يقرر يوقف Pod، بيبعتله إشارة SIGTERM. دي مش قتل فوري، دي طلب "ابدأ تقفل بأدب". التطبيق المفروض يمسك الإشارة دي، يبطّل استقبال اتصالات جديدة، يكمّل الاتصالات الحالية، وبعدها يخرج. لو ماخرجش خلال مهلة اسمها terminationGracePeriodSeconds (الافتراضي 30 ثانية)، وقتها بس Kubernetes بيبعت SIGKILL اللي بيقتل العملية فورًا.
اللي بيحصل فعلاً: الـ race condition
فيه مصيدة أغلب الناس بتقع فيها. إزالة الـ Pod من قائمة الـ endpoints وإرسال SIGTERM بيحصلوا في نفس اللحظة تقريبًا وبشكل غير متزامن. يعني ممكن الـ Pod يبدأ يقفل وهو لسه مسجّل كوجهة صالحة، فيوصله طلب جديد على اتصال بيتقطع. النتيجة 502.
الحل: SIGTERM handler مع preStop
الحل جزئين. الأول: خلّي تطبيقك يمسك SIGTERM ويقفل بأدب. مثال بـ Node.js:
const server = app.listen(8080);
process.on('SIGTERM', function () {
// بطّل استقبال اتصالات جديدة، وكمّل الجارية
server.close(function () {
process.exit(0);
});
});
الجزء التاني: عالِج الـ race بإضافة preStop hook بينام شوية قبل SIGTERM، عشان تدّي وقت لإزالة الـ Pod من الـ endpoints تنتشر على كل الـ nodes. الإعداد في الـ Deployment: