12. Отладка и устранение неполадок
Цель темы: уметь диагностировать проблемы контейнеров, не ограничиваясь пересборкой: использовать docker logs, inspect, exec, stats, при необходимости nsenter и tcpdump, и методично разбирать типовые сценарии — контейнер сразу падает, приложение не принимает трафик, OOMKilled.
Определения терминов
Troubleshooting (устранение неполадок)
Troubleshooting — процесс поиска и устранения причины сбоя: сбор логов, проверка конфигурации и состояния контейнера, сети и ресурсов. В контексте Docker — использование CLI и утилит хоста для понимания, почему контейнер не стартует, падает или не отвечает на запросы.
Debug-контейнер
Debug-контейнер — образ с отладочными утилитами (shell, curl, tcpdump, strace и т.д.), который запускают в той же сети или с теми же namespace, что и проблемный контейнер, чтобы проверить связность, DNS, порты или временно заменить команду контейнера для воспроизведения. В Kubernetes аналог — ephemeral debug container (kubectl debug).
Основные команды диагностики
docker logs
Просмотр stdout/stderr контейнера (то, что пишет основной процесс).
docker logs <container>
docker logs -f <container> # поток в реальном времени
docker logs --tail 100 <container>
docker logs --since 10m <container>
docker logs -t <container> # с метками времени
Если контейнер сразу падает — логи покажут последний вывод перед выходом (ошибка приложения, отсутствующий файл, неверный порт и т.д.).
docker inspect
Полная информация о контейнере (конфиг, сеть, тома, состояние, PID на хосте, лимиты, env).
docker inspect <container>
docker inspect --format '{{.State.Status}}' <container>
docker inspect --format '{{.State.ExitCode}}' <container>
docker inspect --format '{{.State.Pid}}' <container>
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container>
Полезно для проверки: какой образ, какие порты и тома, какой exit code при падении, есть ли лимиты.
docker exec
Запуск команды внутри работающего контейнера. Для отладки — получить shell или выполнить проверки (curl, ping, просмотр файлов).
docker exec -it <container> sh
docker exec <container> cat /etc/config/app.json
docker exec <container> curl -s http://localhost:8080/health
Если контейнер не в состоянии «running», exec недоступен — тогда смотрят логи и inspect остановленного контейнера.
docker stats
Использование CPU, памяти и сети контейнерами в реальном времени.
docker stats
docker stats --no-stream
Помогает увидеть утечку памяти, высокую нагрузку по CPU и какой контейнер потребляет ресурсы.
nsenter
nsenter — утилита для входа в namespace другого процесса (на хосте). Позволяет «зайти» в network, PID, mount namespace контейнера без docker exec (например, если внутри контейнера нет shell).
Узнать PID процесса контейнера на хосте:
docker inspect --format '{{.State.Pid}}' <container>
Войти в network namespace (от имени root на хосте):
nsenter -t <pid> -n ip addr
nsenter -t <pid> -n curl http://localhost:8080
-n — network namespace; можно комбинировать с -m (mount), -p (PID) и т.д. На практике чаще используют docker exec, если в образе есть shell; nsenter нужен, когда exec невозможен (минимальный образ без shell).
tcpdump внутри контейнера
Проверка сетевого трафика: доходят ли пакеты до контейнера, что уходит наружу.
Варианты:
-
docker exec в контейнер, где есть tcpdump (не все образы его содержат):
docker exec <container> tcpdump -i eth0 -n port 80 -
Временный контейнер с сетевым режимом проблемного — подключить контейнер к той же сети и запустить tcpdump оттуда, либо использовать
--network container:<id>(контейнер разделяет сеть с целевым):docker run --rm -it --network container:<target_id> nicolaka/netshoot tcpdump -i eth0 -n
На хосте можно снимать трафик с интерфейса veth, привязанного к контейнеру (нужен вывод docker inspect или ip link).
Debug-образы и временные контейнеры
Образы с набором утилит для сети и отладки:
- nicolaka/netshoot — tcpdump, curl, dig, nslookup, iperf и др.
- alpine или busybox — минимальный shell, wget, nc.
Запуск в той же сети, что и проблемный сервис:
docker run --rm -it --network container:web nicolaka/netshoot
# или подключиться к сети проекта Compose:
docker run --rm -it --network myproject_default nicolaka/netshoot
Из такого контейнера проверяют: резолвится ли имя сервиса, доступен ли порт (curl, nc), как выглядит маршрутизация.
Сценарий 1: контейнер стартует и сразу падает
Что делать
- Логи:
docker logs <container>— последние строки перед выходом (ошибка приложения, отсутствующий файл, неверная конфигурация). - Exit code:
docker inspect --format '{{.State.ExitCode}}' <container>. Ненулевой код — приложение или скрипт завершились с ошибкой. - Запуск без detached:
docker run --rm <image>(без-d) — вывод в текущий терминал; при падении сразу видно сообщение. - Переопределить команду:
docker run --rm -it <image> sh— войти в контейнер и вручную запустить команду из CMD/ENTRYPOINT, проверить пути, переменные, права. - Проверить образ: тот же Dockerfile и контекст — возможно, проблема в коде или в отсутствующем файле в образе.
Типичные причины: неверный путь к бинарнику или конфигу, отсутствие обязательной переменной окружения, приложение слушает только 127.0.0.1 вместо 0.0.0.0, недостаточно прав на запись в каталог.
Сценарий 2: приложение не принимает трафик
Что делать
- Контейнер в состоянии running:
docker ps. - Порты:
docker port <container>иdocker inspect— убедиться, что порт опубликован и привязан к ожидаемому интерфейсу (0.0.0.0 или 127.0.0.1). - Слушает ли приложение: из контейнера
docker exec <container> netstat -tlnpилиss -tlnp(если есть) — порт в LISTEN и на 0.0.0.0, а не только на 127.0.0.1. - Доступ изнутри:
docker exec <container> curl -s http://localhost:PORT/health— отвечает ли приложение локально. - С хоста:
curl http://localhost:PORT(или с другого контейнера в той же сети по имени сервиса). Если с хоста не доходит — проверить iptables, firewall, привязку порта. - Сеть и DNS: из другого контейнера в той же сети — резолвится ли имя, доступен ли порт (debug-контейнер с curl/nc).
- Логи приложения и демона: ошибки биндинга, TLS, конфигурации.
Типичные причины: приложение слушает 127.0.0.1; порт не опубликован или опубликован на другой; firewall на хосте; контейнер в другой сети и имя не резолвится.
Сценарий 3: OOMKilled
Контейнер завершился с кодом 137 (SIGKILL после исчерпания лимита памяти).
Что делать
- Подтвердить:
docker inspect --format '{{.State.OOMKilled}}' <container>— true; в логах/событиях может быть указана причина выхода. - Лимит:
docker inspect --format '{{.HostConfig.Memory}}' <container>— какой лимит был задан. - Оценка потребления: при следующем запуске смотреть
docker statsдо падения; либо включить детальную метрику памяти (cAdvisor, метрики cgroups). - Действия: увеличить
--memoryдля контейнера или оптимизировать приложение (утечки, избыточное кэширование). Добавить мониторинг и алерты на OOMKilled.
Практика
Всегда задавайте лимит памяти в production; при OOMKilled сначала оцените, достаточно ли лимита для нормальной работы приложения или есть утечка. Временное увеличение лимита — не заменяет исправление утечки.
Паттерны использования
| Паттерн | Описание |
|---|---|
| Сначала логи и inspect | Логи дают причину падения; inspect — конфиг, exit code, OOMKilled. |
| Воспроизведение без -d | Запуск без detached для немедленного вывода при падении. |
| exec и debug-контейнер | Проверка изнутри (curl, порты, DNS) и из соседнего контейнера в той же сети. |
| Проверка портов и binding | Убедиться, что приложение слушает 0.0.0.0 и порт опубликован. |
Антипаттерны
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Сразу пересобирать образ | Не устраняет причину (конфиг, env, лимиты). | Сначала логи, inspect, воспроизведение. |
| Игнорировать exit code | Код 137 = OOM; 1 = ошибка приложения. | Смотреть State.ExitCode и State.OOMKilled. |
| Не проверять binding | Приложение на 127.0.0.1 недоступно снаружи. | Проверять netstat/ss и слушать 0.0.0.0. |