تطبيقاتي كانت تموت وتعود للحياة: كيف أنقذتني ‘مسابير الحياة والجاهزية’ من جحيم CrashLoopBackOff في Kubernetes

ليلة النشر التي كادت أن تكسر ظهري

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

بكل ثقة، ضغطت على زر النشر على نظام Kubernetes الخاص بنا. فتحت لوحة المراقبة (Dashboard) لأرى الـ Pods الجديدة وهي تولد وتأخذ مكان القديمة. لكن شيئًا غريبًا كان يحدث. الـ Pod الجديد يظهر للحظات، ثم يختفي، ثم يعود، ثم يختفي مرة أخرى. نظرت إلى حالته، فرأيت تلك الكلمة المشؤومة التي يعرفها كل من تعامل مع Kubernetes: CrashLoopBackOff.

يا إلهي! التطبيق يدخل في حلقة لانهائية من الانهيار وإعادة التشغيل. Kubernetes، في محاولته لمساعدتي، كان يعيد تشغيل الحاوية (Container) كلما انهارت. لكنها كانت تنهار بسرعة لدرجة أني لم أكن ألحق حتى أن أقرأ سجلات الأخطاء (Logs) لأفهم ما الذي يجري. شعرت بالدم يغلي في عروقي. هل هي مشكلة في الكود؟ هل نسيت متغير بيئة (Environment Variable)؟ هل هناك تسريب في الذاكرة؟

أمضيت الساعات التالية في حالة من الذعر والترقب، أحاول “صيد” الخطأ قبل أن يموت الـ Pod مجددًا. كانت معركة خاسرة. كلما اعتقدت أنني وجدت طرف الخيط، كان الـ Pod يختفي من أمامي. في تلك الليلة، تعلمت درسًا قاسيًا ومهمًا: أحيانًا، المشكلة ليست في تطبيقك، بل في الطريقة التي تخبر بها Kubernetes أن تطبيقك “بخير”. وهنا يأتي دور أبطال قصتنا: مسابير الحياة والجاهزية.

ما هو جحيم الـ CrashLoopBackOff؟

قبل أن نغوص في الحل، دعونا نفهم المشكلة. عندما ترى حالة CrashLoopBackOff في Kubernetes، فهذا يعني أن Kubernetes يحاول أن يكون صديقك الوفي. هو يرى أن الحاوية الخاصة بك قد توقفت عن العمل (انهارت) بعد وقت قصير من بدئها. فماذا يفعل؟ يقول لنفسه: “لعلها كانت مشكلة عابرة، سأحاول تشغيلها مرة أخرى”.

لكن إذا استمرت الحاوية في الانهيار مباشرة بعد كل إعادة تشغيل، يدرك Kubernetes أن هناك مشكلة حقيقية. ولكي لا يستهلك موارد السيرفر في محاولات إعادة تشغيل فاشلة كل ثانية، يبدأ في زيادة الوقت بين كل محاولة وأخرى. هذا هو ما يسمى “BackOff” أو التراجع. فتجد نفسك في هذه الحلقة المفرغة: تشغيل -> انهيار -> انتظار -> تشغيل -> انهيار -> انتظار أطول…

المشكلة في حالتي لم تكن أن التطبيق ينهار بسبب خطأ برمجي. المشكلة كانت أن تطبيق الذكاء الاصطناعي الجديد كان يحتاج حوالي 45 ثانية لتحميل الموديل الضخم في الذاكرة وبدء الاستماع على الشبكة. Kubernetes، بافتراضاته الأولية، كان يرى أن التطبيق لم “يستجب” خلال ثوانٍ قليلة، فافترض أنه “ميت” وقام بقتله وإعادة تشغيله. وهكذا دواليك.

المنقذون: مسابير كوبرنيتيس (Kubernetes Probes)

مسابير كوبرنيتيس هي الطريقة التي يتواصل بها نظام الأوركسترا مع تطبيقاتك ليفهم حالتها الصحية. بدلًا من الافتراضات العمياء، يقوم Kubernetes بطرح أسئلة مباشرة على الحاوية، مثل “كيف حالك يا صاحبي؟ هل أنت على قيد الحياة؟ هل أنت جاهز لاستقبال الزوار؟”.

هناك ثلاثة أنواع رئيسية من المسابير، لكننا سنركز على أهم اثنين لحل مشكلتنا:

  • مسبار الحياة (Liveness Probe): يسأل السؤال: “هل ما زلت تعمل أم أنك تجمدت؟”. إذا فشل هذا المسبار، فهذا يعني أن التطبيق في حالة سيئة (مثل deadlock) ويجب إعادة تشغيله.
  • مسبار الجاهزية (Readiness Probe): يسأل السؤال: “هل أنت مستعد لاستقبال طلبات جديدة؟”. إذا فشل هذا المسبار، فهذا لا يعني أن التطبيق ميت، بل قد يكون مشغولًا بالبدء أو يقوم بعملية طويلة. في هذه الحالة، يتوقف Kubernetes عن إرسال الترافيك إليه، لكنه يبقيه يعمل.

1. مسبار الحياة (Liveness Probe): هل أنت على قيد الحياة؟

هذا المسبار هو شريان الحياة لتطبيقك. وظيفته هي التأكد من أن التطبيق لم يدخل في حالة تجمد (deadlock) حيث يكون البرنامج يعمل لكنه لا يستجيب لأي شيء. إذا فشل هذا المسبار لعدد معين من المرات، يقوم Kubernetes بقتل الحاوية وإعادة تشغيلها، على أمل أن تحل إعادة التشغيل المشكلة.

نصيحة أبو عمر: كن حذرًا جدًا مع مسبار الحياة. إذا كان المسبار نفسه ثقيلًا أو يعتمد على خدمات خارجية، قد يفشل بسبب بطء الشبكة وليس لأن تطبيقك ميت، مما يؤدي إلى إعادة تشغيل غير ضرورية. اجعل نقطة فحص الحياة (health check endpoint) بسيطة وسريعة قدر الإمكان.

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


apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: my-app-container
    image: my-image
    ports:
    - containerPort: 8080
    livenessProbe:
      httpGet:
        path: /healthz  # المسار الذي يجب أن يرد بـ 200 OK
        port: 8080
      initialDelaySeconds: 15  # انتظر 15 ثانية بعد بدء الحاوية قبل أول فحص
      periodSeconds: 20      # افحص كل 20 ثانية
      failureThreshold: 3    # اعتبره فاشلاً بعد 3 محاولات فاشلة متتالية

في هذا المثال، ينتظر Kubernetes لمدة 15 ثانية بعد بدء الحاوية، ثم يبدأ بإرسال طلب HTTP GET إلى /healthz على المنفذ 8080 كل 20 ثانية. إذا فشل الطلب 3 مرات متتالية، سيقوم بإعادة تشغيل الحاوية.

2. مسبار الجاهزية (Readiness Probe): هل أنت جاهز للعمل؟

هذا هو المسبار الذي كان سينقذني في تلك الليلة المشؤومة. وظيفته هي إخبار Kubernetes متى يكون تطبيقك مستعدًا بالفعل لخدمة المستخدمين. هذا مهم جدًا للتطبيقات التي تحتاج وقتًا للبدء، مثل تطبيقاتي التي تحمل موديلات تعلم الآلة، أو أي تطبيق يحتاج للاتصال بقاعدة بيانات، أو تهيئة ذاكرة تخزين مؤقت (cache).

عندما يفشل مسبار الجاهزية، لا يقوم Kubernetes بقتل الحاوية. بدلًا من ذلك، يقوم بإزالتها مؤقتًا من “قائمة الخدمة” (Service Endpoints). هذا يعني أنه لن يتم إرسال أي ترافيك جديد إليها حتى ينجح مسبار الجاهزية مرة أخرى.

نصيحة أبو عمر: مسبار الجاهزية هو سر عمليات النشر بدون انقطاع (Zero-Downtime Deployment). عندما تنشر نسخة جديدة، لن يرسل Kubernetes أي مستخدمين إلى الـ Pod الجديد إلا بعد أن يخبره مسبار الجاهزية “أنا جاهز يا كبير!”. هذا يضمن عدم مواجهة أي مستخدم لصفحة خطأ أثناء عملية التحديث.

هنا مثال على كيفية إضافته:


apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: my-app-container
    image: my-image
    ports:
    - containerPort: 8080
    readinessProbe:
      httpGet:
        path: /readyz # مسار مختلف للتأكد من الجاهزية
        port: 8080
      initialDelaySeconds: 5  # ابدأ الفحص مبكرًا
      periodSeconds: 5      # افحص بشكل متكرر
    livenessProbe:
      # ... نفس مسبار الحياة من المثال السابق
      # لكن مع initialDelaySeconds أطول بكثير
      initialDelaySeconds: 60 

