مدونة عمار الخوالده

إدارة تشغيل الـ Containers باستخدام Docker Compose


إدارة تشغيل الـ Containers باستخدام Docker Compose
سلسلة أساسيات Docker

مقدمة

عند التعامل مع Docker، غالبا لن تستخدم Container واحدة، المشروع يتعامل مع عدة أنظمة بالغالب، أو قد يتكون من أكثر من Service، قد تحتاج تشغيل Redis و MySql و Elastic Search و NodeJs لتشغيل أحد المشاريع مثلا.

لماذا أشغل أكثر من Container؟

في المقال السابق، قمنا بتشغيل حاويتين، واحدة لـ PHP والأخرى لـ MariaDB، استخدام أكثر من Container مفيد من ناحية إمكانية التخصيص (modularity)، كان من الممكن بسهولة تبديل حاوية MariaDB بحاوية MySql أو إضافة حاوية Redis.

كما أن تشغيل عدة حاويات للمشروع الواحد مفيد لعزل الأنظمة بعضها عن بعض باستخدام خصائص الـ Networking في Docker.

صعوبة إدارة عدد كبير من Containers

لاحظ أننا نمرر العديد من الخيارات للأمر docker run لتشغيل الـ Container، خيارات الـ Volumes والـ Networking وغيرها.

عند تشغيل عدد كبير من الـ Containers يصبح من المتعب تشغيلها كلها باستخدام docker run، بعض الـ Containers تحتاج الكثير من الخيارات التي يصعب تذكرها في كل مرة أردت فيها تشغيل المشروع، إضافة إلى الحاجة أحيانا إلى تشغيلها بترتيب معين كونها تعتمد على بعضها البعض.

لحل هذه المشكلة نلجأ لاستخدام أداة Docker Compose.

ما هو Docker Compose

يستخدم Docker Compose لتعريف إعدادات تشغيل الحاويات في ملف YAML، بحيث يصبح من السهل معرفة الطريقة الصحيحة لتشغيل المشروع حيث أن جميع الـ Containers المطلوبة معرفة بإعداداتها في الملف.

كما يساعد على إدارة هذه الحاويات باستخدام سطر الاوامر (CLI)، حيث يمكن تشغيلها وإيقافها دفعة واحدة باستخدام أمر واحد.

تجد إعدادات تثبيت Docker Compose على هذا الرابط Installation | Docker Documentation.

ملف Docker Compose

إنشاء الملف وتحديد الـ Version

نقوم اولا بإنشاء ملف compose.yml لتعريف الـ containers التي نحتاجها مع إعداداتها، لكن قبل البدء بكتابة الملف والدخول في التفاصيل.

التوثيق الرسمي حاليا يذكر ان اسم الملف هو compose.yml سابقا كان الاسم المستخدم هو docker-compose.yml ولا يزال Docker Compose يدعم هذا الاسم للحفاظ على الـ Backward compatibility.

إذا بحثت ستجد أن أغلب الناس يبدأون الملف بتحديد نسخة الـ Syntax المدعومة، مثلا:

1version: "3.8"

تم تحديد العنصر version على أنه عنصر مهمل (Deprecated)، لن تحصل على رسالة خطأ إن أضفته، لكن لا داعي لاستخدامه، قمت بالكتابة عنه في هذا المقال فقط حتى تعرف سبب وجوده في معظم الملفات.

تعريف الحاويات داخل الملف

يتم تعريف الحاويات تحت عنصر services، في ملف Docker Compose يُسمى كل تعريف لـ Container باسم Service، قمت أولا بتعريف حاوية mariadb داخل الملف:

1services:
2  my-database:
3    image: mariadb

في المثال السابق، my-database هو اسم الـ Service، والذي ينعكس في النهاية على اسم الـ Container كما سنرى عند تنفيذ الملف، باستخدام العنصر image يمكن تحديد اسم الـ Image المستخدمة.

لكن هذه ليست كل الاعدادات التي نحتاجها، في المقال السابق قمنا بإضافة environment variable لتحديد كلمة مرور قاعدة البيانات باستخدام الخيار --env، لذلك سنعدل الملف لتحديد كلمة المرور في ملف Compose:

1services:
2  my-database:
3    image: mariadb
4    environment:
5      MARIADB_ROOT_PASSWORD: "password"

وهكذا يصبح الملف بعد إضافة تعريف للـ Service الخاصة بـ PHP:

 1services:
 2  my-database:
 3    image: mariadb
 4    environment:
 5      MARIADB_ROOT_PASSWORD: "password"
 6
 7  my-app:
 8    image: ammardev0/php:8.1
 9    volumes:
10      - "./:/app"
11    command: "php /app/database_test.php"

لاحظ أن الخيارات لا تختلف كثيرا عن خيارات docker run لكن يتم كتابتها في ملف، وهذا يقدم عدة فوائد:

  • إمكانية إدراج هذا الملف في Git ومشاركته مع الآخرين ومعرفة التغييرات التي تحصل عليه.

  • سهولة إدارة جميع هذه الـ Containers عن طريق تنفيذ أوامر قليلة بدلا من تنفيذ الأمر docker run أو docker stop على الـ Containers واحدة واحدة.

لا يمكننا تشغيل الـ Containers بعد، نحتاج أولا لإنشاء Network بين الـ Containers كما فعلنا في المقال السابق.

إنشاء الشبكات وربطها بالـ Containers

تعرفنا حتى الآن على عنصرين من العناصر العُليا (Top Level Elements)، version و services، سنتعرف الآن على عنصر آخر وهو networks، في المثال التالي قمت بإنشاء شبكة جديدة باسم my-net، ولاحظ تحديد نوع الـ driver المستخدم bridge للاستفادة من ميزة الـ DNS Resolution:

1networks:
2  my-net:
3    driver: bridge

لنقم الآن بإضافة هذه الشبكة إلى الـ Services التي قمنا بتعريفها، ليصبح الملف كاملا بهذا الشكل:

 1services:
 2  my-database:
 3    image: mariadb
 4    environment:
 5      MARIADB_ROOT_PASSWORD: "password"
 6    networks:
 7      - my-net
 8
 9  my-app:
10    image: ammardev0/php:8.1
11    volumes:
12      - "./:/app"
13    command: "php /app/database_test.php"
14    networks:
15      - my-net
16
17networks:
18  my-net:
19    driver: bridge

لاحظ إضافة عنصر networks داخل كل Service.

ترتيب التشغيل

لا تزال هناك مشكلة بسيطة تمنعنا من تشغيل الملف والحصول على نفس النتيجة التي حصلنا عليها في المقال السابق، في المقال السابق قمنا بتنفيذ الأوامر يدويا، في البداية شغلنا MariaDB، وبعدها قمنا بتشغيل الـ PHP Script.

بما أن PHP ستتصل بقاعدة البيانات، فهذا يعني أن على قاعدة البيانات أن تعمل أولا، ويمكن التحكم بترتيب التشغيل في ملف Docker Compose عن طريق depends_on:

 1services:
 2  my-database:
 3    image: mariadb
 4    environment:
 5      MARIADB_ROOT_PASSWORD: "password"
 6    networks:
 7      - my-net
 8
 9  my-app:
10    image: ammardev0/php:8.1
11    volumes:
12      - "./:/app"
13    command: "php /app/database_test.php"
14    depends_on:
15      - my-database
16    networks:
17      - my-net
18
19networks:
20  my-net:
21    driver: bridge

قمنا بإضافة العنصر depends_on إلى الـ PHP Service، الآن لن يتم تشغيلها إلا بعد تشغيل MariaDB، لكن المشكلة أن تشغيل MariaDB بطيء، ولن يكون سيرفر MariaDB جاهزا بمجرد تشغيل الـ Container الخاص به.

يمكن استخدام أمر health_check للتحقق من تشغيل MariaDB وجاهزيتها لاستقبال الـ Connections.

1    healthcheck:
2      test: mysqladmin ping

سيتم تنفيذ الأمر mysqladmin ping عدة مرات للتحقق من جاهزية قاعدة البيانات، ونحتاج لتعديل الأمر depends_on لينتظر تغير حالة الـ Container إلى Healthy بدلا من مجرد تشغيلها:

1    depends_on:
2      my-database:
3        condition: service_healthy

هذا هو الملف كاملا بعد التعديل:

 1services:
 2  my-database:
 3    image: mariadb
 4    environment:
 5      MARIADB_ROOT_PASSWORD: "password"
 6    networks:
 7      - my-net
 8    healthcheck:
 9      test: mysqladmin ping
10
11
12  my-app:
13    image: ammardev0/php:8.1
14    volumes:
15      - "./:/app"
16    command: "php /app/database_test.php"
17    depends_on:
18      my-database:
19        condition: service_healthy
20    networks:
21      - my-net
22
23networks:
24  my-net:
25    driver: bridge

إدارة الـ Containers من سطر الأوامر

لتشغيل الـ Containers التي قمنا بتعريفها، يتم استخدام الأمر docker-compose up، يجب تنفيذ هذا الأمر من داخل مجلد المشروع الذي يتواجد فيه ملف compose.yml أو docker-compose.yml، هذه نتيجة تنفيذ الأمر السابق على الملف الذي قمنا بتعريفه:

ناتج تنفيذ الأمر docker-compose up

لاحظ أنه قام أولا بانشاء network جديدة، لكنها باسم project_my-net وليس my-net فقط، لأن Docker Compose يقوم افتراضيا بإضافة اسم الـ Directory إلى أسماء الشبكات والحاويات وغيرها، بعدها قام بتشغيل الحاويات بالترتيب المطلوب، وربطهما بالـ Network.

الأمر docker-compose up يقوم افتراضيا بعرض الـ logs الخاصة بالحاويات، وهذا مشابه لسلوك docker run، يمكن استخدام الخيار -d لتشغيل الـ Containers في الخلفية.

بالمثل، لاغلاق الحاويات، يمكن استخدام الأمر docker-compose stop، لحذف الحاويات والـ networks والـ volumes يمكن استخدام docker-compose down.

معظم الأوامر الأخرى الخاصة بـ Docker مثل logs و ps وغيرها متوفرة في الأمر docker-compose، لكن بدلا من استخدام اسم الـ Container يتم استخدام اسم الـ Service، كما أن استخدام هذه الأوامر مع docker-compose بدلا من docker ينفذها عى مستوى ملف compose.yml الموجود في نفس المجلد.

خاتمة سلسلة أساسيات Docker

هذا المقال هو الأخير من سلسلة أساسيات Docker، هدف هذه السلسلة أن تكون مقدمة لمن يريد التعامل مع Docker.

يفترض بعد قراءة وفهم هذه السلسلة والتطبيق عليها أن تفهم الهدف من استخدام Docker وتتعامل معه بشكل بسيط، قد تتمكن الآن من استخدام بعض البرامج والأدوات عن طريق Docker دون الحاجة لتثبيتها على جهازك، أو قد تتمكن من تشغيل بعض المشاريع التي تحتوي بالفعل على ملف Compose.

ماذا أتعلم بعد هذه السلسلة؟

قد تلاحظ أنني استخدمت هذه الـ Image في بعض المقالات ammardev0/php، والسبب هو وجود PHP Extensions مفعلة للتعامل مع قواعد البيانات، وغير مفعلة في الـ Image الرسمية لـ PHP.

هذا يعني أن بإمكانك بناء Images مخصصة لـ Docker بحيث تناسب احتياجاتك وتناسب مشروعك، هذه السلسلة لم تتطرق أبدا لبناء الـ Images، فالهدف من السلسلة هو التعريف بـ Docker وطريقة استخدامه، إذا كنت مبرمجا يحتاج لتشغيل بعض المشاريع باستخدام Docker دون الحاجة لبناء أي شيء مخصص، قد تكفيك هذه السلسلة، لكن عادة ستحتاج لبناء Images خاصة بك.

خطواتك القادمة للتعمق في Docker أكثر:

  • الاكثار من التطيبق على ما تعلمته بالفعل.

  • قراءة التوثيق لفهم المزايا الموجودة في Docker و Compose بشكل أعمق مما ذُكر في هذه السلسلة.

  • تعلم بناء Docker Images لفهم طريقة عملها وتخصيصها بما يناسب احتياجاتك.

6 التواصل بين الـ Containers عبر الشبكة - Docker Networking

اقرأ أيضا