Главная/Справочники/Справочник

Debugging checklist

Последовательности проверок по симптомам. Каждая — упорядоченный список команд, сужающих область поиска, а не перебирающих всё подряд.

Правило, общее для всех списков: проверять командой, а не рассуждением. Гипотеза, не проверенная командой, стоит дешевле, чем время, потраченное на её обдумывание.

Как пользоваться

  1. Найти симптом в таблице входов.
  2. Пройти список по порядку: каждый шаг отсекает часть гипотез.
  3. Остановиться, когда причина найдена, — остальные шаги не выполнять.

Если симптома нет в таблице — начать с общего входа.


Таблица входов

СимптомСписок
Container сразу завершается1
Container исчез через часы работы2
Логи пусты3
Сервис недоступен снаружи4
Один сервис не видит другой5
Healthcheck красный6
Отказ в правах при записи7
Сборка стала медленной8
Кончилось место на диске9
Работает не тот код10
Остановка занимает 10 секунд11

Общий вход

Три команды, с которых начинается любое расследование:

bash
docker ps -a --filter name=ИМЯ --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker inspect ИМЯ --format '{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}}'
docker logs --tail 50 ИМЯ

Они отвечают на вопросы «работает ли», «как завершился», «что сказал». Дальше — по симптому.


1. Container сразу завершается

bash
# 1. С ошибкой или сделал работу и вышел?
docker inspect ИМЯ --format '{{.State.ExitCode}}'
КодДальше
0Процесс закончил работу. Это не отказ — см. шаг 2
1, 2Ошибка приложения — шаг 3
125Ошибка самого docker run — проверить аргументы
126Команда найдена, но не исполняемая — права на файл
127Команда не найдена — опечатка или нет в образе
137SIGKILL, часто OOM — см. список 2
bash
# 2. Код 0: приложение действительно завершилось само
docker logs ИМЯ

# 3. Ненулевой код: что сказало приложение
docker logs ИМЯ 2>&1 | tail -30

# 4. Точка входа: что вообще запускается
docker image inspect ОБРАЗ --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'

# 5. Проверить руками
docker run --rm -it --entrypoint sh ОБРАЗ

Частые причины: скрипт без бесконечной работы; опечатка в команде; отсутствие файла, ожидаемого приложением; неверная форма ENTRYPOINT.


2. Container исчез через часы работы

bash
# 1. Убит ядром?
docker inspect ИМЯ --format '{{.State.ExitCode}} {{.State.OOMKilled}}'

Если 137 true — OOM killer. Логов приложения не будет: SIGKILL не перехватывается.

bash
# 2. Подтверждение со стороны ядра
dmesg | grep -i 'killed process' | tail -5
docker events --filter 'event=oom' --since 24h

# 3. Давление памяти до отказа
docker exec ИМЯ cat /sys/fs/cgroup/memory.events
text
max 4213        ← процесс упирался в предел 4213 раз
oom_kill 3      ← убит 3 раза
bash
# 4. Какой лимит применён на самом деле
docker exec ИМЯ cat /sys/fs/cgroup/memory.max
docker inspect ИМЯ --format '{{.HostConfig.Memory}}'

Расхождение между этими двумя означает, что запрос не выполнился.

Три гипотезы и как их различить:

ГипотезаПризнак
УтечкаПотребление растёт монотонно между перезапусками
Лимит заниженВыходит на плато выше лимита
Всплеск на редком запросеСтабильно, скачок перед отказом

Поднимать лимит, не различив, — обычная ошибка: при утечке это лишь отодвигает отказ.


3. Логи пусты

bash
# 1. Приложение вообще работает?
docker inspect ИМЯ --format '{{.State.Status}}'
docker top ИМЯ

# 2. Буферизация вывода — самая частая причина у Python
docker inspect ИМЯ --format '{{json .Config.Env}}' | grep -o PYTHONUNBUFFERED=. || echo "не задан"

