Урок курса

Healthcheck и готовность базы

Docker и Docker Compose: контейнеризация приложений

1. Процесс работает, база ещё готовится

В двухконтейнерном стеке app и db запуск процесса операционной системой не означает немедленную готовность приложения отвечать на запросы. Когда Docker запускает контейнер с PostgreSQL, процесс postgres стартует сразу, однако серверу требуется время: прочитать конфигурацию, восстановить файлы данных, а при первом запуске — выполнить служебную процедуру инициализации каталога (initdb).

Выданное приложение course-notes спроектировано устойчиво к сетевым задержкам: режим python -u app.py serve поднимает HTTP-сервер на порту 8000 даже тогда, когда PostgreSQL ещё не отвечает. Контейнер переходит в статус Up, маршрут проверки процесса GET /health возвращает код 200 с телом {"status": "alive"}. Но при обращении к маршруту проверки готовности хранилища GET /ready приложение пытается выполнить проверочный запрос к базе данных. Если сокет базы ещё не принимает клиентские соединения или в базе ещё не создана требуемая таблица схемы, запрос возвращает код 503 ({"status": "not_ready"}).

Для проверки доступности PostgreSQL существует стандартная системная утилита pg_isready, входящая в образ postgres:17.4-bookworm. Она проверяет, готов ли сервер принимать подключения, не выполняя при этом тяжёлых пользовательских SQL-запросов и не требуя передачи пароля.

Контракт кодов возврата утилиты pg_isready:

  • 0 — сервер принимает подключения и готов к работе;
  • 1 — сервер отклоняет подключения (например, завершает стартовую инициализацию или находится в режиме восстановления);
  • 2 — сервер не отвечает (порт закрыт, процесс не запущен);
  • 3 — попытка подключения не предпринималась (переданы синтаксически некорректные параметры или не заданы обязательные флаги вызова).

Проверка отвечает только на свой вопрос. Утилита pg_isready -h 127.0.0.1 -p 5432 -U course -d course проверяет исключительно способность сетевого демона СУБД принять входящее TCP-соединение на указанном порту. Сервер вернёт код 0, даже если указаны несуществующие имя пользователя или базы данных, поскольку pg_isready не выполняет аутентификацию. Соответственно, код 0 не гарантирует, что пароль приложения верен, что в базе развёрнуты таблицы или что не исчерпан лимит прикладных сессий. Корректность учётных данных и наличие схемы проверяются прикладными командами init/check и запросом к /ready.

Периодическое исполнение такой проверочной команды внутри контейнера называется механизмом healthcheck. Демон Docker отслеживает код возврата команды и присваивает контейнеру статус здоровья:

  • starting — контейнер только что запущен, первая проверка ещё выполняется либо идёт начальный льготный интервал;
  • healthy — команда проверки завершилась с кодом 0;
  • unhealthy — команда завершилась с ненулевым кодом подряд заданное количество раз (retries).

Текущий статус отображается в выводе списка контейнеров рядом с временем работы: например, Up 10 seconds (health: starting) или Up 45 seconds (healthy), а полная история последних запусков проверки записывается в блок .State.Health вывода docker inspect.

Интерактивная модель готовности сервисов

Скачать docker_readiness_model.zip

Распакуйте архив и откройте readiness_lab.html в браузере. Это автономная учебная модель; Docker она не запускает.

2. Дождаться проверки зависимости

По умолчанию директива depends_on: [db] в Compose определяет только порядок запуска контейнеров: демон Docker стартует db и сразу же запускает app, не дожидаясь готовности сетевого сокета PostgreSQL. Чтобы связать запуск процесса приложения с фактической готовностью базы данных, в файле конфигурации используется расширенная форма depends_on с условием condition: service_healthy.

Механизм проверки можно задать как в Dockerfile через инструкцию HEALTHCHECK, так и напрямую в секции сервиса внутри compose.yaml. Определение проверки в Compose позволяет настраивать параметры под конкретное окружение и сервисы без пересборки базовых образов.

Перед запуском убедитесь, что хостовый порт 18126 свободен, а ресурсы нового учебного проекта с префиксом cf_docker_course_l26 ещё не существуют в системе:

docker container ls -a --filter name=cf_docker_course_l26
docker network ls --filter name=cf_docker_course_l26
docker volume ls --filter name=cf_docker_course_l26

В каталоге проекта подготовьте файл compose.yaml со связкой сервисов, выделенным томом db_data и портом 18126:

services:
  db:
    image: postgres:17.4-bookworm
    environment:
      POSTGRES_DB: course
      POSTGRES_USER: course
      POSTGRES_PASSWORD: secret
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-p", "5432", "-U", "course", "-d", "course"]
      interval: 3s
      timeout: 3s
      retries: 20
      start_period: 10s
  app:
    image: course-notes:1.0.0
    ports:
      - "127.0.0.1:18126:8000"
    environment:
      STORAGE: postgres
      DB_HOST: db
      DB_PORT: "5432"
      DB_NAME: course
      DB_USER: course
      PASSWORD: secret
    depends_on:
      db:
        condition: service_healthy
volumes:
  db_data:

Параметры блока healthcheck задают правила проверки:

  • test — исполняемая команда. Список ["CMD", ...] вызывает бинарник напрямую без шелла. Явный хост -h 127.0.0.1 исключает ложную готовность: во время initdb PostgreSQL запускает временный сервер только на Unix-сокете, поэтому без флага -h 127.0.0.1 проверка сочла бы базу готовой ещё до открытия внешнего TCP-порта 5432;
  • interval — интервал между повторными проверками (3 секунды);
  • timeout — лимит времени ожидания ответа на одну попытку (3 секунды);
  • retries — порог последовательных ошибок (exit code не 0) до перехода в статус unhealthy;
  • start_period — начальный период (10 секунд), во время которого неудачные проверки не расходуют счетчик retries. Если pg_isready вернёт 0 в пределах этого окна, статус healthy установится сразу.

Запустите проект в первом терминале на переднем плане:

docker compose -p cf_docker_course_l26 up

Важно различать создание контейнера и запуск его главного процесса. Если во втором терминале выполнить docker compose -p cf_docker_course_l26 ps -a прямо в момент подъёма стека, можно увидеть, что контейнер app уже создан (Created), но его процесс ещё не стартует. Compose ожидает, пока контейнер db пройдёт проверку готовности: цепочка статусов в логе выглядит как app Created → db Waiting → db Healthy → app Starting. Условие condition: service_healthy блокирует именно старт (Starting/Running) зависимого сервиса, а не факт создания контейнера демоном.

Если запустить проект в фоновом режиме через docker compose -p cf_docker_course_l26 up -d, команда завершится только после того, как db получит статус healthy, а app запустится. Проверьте состояние сервисов:

docker compose -p cf_docker_course_l26 ps

В столбце STATUS база db имеет статус Up ... (healthy), а сервис app находится в рабочем состоянии Up. Для сервиса app отдельный healthcheck в этом манифесте не задан, поэтому статус здоровья отображается только у СУБД.

Убедитесь по логам приложения, что сервер начал принимать входящие запросы:

docker compose -p cf_docker_course_l26 logs app

В логе должна появиться запись started с параметрами service: course-notes и storage: postgres. Теперь проверьте маршруты с хоста через опубликованный порт 18126:

curl -fsS http://127.0.0.1:18126/health

Ответ {"status": "alive"} подтверждает, что HTTP-процесс активен. Далее проверьте маршрут проверки хранилища:

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18126/ready

Запрос вернёт код 503. База данных принимает TCP-подключения, но на новом томе db_data отсутствует схема таблиц приложения. Выполните одноразовую команду инициализации структуры данных:

docker compose -p cf_docker_course_l26 exec app python app.py init

Команда возвращает {"status": "initialized", "schema": 1} с кодом выхода 0. Повторный запрос curl -fsS http://127.0.0.1:18126/ready вернёт код 200 и тело {"status": "ready"}.

3. Что проверка не исправляет

Статус unhealthy является исключительно информационным свойством в метаданных рантайма Docker. Сам по себе переход проверки в неуспешное состояние не заставляет демон Docker перезапускать процесс, останавливать контейнер или заменять его новым экземпляром.

Поведение демона привязано к жизненному циклу процесса: стандартные механизмы перезапуска реагируют только на фактическое завершение основного процесса в ОС (появление кода выхода). Если процесс продолжает выполняться, но регулярная команда healthcheck завершается ошибкой, контейнер остаётся активным в статусе Up (unhealthy).

Это подтверждает контрольное наблюдение жизненного цикла контейнера с заведомо проваленной проверкой:

