EduFlow

الوحدة 3 · بيئات محلية مع Docker Compose

أساسيات Compose: الخدمات والشبكات

18 دقراءة 4 داكتشف الخطأ

الدرس في لمحة

صِف notes-api وPostgres في ملف compose.yaml واحد، وشغّل البيئة وافحصها بـ docker compose، واربط الخدمات بأسمائها عبر الشبكة الافتراضية التي ينشئها Compose.

  1. 1أول compose.yaml
  2. 2شغّل البيئة
  3. 3كيف تجد الخدمات بعضها
  4. 4فصل الشبكات حين يهمّ ذلك
  5. 5أين يناسب Compose

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

يحتاج notes-api إلى Postgres. تستطيع تشغيل الاثنين بأمرَي docker run، وشبكة مصنوعة يدويًا، وصفحة من الخيارات، ثم تشرح ذلك كله من جديد لأول شخص ينضمّ إلى الفريق. ‏Compose يستبدل ذلك بملف واحد في المستودع يصف البيئة كاملة، وأمر واحد يحوّلها إلى واقع.

أول compose.yaml

أنشئ compose.yaml بجوار الـ Dockerfile:

compose.yaml
name: notes

services:
  api:
    build: .
    image: notes-api:dev
    ports:
      - "8080:3000"
    environment:
      PORT: "3000"
      DATABASE_URL: postgres://notes:notes@db:5432/notes

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: notes
      POSTGRES_PASSWORD: notes
      POSTGRES_DB: notes

خدمتان. ‏api تُبنى من الـ Dockerfile في هذا المجلد وتحمل الـ tag ‏notes-api:dev؛ وdb تستخدم الـ image الرسمية لـ Postgres 17، التي تنشئ نقطة دخولها المستخدم وقاعدة البيانات من هذه المتغيّرات الثلاثة عند أول تشغيل. وname: notes يضبط اسم المشروع، الذي يُضاف كبادئة إلى كل ما ينشئه Compose.

حين تحتوي الخدمة على build: وimage: معًا، يبني Compose من الـ Dockerfile ويعطي النتيجة الاسم الموجود في image:. هذا يمنح الـ image المحلية اسمًا متوقّعًا تستطيع تشغيله يدويًا أيضًا، بدلًا من الاسم الافتراضي الذي كان Compose سيولّده من اسمَي المشروع والخدمة.

لا يوجد سطر version:. ‏Compose v2 يطبّق مواصفة Compose ‏(Compose Specification)، وإذا نسخت ملفًا قديمًا يبدأ بـ version: "3.8"، فسيتجاهله Compose ويحذّر من أن الخاصية قديمة. احذفه.

الأمر هو docker compose، بمسافة. هذا Compose v2، إضافة لواجهة Docker CLI. أما docker-compose القديم المكتوب بـ Python والموصول بشرطة فقد تقاعد منذ سنوات، والدروس التي تستخدمه قديمة من نواحٍ أخرى أيضًا.

شغّل البيئة

Terminal
docker compose up -d --build
docker compose ps
docker compose logs -f api
curl localhost:8080/readyz

الأمر up يبني الـ image إن لزم، وينشئ شبكة، ويشغّل كلا الـ containers، ثم يعود. و--build يفرض إعادة البناء حتى تُلتقط تغييرات الكود. والآن يجيب /readyz بـ ready، لأن الـ API يستطيع الوصول إلى قاعدة البيانات.

الأمر docker compose ps يعرض الـ containers التي أنشأها Compose، مسمّاة باسم المشروع والخدمة ورقم: notes-api-1 وnotes-db-1. الرقم موجود لأن الخدمة الواحدة قد تشغّل عدّة containers متطابقة. وكل الأوامر تأخذ أسماء الخدمات لا أسماء الـ containers، فنادرًا ما تكتب الاسم الكامل.

أوامر أخرى ستستخدمها يوميًا:

Terminal
docker compose exec db psql -U notes -d notes   # SQL shell in the db container
docker compose restart api                       # restart one service
docker compose down                              # stop and remove containers and network

الأمر up تصريحي (declarative): يقارن الملف بما يعمل، ولا يغيّر إلا الفرق. عدّل متغيّرات بيئة الـ API وشغّل up -d مجددًا، فيعيد Compose إنشاء api بينما يستمر db في العمل. ‏Kubernetes يعمل بالفكرة نفسها على نطاق أكبر بكثير؛ وستقابله في القسم الرابع.

كيف تجد الخدمات بعضها

ينشئ Compose شبكة للمشروع، notes_default، ويربط كل خدمة بها. على تلك الشبكة، يحلّ DNS الخاص بـ Docker كل اسم خدمة إلى الـ container الخاص بها. لهذا يقول DATABASE_URL ‏@db:5432.

