Урок курса

Логи и код завершения

Docker и Docker Compose: контейнеризация приложений

1. Где искать сообщения процесса

В предыдущем уроке фоновый запуск с флагом -d освобождал терминал, скрывая вывод выполняемой программы. Чтобы выяснить, что происходит внутри контейнера без интерактивного подключения к нему, используют встроенный журнал Docker.

Docker Engine перехватывает два стандартных потока вывода основного процесса (PID 1): стандартный вывод (stdout, дескриптор 1) и стандартный поток ошибок (stderr, дескриптор 2). Всё, что процесс отправляет в эти дескрипторы, демон Docker сохраняет во внутреннем журнале контейнера на хосте. Запись сохраняется независимо от того, работает контейнер в данный момент или уже завершился.

Проверим поведение демона на трёх изолированных процессах с разным характером завершения и вывода. В командах ниже используется запуск командной оболочки POSIX через sh -c: утилита echo выводит строку в stdout, а конструкция >&2 перенаправляет поток в stderr.

Первый процесс имитирует успешное выполнение задачи и завершается с кодом 0:

docker run --name cf_docker_course_l06_ok busybox:1.36 sh -c "echo job done; exit 0"
job done

Второй процесс намеренно выводит сообщение об ошибке в stderr и завершается с кодом 7:

docker run --name cf_docker_course_l06_err busybox:1.36 sh -c "echo 'missing CONFIG' >&2; exit 7"
missing CONFIG

Третий процесс завершается с ненулевым кодом 3, не выводя ни одного символа в консоль:

docker run --name cf_docker_course_l06_silent busybox:1.36 sh -c "exit 3"

Для чтения перехваченных потоков контейнера служит команда docker logs <container>. Она выводит накопленные записи обоих потоков (stdout и stderr) из журнала Docker.

Проверим журнал контейнера cf_docker_course_l06_ok:

docker logs cf_docker_course_l06_ok
job done

Проверим журнал завершённого контейнера cf_docker_course_l06_err:

docker logs cf_docker_course_l06_err
missing CONFIG

Docker сохранил поток stderr остановленного процесса в журнале и вывел его при запросе.

Если журнал успел накопить много строк, флаг --tail N позволяет ограничить чтение последними N записями. Запустим контейнер, создающий несколько строк вывода подряд:

docker run --name cf_docker_course_l06_tail busybox:1.36 sh -c "echo line1; echo line2; echo line3"

Запросим ровно две последние строки:

docker logs --tail 2 cf_docker_course_l06_tail
line2
line3

Флаг -f (или --follow) оставляет сессию чтения открытой: команда не завершает работу, а ожидает и в реальном времени выводит новые строки по мере их появления в stdout или stderr. Чтобы наблюдать этот механизм в действии, запустим в фоне длительный генератор логов. Команда запускает бесконечный цикл while true; do sleep 2; echo tick; done, где точка с запятой разделяет команды, sleep 2 выдерживает двухсекундную паузу, а echo tick регулярно пишет в stdout:

docker run -d --name cf_docker_course_l06_follow busybox:1.36 sh -c "echo started; while true; do sleep 2; echo tick; done"

Подключимся к чтению журнала в режиме слежения:

docker logs --follow cf_docker_course_l06_follow

В терминале сразу появится строка started, а через две секунды отобразится новое сообщение:

started
tick

Нажмите сочетание клавиш Ctrl+C, чтобы прервать чтение потока. В отличие от команды docker attach, нажатие Ctrl+C в режиме docker logs -f завершает только локальный клиент чтения логов на хосте и не отправляет сигнал остановки внутрь контейнера. Убедимся, что фоновый процесс продолжает работать:

docker inspect --format '{{.State.Status}}' cf_docker_course_l06_follow
running

Перед переходом к разбору кодов завершения остановим и удалим вспомогательный генератор логов:

docker stop cf_docker_course_l06_follow
docker rm cf_docker_course_l06_follow

2. Код завершения сохранённого экземпляра

Когда процесс завершает работу, операционная система фиксирует числовой статус возврата. По соглашению стандарта POSIX значение 0 означает успешное завершение, а любое ненулевое число от 1 до 255 указывает на сбой или специальный код выхода.

В Docker статус возврата основного процесса сохраняется в структуре метаданных контейнера. Для точечного чтения таких полей используется команда docker inspect с флагом --format. Шаблон вида '{{.Поле}}' указывает шаблонизатору путь к конкретному значению внутри JSON-дерева метаданных без необходимости разбирать весь массив параметров вручную.

Поле State.ExitCode хранит целочисленный статус возврата процесса PID 1. Проверим код завершения контейнера cf_docker_course_l06_err, в котором процесс завершался через exit 7:

docker inspect --format '{{.State.ExitCode}}' cf_docker_course_l06_err
7

Для комплексной проверки состояния можно запросить одновременно текстовый статус жизненного цикла и код завершения:

docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' cf_docker_course_l06_err
exited 7

Проверим контейнер cf_docker_course_l06_silent, где процесс завершался через команду exit 3:

docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' cf_docker_course_l06_silent
exited 3

Число, возвращаемое через State.ExitCode, относится исключительно к результату работы PID 1 внутри контейнера. Оно не зависит от того, с каким статусом завершились промежуточные диагностические команды на самом компьютере разработчика.

3. Пустой журнал тоже наблюдение

Распространённая ошибка диагностики — попытка сделать вывод о причине аварии исключительно по ненулевому коду возврата или предположение, что при пустом журнале ошибки не произошло.

Запросим журнал контейнера cf_docker_course_l06_silent:

docker logs cf_docker_course_l06_silent

Команда не выводит ничего. Однако статус контейнера подтверждает сбой:

docker inspect --format '{{.State.ExitCode}}' cf_docker_course_l06_silent
3

Сравним три созданных процесса по трём наблюдаемым признакам:

Контейнер Состояние (State.Status) Код (State.ExitCode) Вывод docker logs Фактический результат
cf_docker_course_l06_ok exited 0 job done Процесс успешно выполнил задачу и завершился без ошибок
cf_docker_course_l06_err exited 7 missing CONFIG Сбой подтверждён ненулевым кодом и текстом ошибки в stderr
cf_docker_course_l06_silent exited 3 (пусто) Сбой зафиксирован кодом, но причина в стандартные потоки не записана

Ненулевой код State.ExitCode фиксирует сам факт сбоя, но не объясняет его механизм: код 1, 3 или 7 может быть следствием неверного аргумента, отсутствующего файла конфигурации или внутренней ошибки приложения. Пустой вывод docker logs также не означает исправность: программа могла записать ошибку во внутренний файл на диске, который не направлен в stdout/stderr, либо аварийно завершиться ещё до инициализации логгера.

Корректная диагностика всегда опирается на сопоставление трёх источников: статуса и кода завершения в docker inspect, записей в docker logs и ожидаемого поведения самой программы.

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

docker rm cf_docker_course_l06_ok cf_docker_course_l06_err cf_docker_course_l06_silent cf_docker_course_l06_tail

Удаление остановленных контейнеров очищает их метаданные и перехваченные журналы, поэтому диагностические сведения считывают до вызова docker rm.

Три завершившихся процесса: job done с кодом0, missing CONFIG с намеренно заданным кодом7, пустой журнал с кодом3. Пустой журнал не доказывает успех.

Локальная лабораторная / самопроверка

Что уже дано

Файлы не нужны. В alpine:3.20 доступны sh, echo и exit. Три выданные нагрузки для sh -c: A — echo 'job done'; exit 0; B — echo 'config missing' >&2; exit 2; C — exit 1. >&2 направляет строку в stderr. Имена ${LAB}_a, ${LAB}_b, ${LAB}_c.

Все команды выполняются на хосте в POSIX-терминале; для Linux-ветки с sudo используйте sudo docker. Рабочая папка — любая пустая учебная папка, исходных файлов нет. Задайте LAB=cf_docker_task_l06_run1. Перед началом выполните docker container ls -a --filter "name=^/${LAB}_": строк со своим префиксом быть не должно. Если имя занято, выберите новый суффикс вместо run1 и используйте его во всех именах этой лабораторной. Не удаляйте найденные чужие контейнеры.

Что нужно сделать

Запустите три нагрузки, сохраняя завершённые объекты. Для каждой самостоятельно получите журнал Docker, состояние и State.ExitCode. Соберите таблицу наблюдений и объясните, что можно утверждать о C при пустом журнале. Не подменяйте State.ExitCode кодом диагностической команды на хосте.

Ввод и вывод

Вы выбираете и вводите команды Docker на хосте. Результат — реальные выводы команд и состояние локальных ресурсов. Форма автоматического ответа отсутствует. Сохраните команды и наблюдения в свой текстовый отчёт; ввод в контейнер нужен только там, где прямо требуется отдельная интерактивная оболочка.

Проверка результата:

  • A: exited, код 0, журнал job done. B: exited, код 2, журнал config missing. C: exited, код 1, журнал пуст.
  • В таблице приведены команды получения наблюдений; про C сделан вывод о ненулевом завершении без утверждения неизвестной первопричины.
  • Диагностические сведения сохранены до очистки контейнеров.

Оставьте у себя отчёт с выполненными командами, динамическими ID и наблюдениями до очистки. Самопроверка — сравнение с критериями выше; вводить кодовое слово или загружать отчёт на платформу не требуется. После сохранения отчёта завершите свои ресурсы:

docker stop "${LAB}_a"
docker rm "${LAB}_a"
docker stop "${LAB}_b"
docker rm "${LAB}_b"
docker stop "${LAB}_c"
docker rm "${LAB}_c"

Если экземпляр уже удалён, сообщение No such container допустимо; если он уже остановлен, stop не требуется. Перед повторной попыткой осмотрите только имена своего LAB.

Ограничения:

  • Выполнение локальное; автоматической оценки и проверки вашего отчёта нет.
  • Не используйте глобальную очистку Docker. Команды очистки относятся только к выбранным именам LAB; общие образы Alpine/Python сохраняются.
  • Команды проверены в Linux-контейнерах Docker Engine 29.6.2 на Windows Docker Desktop; установка Docker на других ОС и аппаратных архитектурах отдельно не проверялась.

Лабораторная выполняется локально. Latorn не запускает Docker Compose или весь проект и не подтверждает выполнение лабораторной автоматически.

Задача 1

Работающий контейнер cf_docker_course_l06_service уже записал больше 20 строк. Нужно вывести последние 20 строк и затем наблюдать новые записи. Какой вариант задаёт оба условия?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Сохранённый контейнер worker-job находится в состоянии exited. State.ExitCode равен 1, а docker logs не выводит строк. Какой вывод подтверждён этими данными?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку