المستوى: متوسط. الشرح مبني على فرضية إنك بتشغّل تطبيق ويب خلف load balancer على Kubernetes، وبتعمل rolling deploy عادي. لو عندك خدمة واحدة بلا تنسيق (orchestration)، الجزء التطبيقي هيفيدك بردو لكن أوامر YAML مش هتلزمك.
الإيقاف الآمن في Kubernetes: أوقف قطع الطلبات وقت النشر
لو بتلاحظ إن أخطاء 5xx بتقفز كل مرة تعمل deploy وبعد دقيقة بتهدأ لوحدها، المشكلة غالبًا مش في الكود الجديد. الـ pod القديم بيموت وهو ماسك طلبات في نصها.
المشكلة باختصار
وقت الـ rolling deploy، Kubernetes بيوقف الـ pods القديمة ويطلع جديدة. لو الـ pod القديم اتقفل وهو لسه بيرد على طلبات، المستخدم بياخد اتصال مقطوع (connection reset) بدل رد سليم. النتيجة: موجة أخطاء صغيرة كل نشرة. على تطبيق بـ 1200 طلب/ثانية و4 بودات، ده ممكن يوصل لمئات الطلبات المقطوعة في النشرة الواحدة.
الفكرة بمثال بسيط قبل الشرح العلمي
تخيّل مطعم هيقفل الفرع بتاعه الساعة 12. فيه طريقتين. الأولى: الساعة 12 بالظبط، الجرسون يقفل الباب ويشيل الأكل من على الترابيزات، حتى اللي لسه بياكلوا. دي زبائن اتضايقوا وأكل راح في الزبالة. الطريقة التانية: قبل 12 بشوية، توقف استقبال زبائن جدد بس، وتسيب اللي جوا يخلّصوا أكلهم، وبعدين تقفل. صفر ضيق وصفر أكل ضايع.
الإيقاف الآمن هو الطريقة التانية بالظبط. علميًا: لما Kubernetes يقرر يقتل pod، بيبعتله إشارة نظام اسمها SIGTERM. الافتراض إن التطبيق يستقبل الإشارة دي، يوقف قبول اتصالات جديدة، يخلّص الطلبات اللي شغّالة (in-flight)، وبعدها يقفل بنفسه. لو التطبيق تجاهل SIGTERM، Kubernetes بيستنى مدة اسمها terminationGracePeriodSeconds (افتراضيًا 30 ثانية) وبعدها بيبعت SIGKILL اللي بيقتل العملية فورًا بلا رحمة.
ليه بيحصل القطع أصلًا
فيه سببين بيشتغلوا مع بعض:
- سباق التوقيت: إزالة الـ pod من الـ load balancer (endpoints) وإرسال SIGTERM بيحصلوا في نفس اللحظة تقريبًا، بشكل غير متزامن. فممكن الـ pod يقفل قبل ما الـ load balancer يعرف إنه راح، فيفضل يوجّهله طلبات لثواني.
- تطبيق مايسمعش SIGTERM: كتير من التطبيقات بتقفل على طول أول ما توصلها الإشارة، من غير ما تخلّص اللي في إيدها.
الحل: خطوتين متكاملتين
- preStop hook يعمل انتظار بسيط قبل SIGTERM، عشان الـ load balancer يشيل الـ pod من التوزيع قبل ما يبدأ يقفل. ده بيقفل سباق التوقيت.
- graceful shutdown في الكود يوقف قبول الجديد ويصرّف (drain) الطلبات الجارية.
ملف الـ Deployment بيبقى كده:
spec:
terminationGracePeriodSeconds: 45 # لازم أكبر من preStop + أطول طلب
containers:
- name: web
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # اسمح للـ endpoints تتحدّث
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 2