جهازك يصل إلى الـ container ‏api عبر المنفذ المنشور 8080 المربوط بـ 3000. داخل الشبكة notes_default، يتّصل api بـ db على المنفذ 5432 باسم الخدمة. قاعدة البيانات ليس لها منفذ منشور. جهازك localhost:8080 الشبكة: notes_default api :3000 db :5432 -p 8080:3000 db:5432 لا منفذ منشور
الـ API وحده منشور؛ وقاعدة البيانات يُوصل إليها بالاسم داخل شبكة المشروع

لاحظ أن db ليس فيه مدخل ports:. يصل إليه الـ API عبر شبكة المشروع؛ ولا يستطيع ذلك أي شيء على حاسوبك أو على شبكة مكتبك. إن أردت توجيه عميل SQL مكتبي إليه، فانشره على الـ loopback فقط: "127.0.0.1:5432:5432".

فصل الشبكات حين يهمّ ذلك

في البيئات الأكبر، تستطيع التصريح بالشبكات وربط كل خدمة بما يخصّها فقط. قاعدة البيانات على شبكة backend لا يمكن أن تصل إليها واجهة ويب أمامية لا تنضمّ إلا إلى frontend:

YAML
services:
  web:
    networks: [frontend]
  api:
    networks: [frontend, backend]
  db:
    networks: [backend]

networks:
  frontend:
  backend:

لـ notes-api تكفي الشبكة الافتراضية. الجأ إلى هذا حين تحتوي البيئة على مكوّنات يجب ألا يتحدّث بعضها إلى بعض أبدًا.

أين يناسب Compose

‏Compose ممتاز لثلاث مهام: التطوير المحلي، واختبارات التكامل في الـ CI (شغّل البيئة، ونفّذ الاختبارات، ثم فكّكها)، والخدمات الصغيرة التي تعيش على مضيف واحد. ما لا يفعله هو توزيع الـ containers على أجهزة متعدّدة، أو نقلها حين يموت جهاز، أو نشر إصدار جديد تدريجيًا مع مراقبة فحوص الصحة. هذه مهام المنسّق (orchestrator)، وهي ما يفعله Kubernetes في القسم الرابع. والعادات التي تبنيها هنا تنتقل معك مباشرة: صرّح بالحالة المرغوبة في ملف، ودع أداة تحقّقها، واربط الخدمات بأسمائها.

تبقى مشكلة واحدة. شغّل docker compose down، ثم up مجددًا، فتجد أن كل الملاحظات اختفت: خزّن Postgres بياناته في الـ layer القابلة للكتابة داخل الـ container، والتي حذفها down. الدرس التالي يعطي قاعدة البيانات مكانًا دائمًا تعيش فيه.

أهم النقاط

  • ملف compose.yaml يصرّح بالخدمات والشبكات والـ volumes؛ والأمر docker compose up ينشئ كل ما هو ناقص ويترك الباقي كما هو.
  • ‏Compose v2 هو الإضافة docker compose؛ والمفتاح version: في المستوى الأعلى قديم ويجب حذفه.
  • كل خدمة تنضمّ إلى الشبكة الافتراضية للمشروع ويمكن الوصول إليها باسمها، لذا يتّصل الـ API بـ db:5432 لا بـ localhost.
  • انشر على المضيف المنافذ التي تحتاج إليها فقط؛ وقاعدة البيانات التي لا يتحدّث معها إلا الـ API لا تحتاج إلى مدخل ports: إطلاقًا.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

داخل الـ container ‏api، أي مضيف في DATABASE_URL يصل إلى خدمة Postgres المسمّاة db؟

السؤال 2 من 4

تضيف version: "3.8" في أعلى compose.yaml. ماذا يفعل Docker Compose الحالي بها؟

السؤال 3 من 4

قاعدة Postgres في بيئة Compose لديك لا يستخدمها إلا api. أي إعداد ports: يجب أن يكون لـ db؟

السؤال 4 من 4

تغيّر متغيّرات البيئة لخدمة api فقط في compose.yaml وتشغّل docker compose up -d. ماذا يحدث للـ container ‏db الذي يعمل؟

تدريب

اكتشف الخطأ

خطآن في Compose، واتصال واحد

مع ملف compose.yaml هذا، يكتب الـ API في سجلّه ECONNREFUSED 127.0.0.1:5432، وزميل على شبكة Wi-Fi المكتب اتّصل للتوّ بقاعدة بياناتك بكلمة المرور notes. اختر السطرين الاثنين المسؤولين.

حدّد السطر أو الأسطر التي تظنها خاطئة، ثم تحقّق.