إدارة تشغيل الـ Containers باستخدام Docker Compose
مقدمة
عند التعامل مع 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
، هذه نتيجة تنفيذ الأمر السابق على الملف الذي قمنا بتعريفه:
لاحظ أنه قام أولا بانشاء 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 لفهم طريقة عملها وتخصيصها بما يناسب احتياجاتك.