CMD مقابل ENTRYPOINT والإعداد
CMD مقابل ENTRYPOINT والإعداد
كل حاوية تحتاج أن تعرف أي عملية تُشغّل. يمنحك Docker تعليمتين لتعريف ذلك — CMD وENTRYPOINT — وفهم الفرق بينهما هو من أكثر ما يمكنك تعلمه عن Dockerfiles قيمةً عملياً. أخطئ فيه وستتجاهل الحاويات بصمت المعطيات والإشارات أو تتصرف بشكل مختلف تاماً بين بيئة التطوير وCI والإنتاج. أتقنه وستعمل صورك بشكل بديهي على سطر الأوامر وفي Kubernetes وفي الإنتاج.
دلالات الإقلاع: كيف يختار Docker العملية PID 1
حين يُشغّل Docker حاوية، يُنشئ عملية Linux معزولة بنطاق اسمي (namespace). العملية الأولى التي تعمل — PID 1 — هي init الحاوية. حين يخرج PID 1، تتوقف الحاوية. هذا مهم لسببين:
- توزيع الإشارات: يُرسل النواة
SIGTERMإلى PID 1 حين تُشغّلdocker stop. إن كان PID 1 غلافاً من shell لا يُعيد توجيه الإشارات، فعمليتك الحقيقية لن تحصل أبداً على إشارة الإغلاق السلس وسيقتلها Docker بـSIGKILLبعد مهلة 10 ثوانٍ — تاركاً اتصالات مفتوحة ومعاملات غير مُودَعة أو ملفات نصف مكتوبة. - تبنّي العمليات الزومبي: PID 1 مسؤول عن تبنّي العمليات الزومبي (الأبناء التي خرجت لكن حالة خروجها لم تُجمَع). معظم بيئات تشغيل التطبيقات غير مكتوبة للقيام بذلك. استخدام exec form وinit بسيط مثل
tiniيعالج كلا المشكلتين.
Shell Form مقابل Exec Form
يقبل كلٌّ من CMD وENTRYPOINT صيغتين. هذا التمييز يقود كل شيء آخر.
تُغلّف Shell form أمرك في /bin/sh -c. يصبح ذلك الـ shell هو PID 1 لا عمليتك. لا يُعيد توجيه SIGTERM إلى الأبناء افتراضياً، والـ shell يخرج حين ينتهي الأمر — لكن الـ shell نفسه هو PID 1، فالتوقيت غير قابل للتنبؤ. افضّل دائماً exec form في صور الإنتاج.
CMD node server.js) ستستقبل SIGTERM عند عملية الـ shell لا عند Node. يتجاهلها الـ shell (أو ينهي فوراً)، ويتلقى Node الـSIGKILL بعد مهلة إيقاف Docker. في Kubernetes، هذا يعني أن pods ستصل دائماً لمهلة إنهاء الخدمة (30 ث افتراضياً) وتُقتل قسراً، تاركةً طلبات HTTP جارية غير مكتملة. انتقل إلى exec form: CMD ["node", "server.js"].ENTRYPOINT مقابل CMD: نموذج التفاعل
القاعدة الجوهرية بسيطة: ENTRYPOINT يُعرّف الملف التنفيذي؛ CMD يُزوّده بمعطيات افتراضية. حين يوجد كلاهما، يُسلسلهما Docker: ENTRYPOINT + CMD. المعطيات المُمرَّرة على سطر الأوامر تستبدل CMD لكن لا تستبدل ENTRYPOINT أبداً (إلا باستخدام --entrypoint).
هذا التفاعل هو ما يجعل الصور ذات أسلوب CLI ممكنة — صور لأدوات مثل curl أو aws-cli أو منفذي الهجرات المخصصين حيث نقطة الدخول هي الأداة نفسها والمستخدمون يُمرّرون الأوامر الفرعية كمعطيات.
ENTRYPOINT حين يكون للصورة غرض واحد واضح (أداة CLI أو خادم أو عامل). استخدم CMD وحده للصور الأساسية حيث يُتوقع من المستدعي تقديم أمر مختلف تاماً. استخدمهما معاً حين تريد ملفاً تنفيذياً ثابتاً بمعطيات افتراضية قابلة للضبط.متغيرات البيئة: الإعداد وقت التشغيل
تقول منهجية تطبيق الاثني عشر عاملاً (العامل III) أن الإعداد الذي يتغير بين بيئات النشر يجب أن يأتي من متغيرات البيئة لا أن يُخبَّأ في الصورة. يمنحك Docker آليتين:
ENV KEY=value— يضبط متغيراً وقت البناء يستمر في الحاوية الجارية كقيمة افتراضية. يظهر فيdocker inspect.docker run -e KEY=valueأو Kubernetesenv:— تجاوز وقت التشغيل. هذا هو نمط الإنتاج.
ENV SECRET_KEY=abc123 يُخبّئ السر في كل طبقة صورة بشكل دائم. يظهر في docker history وdocker inspect وأي سجل صور تدفع إليه. يجب أن تصل الأسرار وقت التشغيل عبر ملفات مُحمَّلة (Kubernetes Secrets، Docker secrets) أو متغيرات بيئة تُحقنها جهة التنسيق أو SDK لإدارة الأسرار. يجب ألا تحمل الصورة أبداً بيانات اعتماد.معطيات البناء: التحجيم وقت الترجمة
ARG هو المكافئ وقت البناء لـCMD. يُعرّف متغيراً متاحاً فقط خلال مرحلة docker build — لا يستمر في الحاوية الجارية. الاستخدامات الشائعة: تثبيت إصدارات التبعيات، تبديل أعلام التنقيح، تمرير Git SHA لتتبع البناء.
ARG يتغير (كـ Git SHA) يُبطل ذاكرة البناء المؤقتة في تلك الطبقة وكل ما يليها. ضع قيم ARG كثيرة التغيير في أواخر Dockerfile قدر الإمكان — بعد تثبيت التبعيات — حتى تظل طبقات RUN npm ci / RUN pip install مخزنة مؤقتاً. وضع ARG GIT_SHA في السطر الثاني يعني إعادة تثبيت كاملة مع كل commit.سكريبتات Shell كنقاط دخول: نمط init
تحتاج الخدمات المعقدة كثيراً إلى القيام بعمل قبل بدء العملية الرئيسية: انتظار قاعدة البيانات، إنشاء ملف إعداد من متغيرات البيئة، تشغيل هجرات قاعدة البيانات. النمط القياسي هو سكريبت shell كنقطة دخول يستخدم exec للتسليم إلى العملية الرئيسية — مع الحفاظ على ملكية PID 1 وإعادة توجيه الإشارات.
exec "$@" في نهاية سكريبت الـ shell هو السر كله. بدونه يظل الـ shell هو PID 1 وعملية gunicorn ابنٌ له. بها، يستبدل الـ shell نفسه بـgunicorn الذي يصبح PID 1 ويستقبل الإشارات مباشرة. هذا النمط يُستخدم حرفياً في صور Docker الرسمية لـPostgreSQL وRedis وNginx.
tini كنظام init بسيط. أضف RUN apt-get install -y tini واضبط ENTRYPOINT ["/usr/bin/tini", "--", "تطبيقك"]. يتبنى Tini الزومبي ويُعيد توجيه الإشارات بشكل صحيح، وهو ما لا تفعله الـ shells العارية ومعظم بيئات تشغيل التطبيقات. مستخدمو Kubernetes يمكنهم أيضاً ضبط shareProcessNamespace: true والسماح لـkubelet بمعالجة ذلك.ملخص: دليل اتخاذ القرار
- استخدم exec form لكلٍّ من
CMDوENTRYPOINT— دائماً. - استخدم
ENTRYPOINTلتعريف ما هي الحاوية؛ استخدمCMDللمعطيات الافتراضية التي قد يتجاوزها المستخدم. - تجنب
ENVللأسرار — احقنها وقت التشغيل من جهة التنسيق أو مدير الأسرار. - ضع تعليمات
ARGفي أواخر Dockerfile قدر الإمكان للحفاظ على ذاكرة الطبقات المؤقتة. - يجب أن تنتهي سكريبتات shell كنقاط دخول بـ
exec "$@"حتى ترث العملية الرئيسية PID 1.