# 3. Проверить гипотезу
docker run --rm -e PYTHONUNBUFFERED=1 ОБРАЗ

# 4. Приложение пишет в файл вместо stdout?
docker diff ИМЯ | grep -i log

# 5. Драйвер журналирования сохраняет вывод?
docker inspect ИМЯ --format '{{.HostConfig.LogConfig.Type}}'

Драйвер none или внешний сборщик означает, что docker logs не покажет ничего по устройству.


4. Сервис недоступен снаружи

bash
# 1. Порт действительно опубликован?
docker port ИМЯ

# 2. Слушает ли приложение — и на каком адресе
docker exec ИМЯ python -c "
import socket
for addr in ('127.0.0.1', '0.0.0.0'):
    s = socket.socket(); s.settimeout(1)
    print(addr, 'открыт' if s.connect_ex((addr, 8000)) == 0 else 'закрыт')"
РезультатВывод
127.0.0.1 открыт, 0.0.0.0 нетСлушает петлю — исправить адрес привязки
оба закрытыПриложение не слушает этот порт
оба открытыПроблема в публикации или правилах хоста
bash
# 3. Что приложение сказало при старте
docker logs --tail 30 ИМЯ | grep -i -E 'listen|bind|serving|started'

# 4. Дошёл ли запрос до container'а
docker logs -f ИМЯ &
curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/

Запрос не появился в логах — не дошёл; появился с ошибкой — дошёл, проблема в приложении.

Подробнее: networking cheat sheet.


5. Один сервис не видит другой

bash
# 1. В одной ли сети
docker inspect ПЕРВЫЙ --format '{{json .NetworkSettings.Networks}}'
docker inspect ВТОРОЙ --format '{{json .NetworkSettings.Networks}}'

# 2. Разрешается ли имя
docker exec ПЕРВЫЙ getent hosts ВТОРОЙ

# 3. Доступен ли порт
docker exec ПЕРВЫЙ python -c "
import socket
s = socket.create_connection(('ВТОРОЙ', 5432), 2); s.close(); print('доступен')"

# 4. Что в строке подключения
docker inspect ПЕРВЫЙ --format '{{json .Config.Env}}' | tr ',' '\n' | grep -i url
Что нашлосьПричина
Разные сетиПодключить к общей
Сеть по умолчанию bridgeВ ней нет разрешения имён
Имя разрешается, порт закрытСосед не слушает или ещё не готов
В строке localhostУказывает на сам container

6. Healthcheck красный

bash
# 1. Что именно вернула проверка
docker inspect ИМЯ --format '{{json .State.Health}}' | python3 -m json.tool
ExitCodeПричина
127Команды нет в образе — обычно curl в slim
124Истёк timeout
1Проверка выполнилась и вернула ошибку
bash
# 2. Выполнить проверку вручную
docker exec ИМЯ python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8000/healthz').status)"

# 3. Достаточно ли start-period
docker image inspect ОБРАЗ --format '{{json .Config.Healthcheck}}'
docker logs ИМЯ | head -20      # сколько занимает старт

Четвёртая причина, не видная в кодах: проверка обращается к зависимости. Тогда отказ базы делает нездоровым приложение, и перезапуск ничего не чинит (урок 11.3).


7. Отказ в правах при записи

bash
# 1. От кого работает процесс
docker exec ИМЯ id

# 2. Кому принадлежит каталог
docker exec ИМЯ ls -ldn /путь

# 3. Что задано в образе
docker image inspect ОБРАЗ --format '{{.Config.User}}'

# 4. Что смонтировано
docker inspect ИМЯ --format '{{json .Mounts}}' | python3 -m json.tool
Что нашлосьИсправление
Владелец 0 0, процесс не rootСоздать каталог в образе с нужным владельцем
Том создан раньше со старыми правамиdocker volume rm либо разовый chown
Bind mount, UID хоста не совпадает-u "$(id -u):$(id -g)"
read_only без tmpfsДобавить tmpfs для путей записи

Подробнее: storage cheat sheet.


8. Сборка стала медленной

bash
# 1. Где кэш перестаёт работать
docker build -t проба . 2>&1 | grep -n -E 'CACHED|^#[0-9]+ \[' | head -30

Первая инструкция без CACHED — та, что сбросила кэш.

bash
# 2. Проверка по времени
docker build -t проба . && touch src/*.py && time docker build -t проба .

Секунды — кэш работает; минуты — нет.

bash
# 3. Размер контекста сборки
docker build . 2>&1 | head -3     # строка «transferring context»
cat .dockerignore || echo ".dockerignore отсутствует"
ПричинаИсправление
COPY . . до установки зависимостейРазделить копирование
Нет .dockerignoreИсключить .git, кэши, окружения
Меняется ARG до FROMПереместить ниже
В CI mode=minmode=max в cache-to

9. Кончилось место на диске

bash
# 1. Что занимает
docker system df -v | head -20

# 2. Логи container'ов — их в system df НЕТ
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -5

# 3. Кэш сборки
docker builder du 2>/dev/null || docker system df --format '{{.BuildCache}}'

Очистка — по возрастанию опасности:

bash
docker builder prune                 # кэш сборки: безопасно
docker image prune                   # безымянные образы: почти безопасно
docker container prune               # остановленные container'ы
docker volume prune                  # ДАННЫЕ: посмотреть список до запуска

Не выполнять в эксплуатации: docker system prune -a удаляет образы, на которых можно откатиться.

Подробнее: cleanup checklist.


10. Работает не тот код

bash
# 1. На каком образе работает container
docker inspect ИМЯ --format '{{.Image}}'

# 2. На что сейчас указывает тег
docker image inspect ОБРАЗ:ТЕГ --format '{{.Id}}'

Разные значения — container не пересоздан после pull.

bash
# 3. Какой digest у локального образа
docker image inspect ОБРАЗ:ТЕГ --format '{{index .RepoDigests 0}}'

# 4. Какой digest в registry сейчас
docker buildx imagetools inspect ОБРАЗ:ТЕГ | grep -i digest
Что нашлосьПричина
Образ container'а ≠ образ тегаpull был, пересоздания не было
Локальный digest ≠ digest в registryТег перезаписан после pull
Оба совпадают, код всё равно не тотОпубликовано не то, что собрано

Устраняет весь класс: развёртывание по digest (урок 14.4).


11. Остановка занимает 10 секунд

bash
# 1. Кто такой PID 1
docker exec ИМЯ ps -eo pid,ppid,cmd
text
  PID  PPID CMD
    1     0 /bin/sh -c python app.py     ← оболочка: сигнал не дойдёт
    7     1 python app.py
bash
# 2. Форма точки входа в образе
docker image inspect ОБРАЗ --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'

# 3. Замер
docker run -d --name t ОБРАЗ && sleep 2 && time docker stop t
Что нашлосьПричина
PID 1 — shShell-форма CMD или ENTRYPOINT
PID 1 — приложениеНет обработчика SIGTERM в коде
Точка входа — скрипт-обёрткаНет exec перед вызовом приложения

Различить первое и второе можно только шагом 1 — симптом одинаков.


Что делать, если ничего не помогло

ПриёмКоманда
Полный набор сетевых утилитdocker run --rm -it --network container:ИМЯ nicolaka/netshoot
Что записано в слойdocker diff ИМЯ
Все события за часdocker events --since 1h --filter container=ИМЯ
Показания ядра изнутриdocker exec ИМЯ cat /sys/fs/cgroup/memory.events
Сравнить с запуском без container'аЗапустить то же приложение на хосте

Последняя строка недооценена: если вне container'а симптом воспроизводится, искать нужно в приложении, а не в Docker. Это отсекает половину гипотез одной проверкой.


Навигация

Вернуться к справочникам
Docker CLI cheat sheet
Networking cheat sheet
Debugging challenges
Диагностическая таблица
Главное оглавление

Markdown на GitHub ↗