لماذا يفشل `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 خطوة بخطوة.