لاحظ الفرق في initialDelaySeconds. بالنسبة لمسبار الجاهزية، نبدأ الفحص مبكرًا (5 ثوانٍ) وسيفشل بشكل طبيعي حتى يصبح التطبيق جاهزًا. أما مسبار الحياة، فنعطيه وقتًا طويلاً (60 ثانية) قبل أن نبدأ في القلق بشأن ما إذا كان التطبيق قد تجمد، لإعطائه فرصة كافية للبدء.

حل مشكلة تطبيق الذكاء الاصطناعي

بالعودة إلى قصتي، الحل كان في تطبيق هذين المسبارين بذكاء. إليك ما فعلته:

  1. أنشأت نقطتي فحص (Health Check Endpoints) في تطبيقي:
    • /healthz: نقطة بسيطة جدًا ترد بـ 200 OK بمجرد بدء الخادم. هذا ليخبر مسبار الحياة أن العملية لم تمت.
    • /readyz: نقطة أكثر ذكاءً. لا ترد بـ 200 OK إلا بعد أن يتم تحميل موديل الذكاء الاصطناعي بالكامل في الذاكرة.
  2. قمت بتحديث ملف تعريف النشر (Deployment YAML):

# ... (deployment spec)
    spec:
      containers:
      - name: ai-model-server
        image: my-ai-app:v2
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /readyz
            port: 80
          initialDelaySeconds: 10 # ابدأ الفحص بعد 10 ثوانٍ
          periodSeconds: 5
          failureThreshold: 12 # اسمح له بالفشل لمدة دقيقة (12 * 5 ثوانٍ)
        livenessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 90 # لا تبدأ فحص الحياة إلا بعد 90 ثانية!
          periodSeconds: 30

بهذا الإعداد، حدث السحر. عند نشر الـ Pod الجديد:

  1. يبدأ الـ Pod.
  2. مسبار الجاهزية يبدأ بالفحص بعد 10 ثوانٍ ويفشل، وهذا طبيعي. Kubernetes يرى الـ Pod “غير جاهز” ولا يرسل له أي ترافيك.
  3. يستمر التطبيق في تحميل الموديل الضخم (يستغرق حوالي 45 ثانية).
  4. بعد حوالي 45 ثانية، يصبح التطبيق جاهزًا، ويبدأ مسار /readyz بالرد بـ 200 OK.
  5. ينجح مسبار الجاهزية، فيقوم Kubernetes بإضافة الـ Pod إلى الخدمة ويبدأ في إرسال الترافيك إليه.
  6. مسبار الحياة يبدأ عمله بعد 90 ثانية (وقت كافٍ جدًا)، ليضمن فقط أن التطبيق لن يتجمد لاحقًا.

انتهى كابوس CrashLoopBackOff. وأصبحت عمليات النشر سلسة وهادئة.

ملاحظة للمحترفين: مسبار البدء (Startup Probe)

في الإصدارات الحديثة من Kubernetes (1.18+)، تم تقديم نوع ثالث من المسابير يسمى Startup Probe. هذا المسبار مصمم خصيصًا لحل مشكلة التطبيقات بطيئة البدء بشكل أكثر أناقة. فكرته هي أنه يعطِّل مسبار الحياة والجاهزية حتى ينجح هو أولًا. هذا يمنعنا من الحاجة إلى استخدام initialDelaySeconds كبيرة جدًا في مسبار الحياة.

إذا كنت تستخدم إصدارًا حديثًا، فهذا هو الأسلوب الموصى به:


# ...
    startupProbe:
      httpGet:
        path: /readyz # يمكن استخدام نفس مسار الجاهزية
        port: 80
      failureThreshold: 30
      periodSeconds: 10
    # بعد نجاح startupProbe، تبدأ المسابير التالية بالعمل
    readinessProbe:
      # ...
    livenessProbe:
      # ...

هنا، سيحاول Kubernetes فحص /readyz كل 10 ثوانٍ لمدة تصل إلى 300 ثانية (30 * 10). فقط بعد نجاح هذا الفحص، ستبدأ مسابير الحياة والجاهزية العادية في العمل. هذا هو الحل الأمثل والأكثر نظافة.

الخلاصة والنصيحة الأخيرة 💡