{
  "Id": "7c82c4db8c0322c8c50eb2f061d30f8af8a3fa63a7a2e865eca9f1d83d664325",
  "State": {
    "Running": true,
    "Health": {
      "Status": "unhealthy"
    }
  },
  "RestartCount": 0
}

Обратите внимание на три поля: идентификатор контейнера Id не изменился, поле Running остаётся true (главный процесс всё ещё исполняется), статус проверки перешёл в unhealthy, а счётчик перезапусков RestartCount равен 0. Демон зафиксировал поломку контракта проверки, но не предпринял попыток принудительного перезапуска.

Второе важное ограничение касается связки в Compose: директива depends_on: { condition: service_healthy } управляет только начальным порядком развёртывания стека при docker compose up. Она не превращается в постоянный мониторинг топологии после запуска и не переводит зависимые сервисы в аварийный режим, если соседний контейнер вышел из строя.

Проверьте это на запущенном проекте текущего урока. Во втором терминале остановите контейнер базы данных:

docker compose -p cf_docker_course_l26 stop db

Запросите статус сервисов:

docker compose -p cf_docker_course_l26 ps -a

Сервис db перешёл в статус Exited, однако сервис app продолжает работать в статусе Up. Compose не остановил и не перезапустил app вслед за упавшей зависимостью.

Запрос к маршрутам приложения через порт 18126 подтверждает независимость процессов:

curl -fsS http://127.0.0.1:18126/health
{"status": "alive"}

Маршрут /health возвращает код 200, поскольку сам процесс веб-сервера жив. Но проверка хранилища через маршрут /ready зафиксирует потерю связи:

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18126/ready

Команда возвращает 503, так как база остановлена и TCP-порт 5432 недоступен. При этом веб-процесс не падает и сохраняет способность обрабатывать входящие HTTP-запросы.

После завершения экспериментов остановите и удалите учебный проект, сохранив том с данными базы:

docker compose -p cf_docker_course_l26 down

Таким образом, healthcheck сообщает статус работоспособности внешним наблюдателям, а condition: service_healthy упорядочивает первоначальный старт. Автоматическое восстановление разорванных соединений во время работы приложения остаётся задачей кода самого сервиса (повторные попытки подключения), а не базового рантайма контейнеров.

При здоровой новой базе HTTP-процесс отвечает health200, но ready503 до init. После init ready200. После stop db приложение продолжает работать: health200, ready503.

Локальная лабораторная / самопроверка

Что уже дано

Работайте в POSIX-терминале с Linux-контейнерами. Сохраните выданные файлы в новый каталог l26_lab, который ещё не существует; если занят, выберите другой. Выберите уникальный префикс, например LAB=cf_docker_task_l26_run01; замените run01 при повторе. Перед созданием проверьте свои предполагаемые имена через docker container ls -a, network inspect и volume inspect, если они используются. Занятый объект не удаляйте: выберите другой префикс. Ошибку связи с Engine не принимайте за отсутствие ресурса. Порты хоста должны быть свободны; PORTS не исключает отдельный процесс ОС. При неожиданном bind-конфликте остановите опыт и согласованно замените host-порт/URL, не останавливая прежние службы. Все новые имена контейнеров/сетей/томов и проект Compose начинаются с выбранного LAB. Готовые app.py/SQL менять или писать не требуется. Соберите выданный Dockerfile из этого каталога в свой образ ${LAB}:app как подготовку; рецепт уже известен, кроме явно дефектного файла текущего задания.

Дан полный намеренно неисправный compose.yaml: db healthcheck опрашивает5433 вместо5432, app ожидает service_healthy. PostgreSQL17.4, новый db_data, task-demo-26, HTTP18226. Приложение инициализируется supplied init; app healthcheck, если будет добавлен во втором опыте, не должен ждать схему до init.

Что нужно сделать

Сначала на отдельном запуске того же нового проекта замените depends_on у app на краткое [db] и добавьте db готовую command: ["sh","-c","sleep 8; exec docker-entrypoint.sh postgres"]. sleep задерживает штатный старт БД на8секунд, exec заменяет оболочку штатным entrypoint. Пока TCPpg_isready возвращает2, получите /health200 и /ready503 работающего app. После pg_isready0 до первого init /ready по-прежнему503: это уже отсутствие схемы. Затем выполните init, подтвердите /ready200, выполните down без-v и восстановите исходную длинную зависимость/ошибочный healthcheck, удалив задержку. Получите реальное состояние db и зависимого app при проваленной проверке. Исправьте проверку, дождитесь старта app, выполните init и проверьте готовность. Затем в своей копии задайте для app контрольную заведомо неуспешную проверку test: ["CMD","python","app.py","fail"] с коротким interval/retries. Команда fail завершает только проверочный процесс с7; основной serve остаётся. Сопоставьте Running, Health, Container ID и RestartCount до/после unhealthy. В конце отдельно остановите db и сравните /health с /ready app.

