Устранение неполадок: Быстрая диагностика распространенных ошибок Docker-контейнеров
Освойте искусство быстрого устранения неполадок Docker-контейнеров с помощью этого руководства. Изучите структурированный процесс диагностики сбоев запуска, используя основные команды Docker. Мы подробно рассказываем, как использовать `docker ps -a` для выявления сбоев, извлекать критическую информацию с помощью `docker logs` и выполнять расширенный анализ конфигурации с помощью `docker inspect`. В статье приведены практические примеры и целенаправленные решения для частых проблем, включая ошибки с кодом выхода 127, конфликты портов и события OOMKilled, что позволит вам быстро определить первопричину и восстановить работу сервиса.
Устранение неполадок: Быстрая диагностика распространенных ошибок Docker-контейнеров
Когда контейнер Docker завершается сразу, не начинайте с пересборки образа или изменения случайных флагов. Начните с выяснения того, что известно Docker: состояние контейнера, код выхода, журналы и точная команда, которую Docker пытался выполнить. Эти четыре элемента обычно быстро сужают круг проблемы.
Контейнер — это просто процесс с изоляцией вокруг него. Если основной процесс завершается, контейнер завершается. Это может быть сбой, отсутствующий исполняемый файл, завершенная пакетная задача, неудачная проверка зависимости работоспособности или ядро, убивающее процесс из-за чрезмерного использования памяти. Приведенные ниже команды помогают различить эти случаи.
Сначала найдите остановленный контейнер
docker ps показывает только работающие контейнеры. Контейнеры, которые не запустились, обычно скрыты, если не запросить все контейнеры:
docker ps -a
Посмотрите на STATUS, COMMAND и NAMES:
CONTAINER ID IMAGE COMMAND STATUS NAMES
2d3f4b5c6e7a my-app:latest "/usr/bin/start" Exited (127) 2 minutes ago web-service
91aa34c0db22 worker:latest "python worker.py" Exited (0) 10 minutes ago nightly-worker
Exited (0) часто означает, что процесс успешно завершился. Это нормально для одноразовых задач. Для веб-сервиса это может означать, что команда выполнилась и завершилась, вместо того чтобы оставаться на переднем плане.
Ненулевые коды выхода указывают на сбой, но относитесь к ним как к подсказкам, а не как к окончательным ответам. Код выхода 127 обычно означает, что команда не найдена. 126 — найдена, но не исполняема. 137 часто означает, что процесс получил SIGKILL; в контейнерах это часто, но не всегда, связано с нехваткой памяти. Всегда подтверждайте с помощью журналов и вывода inspect.
Читайте журналы, прежде чем что-либо менять
Docker захватывает stdout и stderr основного процесса контейнера для драйвера ведения журнала по умолчанию. Используйте:
docker logs web-service
Полезные опции:
docker logs --tail 100 web-service
docker logs --since 15m web-service
docker logs -t web-service
docker logs -f web-service
Если в журналах сказано config file not found, проверьте точки монтирования и окружение. Если отображается стек вызовов приложения, отлаживайте приложение. Если они пусты, процесс мог завершиться до того, как выдал вывод, или точка входа образа может быть неправильной.
Для цикла сбоев не используйте docker logs -f как единственный инструмент. Это может создать впечатление активной ошибки, не давая информации о состоянии. Сочетайте журналы с docker inspect.
Проверьте состояние контейнера
docker inspect возвращает большой JSON-документ. Редко нужен весь. Начните с форматированных полей:
docker inspect -f 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}' web-service
Затем проверьте команду и конфигурацию образа:
docker inspect -f 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{.Config.User}}' web-service
Проверьте точки монтирования, если ошибка связана с файлами:
docker inspect -f '{{json .Mounts}}' web-service
Если контейнер был убит из-за памяти, важным полем является .State.OOMKilled. Если оно true, увеличение памяти может помочь, но лучший следующий вопрос — почему память выросла. Больший лимит может скрыть утечку достаточно долго, чтобы она проявилась позже.
Воспроизведите с интерактивной оболочкой, если возможно
Если образ содержит оболочку, переопределите точку входа и проверьте файловую систему:
docker run --rm -it --entrypoint /bin/sh my-app:latest
В некоторых образах есть Bash:
docker run --rm -it --entrypoint /bin/bash my-app:latest
Внутри проверьте файлы и пути команд:
ls -l /usr/bin/start
id
env
Минимальные образы могут не содержать оболочку. В этом случае используйте доступные инструменты образа, пересоберите временный отладочный вариант или проверьте Dockerfile и вывод сборки. Не добавляйте навсегда отладочные пакеты в рабочий образ только потому, что устранение неполадок было неудобным один раз.
Команда не найдена: выход 127
Выход 127 обычно означает, что Docker не смог найти исполняемый файл, указанный в ENTRYPOINT или CMD, или скрипт запуска попытался выполнить отсутствующую команду.
Распространенные причины:
- Исполняемый файл никогда не был скопирован в образ.
- Путь правильный на хосте, но не внутри образа.
- Скрипт использует
/bin/bash, но в образе только/bin/sh. - Команда зависит от
PATH, иPATHотличается от ожидаемого.
Проверьте команду образа:
docker inspect -f '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' web-service
Если точка входа — скрипт, проверьте его shebang и окончания строк. Скрипт с окончаниями строк Windows CRLF может завершиться с запутанным сообщением "not found", потому что путь к интерпретатору фактически содержит символ возврата каретки.
Отказано в доступе: выход 126 или ошибки файлов
Выход 126 часто означает, что Docker нашел команду, но не смог ее выполнить. Для скриптов файлу может не хватать бита исполняемости:
COPY start.sh /usr/local/bin/start.sh
RUN chmod 0755 /usr/local/bin/start.sh
ENTRYPOINT ["/usr/local/bin/start.sh"]
Для файлов, смонтированных через том, помните, что применяются права хоста. Если контейнер работает от UID 1000, а каталог на хосте принадлежит root без права записи, контейнер не сможет туда записывать только потому, что он "внутри Docker".
Проверьте пользователя времени выполнения:
docker inspect -f 'user={{.Config.User}}' web-service
Если поле пустое, многие образы по умолчанию работают от root, но не все. Официальные образы и образы с усиленной безопасностью часто используют непривилегированного пользователя.
Порт уже занят
Ошибка привязки обычно появляется, когда вы публикуете порт хоста, который уже используется:
docker run -p 8080:80 nginx
Docker может сообщить что-то вроде bind: address already in use. Найдите конфликт:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
lsof -iTCP:8080 -sTCP:LISTEN
Затем остановите конфликтующий процесс или выберите другой порт хоста:
docker run -p 8081:80 nginx
Порт контейнера может остаться тем же. Порт хоста — это часть перед двоеточием.
Отсутствующие файлы и неправильные точки монтирования
Если в журналах сказано, что файл конфигурации отсутствует, сравните, что ожидает приложение, с тем, что смонтировал Docker:
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
Распространенная ошибка — монтирование каталога хоста поверх пути, который уже содержал файлы в образе. Монтирование скрывает содержимое образа в этом месте. Если образ содержит /app/config/default.yml, и вы монтируете пустой каталог хоста в /app/config, файл по умолчанию исчезает из поля зрения контейнера.
Также проверяйте относительные пути. -v ./config:/app/config зависит от каталога, в котором вы выполнили docker run, а не от каталога, где находится Dockerfile.
Сбои проверки работоспособности — это не всегда сбои контейнера
Контейнер может работать, но быть нездоровым:
docker ps
Вы можете увидеть Up 2 minutes (unhealthy). Проверьте вывод проверки здоровья:
docker inspect -f '{{json .State.Health}}' web-service
Проверки работоспособности часто не проходят, потому что приложение прослушивает другой порт, привязывается только к 127.0.0.1, запускается дольше, чем позволяет проверка, или требует базу данных, которая еще не готова. Не путайте нездоровый контейнер с завершенным; путь диагностики разный.
Быстрая последовательность устранения неполадок
Используйте этот порядок, когда нужен быстрый ответ:
docker ps -aдля поиска контейнера и кода выхода.docker logs --tail 100 <name>для чтения ошибки приложения.docker inspect -f ...для проверки состояния, команды, пользователя и точек монтирования.- Запустите временную оболочку в образе, если есть подозрения на команду или файловую систему.
- Проверьте конфликты на хосте для портов и разрешения смонтированных каталогов.
- Пересобирайте только после того, как узнаете, связана ли проблема с содержимым образа, флагами времени выполнения или конфигурацией приложения.
Эта последовательность держит расследование на земле. У Docker обычно достаточно доказательств; фокус в том, чтобы прочитать их, прежде чем менять сцену.
Проверьте политику перезапуска, прежде чем доверять увиденному
Политика перезапуска может заставить контейнер выглядеть так, будто он постоянно выходит из строя или постоянно восстанавливается. Проверьте ее:
docker inspect -f 'restart={{json .HostConfig.RestartPolicy}}' web-service
Если политика always или unless-stopped, Docker может перезапускать контейнер после каждого сбоя. docker ps может показывать его работающим несколько секунд, а затем снова перезапускающимся. В этом случае используйте журналы с временными метками и проверьте количество перезапусков:
docker inspect -f 'restarts={{.RestartCount}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}' web-service
Большое количество перезапусков обычно означает, что основной процесс быстро завершается. Исправление редко заключается в "изменении политики перезапуска". Политика только выявляет основную ошибку.
Различайте проблемы времени сборки и времени выполнения
Если файл отсутствует внутри контейнера, спросите, когда он должен был появиться. Файлы, скопированные в Dockerfile, — это проблемы времени сборки. Файлы, смонтированные с помощью -v или томов Compose, — это проблемы времени выполнения.
Проверки времени сборки:
docker image inspect my-app:latest
docker run --rm --entrypoint /bin/sh my-app:latest -c 'ls -la /app'
Проверки времени выполнения:
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
Это разделение экономит время. Пересборка образа не исправит неправильное монтирование хоста. Изменение флага тома не исправит Dockerfile, который никогда не копировал бинарный файл.
Переменные окружения и секреты могут тихо приводить к сбоям
Многие приложения завершаются из-за отсутствия обязательной переменной окружения, но ошибка Docker говорит только о том, что процесс завершился с кодом 1. Внимательно проверьте настроенное окружение:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service
Будьте осторожны, где выполняете эту команду; она может вывести секреты. В общих журналах выводите только имена переменных:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service | sed 's/=.*//'
Если вы используете --env-file, проверьте окончания строк CRLF, незакавыченные пробелы и отсутствующие файлы. Файлы окружения Docker — это не полноценные shell-скрипты. Держите их простыми: строки KEY=value, комментарии, если поддерживаются вашей версией Docker, и никаких предположений, что внутри файла будет выполняться подстановка shell.
Реальный обзор перед отправкой
Прежде чем назвать скрипт или настройку контейнера завершенными, прочитайте его так, как будто вы следующий человек, которому придется его отлаживать в 2 часа ночи. Это меняет то, что вы замечаете. Подсказка, которая имела смысл при написании скрипта, может быть неоднозначной, когда появится в логе CI. Имя сервиса Docker, которое казалось очевидным, может не соответствовать имени переменной в приложении. Значение Bash по умолчанию может быть безопасным для разработки и опасным для продакшена.
Мне нравится проводить короткий пробный прогон с намеренно неудобными значениями. Используйте путь с пробелами. Используйте пустое необязательное значение. Попробуйте имя файла, начинающееся с дефиса. Запустите скрипт из другого рабочего каталога. Запустите контейнер без одной ожидаемой переменной окружения. Эти тесты не изысканны, но они выявляют предположения, которые обычно ломаются в первую очередь.
Также проверьте сообщение об ошибке. Если единственный вывод — failed, значит, совет из статьи не был реализован. Полезное сообщение об ошибке говорит, какое значение использовалось, какая проверка не удалась и что оператор может изменить. Это не означает дамп всех переменных окружения или вывод секретов. Это означает быть конкретным там, где конкретика помогает: путь к конфигурации, имя отсутствующей команды, имя сети, имя хоста сервиса или порт, к которому процесс пытался привязаться.
Последняя привычка — держать примеры близкими к тому, как система на самом деле запускается. Если в продакшене используется Compose, тестируйте с Compose. Если скрипт запускается systemd, тестируйте его с systemd или с аналогичным минимальным окружением. Если команда должна быть безопасной для копирования и вставки, включите в сам пример кавычки, разделители -- и проверку. Читатели копируют рабочие шаблоны чаще, чем предупреждения.
Этот обзор — не бюрократия. Это то, как маленькая автоматизация остается скучной. Скучное — это то, что нужно от подсказок shell, загрузчиков конфигурации, подстановки переменных, диагностики контейнеров и сетей Docker. Чем менее удивительно поведение, тем легче следующему оператору ему доверять.
Для сбоев Docker сохраняйте точную команду, которая создала контейнер, когда это возможно. docker inspect может показать текущую конфигурацию, но запись об инциденте, включающая исходную команду docker run, сервис Compose, тег образа и имя файла env, гораздо легче воспроизвести позже. Избегайте использования latest во время серьезной отладки. Плавающий тег может превратить один сбой в два разных расследования, потому что образ меняется, пока вы еще читаете журналы.
Если вы диагностируете проблему, похожую на продакшен, локально, используйте тот же дайджест или тег образа, ту же структуру томов и передайте ту же форму несекретного окружения. Воспроизведение побеждает предположения.