Контейнер — это изолированная среда, в которой выполняются процессы и запускаются приложения. Одна из ключевых особенностей контейнеров — их переносимость между хостами и предсказуемо стабильная работа, поскольку все необходимые зависимости, пакеты и окружение находятся вместе с приложением внутри контейнера.
Тем не менее нередки ситуации, когда контейнеры после запуска (команда запуска контейнера docker run и ее параметры описаны в отдельной статье «Запуск контейнера») падают или ведут себя не так, как ожидается. В таких случаях нужны инструменты мониторинга и управления контейнерами. Дальше мы приведем основные команды работы с контейнерами.
Получить список запущенных контейнеров
Когда контейнер или контейнеры запускаются на хосте, первое, что хочется сделать, — убедиться в том, что они запустились исправно, работают и не падают. Такую информацию поможет получить команда docker ps. Она выведет в терминал информацию следующего вида:
docker_ps
Из таблицы можно узнать ID и название контейнера, образ, из которого он собран (IMAGE), команду, которая была выполнена автоматически при старте контейнера (COMMAND), сколько времени прошло с момента его запуска (CREATED), статус контейнера (STATUS, например Up 5 minutes, Exited (0) 2 hours ago, Restarting (1) 10 seconds ago) и внутренние и внешние порты контейнера (PORTS).
Если контейнер работает исправно, его статус обычно выглядит как Up 5 minutes (контейнер запущен и отвечает). Если контейнер был собран, запущен, но по какой-то причине основной рабочий процесс внутри него завершился с ошибкой, статус будет Exited (1) или с другим ненулевым кодом выхода. Однако, когда на хосте запущено множество контейнеров, разбираться в стандартном выводе команды docker ps становится неудобно.
Выделить только важное поможет фильтр формата вывода --format, где можно указать только нужные колонки и формат данных. Например, чтобы получить только ID, названия контейнеров и их статусы в табличном виде, достаточно выполнить следующую команду: docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}":
docker_format
Для включения других колонок в отчет их нужно добавить в команду docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}" в следующем виде:
-
{{.Created}} — человекочитаемое время создания контейнера (столбец CREATED).
-
{{.Image}} — имя и тег образа, из которого создан контейнер (столбец IMAGE).
-
{{.Ports}} — проброшенные порты контейнера (столбец PORTS).
-
{{.Command}} — команда, с которой был запущен контейнер (столбец COMMAND).
Просмотр логов контейнера
Заглянуть внутрь контейнера и понять, что происходит с процессом, помогает команда docker logs container_name. Команда возвращает лог работы процесса, указанного в ENTRYPOINT или в CMD. Вывод зависит от конкретного процесса, например, вот лог сервиса Caddy (HTTPS reverse proxy):
docker_logs
В выводе видны ошибки обработки URL, но сервис работает исправно. Проблема команды docker logs в том, что она по умолчанию выводит всю историю лога, и это часто бывает излишне. Получить лог только по последним событиям можно с помощью флага --tail, который ограничивает количество выводимых строк. Например, команда docker logs --tail 1 caddy-container выведет только последнюю строку лога:
docker_logs_tail
Так вывод лога Caddy становится короче, при этом мы видим только последние события. Если нужно «подписаться» на новые записи и одновременно видеть только последние строки, можно добавить флаг -f: docker logs --tail 1 -f caddy-container.
Мониторинг и сбор статистики контейнера
Быстро понять, сколько ресурсов потребляет конкретный контейнер или все контейнеры вместе, помогает команда docker stats (или docker stats container_name для одного контейнера). Она возвращает таблицу со следующей информацией:
- CONTAINER ID / NAME — идентификатор и имя контейнера.
- CPU % — доля CPU, которую сейчас потребляет контейнер.
- MEM USAGE / LIMIT — объем памяти, который использует контейнер, и установленный лимит.
- MEM % — процент использования памяти от лимита.
- NET I/O — объем сетевого трафика (входящего/исходящего) для контейнера.
- BLOCK I/O — чтение/запись на диске, связанные с контейнером.
- PIDS — количество процессов, запущенных внутри контейнера.
Если нужно понять, какие именно процессы запущены в контейнере, поможет команда docker top container_name. Она выводит список процессов внутри контейнера в формате, похожем на классическую команду ps в Linux: PID, пользователя, время работы и команду запуска.
Работа внутри контейнера
Есть разные причины, зачем может потребоваться войти в контейнер. Например, сервис в одном контейнере по какой-то причине не может передать данные в сервис в другом контейнере. Для отладки можно зайти в контейнер с помощью команды docker exec -it container_name /bin/sh (или /bin/bash, если он установлен в образе) и выполнить, например, команду curl http://service-b:80/health для проверки сетевого соединения. Кроме проверки сети, вход в контейнер полезен и для других задач администрирования и отладки:
- Проверка зависимостей и окружения: python --version, node --version, printenv для просмотра переменных окружения.
- Диагностика файловой системы: ls -la /app, du -sh /var/log для проверки структуры каталогов и занятого места.
- Просмотр локальных логов приложения внутри контейнера, если оно пишет в файлы: tail -n 50 /var/log/app.log.
- Запуск разовых диагностических команд: ping service-b, nslookup service-b, telnet service-b 80 (если нужные утилиты установлены в образе).
- Ручной запуск приложения с дополнительными флагами отладки, чтобы воспроизвести проблему прямо в контейнере.
Вход в контейнер можно рассматривать как инструмент точечной диагностики и отладки. Кстати, многие перечисленные выше действия можно выполнить, не заходя в контейнер, с помощью docker exec. В этом случае команда будет выполнена в контейнере, но интерактивная сессия не откроется и терминал не будет заблокирован. Например, вот так можно вызвать утилиту curl в контейнере без входа в него: docker exec container_name curl http://service-b:80/health.
Запуск, остановка, перезапуск и приостановка контейнера
Первый запуск контейнера обычно выполняется командой docker run -d image_name, при этом Docker создает новый контейнер из указанного образа и сразу же его запускает. Если контейнер уже был создан и затем остановлен командой docker stop container_name, повторно создавать его не нужно — достаточно запустить его командой docker start container_name. В отличие от run, команда start не создает новый контейнер, а поднимает существующий с его состоянием (томами, именем, переменными окружения).
Корректно остановить работающий контейнер помогает команда docker stop container_name. Она отправляет основному процессу внутри контейнера сигнал SIGTERM и дает ему время на завершение (по умолчанию 10 секунд), а затем, при необходимости, принудительно посылает SIGKILL.
Иногда контейнер нужно не останавливать полностью, а временно приостановить. Команда docker pause container_name приостанавливает все процессы внутри контейнера (сигнал SIGSTOP на уровне cgroups): они перестают потреблять CPU, но память и состояние остаются в том же виде. Это удобно, когда нужно временно «заморозить» контейнер, не завершая работу приложения.
Команда docker unpause container_name возобновляет работу приостановленного контейнера — процессы продолжают выполняться с того же места, где были остановлены.
Бывает, что контейнер не отвечает на команды stop и pause. В такой ситуации можно принудительно завершить работу процесса командой docker kill container_name. Она сразу отправляет сигнал SIGKILL (или другой указанный сигнал), не давая приложению времени на корректное завершение. Такая остановка может привести к потере несохраненных данных или повреждению файлов, с которыми работал процесс, поэтому использовать docker kill стоит только в крайних случаях.
Удаление контейнеров
Команда docker ps показывает список активных контейнеров, а с ключом -a — список всех контейнеров, в том числе остановленных и неактивных. Такие неактивные контейнеры занимают место, и от них время от времени нужно избавляться. Поддерживать контейнерную гигиену помогают несколько команд:
- docker rm container_name — удалить остановленный контейнер.
- docker rm -f container_name — остановить запущенный контейнер и сразу удалить его.
- docker container prune — удалить все остановленные контейнеры.