لماذا يفشل `depends_on` الافتراضي
يستخدم depends_on افتراضياً شرط service_started، مما يعني أن Docker ينتظر فقط انطلاق الحاوية المستهدفة — أي أن عمليتها الرئيسية قد بدأت. هذا لا يقول شيئاً عن الحالة الداخلية للخدمة.
يمرّ PostgreSQL، كمعظم قواعد البيانات، بعدة مراحل عند التهيئة: تُنفّذ الصورة الرسمية سكريبتات الإقلاع، وتنشئ الأدوار، وتُهيّئ الإضافات، وتُعدّ الـ cluster قبل أن تبدأ في قبول الاتصالات. يمكن أن تستغرق هذه العملية من ثوانٍ إلى أكثر من ثلاثين ثانية على VPS بقرص دافئ أو حجم غير محضَّر أو إضافات كثيرة.
خلال هذا الوقت، يحاول تطبيقك — الذي يحترم توجيه depends_on — الاتصال بقاعدة البيانات، فيتلقى رفضاً صريحاً.
العواقب العملية لـ race condition عند الإقلاع
connection refused— مقبس TCP الخاص بـ PostgreSQL لم يُفتح بعد، ويفشل التطبيق عند أول استدعاء PDO أو SQLAlchemy.FATAL: role does not exist— PostgreSQL يستمع، لكن سكريبتات التهيئة (docker-entrypoint-initdb.d) لم تُنشئ بعد الدور أو قاعدة البيانات.FATAL: the database system is starting up— الـ cluster في طور الاسترداد بعد إيقاف نظيف؛ الاتصالات مرفوضة مؤقتاً.- حلقة تعطل صامتة — يعيد Docker تشغيل التطبيق باستمرار بفضل
restart: unless-stopped، وتتكرر السجلات، ويبدو الأمر كخطأ في التطبيق. - نتائج كاذبة في CI — تفشل اختبارات التكامل بصورة متقطعة بحسب سرعة الـ runner.
- تبعيات متسلسلة — إذا كانت واجهة برمجية تعتمد على تطبيق يعتمد على قاعدة بيانات، توارثت المشكلة ذاتها إن لم تكن سلسلة
depends_onصحيحة في كل حلقة.
المتطلبات الأساسية: Docker Compose v2 والإضافة الرسمية
شرط service_healthy غير متاح في Docker Compose v1 (ثنائي Python المُهمَل docker-compose). مدعوم منذ Docker Compose v2، الموزَّع كإضافة Go تحت أمر docker compose (بدون شرطة).
للتحقق من إصدارك:
docker compose versionيجب أن تُظهر المخرجات Docker Compose version v2.x.x أو أعلى. على Debian 12 وUbuntu 22.04+، الإضافة متاحة من مستودعات Docker الرسمية. إن كان لديك docker-compose (v1) بعد، انتقل: المشروع مُؤرشَف ولا يتلقى إصلاحات أمنية.
لا تحتاج إلى أي تبعية إضافية لـ PostgreSQL: pg_isready أداة أصيلة في الصورة الرسمية postgres، موجودة في جميع الـ tags منذ سنوات.
إعداد healthcheck موثوق لـ PostgreSQL خطوة بخطوة
فهم الفرق بين service_started و service_healthy
يقبل
depends_onثلاثة شروط:-
service_started(افتراضي) — ينتظر انطلاق الحاوية فقط.
-service_healthy— ينتظر حتى يُعيد healthcheck الحاويةhealthy.
-service_completed_successfully— للحاويات قصيرة العمر (jobs، migrations).لأي قاعدة بيانات،
service_healthyهو الشرط الوحيد الذي يضمن أن الخدمة تقبل الاتصالات فعلاً.كتابة healthcheck الخاص بـ PostgreSQL في خدمة `db`
أضف كتلة
healthcheckمباشرة في تعريف خدمةdb:services: db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: appdb healthcheck: test: ["CMD", "pg_isready", "-U", "app", "-d", "appdb"] interval: 5s timeout: 5s retries: 5 start_period: 30sيستقبل الحقل
testقائمة: العنصر الأولCMD(يُنفّذ Docker الأمر مباشرة)، تليه الوسائط. يُعيدpg_isreadyالقيمة0إذا كان PostgreSQL جاهزاً لقبول الاتصالات بالمستخدم وقاعدة البيانات المحددَين، وكوداً غير صفري إذا لم يكن كذلك — وهو ما يُفسّره Docker على أنهhealthyأوunhealthy.فهم دور `start_period`
start_periodهو فترة السماح الممنوحة للحاوية للتهيئة قبل أن تبدأ إخفاقات healthcheck في الاحتساب ضمنretries. خلال هذه الفترة، ينفّذ Docker healthcheck، لكن الإخفاق لا يزيد العداد.بدون
start_period، فإن PostgreSQL الذي يستغرق 15 ثانية للتهيئة سيُخفق في أول 5 فحوصات (interval: 5s× 5 محاولات = 25 ثانية) وينتهيunhealthyقبل أن يصبح تشغيلياً.القيمة الموصى بها هي 30 ثانية لـ PostgreSQL القياسي: كافية لاستيعاب التهيئات البطيئة (أول إقلاع بحجم فارغ، إضافات ثقيلة) دون تأخير غير ضروري عند الإقلاع في الظروف العادية.
intervalوstart_periodمتمايزان:intervalيُوقّت الفحوصات في التشغيل الطبيعي، وstart_periodيحمي مرحلة الإقلاع.كتابة `depends_on` مع `condition: service_healthy`
في كل خدمة تعتمد على قاعدة البيانات، استبدل الشكل المختصر لـ
depends_onبالشكل المفصّل مع الشرط:services: app: image: myapp:latest depends_on: db: condition: service_healthy environment: DATABASE_URL: postgresql://app:secret@db:5432/appdbبهذا الإعداد، ينتظر Docker حتى يُعيد healthcheck خدمة
dbالقيمةhealthyقبل تشغيلapp. إذا أصبحdbبحالةunhealthyبعدretriesإخفاق، لا تنطلقapp.الاختبار باستخدام `docker compose up`
أطلق الـ stack وراقب التسلسل:
docker compose upسترى في السجلات أسطراً من قبيل:
db | database system is ready to accept connections app | Waiting for db to be healthy... app | Starting application serverللتحقق من حالة healthcheck في أي وقت:
docker inspect <db_container_name> | grep -A 5 '"Health"'تُظهر المخرجات
Status: healthyأوstartingأوunhealthy، وتسرد مخرجات آخر الفحوصات.حالة Redis: healthcheck مُكيَّف
لا يمتلك Redis أداة
redis-isready، لكن معادلها هوredis-cli ping:redis: image: redis:7-alpine healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5 start_period: 10sيُعيد
redis-cli pingالقيمةPONGوكود الخروج0إذا كان Redis يقبل الاتصالات. بما أن Redis يبدأ أسرع من PostgreSQL، تكفيstart_period: 10sفي الغالب.حالة MySQL / MariaDB: استخدام `mysqladmin ping`
لـ MySQL أو MariaDB، استخدم
mysqladmin ping:mysql: image: mariadb:11 environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: appdb healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-psecret"] interval: 5s timeout: 5s retries: 5 start_period: 30sتنبيه: يُدمج كلمة المرور مباشرة مع
-pبلا مسافة (-psecret)، وهو السلوك المتوقع لـmysqladmin. يظهر هذا الأمر في مخرجاتdocker inspect، لذا فضّل استخدام Compose secret أو متغير بيئة إن كانت السرية مطلوبة.استكشاف الأخطاء: أربعة أخطاء شائعة
pg_isready: command not found— لا تستخدم الصورة الرسميةpostgres(أو صورة مشتقة تتضمنها). تحقق باستخدام:docker compose exec db which pg_isready.healthcheck في حلقة، لا يصل أبداً إلى
healthy— لا يُعيدtestالقيمة0. اختبر يدوياً:docker compose exec db pg_isready -U app -d appdb. إذا فشل الأمر، تحقق من متغيراتPOSTGRES_USERوPOSTGRES_DB.start_periodقصير جداً — عند أول إقلاع بحجم فارغ، قد يستغرق PostgreSQL أكثر من 30 ثانية. ارفع القيمة إلى60sأو راقب السجلات:database system was shut down at … LOG: database system is ready to accept connectionsيُشير إلى التأخير الفعلي.الحاوية في حالة
unhealthyدائمة — بعدretriesإخفاق، يُصنّف Docker الحاويةunhealthyلكنه لا يُعيد تشغيلها (ذلك دورrestart). راجعdocker inspectلمعرفة مخرجات آخر الفحوصات وتحديد الأمر الفاشل.
السلوك الافتراضي مقابل السلوك مع healthcheck
مرّر الجدول أفقيًا
| الحالة | السلوك الافتراضي (`service_started`) | مع `service_healthy` |
|---|---|---|
| أول إقلاع، حجم فارغ | يبدأ التطبيق قبل جاهزية قاعدة البيانات → حلقة تعطل | ينتظر التطبيق حتى تكتمل تهيئة PostgreSQL ويقبل الاتصالات |
| إعادة التشغيل بعد إيقاف نظيف | قد يبدأ التطبيق خلال مرحلة استرداد PostgreSQL | ينتظر التطبيق حتى اكتمال الاسترداد |
| قاعدة بيانات بطيئة (إضافات، تهيئة ثقيلة) | race condition تعتمد على سرعة المضيف | لا race condition: healthcheck يتحقق من الحالة الفعلية |
| تبعيات متسلسلة (app → worker → db) | كل مكوّن يُدير محاولات إعادة الاتصال بنفسه | سلسلة الشروط تضمن ترتيب الإقلاع |
| اختبارات التكامل في CI | نتائج متقطعة تعتمد على سرعة الـ runner | نتائج حتمية ومتسقة |
| Redis أو MySQL بدلاً من PostgreSQL | المشكلة ذاتها، `depends_on` الافتراضي لا يُميّز | الحل ذاته، أمر الفحص مُكيَّف لكل محرك |
`pg_isready` أم `SELECT 1`: أيهما تختار؟
يظهر نوعان من healthcheck لـ PostgreSQL في الاستخدام الشائع:
- ["CMD", "pg_isready", "-U", "postgres"]
- ["CMD-SHELL", "psql -U postgres -c 'SELECT 1'"]"
pg_isready أكثر موثوقية لسبب بسيط: يختبر فقط قدرة الخادم على قبول اتصالات TCP، دون فتح جلسة SQL. يُعيد القيمة 0 حالما يكون الخادم مستمعاً ويقبل المصافحة، وهو بالضبط ما يحتاجه التطبيق لمحاولة اتصاله الخاص.
أما SELECT 1 عبر psql، فيفتح جلسة SQL حقيقية وينفّذ استعلاماً. هو اختبار أعمق، لكنه قد يفشل لأسباب لا علاقة لها بتوافر الخادم (استنفاد حصة الاتصالات، pg_hba.conf غير مُعدَّ بصواب). للـ healthcheck، الاختبار الأدنى والمباشر هو الأفضل.
تكييف فحص الصحة لـ Redis وMySQL
بالنسبة لـ Redis، استبدل pg_isready بـ CMD redis-cli PING — تُرجع PONG فور قبول الخادم للاتصالات. بالنسبة لـ MySQL أو MariaDB، استخدم CMD mysqladmin ping -h localhost -u root --password=$$MYSQL_ROOT_PASSWORD مع رفع start_period إلى 60s، إذ يستغرق تهيئة MySQL وقتاً أطول من PostgreSQL. نمط condition: service_healthy متطابق بغض النظر عن الخدمة المستهدفة.
الخطوات التالية
يُعدّ healthcheck مع service_healthy أحد إعدادات المتانة الواجب تفعيلها في الإنتاج. تستحق عدة نقاط أخرى الاهتمام ذاته قبل أي نشر مستدام: سياسة إعادة التشغيل restart: unless-stopped، حدود الموارد deploy.resources.limits، وتدوير السجلات logging.options. يمكنك الاطلاع على القائمة الكاملة في دليل Docker Compose في الإنتاج: قائمة التحقق من 10 نقاط.
إذا نما الـ stack — خدمات متعددة أو مضيفون متعددون — فإن reverse proxy كـ Caddy أو Traefik ضروري لإدارة توجيه HTTPS. يفصّل دليل Caddy أو Traefik أو Nginx Proxy Manager معايير الاختيار حسب ملفك.
لأتمتة نشر بنيتك التحتية بالكامل (VPS، Docker، الإعداد) بطريقة قابلة للتكرار، يرشدك دليل Ansible لأتمتة خوادم VPS خطوة بخطوة.