FlowingDev

شرح Docker Compose: الريموت كنترول الشامل للكونتينرز بتاعتك

تعلم كيف يستخدم Docker Compose ملف YAML لإدارة التطبيقات متعددة الكونتينرز، محولاً أوامر docker run المعقدة إلى مخطط واحد قابل للمشاركة.

جرّب الأداة: Docker Run لـ Compose

في جملة واحدة

Docker Compose هي أداة تسمح لك بتعريف وتشغيل التطبيقات اللي فيها أكتر من كونتينر باستخدام ملف YAML واحد سهل القراءة، بدلًا من مجموعة من أوامر docker run الطويلة والمعقدة.

المشكلة اللي بيحلها

في البداية، كان فيه أمر docker run. وكان... يعني، ماشي حاله. عشان تشغل كونتينر واحد، هو كويس. بتكتب الأمر بتاعك، يمكن معاه port mapping و volume، وبوم، بقى عندك خدمة شغالة.

بس إيه اللي بيحصل لما تطبيقك ميكونش مجرد خدمة واحدة؟ ماذا لو عندك واجهة ويب (frontend)، و backend API، وقاعدة بيانات PostgreSQL، و Redis cache؟ فجأة، أنت مش بتشغل أمر واحد بس. أنت بتحاول توفق بين أربعة أوامر في نفس الوقت.

لازم تعمل شبكة (network) بنفسك عشان يقدروا يكلموا بعض. لازم تتأكد إنك مش هتربطهم كلهم بنفس الـ port على جهازك بالغلط. لازم تفتكر أمر docker run الطويل جدًا بتاع كل خدمة، بكل متغيرات البيئة (environment variables) والـ volume mounts بتاعته. ولو عايز تشارك الإعدادات دي مع زميل جديد في الفريق، هتكتب ملف README.md ضخم هو عبارة عن صلاة إنه ينسخ ويلصق كل حاجة صح. دي وصفة للفوضى، والأخطاء الإملائية، والمعضلة الكلاسيكية "بس هي شغالة على جهازي!".

هذه هي الفوضى التي صُمم Docker Compose لترويضها، وهو الذي وُلد من مشروع اسمه Fig استحوذت عليه Docker في 2014. الفكرة هي الانتقال من إدارة "حيوانات أليفة" (كونتينرز فردية) إلى تنسيق "قطيع" كامل من التطبيقات. بدلًا من إصدار أوامر إلزامية (imperative) زي (شغّل ده، وبعدين شغّل ده، واربطهم ببعض بالشكل ده...)، بتكتب ملف واحد تعريفي (declarative) بيقول (ده شكل الـ application stack بتاعي بالكامل) وتخلي Compose يتولى التفاصيل المعقدة لتحويل ده إلى حقيقة.

كيف يعمل من الداخل

في جوهره، Docker Compose هو مجرد مترجم. هو بيترجم ملف YAML وصفي إلى سلسلة من استدعاءات الـ API لمحرك Docker. هو بمثابة مبدأ "وريني، متقوليش" (show, don't tell) في عالم إدارة الكونتينرز.

من docker run flags إلى مفاتيح YAML

لنفترض أنك تريد تشغيل قاعدة بيانات PostgreSQL. أمر docker run قد يبدو وكأنه سطر من شوربة الحروف الأبجدية في فيلم هاكرز:

docker run --name my-postgres-db \
  -e POSTGRES_PASSWORD=supersecret \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  -d postgres:14

هذا الأمر له اسم، ومتغير بيئة لكلمة المرور، وربط منفذ (port mapping)، و named volume لتخزين البيانات بشكل دائم، وسياسة إعادة تشغيل (restart policy)، ويعمل في الخلفية (-d).

الآن، دعنا نرى المكافئ له في Docker Compose داخل ملف docker-compose.yml:

version: '3.8' # يحدد إصدار ملف Compose

services:
  db: # هذا اسم لطيف لخدمتنا
    image: postgres:14
    container_name: my-postgres-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: supersecret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: # هنا نُعرّف الـ named volume

شايف التشابه؟ الـ flags بتاعت سطر الأوامر أصبحت مفاتيح YAML مرتبة ومنظمة. الأمر ليس مجرد شكل أجمل؛ بل هو تعريف لخدمتك يمكن التحكم في إصداراته، ومشاركته، وهو أقل عرضة للخطأ بكثير.

docker run Flag مفتاح docker-compose.yml وظيفته
postgres:14 (الـ image) image: postgres:14 يحدد الـ container image المراد استخدامها.
--name my-postgres-db container_name: my-postgres-db يعطي الكونتينر اسمًا محددًا.
-p 5432:5432 ports: ["5432:5432"] يربط منفذ (port) من الجهاز المضيف بالكونتينر.
-v pgdata:/var/lib/... volumes: [pgdata:/var/lib/...] يربط volume لتخزين البيانات بشكل دائم.
-e VAR=value environment: { VAR: value } يضبط متغيرات البيئة (environment variables) داخل الكونتينر.
--restart unless-stopped restart: unless-stopped يحدد سياسة إعادة تشغيل الكونتينر.

سحر الشبكات (Networking)

هنا يكمن إبداع Compose الحقيقي. لو أضفت خدمة تطبيق ويب إلى نفس ملف YAML، مثل هذا:

services:
  db:
    # ... (إعدادات postgres من الأعلى)
  
  api:
    image: my-cool-api:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:supersecret@db:5432/mydatabase
    depends_on:
      - db

عندما تشغل docker-compose up، يقوم Compose بعمل شيء عبقري: إنه ينشئ شبكة افتراضية مخصصة لتطبيقك. ويتم ربط كل من كونتينر db و api بهذه الشبكة.

الآن، يمكن لخدمة api الاتصال بقاعدة البيانات باستخدام اسم المضيف db—وهو نفس الاسم الذي أعطيناه للخدمة في ملف YAML! لا مزيد من البحث عن عناوين IP أو استخدام علامات --link الهشة. Compose يتولى كل عملية تحويل أسماء النطاقات (DNS resolution) داخل شبكته الخاصة. هكذا يستطيع كونتينر تطبيق الويب الخاص بك أن يهمس بكلمات لطيفة (أو يعني، استعلامات SQL) لكونتينر قاعدة البيانات الخاص بك بأمان وموثوقية.

دورة حياة Compose

أمر docker-compose هو لوحة التحكم الخاصة بك لمكدس التطبيقات (application stack) بأكمله.

  • docker-compose up: هذا هو الأمر الرئيسي. يقرأ ملف docker-compose.yml، ويتحقق من حالة نظامك، ويفعل كل ما هو ضروري لجعله يطابق الملف. سيقوم بإنشاء الشبكات، وبناء الـ images (إذا حددت تعليمة build)، وإنشاء الـ volumes، وتشغيل جميع كونتينرات خدماتك. إذا قمت بتشغيله مرة أخرى، فسيتحقق بذكاء من التغييرات ويعيد إنشاء الكونتينرات التي تحتاج إلى تحديث فقط.
  • docker-compose down: هو النقيض اللطيف لـ up. يوقف ويزيل جميع الكونتينرات، وبشكل افتراضي، يزيل أيضًا الشبكة التي أنشأها. إنه زر "نظف الفوضى التي أحدثتها". للحصول على بداية نظيفة تمامًا، docker-compose down -v يزيل أيضًا الـ named volumes.
  • docker-compose ps: يعرض لك حالة جميع الكونتينرات التي يديرها ملف Compose الخاص بك.
  • docker-compose logs -f: يعرض سجلات (logs) جميع خدماتك في بث واحد مناسب، ملون حسب الخدمة. لا يقدر بثمن عند تصحيح الأخطاء (debugging).

قصص من الواقع

ملحمة "شغالة على جهازي"

انتهى "خالد" للتو من ميزة جديدة للوحة تحكم التحليلات في شركته، والتي تستخدم backend بـ Node.js وقاعدة بيانات MongoDB. كل شيء يعمل بشكل مثالي على حاسوبه المحمول. قام برفع الكود وأخبر موظفة جديدة، "سارة"، بسحبه وتشغيله. ملف README الخاص بـ"خالد" يحتوي على أمرين docker run ضخمين. سارة، لكونها بشر، أخطأت في كتابة مسار الـ volume الخاص بقاعدة البيانات. بدأ التطبيق، لكنه لا يستطيع حفظ أي شيء. أمضت سارة ثلاث ساعات في تصحيح أخطاء كود التطبيق، معتقدة أن المشكلة هناك، بينما يصر خالد على أن كل شيء على ما يرام. أخيرًا، قاما بمشاركة الشاشة واكتشفا الخطأ المطبعي. كان الإحباط شديدًا.

الدرس المستفاد: لو كانوا قد استخدموا ملف docker-compose.yml، لكان الانضمام للفريق عبارة عن أمر واحد: docker-compose up. لكانت البيئة بأكملها — المنافذ، الـ volumes، متغيرات البيئة — متطابقة، مما يزيل فئة كاملة من أخطاء الإعداد ويحول نصف يوم من تصحيح الأخطاء إلى 30 ثانية من بدء التشغيل.

كابوس الـ CI/CD Pipeline

كان فريق DevOps يبني pipeline للتكامل المستمر (CI) لاختبار تطبيقهم القائم على الخدمات المصغرة (microservices). كان التطبيق يحتوي على خادم ويب، و API gateway، وخدمة للمستخدمين، و Redis cache. كان ملف Jenkinsfile الخاص بهم عبارة عن سكريبت مرعب من أوامر الـ shell، مع أربعة استدعاءات docker run منفصلة ومجموعة من أوامر sleep 30 على أمل أن تكون قاعدة البيانات جاهزة قبل أن يحاول الـ API الاتصال بها. كانت الاختبارات غير مستقرة؛ أحيانًا تنجح، وأحيانًا تفشل لأن فترة sleep لم تكن كافية.

الدرس المستفاد: أعادوا كتابة الـ pipeline لاستخدام Docker Compose. تم تبسيط ملف Jenkinsfile إلى docker-compose -f docker-compose.ci.yml up -d. استخدموا depends_on مع condition: service_healthy لضمان أن الـ API لا يبدأ إلا بعد أن تجتاز قاعدة البيانات فحصها الصحي الداخلي. اختفت الاختبارات غير المستقرة بين عشية وضحاها. وفر Compose طريقة موثوقة وتعريفية لتحديد وتشغيل بيئة الاختبار المطلوبة بالضبط، في كل مرة.

الـ Sidecar المنسي

قامت مطورة، "مريم"، بنشر تطبيقها الرئيسي المكتوب بلغة Python في كونتينر Docker. بعد بضعة أسابيع، طلب منها مديرها إضافة مراقبة أداء تفصيلية. كانت أفضل طريقة هي تشغيل Prometheus exporter كـ "sidecar" — وهو كونتينر ثانٍ يعيش جنبًا إلى جنب مع التطبيق الرئيسي، ويشاركه شبكته لجمع المقاييس (metrics). قامت مريم بإعداده بأمر docker run آخر. لكنها الآن أصبحت مضطرة لتذكر بدء وإيقاف وإعادة تشغيل كونتينرين في كل مرة تجري فيها تغييرًا. في أحد الأيام، أعادت تشغيل التطبيق لكنها نسيت الـ sidecar، وفقدوا يومًا كاملاً من المقاييس.

الدرس المستفاد: أعادت مريم هيكلة إعداداتها في ملف docker-compose.yml. عرّفت تطبيق Python كخدمة واحدة و Prometheus exporter كخدمة ثانية. الآن، أمر docker-compose up يشغلهما معًا، وأمر docker-compose down يوقفهما معًا. أصبح يتم إدارتهما كوحدة منطقية واحدة، كما ينبغي أن يكون. يجعل Compose أنماطًا مثل الـ sidecars سهلة الإدارة للغاية.

أخطاء وفخاخ شائعة

  • افتراض أن depends_on ينتظر الجاهزية. بشكل افتراضي، depends_on ينتظر فقط حتى يبدأ الكونتينر التابع، وليس حتى يتم تهيئة التطبيق بداخله بالكامل ويكون جاهزًا لقبول الاتصالات. قد يبدأ الـ API الخاص بك قبل أن تكون قاعدة بياناتك جاهزة للاستعلام، مما يسبب انهيارًا. الحل الحقيقي هو استخدام تعليمة healthcheck في تعريف خدمة قاعدة البيانات الخاصة بك ثم استخدام الصيغة الطويلة لـ depends_on مع condition: service_healthy.
  • تسريب الأسرار (secrets) إلى Git. من المغري جدًا أن تكتب POSTGRES_PASSWORD: my-secret-password مباشرة في ملف docker-compose.yml. لا تفعل ذلك. أي شخص لديه حق الوصول إلى مستودعك (repository) لديه الآن كلمة مرور قاعدة بياناتك. الطريقة الصحيحة هي استخدام ملف .env (الذي تضيفه إلى .gitignore) والإشارة إلى المتغيرات مثل ${POSTGRES_PASSWORD} في ملف Compose الخاص بك.
  • الخلط بين bind mounts و named volumes. المسار الذي يبدأ بـ . أو / (مثلًا، ./my-app:/app) هو bind mount، والذي يربط مجلدًا من جهازك المضيف. هذا رائع للتطوير حتى تنعكس تغييرات الكود على الفور. المسار الذي يبدأ باسم فقط (مثلًا، db-data:/var/lib/mysql) هو named volume مُدار بواسطة Docker. هذا أفضل للحفاظ على البيانات مثل قاعدة البيانات، لأنه مستقل عن النظام الأساسي ويديره محرك Docker. استخدام النوع الخاطئ يمكن أن يؤدي إلى مشاكل في الأذونات أو بيانات ليست محمولة كما تعتقد.
  • استخدامه للـ production على نطاق واسع. Docker Compose رائع للتطوير والاختبار وحتى عمليات النشر الصغيرة على مضيف واحد في بيئة الإنتاج. لكنه ليس بديلاً عن منسق كونتينرات (container orchestrator) كامل مثل Kubernetes أو Docker Swarm. ليس لديه مفهوم مدمج للمجموعات متعددة العقد (multi-node clusters)، أو الإصلاح الذاتي (self-healing)، أو موازنة التحميل (load balancing) عبر العقد، أو التحديثات المتداولة (rolling updates) المتطورة. اعرف حدوده.

ليه لازم تخلي عينك عليه

يجب أن تفكر في Docker Compose في اللحظة التي يتضمن فيها تطبيقك أكثر من جزء متحرك واحد.

  • هل تبني تطبيق ويب مع قاعدة بيانات منفصلة؟ استخدم Compose.
  • هل تحتاج إلى تشغيل تطبيقك، و cache، و background worker؟ استخدم Compose.
  • هل تريد أن تمنح زملائك في الفريق طريقة من أمر واحد لتشغيل بيئة التطوير بأكملها؟ استخدم Compose.
  • هل تقوم بإعداد CI pipeline تحتاج إلى تشغيل بيئة مؤقتة للاختبار؟ استخدم Compose.

هي الأداة القياسية والمستخدمة بشكل افتراضي في الصناعة للتطوير المحلي القائم على الكونتينرز. تعلمها هو الخطوة التالية الطبيعية بعد docker run، وهي البوابة لفهم عالم تنسيق التطبيقات الأكثر تعقيدًا. إنها تسد الفجوة بين تشغيل كونتينر واحد وتشغيل نظام موزع متكامل.

للتعمق أكثر

انتهينا من النظرية. حان وقت التطبيق — 100% في متصفحك.

جرّب الأداة: Docker Run لـ Compose