Запуск и готовность
Ситуация
После docker compose up -d оба контейнера Up, а первые секунды сайт отвечает 502 Bad Gateway. Потом всё
работает. А ночью приложение зависло: контейнер Up, процесс жив, но на запросы не отвечает. Docker ничего
не сделал. Почему?
Главная мысль
Running (Up) значит одно: главный процесс существует. Ready — приложение действительно отвечает.
Docker узнаёт о готовности только из проверки здоровья (healthcheck), которую вы ему дадите.
Дальше три разных механизма, и их легко спутать:
- healthcheck — регулярно выполняет команду внутри контейнера и ставит статус
starting→healthyилиunhealthy. Сам ничего не чинит. depends_on: condition: service_healthy— Compose запускает зависимый сервис после того, как зависимость сталаhealthy. Работает только при запуске.- restart policy (
restart: unless-stopped) — перезапускает контейнер, когда главный процесс завершился. Наunhealthyв обычном Docker Engine не реагирует.
Новые слова
- Healthcheck — команда проверки и её расписание:
interval(как часто),timeout(сколько ждать ответа),retries(сколько неудач подряд — ужеunhealthy),start_period(время на запуск, неудачи в нём не считаются). - Политика перезапуска —
no(по умолчанию),on-failure(только при ненулевом коде),always,unless-stopped(всегда, кроме случая, когда вы остановили контейнер сами).
Итоговый compose.yaml
Добавляем в файл главы 6 проверку готовности, условие запуска web и политику перезапуска
(project/compose.yaml):
# «Заметки»: приложение и nginx перед ним
name: rpd
services:
app:
build: .
image: rpd-notes:compose
environment:
GREETING: ${GREETING:-Заметки}
STARTUP_DELAY: ${STARTUP_DELAY:-5}
volumes:
- notes-data:/data
healthcheck:
# Готово, если /health отвечает 200. Ответ 503 или ошибка соединения — код 1
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"]
interval: 5s
timeout: 3s
retries: 3
start_period: 15s
restart: unless-stopped
web:
image: nginx:1.30-alpine
ports:
- "127.0.0.1:${WEB_PORT:-8080}:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app:
condition: service_healthy
restart: unless-stopped
volumes:
notes-data:
name: rpd-notes-data
networks:
default:
name: rpd-net
- Проверка запускает Python внутри контейнера:
curlв образе нет.urlopenбросает исключение на ответ503и на ошибку соединения — Python завершается с кодом 1, Docker считает это неудачей. STARTUP_DELAY: 5— приложение «прогревается» 5 секунд: процесс уже работает, а/healthотвечает503. В настоящих приложениях так выглядит подключение к базе, загрузка кеша, миграции.webзапустится только послеhealthyуapp.
Проверка выполняется каждые 5 секунд и пишет строку GET /health 200 в журнал приложения — это нормально.
Разбор
Папка ~/docker-course/rpd-notes, файл compose.yaml заменён на итоговый. Если проект поднят — docker compose down.
Запуск ждёт готовности
time docker compose up -d
Container rpd-app-1 Started
Container rpd-app-1 Waiting
Container rpd-app-1 Healthy
Container rpd-web-1 Starting
Container rpd-web-1 Started
real 0m6.181s
Compose дождался Healthy и только потом запустил web — около 5 секунд прогрева плюс ожидание очередной проверки.
Запущен, но не готов
Перезапустим приложение и сразу посмотрим:
docker compose restart app
sleep 1
docker compose ps app --format '{{.Name}} {{.Status}}'
curl -sS -i http://127.0.0.1:8080/health
rpd-app-1 Up 1 second (health: starting)
HTTP/1.1 503 Service Unavailable
…
ещё прогревается
Контейнер Up, но ещё не готов. Обратите внимание: nginx продолжает отправлять ему запросы. Docker и Compose
не убирают неготовый контейнер из обращения — статус здоровья только информирует. В самый момент перезапуска
(пока процесс не начал слушать порт) nginx ответит 502 Bad Gateway. Через несколько секунд:
docker compose ps app --format '{{.Name}} {{.Status}}'
rpd-app-1 Up 13 seconds (healthy)
Последние проверки и их вывод — docker inspect -f '{{json .State.Health}}' rpd-app-1.
unhealthy — и ничего не происходит
Приложение умеет сообщать «на обслуживании»: если в папке данных есть файл maintenance, /health отвечает 503.
Это учебный способ сымитировать зависание, не убивая процесс:
docker compose exec app touch /data/maintenance
sleep 18
docker compose ps --format 'table {{.Name}}\t{{.Status}}'
docker inspect -f '{{.State.Status}} {{.State.Health.Status}} restarts={{.RestartCount}}' rpd-app-1
NAME STATUS
rpd-app-1 Up 40 seconds (unhealthy)
rpd-web-1 Up About a minute
running unhealthy restarts=0
Три неудачи подряд — unhealthy. Перезапусков — 0, и через минуту их тоже будет 0: restart: unless-stopped
на unhealthy не реагирует. Перезапуск по нездоровью делают оркестраторы (Swarm, Kubernetes) или отдельный
наблюдатель — в чистом Docker Engine этого нет. Вернём как было:
docker compose exec app rm /data/maintenance
Через одну-две проверки статус снова healthy.
Процесс завершился — перезапуск
Пошлём главному процессу SIGTERM изнутри — приложение штатно завершится с кодом 0:
docker compose exec app python3 -c "import os, signal; os.kill(1, signal.SIGTERM)"
sleep 3
docker inspect -f '{{.State.Status}} restarts={{.RestartCount}}' rpd-app-1
docker compose logs app | tail -3
running restarts=1
app-1 | notes: получен сигнал остановки, завершаю работу
app-1 | notes: остановлено
app-1 | notes: слушаю 0.0.0.0:8000, данные в /data
Процесс закончился — Docker запустил контейнер заново. А остановка вручную не перезапускается:
docker compose stop app
docker compose ps -a app --format '{{.Name}} {{.Status}}'
rpd-app-1 Exited (0) 3 seconds ago
docker compose start app — вернуть.
Перезапуск не чинит ошибку настройки
Одноразовый пример вне Compose — контейнер с неверным портом и политикой перезапуска:
docker run -d --name rpd-loop --restart unless-stopped -e PORT=80x1 rpd-notes:1
sleep 8
docker ps -a --filter name=rpd-loop --format '{{.Names}} {{.Status}}'
docker logs rpd-loop | head -2
docker rm -f rpd-loop
rpd-loop Restarting (2) 2 seconds ago
notes: неверный PORT: '80x1' (нужно число от 1 до 65535)
notes: неверный PORT: '80x1' (нужно число от 1 до 65535)
Контейнер бесконечно падает и перезапускается (с растущей паузой) — Restarting (2). Политика перезапуска
спасает от случайного падения, но не от ошибки, которая повторяется при каждом запуске. Причину ищут в журнале.
Практика: долгий прогрев
Исходное состояние — проект поднят с итоговым compose.yaml.
- Добавьте в
.envстрокуSTARTUP_DELAY=20и предскажите, сколько займётdocker compose up -dи в каком состоянии будетwebв первые 15 секунд. Выполнитеtime docker compose up -d, сравните. - Подумайте: что будет при
STARTUP_DELAY=60, еслиstart_period— 15 секунд, проверка каждые 5 секунд,retries: 3? Проверьте:STARTUP_DELAY=60 docker compose up -d(переменная окружения важнее.env). - Уберите строку из
.envи верните проект в рабочее состояние.
Признаки успеха: для пункта 1 — время около 20 секунд и web запущен после Healthy; для пункта 2 — названо
сообщение Compose и состояния обоих сервисов; в конце docker compose ps — app (healthy), web Up.
Подсказка 1
web ждёт healthy у app. Когда приложение начнёт отвечать 200 на /health?
Подсказка 2
Неудачные проверки в первые 15 секунд не считаются. После этого три неудачи подряд — unhealthy. Успеет ли
приложение прогреться за это время?
Подсказка 3
Вернуть: удалите STARTUP_DELAY=… из .env (sed -i '/^STARTUP_DELAY=/d' .env) и выполните docker compose up -d.
Решение — в answers/07-health-restart.md.
Если результат другой
dependency failed to start: container rpd-app-1 is unhealthy. Приложение не стало готовым за отведённое время. Смотритеdocker compose logs appиdocker inspect -f '{{json .State.Health}}' rpd-app-1: что отвечает проверка. Увеличиватьstart_period— только если приложение действительно долго стартует.- Статус всегда
startingилиunhealthy, а сайт работает. Ошибка в самой команде проверки (опечатка в адресе, нет нужной программы в образе). Выполните её вручную:docker compose exec app python3 -c "…". - Контейнер в
Restarting. Главный процесс падает сразу после старта. Журнал покажет причину; перезапуски её не исправят.
Проверка понимания
- Контейнер
Up (unhealthy)уже десять минут,restart: unless-stopped. Перезапустит ли его Docker? - Почему
depends_onбезcondition: service_healthyне спасает от502при первом запуске?
Ответы — в answers/07-health-restart.md.
Дальше: 8. Диагностика: найти и починить.
Разбор и проверка понимания
Лист ученика: 07-health-restart.md.
Практика
1. Прогрев 20 секунд.
echo 'STARTUP_DELAY=20' >> .env
time docker compose up -d
Container rpd-app-1 Waiting
Container rpd-app-1 Healthy
Container rpd-web-1 Started
real 0m21.121s
Около 20 секунд. Всё это время web в состоянии Created — Compose его не запускает, пока app не healthy.
Неудачные проверки в первые 15 секунд (start_period) не считаются, а следующие две ещё не дотягивают до трёх
подряд — к 20-й секунде приложение готово.
2. Прогрев 60 секунд.
STARTUP_DELAY=60 docker compose up -d
docker compose ps -a --format 'table {{.Name}}\t{{.Status}}'
dependency failed to start: container rpd-app-1 is unhealthy
NAME STATUS
rpd-app-1 Up 26 seconds (unhealthy)
rpd-web-1 Created
15 секунд льготного периода плюс три неудачи по 5 секунд — около 25–30 секунд до unhealthy. app работает
(и через 60 секунд станет готовым), но web так и не запущен: условие проверяется только при запуске.
3. Вернуть.
sed -i '/^STARTUP_DELAY=/d' .env
docker compose up -d
docker compose ps
app пересоздаётся с прогревом 5 секунд, после healthy запускается web.
Воспроизведено: Docker Engine 29.8.2, Compose 5.5.1, образы-заменители.
Проверка понимания
- Нет.
restart: unless-stoppedреагирует только на завершение главного процесса. Статусunhealthyв обычном Docker Engine — информация для людей и оркестраторов; перезапускать придётся самому (docker compose restart app) или отдельным наблюдателем. - Без условия Compose ждёт только запуска контейнера
app, то есть появления процесса. Приложение в этот момент может ещё не слушать порт или прогреваться — nginx уже принимает запросы и отвечает502.
Практика без терминала
Разобрать два варианта в браузере. Выберите наблюдения, проверьте ремонт и сохраните прогресс в аккаунте.
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.