Ввод и вывод

Вы самостоятельно вводите Docker/Compose-команды на хосте и сохраняете их реальные результаты в локальный текстовый отчёт. Печатать кодовое слово, писать Python/SQL или заполнять форму ответа не требуется. Эта TEXT-лабораторная не имеет автоматической платформенной проверки.

Проверка результата:

  • Краткая зависимость допускает работающий app при TCPpg_isready2; после TCP0, но до init, readiness всё ещё503. После init200: причины различены, ранний старт не объявлен обязательным падением app.
  • В начале db работает, но healthcheck не проходит; app может быть Created, однако его основной процесс не запущен до успешной проверки зависимости.
  • После ремонта init0 и /ready200. При failing app-health основной процесс остаётсяRunning с тем жеID и RestartCount, хотя Health=unhealthy. После stopdb /health200 и /ready503 — это другой опыт, не доказательство health-рестарта.

Сохраните команды, фактические ID/коды и наблюдения до очистки. После опыта штатно остановите и удалите только контейнеры своего LAB; для Compose используйте то же -p и down без-v, затем удалите только свою свободную сеть. Тома с контрольными записями сохраняйте, кроме отдельно оговорённого разрушительного сравнения урока27. Если позже удаляете собственный одноразовый том, сначала назовите точное имя и потерю его данных; чужие ресурсы и общие образы сохраняйте.

Ограничения:

  • Локальная самопроверка; платформа не запускает весь Docker-проект, не оценивает отчёт автоматически и не подтверждает выполнение ученика.
  • Никакого prune, privileged, Docker socket или реальных секретов. Только собственные новые учебные ресурсы.
  • Эталон/ошибочные варианты проверяются отдельно на локальном Engine29.6.2; это не сертификация Latorn Docker27 или всех ОС.

Файл app.py:

import json
import os
from pathlib import Path
import signal
import sys
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.request import urlopen

META = json.loads(Path(__file__).with_name("app_metadata.json").read_text())
VERSION = Path(__file__).with_name("version.txt").read_text().strip()
MODE = os.getenv("STORAGE", "memory")
LOCK = threading.RLock()
MEMORY = []

def log(event, **fields):
    print(json.dumps({"event": event, **fields}), flush=True)

def password():
    direct, filename = os.getenv("PASSWORD", ""), os.getenv("PASSWORD_FILE", "")
    if direct and filename:
        raise ValueError("PASSWORD and PASSWORD_FILE are mutually exclusive")
    value = Path(filename).read_text().rstrip("\r\n") if filename else direct
    if not value:
        raise ValueError("postgres mode requires PASSWORD or PASSWORD_FILE")
    return value

def connect():
    import psycopg
    return psycopg.connect(host=os.getenv("DB_HOST", "db"), port=int(os.getenv("DB_PORT", "5432")),
        dbname=os.getenv("DB_NAME", "course"), user=os.getenv("DB_USER", "course"),
        password=password(), connect_timeout=2, options="-c statement_timeout=3000")

def file_path():
    return Path(os.getenv("DATA_FILE", "/data/items.json"))

def rows():
    with LOCK:
        if MODE == "memory":
            return list(MEMORY)
        if MODE == "file":
            p = file_path()
            return json.loads(p.read_text()) if p.exists() else []
        with connect() as db:
            records = db.execute("SELECT id, text FROM course_items ORDER BY id").fetchall()
            return [{"id": r[0], "text": r[1]} for r in records]

def add(text):
    with LOCK:
        if MODE == "postgres":
            with connect() as db:
                row = db.execute("INSERT INTO course_items (text) VALUES (%s) RETURNING id, text", (text,)).fetchone()
                return {"id": row[0], "text": row[1]}
        current = rows()
        item = {"id": max([r["id"] for r in current], default=0) + 1, "text": text}
        current.append(item)
        if MODE == "memory":
            MEMORY[:] = current
        else:
            p = file_path()
            temp = p.with_suffix(".tmp")
            temp.write_text(json.dumps(current), encoding="utf-8")
            temp.replace(p)
        return item

def ready():
    if MODE == "postgres":
        with connect() as db:
            found = db.execute("SELECT version FROM course_schema WHERE singleton = TRUE").fetchone()
            if found != (1,):
                raise RuntimeError("schema 1 required; run supplied init command")
            db.execute("SELECT id FROM course_items LIMIT 1")
    elif MODE == "file":
        p = file_path()
        if not p.parent.is_dir() or not os.access(p.parent, os.W_OK):
            raise RuntimeError("DATA_FILE parent must exist and be writable")
        rows()
    elif MODE != "memory":
        raise ValueError("STORAGE must be memory, file or postgres")

def init():
    if MODE == "postgres":
        with connect() as db:
            db.execute("CREATE TABLE IF NOT EXISTS course_schema (singleton boolean PRIMARY KEY CHECK (singleton), version integer NOT NULL)")
            db.execute("INSERT INTO course_schema VALUES (TRUE, 1) ON CONFLICT (singleton) DO NOTHING")
            version = db.execute("SELECT version FROM course_schema").fetchone()
            if version != (1,):
                raise RuntimeError("refusing incompatible schema")
            db.execute("CREATE TABLE IF NOT EXISTS course_items (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, text varchar(200) NOT NULL)")
    elif MODE == "file":
        p = file_path()
        if not p.exists():
            p.write_text("[]", encoding="utf-8")
    ready()
    print(json.dumps({"status": "initialized", "schema": 1}))

class Handler(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        log("request", method=self.command, path=self.path, detail=fmt % args)

    def send(self, code, data):
        body = json.dumps(data, ensure_ascii=False).encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        try:
            if self.path == "/":
                filename = os.getenv("MESSAGE_FILE")
                message = Path(filename).read_text().strip() if filename else os.getenv("MESSAGE", "hello from Docker")
                self.send(200, {"service": META["service"], "message": message, "storage": MODE})
            elif self.path == "/health":
                self.send(200, {"status": "alive"})
            elif self.path == "/ready":
                ready()
                self.send(200, {"status": "ready"})
            elif self.path == "/version":
                self.send(200, {"service": META["service"], "version": VERSION, "schema": 1})
            elif self.path == META["collection"]:
                ready()
                self.send(200, {"items": rows()})
            elif self.path == "/upstream":
                url = os.environ["DEPENDENCY_URL"]
                with urlopen(url, timeout=2) as reply:
                    self.send(200, {"dependency": json.load(reply)})
            else:
                self.send(404, {"error": "not_found"})
        except Exception as exc:
            log("dependency_error", kind=type(exc).__name__)
            self.send(503, {"status": "not_ready"} if self.path == "/ready" else {"error": "storage_or_dependency_unavailable"})

    def do_POST(self):
        if self.path != META["collection"]:
            self.send(404, {"error": "not_found"})
            return
        try:
            length = int(self.headers.get("Content-Length", "0"))
            if not 0 < length <= 4096:
                raise ValueError("body size")
            data = json.loads(self.rfile.read(length))
            text = data.get("text") if isinstance(data, dict) else None
            if not isinstance(text, str) or not 1 <= len(text.strip()) <= 200:
                raise ValueError("text must have 1..200 characters")
        except (ValueError, TypeError):
            self.send(400, {"error": "invalid_text"})
            return
        try:
            ready()
            self.send(201, add(text.strip()))
        except Exception as exc:
            log("storage_error", kind=type(exc).__name__)
            self.send(503, {"error": "storage_unavailable"})

def serve():
    if MODE not in ("memory", "file", "postgres"):
        raise ValueError("invalid STORAGE")
    if MODE == "postgres":
        password()  # configuration errors fail immediately; DB outages do not kill HTTP
    Path("/tmp/course-app.pid").write_text(str(os.getpid()))
    server = ThreadingHTTPServer((os.getenv("HOST", "0.0.0.0"), int(os.getenv("PORT", "8000"))), Handler)
    server.daemon_threads = True
    stopping = threading.Event()
    def stop(signum, _frame):
        if not stopping.is_set():
            stopping.set()
            log("signal_received", signal=signal.Signals(signum).name)
            threading.Thread(target=server.shutdown, daemon=True).start()
    signal.signal(signal.SIGTERM, stop)
    signal.signal(signal.SIGINT, stop)
    log("started", service=META["service"], version=VERSION, storage=MODE, uid=os.getuid())
    server.serve_forever(poll_interval=0.1)
    server.server_close()
    log("stopped")

def main():
    command = sys.argv[1] if len(sys.argv) > 1 else "serve"
    if command == "serve":
        serve()
    elif command == "init":
        init()
    elif command == "seed":
        ready()
        for text in META["seed"]:
            if not any(row["text"] == text for row in rows()):
                add(text)
        print(json.dumps({"items": rows()}))
    elif command == "inspect":
        ready()
        print(json.dumps({"items": rows()}))
    elif command == "check":
        ready()
        print(json.dumps({"status": "ready", "schema": 1}))
    elif command == "http-check":
        with urlopen("http://127.0.0.1:" + os.getenv("PORT", "8000") + "/ready", timeout=2) as r:
            print(r.read().decode())
    elif command == "fail":
        log("intentional_failure")
        sys.exit(7)
    elif command == "allocate":
        # Use ONLY with a bounded container memory limit; held blocks force observable allocation.
        blocks = []
        while True:
            blocks.append(bytearray(1024 * 1024))
    else:
        raise ValueError("unknown supplied command")

if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        log("fatal", kind=type(exc).__name__)
        sys.exit(1)

Файл requirements.txt:

psycopg==3.2.6
psycopg-binary==3.2.6
typing-extensions==4.12.2

Файл app_metadata.json:

{
  "service": "course-notes",
  "collection": "/notes",
  "seed": [
    "first supplied note",
    "data survives container recreation"
  ]
}

Файл version.txt:

1.0.0

Файл Dockerfile:

FROM python:3.12.10-slim-bookworm AS dependencies
WORKDIR /build
COPY requirements.txt .
RUN python -m pip install --no-cache-dir --prefix=/install -r requirements.txt && printf 'build stage only\n' > /build/build-only.txt

FROM python:3.12.10-slim-bookworm AS runtime
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY --from=dependencies /install /usr/local
COPY app.py app_metadata.json ./
ARG APP_VERSION=1.0.0
COPY versions/${APP_VERSION}.txt ./version.txt
RUN groupadd --gid 10001 app && useradd --uid 10001 --gid 10001 --no-create-home app && mkdir /data && chown 10001:10001 /data
USER 10001:10001
EXPOSE 8000
STOPSIGNAL SIGTERM
CMD ["python", "-u", "app.py", "serve"]

Файл versions/1.0.0.txt:

1.0.0

Файл compose.yaml:

{
  "services": {
    "db": {
      "image": "postgres:17.4-bookworm",
      "environment": {
        "POSTGRES_DB": "course",
        "POSTGRES_USER": "course",
        "POSTGRES_PASSWORD": "task-demo-26",
        "PGDATA": "/var/lib/postgresql/data/pgdata"
      },
      "volumes": [
        "db_data:/var/lib/postgresql/data"
      ],
      "healthcheck": {
        "test": [
          "CMD",
          "pg_isready",
          "-h",
          "127.0.0.1",
          "-p",
          "5433",
          "-U",
          "course",
          "-d",
          "course"
        ],
        "interval": "1s",
        "timeout": "2s",
        "retries": 3,
        "start_period": "1s"
      }
    },
    "app": {
      "image": "cf_docker_task_l26_run01:app",
      "ports": [
        "127.0.0.1:18226:8000"
      ],
      "environment": {
        "STORAGE": "postgres",
        "DB_HOST": "db",
        "DB_PORT": "5432",
        "DB_NAME": "course",
        "DB_USER": "course",
        "PASSWORD": "task-demo-26"
      },
      "depends_on": {
        "db": {
          "condition": "service_healthy"
        }
      }
    }
  },
  "volumes": {
    "db_data": null
  }
}

Лабораторная выполняется локально. Latorn не запускает Docker Compose или весь проект и не подтверждает выполнение лабораторной автоматически.

Задача 1

У app указано depends_on: [db]. Процесс PostgreSQL уже запущен, но начальная инициализация ещё не закончилась. Что гарантирует эта краткая форма зависимости при запуске Compose?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

App уже работает после старта с depends_on db: condition: service_healthy. Позже db получила unhealthy; процесс db продолжает работать. Других оркестраторов и механизмов реакции нет. Что произойдёт с app из-за одного этого изменения health?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку