~/blog / depends-on-tayyorlik-emasback

depends_on tayyorlikni kafolatlamaydi

8 daqiqa o'qish

Toza serverda birinchi marta docker compose up -d qildingiz. db ko'tarildi, api esa bir soniya ichida yiqildi. Qo'lda yana bir marta up -d — endi hammasi ishlaydi. Keyingi toza deploy'da hikoya takrorlanadi. Compose faylida esa hammasi joyida ko'rinadi:

yaml
services:
  api:
    depends_on:
      - db

Tartib yozilgan — api haqiqatan db dan keyin ishga tushadi. Muammo shundaki, depends_on tartib haqida, tayyorlik haqida emas. Quyida farqni real loglarda ko'ramiz, keyin ikki qatlamli yechim yig'amiz: to'g'ri yozilgan healthcheck va ilovaning o'zidagi qayta urinish.

Poygani o'zingiz takrorlang#

Nosozlikni ko'rish uchun ilova ham kerak emas. Quyidagi faylda api o'rnida bir martalik psql turibdi: u bazaga ulanadi, select 1 bajaradi va chiqadi.

yaml
services:
  api:
    image: postgres:16-alpine
    command: ["psql", "-h", "db", "-U", "app", "-d", "app", "-c", "select 1"]
    environment:
      PGPASSWORD: demo
    depends_on:
      - db
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: demo

Parol faylga yozilgan, chunki bu faqat lokal tajriba — ishlab chiqarishda bunday qilinmaydi. Faylni myapp katalogiga saqlang, docker compose up -d qiling va bir necha soniyadan keyin natijaga qarang. Qayta takrorlashdan oldin docker compose down -v qiling: baza volume'i bo'sh bo'lgandagina poyga eng yaqqol ko'rinadi.

console
$ docker compose ps -a --format 'table {{.Service}}\t{{.Status}}'
SERVICE   STATUS
api       Exited (2) 8 seconds ago
db        Up 9 seconds
$ docker compose logs api
api-1  | psql: error: connection to server at "db" (192.168.0.2), port 5432 failed: Connection refused
api-1  | 	Is the server running on that host and accepting TCP/IP connections?

db ishlab turibdi, api esa Connection refused olib, 2 kodi bilan chiqib ketgan — psql serverga ulana olmasa shu kodni qaytaradi. Python yoki Node'dagi ilovada matn boshqacha bo'ladi (psycopg.OperationalError, ECONNREFUSED), lekin ma'nosi bitta: 5432-port hali ochilmagan.

Nima uchun: ishga tushgan konteyner hali tayyor baza emas#

Qisqa yozuv aslida nimani anglatishini docker compose config ochiq aytadi. Uning chiqishida api ning bog'liqligi shunday ko'rinadi:

yaml
    depends_on:
      db:
        condition: service_started
        required: true

service_started — Compose faqat db konteyneri ishga tushishini, ya'ni entrypoint jarayoni start olishini kutadi. Postgres esa shu paytda endi ish boshlayapti. Bo'sh volume'da u avval initdb bilan klaster yaratadi, keyin quyidagi ketma-ketlik keladi (docker compose logs db dan tanlangan qatorlar):

text
db-1  | 2026-09-21 08:15:41.360 UTC [42] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db-1  | 2026-09-21 08:15:41.393 UTC [42] LOG:  database system is ready to accept connections
db-1  | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
db-1  | 2026-09-21 08:15:42.864 UTC [42] LOG:  database system is shut down
db-1  | PostgreSQL init process complete; ready for start up.
db-1  | 2026-09-21 08:15:43.163 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db-1  | 2026-09-21 08:15:43.244 UTC [1] LOG:  database system is ready to accept connections

E'tibor bering: ready to accept connections ikki marta chiqadi. Birinchisi — vaqtinchalik server ([42]). Image'ning entrypoint skripti uni faqat Unix socket'da ko'taradi, /docker-entrypoint-initdb.d/ dagi skriptlarni bajaradi va to'xtatadi. TCP orqali ulanishni faqat ikkinchi, asosiy server ([1]) qabul qiladi. Shu oraliqda api ulanishga urinsa — Connection refused.

Volume allaqachon to'la bo'lsa, initsializatsiya bosqichi bo'lmaydi va oyna ancha qisqaradi, lekin yo'qolmaydi. Konteyner SIGKILL bilan o'ldirilgan yoki server toki uzilgan bo'lsa, Postgres keyingi startda avval WAL bo'yicha tiklanadi. Bu WAL hajmiga qarab soniyalar, ba'zan daqiqalar davom etadi, va shu vaqtda ulanishlar FATAL: the database system is not yet accepting connections xatosini oladi.

Birinchi qatlam: healthcheck va service_healthy#

Compose tayyorlikni o'zi aniqlay olmaydi — unga qanday tekshirishni aytish kerak. db ga healthcheck yoziladi, api esa uni service_healthy sharti bilan kutadi:

yaml
services:
  api:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 30s
      start_interval: 1s

Har bir qatorning o'z sababi bor:

  • -h 127.0.0.1 — tekshiruv TCP orqali boradi. -h siz pg_isready Unix socket'ga ulanadi, vaqtinchalik server esa aynan socket'da tinglaydi. Natijada healthcheck init skriptlari ishlayotgan paytdayoq healthy deydi va api baribir Connection refused oladi. Buni tekshirish oson: /docker-entrypoint-initdb.d/ ga sleep 8 qiladigan skript qo'ying va -h siz variantni ishga tushiring — service_healthy bo'lsa ham poyga qaytadi.
  • -U va -d — pg_isready login ma'lumotlarini tekshirmaydi, faqat server javob beryaptimi-yo'qligini aniqlaydi. Lekin postgres image'ida USER belgilanmagan, healthcheck root nomidan ishlaydi, va -U bo'lmasa Postgres har tekshiruvda logga FATAL: role "root" does not exist yozadi. -d ham shu sababli kerak: busiz foydalanuvchi nomidagi baza so'raladi, u yo'q bo'lsa — FATAL: database "..." does not exist. Haqiqiy xatolar shu shovqin ostida ko'milib ketadi.
  • $$ — Compose ${...} ni faylni o'qiyotganda o'zi almashtiradi. $${POSTGRES_USER} esa konteynerga ${POSTGRES_USER} bo'lib yetadi, va uni konteyner ichidagi shell servisning o'z muhitidan o'qiydi.
  • interval, timeout, retries — tekshiruvlar oralig'i, bitta tekshiruvga ajratilgan vaqt va konteyner unhealthy deb e'lon qilinishidan oldingi ketma-ket muvaffaqiyatsizliklar soni. start_period bo'lmasa, birinchi tekshiruv konteyner ishga tushgandan interval o'tib bajariladi. interval ning standart qiymati esa 30 soniya — baza bir soniyada tayyor bo'lsa ham, api kamida yarim daqiqa kutadi.
  • start_period — ishga tushish uchun ajratilgan vaqt. Bu oraliqdagi muvaffaqiyatsiz tekshiruvlar retries ga qo'shilmaydi, birinchi muvaffaqiyatli tekshiruv esa konteynerni darhol healthy qiladi. Sekin diskli serverda birinchi start ancha cho'ziladi, shuning uchun bu yerda saxiy bo'ling.
  • start_interval — start_period davomidagi tekshiruvlar oralig'i: ishga tushishda tez-tez, keyin interval bo'yicha kamroq. Docker Engine 25.0 va undan yangisini talab qiladi.

Natija docker compose ps da ko'rinadi. Ishga tushish paytida:

console
$ docker compose ps --format 'table {{.Service}}\t{{.Status}}'
SERVICE   STATUS
db        Up 2 seconds (health: starting)

db tayyor bo'lgach, api ishga tushadi va bu safar muvaffaqiyatli chiqadi. Tekshiruvlarning o'zini docker inspect ko'rsatadi (loyiha katalogi myapp bo'lgani uchun konteyner nomi myapp-db-1):

console
$ docker compose ps -a --format 'table {{.Service}}\t{{.Status}}'
SERVICE   STATUS
api       Exited (0) 1 second ago
db        Up 31 seconds (healthy)
$ docker inspect -f '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{end}}' myapp-db-1
2 127.0.0.1:5432 - no response
2 127.0.0.1:5432 - no response
2 127.0.0.1:5432 - no response
1 127.0.0.1:5432 - rejecting connections
0 127.0.0.1:5432 - accepting connections

Docker oxirgi beshta tekshiruv natijasini saqlaydi. pg_isready kodlari: 0 — ulanishlar qabul qilinyapti, 1 — server ishga tushmoqda va ulanishlarni rad etyapti, 2 — javob yo'q. Asosiy server ko'tarilmaguncha TCP tekshiruv healthy demaydi — bizga aynan shu kerak.

depends_on ning uchta sharti#

ShartCompose nimani kutadiQachon ishlatiladi
service_startedKonteyner ishga tushdi. Qisqa yozuv shu deganiTayyorlikni tekshirishning iloji yo'q yoki kerak emas
service_healthyHealthcheck healthy holatga o'tdiBaza, cache, navbat — ulanadigan har qanday servis
service_completed_successfullyKonteyner 0 kodi bilan tugadiMigratsiya, bir martalik tayyorlov ishi

Migratsiya bor stack'da oxirgi ikki shart birga ishlaydi: baza sog'lom — migratsiya boshlanadi, migratsiya muvaffaqiyatli tugadi — API ko'tariladi.

yaml
services:
  api:
    depends_on:
      migrate:
        condition: service_completed_successfully
  migrate:
    command: alembic upgrade head
    restart: "no"
    depends_on:
      db:
        condition: service_healthy

Shart bajarilmasa, Compose cheksiz kutmaydi: up noldan farqli kod bilan tugaydi va aybdorni nomlaydi.

text
dependency failed to start: container myapp-db-1 is unhealthy
service "migrate" didn't complete successfully: exit 1

Shart nomidagi xato esa undan ham oldin ushlanadi: condition: healthy yozilsa, docker compose config faylni rad etadi va ruxsat etilgan uchta qiymatni sanab beradi.

Deploy skripti yoki CI uchun docker compose up -d --wait qulay: buyruq servislar ishlab turgan (healthcheck bo'lsa — healthy) holatga kelguncha kutadi va nimadir yiqilsa xato kodi bilan chiqadi. Bitta nozik joyi bor: hech kim service_completed_successfully bilan kutmaydigan bir martalik servis 0 bilan tugasa ham, --wait buni xato deb hisoblaydi (Compose v2.40 da shunday). Migratsiyani yuqoridagidek bog'lab qo'ysangiz, bu muammo bo'lmaydi.

depends_on qayerda ishlamaydi#

depends_on — Compose'ning qoidasi, Docker daemon'niki emas. U faqat Compose o'zi servislarni ko'tarayotganda amal qiladi. Undan tashqarida himoya yo'q:

  • Server qayta yuklanganda konteynerlarni restart siyosati bo'yicha Docker daemon'ning o'zi ko'taradi. Compose bu jarayonda ishtirok etmaydi va healthcheck'larni kutmaydi — api bazadan oldin turishi mumkin.
  • Baza ish vaqtida qayta ishga tushsa — yangilanish, crash yoki docker compose restart db — api dagi ochiq ulanishlar uziladi. Hech qaysi shart bunga ta'sir qilmaydi.
  • Kubernetes'da servislar orasida ishga tushish tartibi degan narsaning o'zi yo'q.

Demak, kutishni ilovaning o'zi ham bilishi kerak.

Ikkinchi qatlam: ilova qayta urinadi#

Ilova ulanish xatosini kutilgan holat deb qabul qilishi kerak — lekin cheklangan vaqt ichida. Python va psycopg 3 bilan minimal misol:

python
import sys
import time

import psycopg


def connect_with_retry(dsn: str, deadline: float = 60.0) -> psycopg.Connection:
    """Baza tayyor bo'lguncha kutadi, lekin cheksiz emas."""
    started = time.monotonic()
    delay = 0.5
    while True:
        try:
            return psycopg.connect(dsn, connect_timeout=3)
        except psycopg.OperationalError as exc:
            if time.monotonic() - started + delay > deadline:
                raise  # muddat tugadi: jim kutmasdan, ochiq yiqilamiz
            print(f"baza hali tayyor emas ({exc}); {delay:.1f}s dan keyin qayta", file=sys.stderr, flush=True)
            time.sleep(delay)
            delay = min(delay * 2, 5.0)

Bu yerda uchta qaror bor:

  • Kutish oralig'i har safar ikki barobar o'sadi (0.5, 1, 2, 4 soniya), lekin 5 soniyadan oshmaydi. Tiklanayotgan bazaga ulanish so'rovlari yog'dirilmaydi.
  • Muddat cheklangan. 60 soniyadan keyin xato yuqoriga uzatiladi; uni hech kim tutmasa, Python 1 kodi bilan chiqadi, va restart siyosati bo'lsa konteyner qayta ko'tariladi. Cheksiz jim kutish muammoni yashiradi: konteyner Up, lekin hech narsa ishlamaydi.
  • Har bir urinish logga yoziladi, shuning uchun nosozlik paytida docker compose logs api aniq manzarani beradi.

Ish vaqtidagi uzilishlar uchun ham xuddi shu tamoyil. SQLAlchemy'da create_engine(url, pool_pre_ping=True) ulanishni pool'dan berishdan oldin tekshiradi va o'lganini yangisiga almashtiradi. Baza qaytib kelgandan keyingi birinchi so'rov eski, uzilgan ulanishga tushib qolmaydi.

Qanday tekshirish kerak#

  1. docker compose config ni ishga tushiring: qisqa yozuv condition: service_started ga aylanganini, uzun yozuvda esa siz yozgan shart turganini ko'rasiz.
  2. Eng og'ir holatni ataylab yarating — bo'sh volume bilan birinchi start: docker compose down -v, keyin docker compose up -d. Faqat lokal yoki test muhitida: -v nomlangan volume'larni, ya'ni baza ma'lumotlarini o'chiradi.
  3. docker compose ps da db avval (health: starting), keyin (healthy) bo'lishi, api esa shundan keyingina ishga tushishi kerak.
  4. docker inspect dagi health logida no response dan accepting connections ga o'tishni kuzating.
  5. Ikkinchi qatlamni ham sinang: ishlab turgan stack'da docker compose restart db qiling. api o'zini qayta ishga tushirmasdan tiklanishi, keyingi so'rov esa muvaffaqiyatli o'tishi kerak.

Amaliyot#

Quyidagi lab'larda xuddi shu xatolar kichik compose fayllarda turibdi — tuzating va server tekshiruvidan o'tkazing:

labo'rtaBaza ishga tushdi — lekin hali tayyor emas`api` birinchi soniyada *connection refused* oladi va yiqiladi. Konteyner ≠ tayyor jarayon.mashqni ochish labo'rtaBirinchi ishga tushish 40 soniya oladiYangi serverda healthcheck 3 marta yiqilib, konteyner *unhealthy* bo'lib qoldi.mashqni ochish labo'rtaMigratsiya tugamasdan API ko'tarilmasinAPI eski sxema bilan ishga tushib, *column does not exist* xatolarini yog'dirmoqda.mashqni ochish labqiyinBog'liqlik zanjiri: db → api → workerHammasi bir vaqtda ishga tushyapti va birinchi 20 soniya xato bilan to'la.mashqni ochish

docker compose ps, logs va config qo'l xotirasiga o'tishi uchun:

buyruq mashqi3-bosqich · 16 vazifaCompose va deployBir nechta servis, bitta fayl. Ko'tarish, yangilash, tekshirish.bosqichni boshlash