في عالم الحاويات والأوركسترا، التفاصيل الصغيرة هي التي تفرق بين تطبيق “بضل يوقع” وتطبيق صامد مثل شجرة الزيتون. لا تستهينوا بالمسابير أبدًا؛ فهي ليست مجرد إضافة اختيارية، بل هي عيونكم وآذانكم داخل الكلاستر، وهي أساس بناء أنظمة قوية ومستقرة.

تذكروا دائمًا هذا الملخص البسيط:

  • Liveness Probe (مسبار الحياة): هل يجب أن أقتلك وأعيد تشغيلك؟ (للتعامل مع التجمد)
  • Readiness Probe (مسبار الجاهزية): هل يجب أن أرسل لك مستخدمين الآن؟ (لإدارة الترافيك أثناء البدء والتحديثات)
  • Startup Probe (مسبار البدء): هل انتهيت من عملية البدء الطويلة؟ (للتطبيقات بطيئة الإقلاع)

لا تنتظروا حتى تواجهوا كابوس CrashLoopBackOff في منتصف الليل لتتعلموا هذا الدرس. ابدأوا اليوم بإضافة مسابير صحيحة ومنطقية لجميع تطبيقاتكم على Kubernetes. هذا الاستثمار الصغير في ملفات الإعداد سيوفر عليكم ساعات لا تحصى من التوتر وتصحيح الأخطاء في المستقبل.

يلا، شدّوا حيلكم! 💪

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

​معمارية البرمجيات

خدماتي كانت متشابكة كخيوط العنكبوت: كيف أنقذتني ‘المعمارية الموجهة بالأحداث’ (EDA) من جحيم الاقتران المحكم؟

أشارككم قصتي مع نظام برمجي كاد أن ينهار بسبب التشابك والاقتران المحكم بين خدماته. اكتشفوا كيف كانت المعمارية الموجهة بالأحداث (EDA) طوق النجاة الذي حوّل...

5 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

كل شاشة في تطبيقي كانت تبدو مختلفة: كيف أنقذني ‘نظام التصميم’ من جحيم إعادة اختراع العجلة؟

أتذكر جيداً ذلك المشروع الذي كاد أن يودي بي إلى الجنون بسبب عدم اتساق التصميم. في هذه المقالة، أشارككم تجربتي الشخصية وكيف كان "نظام التصميم"...

5 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

استعلاماتي كانت تزحف كالسلحفاة: كيف أنقذتني ‘فهارس قاعدة البيانات’ (Database Indexes) من جحيم الانتظار الطويل؟

أشارككم قصتي مع استعلام SQL استغرق دقائق ليُنفّذ، وكيف تحول إلى أجزاء من الثانية بفضل الفهارس (Indexes). سنغوص في عالم فهارس قواعد البيانات، من هي؟...

5 أبريل، 2026 قراءة المزيد
الشبكات والـ APIs

خدماتي المصغرة كانت فوضى: كيف أنقذتني ‘بوابة الواجهات البرمجية’ (API Gateway) من جحيم الإدارة؟

أشارككم تجربتي الشخصية مع فوضى إدارة الخدمات المصغرة (Microservices) وكيف كانت بوابة الواجهات البرمجية (API Gateway) هي المنقذ الذي أعاد النظام والمنطق إلى بنيتي التحتية....

5 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

فواتيري السحابية كانت تحرق ميزانيتي: كيف أنقذتني ‘علامات الموارد’ (Resource Tags) من جحيم التكاليف الخفية؟

أنا أبو عمر، وأشارككم اليوم قصتي مع فواتير الحوسبة السحابية التي كادت أن تخرج عن السيطرة. اكتشفوا كيف أن أداة بسيطة مثل "علامات الموارد" (Resource...

5 أبريل، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

ملفي الشخصي كان مجرد مستودع أكواد صامت: كيف حوّلني ‘GitHub Profile README’ إلى مغناطيس للفرص؟

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

5 أبريل، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

مستخدم واحد كان يشلّ خدمتي بأكملها: كيف أنقذني ‘تحديد مُعدل الطلبات’ (Rate Limiting) من جحيم هجمات الاستنزاف؟

قصة حقيقية عن ليلة كادت أن تنهار فيها خدمتي بسبب مستخدم واحد فقط، وكيف كانت تقنية 'تحديد معدل الطلبات' (Rate Limiting) هي طوق النجاة. في...

5 أبريل، 2026 قراءة المزيد
البودكاست