الرئيسيةمن أناالدوراتالمدونةسوق الأوامرالمناهج والباقاتالشركاء

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • سوق الأوامر
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالمناهجالمدونةالدخول
DevOps بالعربي

الإغلاق الرشيق: ليه كل نشر بيقطع طلبات مستخدمينك وإزاي توقفه

محترف11 أغسطس 20264 دقائق قراءة
الإغلاق الرشيق: ليه كل نشر بيقطع طلبات مستخدمينك وإزاي توقفه
مستوى المقال: محترف — موجّه لمن يشغّل خدمات خلف موازن حمل أو على Kubernetes ويريد نشرًا بدون إسقاط أي طلب. الافتراض إن عندك readiness probe وService بيوزّع الترافيك.

الإغلاق الرشيق (Graceful Shutdown): ليه النشر بيقطع طلبات مستخدمينك

لو كل deploy بيرجّع أخطاء 503 لثوانٍ للمستخدمين، المشكلة مش في الكود ولا في السيرفر. المشكلة إن العملية بتموت قبل ما تخلّص الطلبات اللي في إيديها. هنا هتوصل لصفر طلب مقطوع في كل نشر.

المشكلة باختصار

وقت الـ rolling update، Kubernetes بيبعت SIGTERM للحاوية القديمة عشان توقف. المشكلة إن إشارة الإيقاف والتوقّف عن توجيه الترافيك بيحصلوا في نفس اللحظة تقريبًا، مش بالترتيب. النتيجة: العملية تموت وهي لسه بتستقبل طلبات، فالطلبات دي بتترفض بـ 503 أو بـ connection reset.

على خدمة بـ 50 ألف طلب/دقيقة أثناء نشر يومي، ده بيترجم لمئات الأخطاء في كل rollout. رقم تقديري شائع: حوالي 2.1% من الطلبات تفشل خلال ثواني التبديل لو مفيش إغلاق رشيق.

ليه بيحصل ده بالظبط (المفهوم الأساسي)

تخيّل إنك بتقفل محل. الإغلاق المفاجئ معناه إنك تطفّي النور وتقفل الباب على زباين لسه جوّه بينتظروا. الإغلاق الرشيق معناه: تبطّل تدخّل زباين جدد، تخلّص اللي جوّه خدمتهم، وبعد كده تقفل الباب. نفس الفكرة بالظبط في الخدمات.

علميًا: لما البود يتشال، الـ kubelet بيعمل حاجتين على التوازي. بيشيل البود من قائمة الـ Endpoints (عشان الـ Service يبطّل يوجّهله)، وبيبعت SIGTERM للحاوية. إزالة الـ Endpoint بتاخد وقت لتنتشر على كل العقد (kube-proxy، الـ Ingress، موازن الحمل). في الفجوة دي، الترافيك لسه بيوصل لعملية بتموت.

الحل إنك تفصل الخطوتين زمنيًا: أوقف قبول الجديد أولًا، استنى انتشار إزالة التسجيل، وبعدها اخرج.

الحل: الإغلاق الرشيق خطوة بخطوة

  1. استقبل SIGTERM بدل ما تتجاهله.
  2. خلّي فحص الجاهزية (readiness) يفشل فورًا عشان تخرج من مجموعة موازن الحمل.
  3. استنى فترة قصيرة لحد ما إزالة التسجيل تنتشر (10–15 ثانية عادة).
  4. اقفل السيرفر: بطّل قبول اتصالات جديدة وخلّص الجارية.
  5. اخرج بـ exit 0 قبل ما ينتهي وقت المهلة.

الجزء البرمجي في Node.js:

JavaScript
const server = app.listen(3000);
let shuttingDown = false;

// فحص الجاهزية: يرجّع 503 بمجرد بدء الإغلاق
app.get('/healthz/ready', (req, res) =>
  res.sendStatus(shuttingDown ? 503 : 200));

process.on('SIGTERM', () => {
  shuttingDown = true;                  // 1) اخرج من مجموعة موازن الحمل
  setTimeout(() => {                     // 2) استنى انتشار إزالة التسجيل
    server.close(() => process.exit(0)); // 3) خلّص الطلبات الجارية ثم اخرج
  }, 15000);
});

نفس المنطق في Go عبر Server.Shutdown اللي بيصرّف الاتصالات المفتوحة:

Go
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM)
<-c
atomic.StoreInt32(&shuttingDown, 1) // خلّي readiness يفشل
time.Sleep(15 * time.Second)       // استنى إزالة التسجيل
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx)                  // صرّف الطلبات الجارية

وربطها في Kubernetes بحيث الـ preStop يغطّي فجوة إزالة التسجيل:

YAML
spec:
  terminationGracePeriodSeconds: 30   # المهلة الكلية قبل SIGKILL
  containers:
    - name: api
      readinessProbe:
        httpGet: { path: /healthz/ready, port: 3000 }
        periodSeconds: 2
      lifecycle:
        preStop:
          exec:
            command: ["sh", "-c", "sleep 15"]  # وقت انتشار إزالة التسجيل

ركّز: عدّاد terminationGracePeriodSeconds بيبدأ من لحظة بدء الإنهاء ويشمل زمن الـ preStop. لازم يكون أكبر من (زمن preStop + أطول طلب متوقّع). هنا 30 ثانية تغطّي 15 ثانية preStop + هامش للتصريف.

الـ trade-offs وما يجب الانتباه له

مفيش وجبة مجانية. الـ trade-off هنا إن كل بود بياخد وقت أطول عشان يخرج، فالنشر الكامل بيبقى أبطأ. مثال: 10 بودات × (15 ثانية preStop + تصريف) ممكن تزود زمن الـ rollout بحوالي دقيقتين إلى ثلاث.

بتكسب: صفر طلب مقطوع وتجربة نشر غير مرئية للمستخدم. بتخسر: نشر أبطأ وإعداد إضافي في كل خدمة. لو زمن الـ preStop أقصر من انتشار إزالة التسجيل عندك، هيفضل يتسرّب جزء صغير من الأخطاء؛ قيسه ولا تخمّنه.

تحذير: لو التصريف طال أكتر من المهلة، الـ kubelet هيبعت SIGKILL ويقطع الطلبات اللي لسه شغّالة. خلّي timeout التصريف أقصر من المهلة دائمًا.

متى لا تستخدم هذه الطريقة

لو خدمتك مهام دفعية قصيرة العمر (batch/worker) والطلبات عندها idempotent والعميل بيعيد المحاولة بتكلفة تافهة، الإغلاق الرشيق مش أولوية. كذلك لو عندك نسخة واحدة في بيئة تطوير بدون موازن حمل، مفيش فجوة إزالة تسجيل أصلًا. أما الاتصالات طويلة العمر (WebSocket، بث)، فمحتاجة مهلة أطول واستراتيجية تصريف مختلفة، مش مجرد sleep.

الخطوة التالية

افتح ملف الـ Deployment بتاع أهم خدمة عندك. ضيف readinessProbe يقرأ حالة الإغلاق، وpreStop: sleep 15، وterminationGracePeriodSeconds: 30. اعمل rollout واقيس نسبة أخطاء 5xx أثناءه من لوحة المراقبة. لو النسبة مش صفر، طوّل الـ preStop لحد ما إزالة التسجيل تسبق SIGTERM.

المصادر

  • Kubernetes — Pod Lifecycle: Termination of Pods: kubernetes.io
  • Kubernetes — Container Lifecycle Hooks (preStop): kubernetes.io
  • Kubernetes — Configure Liveness, Readiness and Startup Probes: kubernetes.io
  • Node.js — http server.close(): nodejs.org
  • Go — net/http Server.Shutdown: pkg.go.dev

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة