Як налагоджувати (debug) проблеми у контейнері?
Debugging Docker container це flowchart: починай з найзагальнішого сигналу (логи, exit code), звужуй до специфічного (exec всередину, entrypoint override), бери спеціалізовані tools, коли більш нічого не допомагає. Правильний порядок економить години.
Теорія
TL;DR
Debugging-flowchart, у порядку:
docker ps -a— який стан container?docker logs --tail 200 <name>— що друкував?docker inspect <name>— exit code, OOM flag, error message, mount, мережі.docker exec -it <name> sh— якщо running, подивися всередині live.docker run -it --entrypoint sh <image>— якщо крашиться надто швидко для attach.- Override зламаного:
--entrypoint,--user root,--cap-add SYS_PTRACEдля strace. - Спеціалізовані tools:
diveдля шарів,tcpdumpдля мережі,docker statsдля ресурсів.
Крок 1: стан
$ docker ps -a --filter name=api
CONTAINER ID IMAGE STATUS NAMES
a3f9d2b8c1e4 myapp Exited (1) 3 seconds ago apiТри факти в одному рядку: крашнувся, exit 1, 3 секунди тому. Більшість failure-шляхів виявляють себе на цьому етапі.
Крок 2: логи
# Останній вивід
docker logs --tail 200 api
# Live-tail (для поточних проблем)
docker logs -f --tail 50 api
# Time-обмежено
docker logs --since 10m api
# З timestamp
docker logs -t --tail 50 apiВажливо: docker logs читає stdout/stderr PID 1. Якщо застосунок логує у файл всередині container, тут буде порожньо. Фікс: змусь застосунок логувати у stdout (12-factor норма).
Для verbose-output capture: docker logs api > app.log 2>&1.
Крок 3: inspect
Повний JSON стану container. Через --format витягуєш конкретні поля:
# Status-квартет, зазвичай відповідає на «що сталося?»
docker inspect api --format \
'{{.State.Status}} (exit={{.State.ExitCode}}) OOM={{.State.OOMKilled}} Err={{.State.Error}}'
# exited (137) OOM=true Err= ← OOM killer
# exited (1) OOM=false Err= ← app error code 1
# exited (139) OOM=false Err= ← segfault# Що змонтовано?
docker inspect api --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}\n{{end}}'
# Яка мережа?
docker inspect api --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}: {{$v.IPAddress}}\n{{end}}'
# Health-check лог (останні 5 спроб)
docker inspect api --format '{{json .State.Health}}'Крок 4: exec всередину (якщо running)
# Drop у shell
docker exec -it api sh
# або bash, якщо доступний
docker exec -it api bash
# Перевірити, що файли існують
docker exec api ls -la /app
# Перевірити env
docker exec api env | grep DATABASE
# Перевірити connectivity до сусіда
docker exec api wget -O- http://db:5432
# Що робить процес
docker exec api ps auxКрок 5: entrypoint-override (якщо крашиться надто швидко)
Якщо container виходить до того, як ти можеш exec всередину:
# Скіпни реальний застосунок, drop у shell
docker run -it --rm --entrypoint sh myimage
# Те саме але з оригінальними env-змінними + volume
docker run -it --rm \
--entrypoint sh \
-e DATABASE_URL=... \
-v ./data:/data \
myimage
# Або запусти оригінальну команду, але pause спочатку для attach
docker run -it --rm --entrypoint /bin/sh myimage -c "sleep 3600 & node server.js"--entrypoint міняє entrypoint image на те, що ти задаєш. У комбінації з -it отримуєш інтерактивний prompt замість умираючого застосунку.
Крок 6: тактичні override
# Запуск як root для діагностики (дефолтний user може не мати permission)
docker exec -it -u root api sh
# Mount strace з host або встанови у image
docker run -it --cap-add=SYS_PTRACE myimage strace -p 1
# Вимкни healthcheck-driven restart-цикли
docker run --health-cmd=NONE ...
# Запуск з restart=no, щоб тримати failed-state видимим
docker run --restart=no ...
# Форсувати IPv4 для DNS-проблем
docker run --dns=8.8.8.8 ...Distroless-специфічний debug
Distroless image не мають shell. Щоб дебажити:
# Більшість проектів публікують :debug варіант з busybox
docker run -it --entrypoint sh gcr.io/distroless/base:debug
# Або скопіюй busybox у твій debug-container
docker run -it --entrypoint /busybox/sh gcr.io/distroless/base:debugДля прод distroless image: збери окремий :debug tag з тим самим контентом + busybox-шар, деплой debug лише коли треба.
Типові помилки
Шукати log-файли у writable-шарі
$ docker exec web cat /var/log/myapp.log
# (часто порожньо або застаріло; застосунок має логувати у stdout)Застосунки, що логують у файли всередині container, не видні для docker logs. Або переконфігуруй застосунок під stdout, або змонтуй volume для логів і читай з host.
Запуск без -t і отримання порожнього виводу
$ docker exec api sh
# (іноді висить або одразу виходить)Потрібен -t для виділення TTY. Завжди -it для інтерактивних shell.
Забути, що docker stop міг вбити застосунок мід-cleanup
Якщо застосунок помирає під час docker stop, exit 143 (SIGTERM) або 137 (SIGKILL після grace), логи можуть бути truncated. Збільши --time на stop або перехоплюй SIGTERM у застосунку.
Плутати docker-proxy помилки з app-помилками
Error starting userland proxy: listen tcp 0.0.0.0:8080: bind: address already in use
Це від Docker, не від твого застосунку, порт 8080 уже зайнятий на host. Перевір lsof -i :8080 або обери інший host-порт.
Спеціалізовані debugging-tools
# Image layer-аналіз
dive myimage # інтерактивний layer-by-layer view
docker history --no-trunc myimage # хто додав що
# Network debugging
docker exec api tcpdump -i any -nn -c 20
docker exec api ss -tnlp # listening-порти
docker exec api ip route
# Process tracing
docker run --cap-add=SYS_PTRACE myimage strace -p 1
# Resource debugging
docker stats --no-stream
docker inspect --format '{{.HostConfig.Memory}}' container
# Live filesystem changes
docker diff <container> # що змінилося у writable-шаріРеальне застосування
- «Мій застосунок одразу виходить»: перевір exit code → перевір логи → exec в через
--entrypoint sh, щоб переконатися, що бінар існує і виконуваний. - «Локально працює, у CI ні»: порівняй env-змінні, mount-шляхи, мережу.
docker inspectце rosetta-камінь. - «Container unhealthy, але я не знаю чому»:
docker inspect --format '{{json .State.Health}}'показує останні 5 healthcheck-спроб з виводом. - «Out-of-memory крашиться»:
docker inspectнаOOMKilled: true. Розмір? Недавно бампнуто? Memory-leak?docker statsза час. - «Не дістає інший container»: exec в, ping по service-імені, перевір
/etc/resolv.conf, переконайся, що обидва container на тій самій мережі.
Питання для поглиблення
Q: Як отримати логи container, що видалено?
A: Не можна, docker rm видаляє log-файли container. Якщо передбачаєш потребу, налаштуй віддалений log-driver (json-file-логи переживають container, але syslog/journald/fluentd шиппять offsite). Або завжди docker logs > backup.log перед rm.
Q: Що означає exit code 125?
A: Помилка Docker daemon до старту container. Зазвичай проблема Docker-config (зламане image, поганий mount, конфлікт порту). Дивися daemon-лог (journalctl -u docker).
Q: Що означає exit code 126 vs 127?
A: 126 = команду знайдено, але не executable (permission або не та arch). 127 = команду не знайдено взагалі. Часто typo або відсутній бінар у image.
Q: Як дебажити Compose-сервіс?
A: Усі ті самі команди працюють через docker compose: docker compose logs api, docker compose exec api sh, docker compose run --rm api sh для one-off. Compose-обгортки знають твій project-контекст.
Q: (Senior) Як дебажити container, що крашиться на kubelet, але працює локально?
A: Відтвори точний run-config kubelet: той самий image-digest, ті самі env, той самий entrypoint, той самий UID, та сама network-policy. Pod-spec → docker run флаги це відоме перетворення. Часто різниця: K8s крутиться як non-root UID за замовчуванням, твій локал ні. Або K8s інжектить sidecar, що блокує трафік. Або K8s монтує secrets/configmaps, що твій локал не має. Бери kubectl debug для інтерактивного container у тому самому pod-контексті.
Приклади
Повна debugging-сесія
$ docker ps -a --filter name=api
STATUS NAMES
Exited (137) 5 seconds ago api
$ docker logs --tail 50 api
... багато виводу ...
ERROR: connect ECONNREFUSED 172.18.0.5:5432
$ docker inspect api --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.HostConfig.Memory}}'
false 137 536870912
# OOM=false, exit 137 → не пам'ять; SIGKILL прийшов звідкись
$ docker logs --tail 50 db
2026-04-30 ... database system is shut down
# db вийшов; api не зміг підключитися → SIGTERM каскадом.
# Фікс: додай depends_on з healthcheck у compose, переконайся, що db переживаєЧотири команди тріангулювали проблему: api було вбито, бо db впав першим.
Дебаг image, що одразу крашиться
$ docker run myimg
# (виходить за 0.1 секунди)
$ docker run --rm myimg --version
# (теж виходить одразу, без виводу)
# Override entrypoint, щоб дослідити
$ docker run -it --rm --entrypoint sh myimg
/ # which myapp
/usr/local/bin/myapp
/ # ls -la /usr/local/bin/myapp
-rwxr-xr-x 1 root root 12M ...
/ # /usr/local/bin/myapp
Segmentation fault
# → не та архітектура! Скоріш за все x86-бінар у ARM-image.
$ docker inspect --format '{{.Architecture}}' myimg
amd64
$ uname -m
aarch64
# Підтверджено: image-arch != host-arch.Без --entrypoint sh segfault бінаря був невидимим; docker logs нічого не мав, бо процес помер до stdio.
Дебаг healthcheck
$ docker ps --format '{{.Names}}: {{.Status}}'
api: Up 5 minutes (unhealthy)
$ docker inspect api --format '{{range .State.Health.Log}}{{.End}}: exit={{.ExitCode}} out={{.Output}}\n{{end}}'
2026-04-30T10:00:00Z: exit=0 out=ok
2026-04-30T10:01:00Z: exit=0 out=ok
2026-04-30T10:02:00Z: exit=7 out=connect: connection refused
2026-04-30T10:03:00Z: exit=7 out=connect: connection refused
2026-04-30T10:04:00Z: exit=7 out=connect: connection refused
# → health-endpoint припинив відповідати 3 хвилини тому. Застосунок мабуть завис.
$ docker exec api ps aux
# Дивися на головний процес; чи він running, але не responsive?
# Якщо так, застосунок застряг (deadlock, нескінченний цикл). Захопи stack trace.Healthcheck-лог золото, п'ять спроб з exit-кодами і виводом.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів