وقتی تست Integration در CI شکست می‌خورد، جمله «کانتینر بالا بود» تقریباً هیچ چیز را ثابت نمی‌کند. ممکن است Process اجرا شده باشد اما Database هنوز آماده نباشد؛ Config واقعی با فایل YAML فرق کند؛ Volume داده اجرای قبلی را نگه داشته باشد؛ Image Tag به Digest دیگری اشاره کند؛ یا تست Fail شده باشد اما Exit Code آن در Pipeline گم شود.

این راهنما Docker Compose را از دید تستر به یک لَب بازتولید و عیب‌یابی تبدیل می‌کند. یاد می‌گیرید هویت اجرای معیوب را Freeze کنید، Config نهایی را ببینید، Readiness را از Running جدا کنید، لاگ و State کمینه جمع کنید، Failure شبکه/وابستگی را کنترل‌شده بسازید و محیط را بدون آسیب به پروژه‌های دیگر Reset کنید.

خلاصه اجرایی: حلقه هفت‌مرحله‌ای تستر با Compose

  1. Identify: Commit، Image digest، Platform، Compose version و Project name را ثبت کنید؛
  2. Render: با docker compose config مدل نهایی و Overrideها را ببینید؛
  3. Start/Wait: سرویس‌ها را با Health/Readiness محدود به زمان آماده کنید؛
  4. Test: Test Runner را سرویس First-class و Exit Code آن را نتیجه Job کنید؛
  5. Diagnose: از ps، logs، inspect/exec و Correlation ID شاهد بگیرید؛
  6. Perturb: فقط در لَب مجاز Failure وابستگی را با Stub یا کنترل Compose تزریق کنید؛
  7. Reset: Artifact را حفظ و فقط Project همان اجرا را همراه Volumeهای تست پاک کنید.

مرز محتوا: این مقاله با راهنمای ۹۷۲ چه تفاوتی دارد؟

راهنمای Docker Compose برای محیط تست معماری کامل Image→Project→Health→Migration→Test→Evidence→Teardown، امنیت Image و Pipeline را پوشش می‌دهد. این صفحه آن معماری را تکرار نمی‌کند؛ تمرکز آن کار روزانه تستر برای بازتولید Failure، تشخیص مرز خراب و ساخت Evidence Pack است.

Docker ناسازگاری را حذف یا ایزولاسیون کامل را تضمین نمی‌کند. Container کرنل میزبان را به اشتراک می‌گذارد؛ CPU architecture، Kernel، DNS، Clock، Volume، Secret، Tag متحرک و سرویس بیرونی هنوز می‌توانند تفاوت بسازند. Compose یک مدل قابل نسخه‌گذاری فراهم می‌کند؛ تکرارپذیری حاصل ثبت هویت و کنترل State است.

Compose امروزی: سه اصلاح ضروری

۱. دستور docker compose، نه docker-compose

Compose V1 با دستور خط تیره‌ای Legacy است. مستندات فعلی تاریخچه Docker Compose، Compose V2/V5 را بر مبنای Compose Specification و دستور docker compose توضیح می‌دهد. همیشه خروجی docker compose version را در گزارش CI ثبت کنید؛ قابلیت‌هایی مثل --wait به نسخه CLI وابسته‌اند.

۲. فیلد version دیگر Schema را انتخاب نمی‌کند

طبق مرجع Version و Name در Compose، فیلد بالادستی version: فقط برای سازگاری عقب‌رو مانده، منسوخ و Informative است؛ Compose جدید همواره مدل فعلی را اعتبارسنجی می‌کند. بنابراین version: "3.8" تضمین سازگاری نیست. فایل را compose.yaml بنامید و قابلیت لازم را در CI بررسی کنید.

۳. Running معادل Ready نیست

depends_on کوتاه ترتیب Start را می‌سازد، اما منتظر آماده‌شدن برنامه داخل Container نمی‌ماند. راهنمای رسمی Startup order برای وابستگی آماده، Healthcheck و condition: service_healthy و برای Migration یک‌باره service_completed_successfully را معرفی می‌کند. Health نیز فقط ادعایی است که Probe شما می‌سنجد؛ باید به توان پاسخ‌گویی واقعی نزدیک باشد.

هویت اجرای معیوب را Freeze کنید

پیش از Restart یا Pull، این فیلدها را ثبت کنید:

  • Repository و Commit SHA؛
  • نام Project و Run/Job ID؛
  • نسخه Docker Engine، Compose و OS/architecture؛
  • نام Image و Digest واقعی هر سرویس؛
  • Compose config hash و Profile/Overrideهای فعال؛
  • زمان، Timezone، Locale و Random seed؛
  • نسخه Schema/Migration و Seed؛
  • Dependencyهای واقعی، Stubشده و قطع‌شده؛
  • Tag expression تست و First-attempt exit code.

Tagهایی مانند postgres:latest یا حتی postgres:17 می‌توانند در آینده به Artifact دیگری اشاره کنند. برای CI قابل بازتولید، Digest مصوب را Lock و برای Upgrade آن Pull Request جدا بسازید. مقاله SBOM و هویت Artifact مرز Tag، Digest و Provenance را عمیق‌تر توضیح می‌دهد.

Config واقعی را قبل از اجرا ببینید

فایلی که می‌خوانید الزاماً مدلی نیست که Engine دریافت می‌کند. Shell، .env، --env-file، چند فایل -f و Profileها روی نتیجه اثر دارند. مرجع docker compose config می‌گوید این دستور فایل‌ها را Merge، متغیرها را Resolve و Short syntax را Canonical می‌کند:

docker compose -p qa-4821 config --quiet
docker compose -p qa-4821 config --services
docker compose -p qa-4821 config --images
docker compose -p qa-4821 config --hash
docker compose -p qa-4821 config --environment

خروجی کامل Config ممکن است Secret resolve‌شده داشته باشد؛ بدون Redaction آن را Artifact عمومی نکنید. برای گزارش، Hash و فهرست Image/Service معمولاً امن‌ترند و نسخه Redacted مدل را جدا نگه دارید.

Project name مرز ایزولاسیون عملی است

Compose منابع را با Project name Scope می‌کند. Docker برای CI توصیه می‌کند نامی یکتا مانند شماره Build تعیین شود تا Jobها تداخل نکنند. -p qa-4821 یا COMPOSE_PROJECT_NAME را صریح کنید؛ نام ثابت، Port ثابت و Volume خارجی مشترک Parallelism را می‌شکنند. مرجع Project name ترتیب اولویت نام را مستند کرده است.

الگوی Compose برای تست: Ready، Migration و Runner

نمونه زیر اسکلت است و نام Image/Digest باید با Artifactهای پروژه جایگزین شود. رمز در Repository قرار نمی‌گیرد:

name: ${COMPOSE_PROJECT_NAME:-qa-lab}

services:
  db:
    image: postgres:17-alpine@sha256:REPLACE_WITH_APPROVED_DIGEST
    environment:
      POSTGRES_USER: qa
      POSTGRES_DB: app_test
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets: [db_password]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U qa -d app_test"]
      interval: 3s
      timeout: 2s
      retries: 20
      start_period: 5s
    networks: [test_net]

  migrate:
    image: registry.example/app@sha256:REPLACE_WITH_APP_DIGEST
    command: ["./app", "migrate"]
    depends_on:
      db:
        condition: service_healthy
    networks: [test_net]

  app:
    image: registry.example/app@sha256:REPLACE_WITH_APP_DIGEST
    depends_on:
      migrate:
        condition: service_completed_successfully
    networks: [test_net]
    healthcheck:
      test: ["CMD", "./app", "ready"]
      interval: 3s
      timeout: 2s
      retries: 20

  tests:
    image: registry.example/tests@sha256:REPLACE_WITH_TEST_DIGEST
    command: ["./run-integration-tests"]
    depends_on:
      app:
        condition: service_healthy
    networks: [test_net]

networks:
  test_net:
    internal: true

secrets:
  db_password:
    file: ./secrets/db_password.txt

اگر Runner به سرویس بیرونی نیاز دارد، شبکه internal را متناسب با معماری تغییر دهید؛ کنترل را کورکورانه کپی نکنید. Healthcheck دیتابیس فقط پذیرش اتصال را می‌سنجد، Migration با Exit Code مستقل Gate می‌شود و App باید Endpoint یا Command آمادگی معنادار داشته باشد.

Secret با env یا .env یکی نیست

Docker صریحاً توصیه می‌کند اطلاعات حساس را در Environment قرار ندهید و از Secret استفاده کنید. .env عمدتاً منبع Interpolation و Configuration است و ممکن است Override شود؛ Vault نیست. مرجع Compose Secrets منبع File/Environment و Grant صریح Secret به Service را توضیح می‌دهد. فایل محلی Secret باید Gitignored، محدود و در CI از Secret store ساخته شود.

Start، Test و Exit Code را درست به هم وصل کنید

دو الگوی معتبر دارید:

الگوی Attached test runner

docker compose -p qa-4821 up \
  --abort-on-container-exit \
  --exit-code-from tests \
  --remove-orphans

طبق مرجع docker compose up، --exit-code-from tests کد خروج همان Service را برمی‌گرداند و توقف سایر Containerها را ضمنی می‌کند. این گزینه با Detached mode سازگار نیست.

الگوی Wait سپس Run

docker compose -p qa-4821 up -d --wait --wait-timeout 90 db migrate app
docker compose -p qa-4821 run --rm tests

Exit Code دستور دوم باید Exit Code Job باشد. Trap/Finally Pipeline باید بعد از ذخیره Evidence، Teardown همان Project را اجرا کند. sleep 30 جای Readiness نیست: روی ماشین سریع اتلاف و روی ماشین کند Flake می‌سازد.

Playbook عیب‌یابی: از بیرون به داخل

۱. آیا Service ساخته و اجرا شده است؟

docker compose -p qa-4821 ps --all
docker compose -p qa-4821 images
docker compose -p qa-4821 top

Created، Running، Exited و Unhealthy مسئله‌های متفاوت‌اند. Exit code و زمان خروج را قبل از Restart ثبت کنید.

۲. اولین خطای زمانی کجاست؟

docker compose -p qa-4821 logs \
  --timestamps --no-color --since 10m --tail 500

مرجع Compose logs فیلتر زمان، Tail و Timestamp را پشتیبانی می‌کند. از خطای تست به عقب برگردید و نخستین خطای علت‌دار را پیدا کنید؛ صدها Connection refused پس از Crash دیتابیس علت تازه نیستند.

۳. DNS، Port و مرز میزبان/کانتینر درست است؟

  • داخل شبکه Compose از نام Service و Container port استفاده کنید؛
  • localhost داخل Container به همان Container اشاره دارد، نه میزبان یا DB؛
  • Host port فقط برای دسترسی میزبان لازم است و در Parallel باید پویا/یکتا باشد؛
  • docker compose port app 8080 Binding واقعی را نشان می‌دهد؛
  • از داخل Runner، DNS و اتصال به Port مقصد را بررسی کنید، نه فقط Ping.

۴. Config و Environment همان چیزی است که انتظار دارید؟

از config --environment برای منشأ Interpolation و از exec فقط برای بررسی متغیرهای غیرحساس، فایل Config و Clock استفاده کنید. Environment precedence را مستند کنید؛ Shell CI می‌تواند مقدار .env را Override کند. هرگز دستور چاپ همه Environment را در Artifact عمومی نگذارید.

۵. State قدیمی یا Migration ناقص است؟

اگر Failure فقط پس از اجرای دوم رخ می‌دهد، Volume، Cache، Queue و شناسه Seed را بررسی کنید. Migration باید Service یک‌باره با Exit Code باشد، Seed باید Idempotent یا Project-scoped باشد و تست نباید به داده اجرای قبلی تکیه کند. راهنمای تولید و مدیریت داده تست طراحی Dataset را تکمیل می‌کند.

Failure Injection امن در لَب Compose

فقط روی Project تست اختصاصی و با مجوز اجرا کنید؛ هرگز روی Host مشترک یا Production تمرین نکنید.

  • وابستگی Down: Service Stub یا DB را Stop و Timeout/پیام/Retry محدود را مشاهده کنید؛
  • Restart: وابستگی را Restart و Reconnect بدون Corruption را بررسی کنید؛
  • پاسخ خطا: WireMock پاسخ ۴۲۹/۵۰۰، Delay یا Payload ناقص بدهد؛
  • Callback تکراری: Fixture یک رویداد را دوبار ارسال و Idempotency را بسنجید؛
  • Resource pressure: فقط با Limit مصوب و آزمایش کوچک، رفتار Graceful را ببینید؛
  • Clock/Locale: با Injection برنامه‌ای، Tehran time و ارقام فارسی را تست کنید؛ ساعت Host را دست‌کاری نکنید.

برای Latency، قطع اتصال و سناریوهای غنی، Stub/Proxy کنترل‌پذیر از دستورهای دستی قابل تکرارتر است. مقاله Service Virtualization با WireMock قرارداد و Faultهای Dependency را پوشش می‌دهد.

Evidence Pack پیش از Teardown

  • Manifest هویت اجرا و Compose config hash؛
  • فهرست Imageها و Digestها؛
  • ps --all با وضعیت و Exit code؛
  • لاگ Timestampدار با بازه محدود؛
  • گزارش تست و تاریخچه Retry؛
  • Migration/Seed version و نتیجه؛
  • Correlation/Trace ID و Screenshot/Payload Redacted؛
  • Resource stats در صورت ظن CPU/Memory؛
  • فهرست Failure injectionهای انجام‌شده؛
  • زمان Teardown و نتیجه Cleanup.

Evidence را قبل از down بگیرید. لاگ کامل ممکن است PII، Token یا SQL حساس داشته باشد؛ Allowlist، Masking، دسترسی و Retention لازم‌اند. برای اجرای Test Runner و Context گزارش در CI، الگوی معماری Cucumber و راهنمای Pipeline تست خودکار مفیدند.

Reset امن و قابل پیش‌بینی

docker compose -p qa-4821 down --volumes --remove-orphans

مرجع docker compose down می‌گوید --volumes Volumeهای Named اعلام‌شده و Anonymous متصل را حذف می‌کند؛ Volume و Network خارجی حذف نمی‌شوند. این دستور برای Project تست Ephemeral مناسب است، نه محیطی که داده باید حفظ شود. Project name را پیش از اجرا Validate کنید و از docker system prune یا حذف سراسری Volumeها در Host مشترک پرهیز کنید.

ملاحظات ایران: Registry، Locale و سرویس بیرونی

  • Image لازم را در Registry خصوصی/Cache کنترل‌شده Mirror و Digest را Verify کنید؛
  • قطع دسترسی Registry را از Failure خود محصول جدا و Pull policy را ثبت کنید؛
  • از Mirror ناشناس فقط به‌دلیل سرعت استفاده نکنید؛ Trust و Provenance مهم‌اند؛
  • PSP، پیامک و نقشه را برای PR با Stub و برای Canary با سهمیه/مجوز تست کنید؛
  • Timezone را Asia/Tehran و تقویم/Clock را در سطح برنامه قابل تزریق کنید؛
  • Seed ریال و تومان را نام‌گذاری‌شده و ارقام فارسی/لاتین را جداگانه پوشش دهید؛
  • RTL و فونت وابسته به Host را با Profile UI جدا از Integration بسنجید.

جدول تشخیص سریع Failure

  • Container Exited: Command/entrypoint، Exit code، Permission و Secret mount؛
  • Unhealthy: Probe، start_period، Dependency واقعی و Log برنامه؛
  • Connection refused: Service name، Container port، Listener و Readiness؛
  • Timeout: DNS، Proxy، Route، Resource saturation یا Dependency Stub؛
  • فقط اجرای دوم Fail: Volume/Cache/Queue/Seed و Cleanup؛
  • فقط Parallel Fail: Project/Port/Data/File/Rate-limit مشترک؛
  • فقط CI Fail: Platform، Memory، Shell env precedence، Registry و Mount path؛
  • تست Fail ولی Job سبز: --exit-code-from یا propagation کد خروج؛
  • Image متفاوت: Tag متحرک، Pull policy و Digest ثبت‌نشده؛
  • لاگ ناکافی: Timestamp/Correlation، Log level و Artifact قبل از teardown.

ضدالگوهای Docker Compose در تست

  • ادعای «همه‌جا یکسان» فقط چون Image داریم؛
  • docker-compose و version: 3.8 به‌عنوان آموزش فعلی؛
  • فرض آمادگی با depends_on کوتاه یا sleep؛
  • Tag latest و نبود Digest/Platform در Evidence؛
  • رمز Hard-code یا نگهداری Secret در .env Commit‌شده؛
  • restart: always که Crash را در تست پنهان می‌کند؛
  • Volume پایدار پیش‌فرض و Seed غیر Idempotent؛
  • Project/Port/Order ID مشترک میان Jobها؛
  • دسترسی Test Container به Docker socket؛
  • چاپ همه Environment یا Payload حساس؛
  • اجرای Failure injection روی Host/Environment مشترک؛
  • Teardown پیش از Evidence؛
  • نادیده‌گرفتن Exit Code Test Runner؛
  • docker system prune در Cleanup پروژه.

چک‌لیست لَب بازتولید با Compose

  • Compose/Engine/Platform و Commit ثبت شده‌اند.
  • Project name و داده هر Run یکتا هستند.
  • Config نهایی Validate و Hash شده است.
  • Imageها با Digest مصوب شناخته می‌شوند.
  • Migration، Health و Readiness از هم جدا هستند.
  • Secret در YAML/Log/Artifact دیده نمی‌شود.
  • Test Runner Service و Exit Code آن نتیجه Job است.
  • Dependency واقعی/Stub در گزارش مشخص است.
  • Failure injection فقط در Project مجاز اجرا می‌شود.
  • لاگ Timestampدار و Correlation ID موجود است.
  • Evidence قبل از Teardown ذخیره می‌شود.
  • Cleanup فقط Project هدف و Volume تست را حذف می‌کند.

پرسش‌های متداول Docker Compose برای تسترها

تفاوت docker compose و docker-compose چیست؟

docker-compose نام CLI نسل اول و Legacy بود. Compose فعلی به‌صورت Plugin/CLI با docker compose اجرا و Compose Specification را مصرف می‌کند. نسخه دقیق را در CI ثبت کنید، چون گزینه‌های CLI به نسخه وابسته‌اند.

آیا depends_on منتظر آماده‌شدن دیتابیس می‌ماند؟

در شکل کوتاه خیر؛ فقط ترتیب Start را تعیین می‌کند. برای آمادگی از Healthcheck معنادار و condition: service_healthy استفاده کنید. Migration یک‌باره نیز می‌تواند با service_completed_successfully Gate شود.

چرا تست داخل Container Fail ولی Job CI سبز می‌شود؟

معمولاً Exit Code Test Runner به Shell منتقل نشده است. Runner را Service مستقل کنید و از up --exit-code-from tests یا run --rm tests استفاده کنید؛ سپس همان کد خروج را بدون پوشاندن به Job برگردانید.

آیا فایل .env جای امنی برای رمز تست است؟

خیر. .env ابزار Configuration/Interpolation است، نه Secret vault. رمز را از Secret store CI به Compose Secret بدهید، فایل موقت را محدود و Gitignored کنید و از چاپ Config resolve‌شده بدون Redaction بپرهیزید.

چگونه محیط چند Job موازی با هم تداخل نکند؟

برای هر Job Project name، داده، Artifact path و در صورت نیاز Host port یکتا بسازید؛ Static container_name و Volume خارجی مشترک نداشته باشید. با دو Job شروع و تداخل Resource/Rate limit را قبل از افزایش Parallelism بسنجید.

جمع‌بندی

Docker Compose برای تستر زمانی ارزشمند است که Failure را قابل بازتولید و قابل توضیح کند، نه صرفاً وقتی چند Container را بالا می‌آورد. هویت Artifact و Config را Freeze کنید، Running را از Ready جدا نگه دارید، Migration و Test Runner را با Exit Code واقعی مدل کنید و از بیرون به داخل—Status، Log، Network، Config و State—عیب‌یابی کنید.

Failure injection باید محدود و مجاز، Evidence پیش از Teardown و Cleanup دقیقاً Project-scoped باشد. این انضباط باعث می‌شود «روی سیستم من کار می‌کند» به پرونده‌ای شامل Digest، Config hash، Readiness، Correlation ID و نتیجه قابل تکرار تبدیل شود.

دیدگاهتان را بنویسید