Learning Platform
Глоссарий Troubleshooting
Урок 15.05 · 22 мин
Средний
dockerdebugtroubleshooting

Debug checklist

В прошлых уроках мы по отдельности разобрали logs, exec, inspect, stats. Этот урок — сводный чек-лист «контейнер не работает, что делать», который ты можешь распечатать и повесить рядом с монитором.


Сигналы и kill: как правильно убивать процессы

Алгоритм: 7 шагов

Когда DAG в Airflow не запускается, app не отвечает на запросы, или Postgres-контейнер постоянно рестартится — иди по шагам:

Шаг 1: docker ps -a — статус и exit code

docker ps -a --filter name=app
CONTAINER ID   IMAGE       COMMAND   STATUS                       NAMES
7f3e1a2b3c4d   my-etl      ...       Exited (1) 5 seconds ago     app

Что смотрим:

  • Up — контейнер работает, проблема внутри (приложение в нём не отвечает).
  • Exited (<code>) <time> ago — упал, время и код подсказывают причину.
  • Restarting (<code>) — крутится в цикле рестартов.
  • Created — был создан, но не запущен.

Шаг 2: docker logs --tail 100 — последние строки

docker logs app --tail 100 --timestamps

Ищешь:

  • Traceback / Error / Exception в последних строках.
  • Сообщение «died» или «killed».
  • Postgres-сообщения типа FATAL: password authentication failed.

90% Junior-проблем закрывается на этом шаге. Если в логах ничего полезного — идём дальше.

Шаг 3: docker inspect для деталей State

docker inspect app --format 'Status: {{.State.Status}}, Exit: {{.State.ExitCode}}, OOM: {{.State.OOMKilled}}, Error: {{.State.Error}}'

Получаем что-то вроде:

Status: exited, Exit: 137, OOM: true, Error:

OOM: true = это был OOM. Exit: 1 без OOM = ошибка в приложении. Exit: 125 = ошибка Docker daemon (например, —memory указан некорректно). Error: ... = низкоуровневая проблема runtime.

Шаг 4: docker exec — если контейнер запущен

Если статус Up, но приложение «не отвечает» — лезем внутрь:

docker exec -it app sh

Что проверяем:

  • Главный процесс крутится? ps aux | head
  • Сеть работает? curl localhost:8000/health
  • Файлы на месте? ls -la /app
  • Env-переменные правильные? env | grep DATABASE

Шаг 5: Healthcheck

Если в Dockerfile/compose есть HEALTHCHECK:

docker inspect app --format '{{.State.Health.Status}}'
# unhealthy

docker inspect app --format '{{json .State.Health.Log}}' | jq
# [{"Start": "...", "ExitCode": 1, "Output": "curl: (7) Failed to connect..."}]

Health.Log хранит последние ~5 проверок с выводом. Это часто даёт точную причину — например, healthcheck стучит в localhost:8000, а приложение слушает 0.0.0.0:8080.

Шаг 6: Ресурсы (docker stats)

Если контейнер «тормозит», но не падает:

docker stats --no-stream app

CPU% > 100% постоянно или MEM% > 80% — ресурсная проблема. CPU% низкий, MEM% низкий — проблема не в ресурсах, а в IO/network/коде.

Шаг 7: Network (docker network inspect)

Если контейнер не достучаться (DNS, connection refused):

docker network inspect <network-name>
docker exec app nslookup postgres
docker exec app curl -v postgres:5432

Стандартный case: app в одной сети, postgres в другой — connection refused. Чинится тем, что оба сервиса должны быть в одной user-defined bridge.


Exit codes — словарь

Знание типичных exit code’ов экономит время:

CodeЧто значитГде искать причину
0Нормальный выход. Процесс сам завершился успешноНе ошибка, контейнер сделал свою работу и вышел
1Generic application errordocker logs — traceback в приложении
2Misuse of shell builtins (часто)Bash-скрипт упал на синтаксисе
125Docker daemon error: бракованные флагиdocker run --xyz=abc — Docker не понимает флаг
126Контейнерная команда не исполняемаяchmod +x забыл, или скрипт от Windows с \r\n
127Команда не найденаCMD ссылается на бинарь, которого нет в PATH
130Killed by SIGINT (Ctrl+C)Пользователь нажал Ctrl+C
137Killed by SIGKILL (128+9)OOM Killer или docker kill
139Segmentation fault (128+11)Баг в C/C++ или Rust unsafe; native-lib проблема
143Killed by SIGTERM (128+15)docker stop (мягкое завершение) — норма

Формула 128 + signal_number объясняет 130, 137, 139, 143:

  • SIGINT = 2 -> exit 130
  • SIGKILL = 9 -> exit 137
  • SIGSEGV = 11 -> exit 139
  • SIGTERM = 15 -> exit 143
Exit codes как первая подсказка
exit 0Норма — процесс отработал. Если контейнер был не сервис, а batch (init-контейнер) — это успех
exit 1Generic error в приложении. Смотри logs — там traceback
exit 125-127125: проблема daemon (бракованные флаги). 126: команда не executable (chmod). 127: команда не найдена в PATH
exit 137128+9 SIGKILL. Чаще всего OOM Killer. inspect State.OOMKilled подтверждает
exit 139128+11 SIGSEGV. Память запорчена — баг в native-libs (psycopg2, numpy native bindings)
exit 143128+15 SIGTERM. Норма для docker stop — приложение получило сигнал и корректно завершилось

Типичные сценарии

Сценарий 1: «Postgres не стартует, exit 1»

docker logs pg --tail 50
# Error: Database is uninitialized and superuser password is not specified.

Забыл POSTGRES_PASSWORD. Чинится через -e POSTGRES_PASSWORD=....

Сценарий 2: «App рестартится в цикле, exit 137»

docker inspect app --format '{{.State.OOMKilled}}'
# true

OOM. Чинится --memory лимитом + оптимизация кода (batching, lazy loading).

Сценарий 3: «App запущен, но HTTP не отвечает»

docker ps  # Up 10 minutes
docker logs app --tail 20  # ничего криминального
docker exec app curl -v localhost:8000/health  # connection refused
docker exec app ss -tlnp  # ничего на :8000

Приложение упало без выхода (зависло), или слушает не тот порт. Смотри ENV (часто PORT env var), или код.

Сценарий 4: «App не видит Postgres»

docker logs app --tail 20
# OperationalError: could not translate host name "postgres" to address

DNS не резолвится. Проверяем сети:

docker inspect app --format '{{json .NetworkSettings.Networks}}'
# {"bridge": ...}

docker inspect pg --format '{{json .NetworkSettings.Networks}}'
# {"my-app_default": ...}

Разные сети — отсюда невидимость. Чинится compose-файлом (оба в одном файле автоматически в общей сети) или ручным docker network connect.

Сценарий 5: «Healthcheck unhealthy, но приложение работает»

docker inspect app --format '{{json .State.Health.Log}}' | jq '.[-1]'
# {"Start": "...", "ExitCode": 1, "Output": "curl: (7) Failed to connect to localhost port 8000"}

Healthcheck стучится на 8000, приложение на 8080. Чинится в Dockerfile (HEALTHCHECK правильным URL).


Anti-patterns

Что не делать при debug:

  • docker restart сразу, не посмотрев логи. Перезапуск часто маскирует проблему. Особенно вреден restart: always для приложений с багами — оно крутится бесконечно, тратя ресурсы.
  • docker logs без --tail. На production-контейнере с гигабайтным логом ты получишь миллион строк в терминал — терминал зависнет, инцидент усугубится.
  • Удалить контейнер и заново запустить «авось заработает». Часто это убирает state, и ты теряешь возможность дебага. Сначала собери информацию (logs, inspect), потом перезапускай.
  • Игнорировать exit code 0. Если init-контейнер вышел с 0 после миграций — это успех, а не проблема. Junior иногда видит «контейнер не Up» и пытается рестартить — а он не должен быть Up.

docker compose-versions команд

В compose-стенде те же команды доступны на уровне сервисов:

docker compose ps           # = docker ps для сервисов
docker compose logs -f app  # = docker logs -f
docker compose top          # = docker top для всех сервисов
docker compose events       # = docker events с фильтром
docker compose exec app sh  # = docker exec -it

Удобнее, чем docker ... --filter, и работает с именами сервисов из compose-файла.

Полный debug-pipeline
docker ps -aШаг 1 — статус. Up / Exited / Restarting. Exit code
docker logs --tail 100Шаг 2 — логи. Traceback, Error, OOM, FATAL
docker inspect StateШаг 3 — ExitCode, OOMKilled, Error, RestartCount. Подтверждение гипотезы
docker exec -it shШаг 4 — внутри контейнера. ps, env, curl, ss. Только если Up
Health + statsШаг 5-6 — healthcheck log, ресурсы. Если приложение работает но что-то не так
network inspectШаг 7 — сети, DNS. Если проблема в межсервисной связи

Попробуй сам

Создай 5 «сломанных» контейнеров и пройдись по чек-листу для каждого:

  1. Postgres без POSTGRES_PASSWORD: docker run -d --name lab1 postgres:16. Что в docker ps -a, что в logs?
  2. CMD не существует: docker run -d --name lab2 alpine notacommand. Какой exit code?
  3. OOM: docker run -d --name lab3 --memory 64m python:3.13-slim python -c "x = ' ' * 200_000_000; print(len(x))". Что в inspect State.OOMKilled?
  4. Контейнер запущен, но «зависший»: docker run -d --name lab4 alpine sh -c "sleep 100". Сделай docker ps (Up), docker top lab4 (sleep), docker exec -it lab4 sh (зайди внутрь).
  5. Сеть: создай две сети, запусти Postgres в одной, app-контейнер в другой, попробуй подключиться. Используй network inspect и exec ... ping.

После каждого случая запиши себе:

  • На каком шаге чек-листа ты нашёл причину?
  • Какая команда дала максимум информации?

Проверка знанийKnowledge check
Если контейнер показывает Exited (137) 2 seconds ago — какие три источника информации ты проверишь и в каком порядке, чтобы подтвердить гипотезу «это OOM»?
ОтветAnswer
Порядок такой: 1) docker inspect <c> --format '{{.State.OOMKilled}}' — это самый прямой и надёжный индикатор. Docker daemon явно отметит OOMKilled=true в State. 2) docker inspect <c> --format '{{.State.ExitCode}}' -> должно быть 137. По UNIX-конвенции exit code = 128 + signal, SIGKILL = 9, значит 128+9=137. Это вторичное подтверждение — exit 137 может быть и от docker kill, не только от OOM. 3) docker logs <c> --tail 50 — посмотреть, что приложение писало непосредственно перед смертью. Иногда виден «Memory increasing... 800MB... 900MB...» если есть собственное логирование памяти, или просто отсутствие graceful shutdown сообщений — приложение убило мгновенно SIGKILL'ом без шанса попрощаться. Дополнительно (Linux-хост): dmesg | grep -i kill — ядро напишет точное сообщение OOM Killer'а с PID, именем процесса и сколько RAM он жрал. Если OOMKilled=true и ExitCode=137 — гипотеза подтверждена. Фикс: добавить --memory лимит (чтобы умирал управляемо), оптимизировать приложение (batching, streaming, профайлинг).

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Контейнер app в статусе Exited (125) 5 seconds ago. Что значит код 125?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5