مقدمة
المفاهيم والبنية قبل الأوامر.
مقدمة إلى Docker
«على جهازي تعمل». تلخّص هذه العبارة وحدها المشكلة التي يعالجها Docker. فالتطبيق لا يقتصر أبدًا على شيفرته: إنه يعتمد على إصدار معيّن من المفسّر، ومكتبة نظام، ومتغيّر بيئة، وملف إعداد موضوع في المكان الصحيح، وخدمة قاعدة بيانات يمكن الوصول إليها عبر منفذ محدد. هذه الهالة من الاعتماديات لا تكاد تُكتب في أي مكان. إنها تعيش في جهاز المطوّر الذي شغّل المشروع أول مرة، ويُعاد تكوينها يدويًا، بحظ متفاوت، على كل جهاز جديد.
ولا تقتصر النتيجة على وقت مزعج يُقضى في تثبيت مشروع، بل هي فئة كاملة من الأخطاء لا توجد إلا في بيئة بعينها: اختبار ينجح محليًا ويفشل في بيئة التكامل، وإصلاح جرى التحقق منه في بيئة ما قبل الإنتاج فيتعطل في الإنتاج لأن إصدار OpenSSL مختلف. وما دامت بيئة التشغيل ضمنية، فهي تبقى متغيّرًا غير مضبوط في نظامك، وتجد نفسك تصحّح الاختلافات بدل أن تصحّح الشيفرة.
يقدّم Docker جوابًا بسيط الصياغة: جعل البيئة صريحة وخاضعة للإصدارات وقابلة للنقل مثل الشيفرة تمامًا. نصف في ملف نصي كل ما يحتاجه التطبيق ليعمل، ونبني منه حزمة (artefact) ثابتة، ثم نشغّل هذه الحزمة كما هي في كل مكان: جهاز المطوّر، وخادم التكامل، والإنتاج. لم يعد الأمر «ثبّت هذه الأشياء السبعة ثم شغّل»، بل «شغّل هذه الحزمة».
لا يتضمن هذا الدرس الأول عمدًا أي إعداد تقريبًا. إنه يرسّخ المفردات والنموذج الذهني — الصورة والحاوية والطبقة والسجل — لأن معظم العقبات التي يواجهها المبتدئون مع Docker تأتي من الخلط بين هذه الكلمات الأربع، لا من خطأ في كتابة خيار أمر ما.
الحاوية أم الآلة الافتراضية: أين الفرق الحقيقي
التشبيه «الحاوية هي آلة افتراضية صغيرة» هو الأكثر انتشارًا والأكثر كلفة. وهو خاطئ في النقطة الجوهرية: ما الذي تتم محاكاته افتراضيًا.
تحاكي الآلة الافتراضية العتاد. يقدّم المشرف الافتراضي (hypervisor) للنظام الضيف معالجًا وذاكرة وأقراصًا مُحاكاة، ثم يشغّل هذا الضيف نواة كاملة ونظام تشغيل خاصًا به وخدمات إقلاعه. وتشغّل آلتان افتراضيتان بنظام Linux على المضيف نفسه نواتين مستقلتين من Linux.
أما الحاوية فلا تحاكي شيئًا من ذلك. إنها تستخدم نواة المضيف وتكتفي بأن تطلب منها رؤية مقيّدة للنظام، بفضل آليتين في نواة Linux:
- مساحات الأسماء (namespaces)، وهي تعزل ما تراه العملية: شجرة العمليات الخاصة بها (تعتقد أنها PID 1)، وحزمة الشبكة الخاصة بها، ونقاط التركيب الخاصة بها، واسم المضيف الخاص بها؛
- مجموعات التحكم (cgroups)، وهي تحدّ مما تستهلكه العملية: حصة المعالج، وسقف الذاكرة، ومعدل عمليات الإدخال والإخراج.
الحاوية إذن عملية عادية في المضيف، معزولة ومقيّدة. وهذا ما يفسّر كل اختلافات سلوكها عن الآلة الافتراضية:
| الآلة الافتراضية | الحاوية | |
|---|---|---|
| ما تتم محاكاته | العتاد | رؤية النظام (namespaces) |
| النواة | نواة لكل آلة افتراضية | نواة المضيف، مشتركة |
| زمن الإقلاع | عشرات الثواني (إقلاع نظام التشغيل) | من أجزاء الثانية إلى بضع ثوانٍ |
| الحجم على القرص | عدة غيغابايت | من بضعة ميغابايت إلى مئات الميغابايت |
| حدّ الأمان | قوي (المشرف الافتراضي) | أرقّ (نواة مشتركة) |
| الأنظمة القابلة للاستضافة | أي نظام تشغيل | الأنظمة المتوافقة مع نواة المضيف |
السطران الأخيران يستحقان التوقف عندهما لا المرور السريع عليهما. فبما أن النواة مشتركة، فإن ثغرة للهروب من الحاوية تمنح الوصول إلى المضيف: ولتنفيذ شيفرة غير موثوقة في بيئة متعددة المستأجرين تبقى الآلة الافتراضية هي الحدّ المرجعي، وغالبًا ما تجمع الصناعة بين الاثنين (حاويات داخل آلات افتراضية). وبما أن النواة مشتركة، فإن صورة Linux تحتاج إلى نواة Linux. فعندما تشغّل حاوية Linux على macOS أو Windows، يشغّل Docker Desktop بصمت آلة افتراضية بنظام Linux من أجلك: ومن هناك تأتي النواة.
يمكنك التحقق بنفسك من اشتراك النواة بعد تثبيت Docker:
# Version du noyau de votre hôte
uname -r
# Version du noyau vue depuis l'interieur d'un conteneur Ubuntu.
# Le resultat est identique a la ligne du dessus : c'est le meme noyau.
docker run --rm ubuntu:24.04 uname -r
أما التوزيعة فهي تختلف: فالصورة هي التي توفّرها.
# L'hote est peut-etre une Debian, le conteneur se presente en Ubuntu 24.04
docker run --rm ubuntu:24.04 cat /etc/os-release
فالصورة لا تحتوي إذن على نظام تشغيل كامل: لا نواة ولا مشغّلات أجهزة. إنها تحتوي على فضاء المستخدم (user space) الخاص بتوزيعة ما: مكتباتها وملفاتها التنفيذية وشجرة ملفاتها. ومن هنا خفّتها.
الصورة والحاوية والسجل: ثلاثة كائنات لا ينبغي الخلط بينها
ثلاث كلمات وثلاث طبائع مختلفة. والخلط بين الأولى والثانية هو، وبفارق كبير، السبب الأول لسوء الفهم.
الصورة (image) قالب للقراءة فقط: مجموعة ملفات مجمّدة مع بيانات وصفية (الأمر المراد تشغيله، والمنفذ المكشوف، ومتغيرات البيئة، والمستخدم). الصورة لا تُنفَّذ ولا «تعمل» أبدًا. إنها مكافئة لملف تنفيذي على القرص، أو لصنف (class) في البرمجة كائنية التوجه.
الحاوية (container) نسخة قيد التشغيل من صورة. يأخذ Docker الصورة، ويضيف إليها طبقة رقيقة قابلة للكتابة، ثم يبدأ العملية المصرَّح بها. إنها مكافئة لعملية جرى تشغيلها، أو لكائن جرى إنشاؤه. ويمكن لصورة واحدة أن تولّد مئة حاوية مستقلة.
السجل (registry) مستودع للصور يمكن الوصول إليه عبر الشبكة. Docker Hub هو السجل العام الافتراضي؛ وتوجد سجلات خاصة (GitLab وGitHub وAWS ECR وHarbor). إنه مكافئ لمستودع حزم مثل npm أو PyPI، لكن لبيئات كاملة.
# 1. Recuperer une image depuis un registre vers le cache local
docker pull nginx:1.27-alpine
# 2. Lister les images presentes localement
docker images
# 3. Instancier DEUX conteneurs a partir de la MEME image,
# sur deux ports differents de l'hote
docker run -d --name web1 -p 8081:80 nginx:1.27-alpine
docker run -d --name web2 -p 8082:80 nginx:1.27-alpine
# 4. Les voir tourner cote a cote
docker ps
حاويتان وصورة واحدة، ولا تكرار للملفات على القرص. وتعديل ملف في web1 لا يمسّ web2 ولا الصورة: فالكتابة تذهب إلى الطبقة القابلة للتعديل الخاصة بـ web1، وتزول معه.
docker exec web1 sh -c 'echo "modifie" > /usr/share/nginx/html/index.html'
# web1 a change...
docker exec web1 cat /usr/share/nginx/html/index.html
# ...web2 n'a rien vu.
docker exec web2 cat /usr/share/nginx/html/index.html
هذه التفصيلة تأسيسية: الحاوية عابرة بحكم التصميم. كل ما تكتبه في نظام ملفاتها الخاص يموت معها. وهذه ليست قيدًا ينبغي الالتفاف عليه، بل هي الخاصية التي تجعل الحاويات قابلة لإعادة الإنتاج — وهي سبب وجود الأحجام (volumes) التي يعالجها الدرس 5 بالتفصيل.
مرجع الصورة: السجل والمستودع والوسم
اسم صورة مثل nginx:1.27-alpine صيغة مختصرة. أما الصيغة الكاملة فهي:
[registre/][espace-de-noms/]depot[:tag]
docker.io/library/nginx:1.27-alpine
│ │ │ │
│ │ │ └── tag : la version
│ │ └──────── depot
│ └──────────────── espace de noms (library = images officielles)
└────────────────────────── registre (docker.io par defaut)
هناك ثلاثة فخاخ ينبغي التنبيه إليها فورًا.
الوسم latest ليس وعدًا بالحداثة: إنه وسم افتراضي كغيره، هو الذي يُستخدم عندما لا تحدد أي وسم. وهو متحرك: فالصورة التي يشير إليها latest تتغير مع الزمن. وأمرا docker pull بينهما ستة أشهر لا يجلبان المحتوى نفسه، مما يهدم قابلية إعادة الإنتاج المنشودة.
اللاحقة -alpine تدل على صور مبنية على Alpine Linux، وهي صغيرة جدًا لأنها تستخدم مكتبة C المسماة musl بدل glibc. ولهذا التوفير كلفة حقيقية: فبعض الملفات التنفيذية المُترجمة مسبقًا تتوقع glibc وتفشل. وغالبًا ما تكون المتغيرات -slim (قاعدة Debian مصغّرة) أفضل حل وسط.
أخيرًا، لتثبيت صورة تثبيتًا تامًا، يمكن الإشارة إليها عبر digest الخاص بها، وهو البصمة التشفيرية لمحتواها، ولا يتغير أبدًا:
# Un tag est mobile ; un digest est immuable
docker pull nginx@sha256:0c86dddac19f2ce4fb916fcd7015e1272ea67e3a3fbb6f88df3eb0e1c9a5c1d1
# Afficher le digest des images locales
docker images --digests
البنية: العميل والـ daemon والسجل
عندما تكتب docker run فأنت لا تشغّل الحاوية. إنك تنفّذ عميلًا بسطر الأوامر يرسل طلب HTTP إلى واجهة REST API يكشفها daemon — هو dockerd — وهو الذي يؤدي العمل الفعلي: يحدّد الصورة، وينزّلها عند الحاجة، وينشئ مساحات الأسماء ومجموعات التحكم (cgroups)، ويبدأ العملية.
docker CLI ──── API REST (socket UNIX) ────> dockerd ────> conteneurs
(votre shell) /var/run/docker.sock (le daemon) (processus isoles)
│
└──── HTTPS ────> registre
هذا الفصل ليس تفصيلًا في التنفيذ، بل له ثلاث نتائج عملية.
أولًا، إن كل أخطاء النوع «Cannot connect to the Docker daemon» تقريبًا ليست أخطاء في الأمر: فإما أن الـ daemon لم يبدأ، وإما أن مستخدمك لا يملك الحق في الكتابة على المقبس (socket). وسيعالج الدرس التالي هذه النقطة.
ثانيًا، يمكن للعميل أن يتحكم في daemon بعيد. وهذا ما يتيح بناء صورة على خادم قوي انطلاقًا من حاسوب محمول:
# Piloter un daemon distant via SSH, sans rien installer de plus
docker --host ssh://deploy@build-01.exemple.com ps
# Ou en fixant le contexte de facon persistante
docker context create serveur --docker "host=ssh://deploy@build-01.exemple.com"
docker context use serveur
docker context ls # affiche le contexte actif
docker context use default
وأخيرًا، ينبغي أن تعلم أن الـ daemon يعمل بصلاحيات المستخدم الأعلى (superuser). ومنح حساب ما حق الوصول إلى مقبس Docker يعادل عمليًا منحه صلاحية الجذر (root) على المضيف — وهي نقطة ينبغي استحضارها في أي خادم مشترك.
نظرة شاملة على التثبيت بأمر واحد:
# Versions du client et du daemon, systeme de stockage, nombre de conteneurs
docker version
docker info
نظام الطبقات: لماذا الصور خفيفة وسريعة
الصورة ليست ملفًا ضخمًا متجانسًا. إنها مجموعة طبقات (layers) متراكمة تُدمَج عند القراءة بواسطة نظام ملفات اتحادي (union filesystem) — هو overlay2 على Linux. وتنتج كل تعليمة بناء طبقة لا تسجّل إلا فرقها عن الطبقة السابقة.
# Afficher l'historique en couches d'une image, taille par couche
docker history nginx:1.27-alpine
تنتج عن ذلك خاصيتان، وهما تفسّران نصف مكاسب الأداء في Docker.
الطبقات مشتركة بين الصور. فإذا اشتقّت عشر من صورك من python:3.12-slim، فإن هذه القاعدة لا تُخزَّن إلا مرة واحدة على القرص ولا تُنزَّل إلا مرة واحدة. ويمكنك ملاحظة ذلك مباشرة: أول pull لعائلة من الصور بطيء، بينما تعلن الأوامر اللاحقة Already exists على معظم الطبقات.
docker pull python:3.12-slim
# Les couches communes s'affichent en "Already exists", pas retelechargees
docker pull python:3.12
الطبقات غير قابلة للتغيير ومعنونة بالمحتوى. فالطبقة التي لم تتغير تُعاد من ذاكرة التخزين المؤقت بدل إعادة بنائها. وهذه هي الرافعة المركزية لتحسين ملفات Dockerfile — موضوع الدرس 3 — وهي سبب كون ترتيب التعليمات في Dockerfile يغيّر زمن البناء جذريًا.
والطبقات كذلك تراكمية، لا تنقص أبدًا: فحذف ملف في طبقة لاحقة يخفيه ولا يزيله من الصورة. والسر الذي نُسخ ثم «حُذف» يظل قابلًا للاستخراج من الصورة. وسنعود إلى ذلك.
ما لا يفعله Docker
وضع الحدود منذ البداية يجنّبنا توقعات خائبة.
Docker لا ينسّق أسطولًا من الآلات. إنه يدير حاويات مضيف واحد. أما توزيع الحاويات على عشرين خادمًا، وإعادة تشغيلها في مكان آخر عند تعطل آلة، والتوسع التلقائي عند ازدياد الحمل، فهذا عمل منسّق (orchestrator) مثل Kubernetes. أما Docker Compose، الذي نراه في الدرس 4، فينسّق عدة حاويات على مضيف واحد فقط.
Docker لا يجعل التطبيق قابلًا للتوسع أو عديم الحالة. فوضع برنامج يكتب في مجلد محلي ويحتفظ بجلساته في الذاكرة داخل حاوية ينتج حاوية تكتب في مجلد محلي وتحتفظ بجلساتها في الذاكرة. إن Docker يُظهر هذه الخيارات المعمارية؛ لكنه لا يصحّحها.
Docker ليس بيئة معزولة آمنة بشكل مطلق. فالحاوية تعمل افتراضيًا بصلاحية root (داخل مساحة أسمائها)، وهناك عدة خيارات شائعة — --privileged وتركيب مقبس Docker — تفتح الباب على مصراعيه نحو المضيف.
وأخيرًا، Docker لم يعد التنفيذ الوحيد. فصيغة الصور وسلوك التشغيل موحَّدان بواسطة Open Container Initiative. وتستهلك أدوات مثل Podman أو containerd الصور نفسها. وما تتعلمه هنا إذن ليس حبيس منتج واحد.
دورة العمل الكاملة: نظرة سريعة
قبل الدخول في الأوامر، إليك الحلقة التي ستتناولها الدروس الخمسة التالية:
Dockerfile ──build──> Image ──push──> Registre
│ │
│ pull
run │
v v
Conteneur <──────── Serveur de production
تكتب ملف Dockerfile (الدرس 3)، وتبني منه صورة، وتشغّلها محليًا للتحقق، ثم تدفعها إلى سجل، ويجلبها خادم لتشغيلها بالمطابقة نفسها (الدرس 6). وفي هذه الأثناء يتيح لك Compose (الدرس 4) تشغيل عدة حاويات معًا، تربطها شبكة وتسندها أحجام (volumes) (الدرس 5).
وهذه الحلقة مختزلة في أوامرها الأساسية، كمرجع فقط، وسيُعاد تناول كل منها بالتفصيل:
docker build -t mon-app:1.0 . # Dockerfile -> image
docker run -d -p 8080:8080 mon-app:1.0 # image -> conteneur
docker push registre.exemple.com/mon-app:1.0 # image -> registre
الخلاصة
- الحاوية ليست آلة افتراضية صغيرة: إنها عملية (process) على المضيف معزولة بواسطة الـ namespaces ومقيّدة بواسطة الـ cgroups، وتستخدم نواة المضيف. ومن هنا خفّتها وسرعة إقلاعها، وحدٌّ أمني أدقّ من ذلك الذي يوفّره الـ hypervisor.
- الصورة (Image) = قالب ثابت للقراءة فقط؛ الحاوية (Container) = نسخة قيد التنفيذ من هذا القالب؛ السجل (Registry) = مستودع للصور. الصورة لا تعمل، والحاوية لا تُشارَك.
- كل ما تكتبه الحاوية في نظام ملفاتها يزول بزوالها. وهذا مقصود، وهو سبب وجود الـ volumes.
- الصورة عبارة عن حزمة من الطبقات غير القابلة للتغيير تُشارَك بين الصور وتُخزَّن مؤقتًا: وهي الرافعة الأهم لعمليات بناء سريعة ونقل خفيف.
- العميل
dockerلا ينجز شيئًا بنفسه: فهو يتخاطب مع الـ daemon المسمّىdockerdعبر API. وهذا الفصل يفسّر أخطاء الاتصال بالـ socket، ويتيح التحكم بمضيف بعيد. - تجنّبوا الوسم
latestمتى كانت قابلية إعادة الإنتاج مهمة: ثبّتوا إصدارًا محددًا، أو digest من النوعsha256:للحصول على تثبيت كامل.
معلم : الوثائق الرسمية متاحة على docs.docker.com. وهي المرجع في خيارات الأوامر، التي تتغير من إصدار إلى آخر.
التعليقات
التعليقات مدعومة عبر GitHub Discussions
سجّل الدخول باستخدام GitHub للمشاركة في النقاش