حلقة إعادة تشغيل حاوية Docker: ماذا تعني وكيف تصلحها
عبارة "Restarting (1) 5 seconds ago" تعني أن حاويتك تواصل الخروج وسياسة إعادة التشغيل تواصل إحياءها. ورمز الخروج والسجلات يقولان السبب.
ما حلقة إعادة التشغيل فعليًا
حلقة إعادة تشغيل Docker هي اجتماع أمرين: حاوية لا تكفّ عمليتها الرئيسية عن الخروج، وسياسة إعادة تشغيل — on-failure أو always أو unless-stopped — لا تكفّ عن إعادتها. ويضيف Docker تأخيرًا يتضاعف بين الإعادات (يبدأ بـ 100 ميلي ثانية بسقف دقيقة)، ولهذا يعرض docker ps عبارة "Restarting (1) X seconds ago" ورمزُ الخروج الأخير بين القوسين.
الحلقة نفسها مجرد سياسة تؤدي عملها؛ والسؤال هو لماذا تخرج العملية. تحيا الحاويات وتموت مع عمليتها الرئيسية (PID 1)، فكل ما ينهي تلك العملية — انهيار، أو خطأ إعدادات، أو سكربت اكتمل، أو قتل بنفاد الذاكرة، أو عملية تحوّل نفسها إلى الخلفية — ينهي الحاوية. ورمز الخروج الذي يسجّله Docker هو الدليل الأول على أيّ هذه حدث.
الأسباب الجذرية الشائعة لحلقة إعادة التشغيل
التطبيق ينهار عند الإقلاع
متغيرات بيئة مفقودة، أو ملف إعدادات مركَّب بشكل خاطئ، أو منفذ مشغول داخل الحاوية، أو خطأ شيفرة في التهيئة. تخرج الحاوية برمز 1 خلال ثوانٍ من إقلاعها، ويعرض docker logs رسالة الخطأ نفسها في كل دورة.
اعتماد غير جاهز عند الإقلاع
يبدأ التطبيق قبل أن تقبل قاعدة بياناته أو طابوره أو كاشه الاتصالات، فيفشل الاتصال الأول ويخرج — حالة كلاسيكية في docker-compose، حيث يرتّب depends_on الإقلاعَ لكنه لا ينتظر الجاهزية. وبعد أن يسخن كل شيء تنجح إعادة تشغيل يدوية، وهذه هي العلامة الفارقة.
العملية لا تبقى في المقدمة
entrypoint يشغّل خدمة تتحوّل إلى الخلفية (nginx بلا "daemon off;" أو خدمة تُطلق بـ & أو بأسلوب systemctl) يعود فورًا — فتخرج PID 1 برمز 0، ويعيدها Docker بإخلاص تحت restart: always. الحاويات تحتاج عملية في المقدمة.
قتل نفاد الذاكرة وتقلّب healthcheck
الحاوية التي تتجاوز حدّ --memory تُقتل برمز 137 (مع State.OOMKilled = true في docker inspect). وعلى نحو منفصل، الـ HEALTHCHECK الصارم لا يجعل Docker نفسه يعيد تشغيل شيء — لكن المنسّقات (Swarm أو Compose مع أدوات إعادة تلقائية أو Kubernetes) تعيد إنشاء الحاويات «غير الصحية»، فتنتج الحلقة ذاتها بمحرّك مختلف.
كيف تحقّق في حلقة إعادة التشغيل وتصلحها
احصل على رمز الخروج، واقرأ السجلات، وأعد الإنتاج تفاعليًا عند الحاجة — فمعظم الحلقات تؤول إلى رسالة خطأ واحدة واضحة ظلت الحاوية تطبعها طوال الوقت.
- 1
افحص الحالة ورمز الخروج
يعرض docker ps -a حالة الإعادة وآخر رمز خروج؛ ويعطيك docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}}' <name> كليهما بدقة. الرمز 1 = خطأ تطبيق، و137 = إشارة SIGKILL (غالبًا نفاد ذاكرة)، و126/127 = أمر غير قابل للتنفيذ أو غير موجود، و0 = العملية انتهت ببساطة.
- 2
اقرأ سجلات الحاوية
يصمد docker logs --tail 100 <name> عبر الإعادات ويعرض المخرجات الأخيرة للعملية من كل محاولة. والخطأ المتكرر في نهاية كل دورة — متغيّر مفقود، أو اتصال مرفوض، أو مسار استدعاء — هو عادة التشخيص بأكمله.
- 3
افحص قتل نفاد الذاكرة
إذا كان رمز الخروج 137، فافحص State.OOMKilled في docker inspect وdmesg على المضيف. إن كانت true فارفع حدّ --memory إلى حاجة الحمل الحقيقية أو عالج نموّ ذاكرة التطبيق؛ وإن كانت false فشيء ما أرسل SIGKILL — افحص التنسيق المدفوع بالـ healthcheck والقتل اليدوي.
- 4
تحقّق أن الـ entrypoint يعمل في المقدمة
إذا خرجت الحاوية برمز 0 فورًا تقريبًا، فالعملية الرئيسية لا تبقى حية. شغّل الخدمة في المقدمة (nginx -g 'daemon off;'، واستخدم exec للعملية الأخيرة في سكربتات shell لتصبح PID 1) وتأكد بـ docker inspect أن تركيبة CMD/ENTRYPOINT هي ما تظنه.
- 5
أعد الإنتاج تفاعليًا
أوقف الحلقة (docker update --restart=no <name> ثم docker stop) وشغّل الصورة يدويًا: docker run -it --entrypoint sh <image> ثم نفّذ الأمر الحقيقي بنفسك. ومشاهدة الفشل في صدفة تفاعلية — بنفس البيئة والتركيبات — أفضل من قراءة السجلات عبر ثقب الباب.
- 6
أصلح ترتيب الإقلاع للاعتمادات
أضف إعادة محاولة بتراجع تدريجي إلى منطق اتصال التطبيق (الإصلاح المتين)، أو استخدم depends_on مع condition: service_healthy وhealthcheck على الاعتماد في Compose. فالخدمة التي تحتمل إقلاعًا بطيئًا لقاعدة البيانات تنجو من إعادات التشغيل والنشر والحادثة القادمة أيضًا.
كيف تمنع حلقات إعادة التشغيل
- ابنِ إعادة محاولات الاتصال بتراجع تدريجي داخل الخدمات بدل افتراض جاهزية الاعتمادات لحظة إقلاع الحاوية.
- تحقّق من الإعدادات المطلوبة عند الإقلاع بسطر سجل فاشل وواضح — فالحلقة قد لا تُتجنّب، لكن التشخيص يجب ألا يضيع.
- اضبط حدود الذاكرة من استخدام مقيس مع هامش، وراقب ذاكرة الحاويات حتى لا يبقى قتل نفاد الذاكرة مفاجأة.
- اكتب healthchecks تختبر الجاهزية الحقيقية بفواصل ومحاولات معقولة — فالفحص المتقلّب يسبّب إعادات التشغيل التي وُجد ليكتشفها.
- نبّه على عدّادات إعادة تشغيل الحاويات وعمرها لا على «هل تعمل» فقط — فسياسة restart: always قد تخفي خدمة تحتضر لأسابيع.
كيف يساعدك AllStak مع حلقات إعادة تشغيل الحاويات
تتتبّع مراقبة Docker في AllStak أحداث دورة حياة الحاويات وعدّادات إعادة التشغيل لكل مضيف، فتتحوّل الحاوية التي تدور بصمت تحت restart: always إلى قفزة مرئية يمكن التنبيه عليها — بدل شيء تكتشفه بعد أسابيع في docker ps. ورسوم ذاكرة كل حاوية بجانب حدّها المضبوط تجعل الحلقات المدفوعة بنفاد الذاكرة بديهية.
وتحفظ سجلات الحاويات المجموعة في المنصّة نفسها مخرجاتِ العملية المحتضرة عبر الإعادات، مع مقاييس المضيف وعلامات النشر على الخط الزمني ذاته — فتتحوّل «بدأت تدور مع الصورة الجديدة» أو «تدور كلما اشتد الضغط على المضيف» من حدس إلى رسم بياني. يبقى التشخيص لك؛ لكن الأدلة في مكان واحد.
أسئلة شائعة عن حلقات إعادة تشغيل Docker
ماذا يعني رمز الخروج 137 لحاوية؟
هو 128 + 9 — تلقت العملية إشارة SIGKILL. والسبب المعتاد قاتل OOM في النواة وهو يفرض حدّ ذاكرة الحاوية؛ ويؤكده State.OOMKilled في docker inspect. وإن كانت OOMKilled تساوي false فالإشارة جاءت من مكان آخر: docker kill، أو منسّق يستبدل حاوية غير صحية، أو إيقاف انقضت فترة سماحه.
أي سياسة إعادة تشغيل أستخدم؟
للخدمات طويلة العمر، unless-stopped هي الخيار المعتاد: تنجو من إعادة تشغيل الـ daemon وتحترم الإيقاف اليدوي. وon-failure[:max-retries] تناسب المهام التي ينبغي أن تعيد المحاولة عددًا محدودًا. أما always فمثل unless-stopped لكنها تُحيي حتى الحاويات الموقوفة يدويًا بعد إعادة تشغيل الـ daemon — وهذا يفاجئ الناس. ولا تُصلح أيٌّ منها تطبيقًا منهارًا؛ هي تقرّر فقط كم تُلحّ الحلقة.
لماذا لا يحل depends_on انهيارات الإقلاع لديّ؟
لأن depends_on المجرد يرتّب بدء الحاويات فقط لا جاهزيتها — فينطلق تطبيقك لحظة بدء حاوية قاعدة البيانات، قبل أن يقبل PostgreSQL الاتصالات بثوانٍ غالبًا. استخدم depends_on مع condition: service_healthy وhealthcheck على الاعتماد، وأضف إعادة محاولات اتصال في التطبيق نفسه لكل ما لا يراه Compose.
كيف أوقف الحلقة بما يكفي للتشخيص؟
يغيّر docker update --restart=no <name> السياسةَ على الحاوية الحية، ثم ينهيها docker stop بلا إحياء. بعدها افحص رمز الخروج والسجلات بهدوء، أو شغّل الصورة تفاعليًا بـ docker run -it --entrypoint sh لتنفّذ الأمر الحقيقي بيدك وتشاهده يفشل.
اعرف متى تبدأ حاوية في الدوران
يراقب AllStak إعادات تشغيل الحاويات وذاكرتها وسجلاتها عبر مضيفيك، فتتحوّل حلقة إعادة التشغيل الصامتة إلى تنبيه مرفق بالأدلة.