Материал открыт для чтения

Войдите, чтобы сохранять прогресс и результаты.

Войти
Docker: Команды, контейнеры и сборка образов

Жизненный цикл контейнера и основные команды

чтение ~14 минУрок 3 из 17

Жизненный цикл контейнера и основные команды

module docker basics Image05

Контейнер — это запущенный процесс в изолированном окружении. Он не “маленькая виртуальная машина”: если главный процесс завершился, контейнер тоже завершился.

Перед первой лабораторной важно связать команды с жизненным циклом:

Состояние Что происходит Команды
image (образ) есть локально Docker может из него создать контейнер docker images, docker pull
container created/running контейнер создан или работает docker run, docker ps
container stopped процесс завершён, файловая система контейнера осталась docker ps -a, docker start
container removed контейнер удалён docker rm

Главная команда: docker run

docker run делает сразу несколько шагов: при необходимости скачивает image, создаёт контейнер, запускает процесс и подключает вывод к терминалу.

В примерах часто встречается запись вида nginx:alpine.

nginx:alpine
│     │
│     └─ tag: вариант образа
└────── repository/image name: имя образа

nginx - имя image, а alpine - tag. Tag уточняет, какую версию или вариант image взять. В этой лаборатории используй nginx:alpine целиком как точное имя образа. Подробно про tags, registry и почему лучше не полагаться на latest будет в следующей статье про images.

docker run nginx:alpine
docker run -d --name web nginx:alpine
docker run --rm busybox:latest echo hello

Разбор docker run -d --name web nginx:alpine: docker run создаёт новый контейнер из image, -d запускает его в фоне, --name web задаёт имя контейнера, nginx:alpine — image с tag. В docker run --rm busybox:latest echo hello после имени image указана команда внутри контейнера: BusyBox запускает echo hello, печатает строку и завершается, а --rm удаляет контейнер после выхода.

Флаги меняют поведение:

Флаг Зачем
-d запустить в фоне
--name дать понятное имя
--rm удалить контейнер после завершения
-p пробросить порт
-v подключить volume или директорию

Не пытайся учить все флаги сразу. Сначала пойми: какой процесс запускается, должен ли контейнер жить долго, нужен ли доступ к порту и нужно ли сохранять данные.

run создаёт новый контейнер

docker run --name web -d nginx:alpine

Эта команда делает несколько действий: при необходимости скачивает image, создаёт новый контейнер, запускает главный процесс и возвращает управление терминалу из-за -d.

Если повторить команду с тем же --name web, Docker выдаст ошибку: контейнер с таким именем уже существует. Чтобы запустить уже существующий контейнер, нужен start:

docker stop web
docker start web

run - новый контейнер. start - существующий контейнер.

Что Docker делает внутри при run

Когда ты вводишь docker run, CLI отправляет запрос Docker daemon. Дальше daemon выполняет цепочку действий:

  1. Проверяет, есть ли image локально.
  2. Если image нет, скачивает его из registry.
  3. Создаёт writable layer — записываемый слой контейнера поверх image.
  4. Настраивает namespaces, cgroups, network и mounts.
  5. Передаёт запуск container runtime.
  6. Runtime запускает главный процесс контейнера.

На этом шаге важно помнить связь с архитектурой Docker: CLI не создаёт namespaces сам, а отправляет запрос в daemon. dockerd подготавливает Docker-часть, containerd управляет жизненным циклом контейнера, а runc делает низкоуровневый запуск процесса через Linux namespaces и cgroups.

Поэтому ошибка docker run может относиться к разным слоям: нет image, нет доступа к registry, занят порт, не существует путь для bind mount, процесс внутри контейнера сразу завершился.

Stop, kill, restart

docker stop web
docker kill web
docker restart web

stop отправляет процессу сигнал завершения и даёт время корректно остановиться. kill завершает принудительно. В обычной работе сначала используй stop, а kill оставляй для зависших контейнеров.

Exec и команда по умолчанию

docker exec web hostname
docker exec -it web sh

exec не создаёт новый контейнер. Он запускает дополнительную команду внутри уже работающего. Для интерактивной shell нужны -it: pseudo-TTY и stdin.

Разбор docker exec -it web sh: docker exec запускает команду в running container, -i держит stdin открытым, -t выделяет терминал, web — имя контейнера, sh — команда внутри контейнера.

Команду по умолчанию можно переопределить при run:

docker run --rm busybox:latest echo hello

Контейнер выполнит echo hello и завершится. Флаг --rm удалит контейнер после завершения, что удобно для одноразовых запусков.

Проверка после docker run: ps, logs, inspect

После docker run не ограничивайся фактом “команда не упала”. В Docker важно проверить, что именно создалось и в каком состоянии оно находится.

docker ps
docker logs web --tail=20
docker inspect web
docker stats --no-stream

Команды отвечают на разные вопросы:

Команда Что проверяет
docker ps контейнер сейчас работает или нет
docker ps -a есть ли контейнер в истории, даже если он уже завершился
docker logs web что главный процесс написал в stdout/stderr
docker inspect web полное состояние контейнера в JSON: status, command, image, сеть, mounts
docker stats --no-stream сколько CPU и памяти контейнер использует прямо сейчас

inspect лучше воспринимать не как первую команду Docker, а как “посмотреть паспорт объекта”. Сначала ты создаёшь контейнер через docker run, потом через inspect проверяешь, что Docker реально сохранил про этот контейнер.

Например:

docker run -d --name web nginx:alpine
docker inspect web

В полном JSON много полей, поэтому часто используют --format:

docker inspect -f '{{.State.Status}}' web
docker inspect -f '{{.Config.Image}}' web
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web

Так ты получаешь не весь JSON, а конкретный факт: статус, image или IP-адрес контейнера.

Если контейнер сразу выходит, смотри:

docker ps -a
docker logs <container>
docker inspect -f '{{.State.ExitCode}}' <container>

Exit code контейнера - код завершения главного процесса. Это такой же сигнал успеха/ошибки, как $? в shell.

inspect есть не только у контейнеров. Docker-объекты устроены одинаково: у них есть состояние и metadata, которые можно посмотреть.

docker image inspect nginx:alpine
docker network inspect bridge
docker volume inspect app-data

Но логика та же: сначала пойми, какой объект проверяешь, потом доставай нужное поле.

Лимиты CPU и RAM для контейнера

Контейнер по умолчанию не получает “маленькую отдельную VM” с заранее выделенной памятью. Он запускается как процесс на хосте и использует ресурсы хоста через Linux cgroups. Поэтому в реальной работе контейнерам часто задают лимиты: чтобы один сервис не забрал всю RAM или CPU и не положил соседние контейнеры.

Для первых запусков Docker можно обойтись без лимитов. Но полезно рано увидеть саму идею: контейнер — это обычный процесс с управляемыми границами по ресурсам.

Пример запуска nginx с ограничением памяти и CPU:

docker run -d \
  --name web-limited \
  --memory 256m \
  --cpus 0.5 \
  nginx:alpine

Что означают параметры:

Параметр Что ограничивает
--memory 256m контейнер может использовать до 256 MB RAM
--cpus 0.5 контейнер может использовать примерно половину одного CPU core
--cpus 2 контейнер может использовать примерно два CPU core

Проверить текущее потребление можно через:

docker stats web-limited --no-stream

Посмотреть сохранённые лимиты контейнера:

docker inspect -f '{{.HostConfig.Memory}}' web-limited
docker inspect -f '{{.HostConfig.NanoCpus}}' web-limited

HostConfig.Memory выводится в байтах. Например, 268435456 — это 256 MB. NanoCpus хранится в “наноядрах”: 500000000 примерно соответствует --cpus 0.5.

Если контейнер превышает memory limit, ядро может завершить процесс внутри контейнера. В Docker это часто видно как OOMKilled:

docker inspect -f '{{.State.OOMKilled}}' web-limited

Лимиты не делают приложение быстрее. Они задают границы. Если поставить слишком маленькую память, сервис будет падать. Если не поставить лимиты вообще, шумный или сломанный контейнер может мешать всему серверу. Поэтому в production обычно начинают с разумного лимита, смотрят docker stats, метрики приложения и постепенно корректируют значения.

Чтобы логи не съели весь диск

docker logs показывает вывод главного процесса контейнера: всё, что приложение пишет в stdout и stderr. Например, nginx пишет access log, приложение пишет ошибки, worker пишет ход выполнения задач. Docker забирает этот поток и сохраняет его через logging driver.

Это уже эксплуатационная настройка, а не обязательный шаг для каждой учебной команды. Но она часто встречается на реальных серверах, поэтому важно понимать принцип.

По умолчанию на многих Linux-серверах используется driver json-file. Это значит, что Docker хранит логи контейнера в JSON-файлах на диске хоста, обычно внутри /var/lib/docker/containers/....

Проблема в том, что без ограничения размера такой файл может расти очень долго. Если приложение шумное, пишет debug-логи или попало в цикл ошибок, логи способны занять весь диск. После этого начинают ломаться уже не только контейнеры: могут не создаваться новые файлы, падать сервисы и перестать нормально работать сам Docker.

Проверить текущий logging driver можно так:

docker info --format '{{.LoggingDriver}}'

Посмотреть log-файл конкретного контейнера:

docker inspect -f '{{.LogPath}}' web

Обычно на сервере задают ограничение размера логов Docker daemon. Для этого создают или редактируют файл /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Что здесь происходит:

Поле Зачем нужно
log-driver говорит Docker, каким способом хранить логи контейнеров
json-file стандартный локальный формат логов, с которым работает docker logs
max-size максимальный размер одного log-файла контейнера
max-file сколько старых файлов Docker оставит при ротации

При настройке выше один контейнер займёт логами примерно до 30m: три файла по 10m. Когда текущий файл достигает лимита, Docker начинает новый, а самые старые удаляет.

После изменения /etc/docker/daemon.json Docker daemon нужно перезапустить:

sudo systemctl restart docker

Важная деталь: настройка daemon применяется к новым контейнерам. Уже созданные контейнеры лучше пересоздать, чтобы они получили новые log options.

Для отдельного контейнера лимит можно задать прямо в docker run:

docker run -d \
  --name web \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  nginx:alpine

Но в реальной работе чаще настраивают daemon глобально, чтобы не вспоминать про log options в каждом запуске. Это не заменяет нормальную систему логирования, например Loki, Elasticsearch или journald, но защищает сервер от простой и частой проблемы: один шумный контейнер забил весь диск.

Удаление

docker rm web
docker rm -f web

docker rm удаляет остановленный контейнер. -f сначала принудительно остановит running container, потом удалит. Не путай удаление контейнера с удалением image: docker rmi работает с образами.

Имена, ID и выбор контейнера

Docker позволяет обращаться к контейнеру по имени или ID:

docker ps
docker logs web
docker logs 4f2a

В лабораторных лучше задавать --name, потому что имя стабильно и команды читаются понятнее. Если имя не задано, Docker сгенерирует случайное.

Автоудаление --rm

--rm хорошо подходит для одноразовых контейнеров:

docker run --rm busybox:latest date

Но если нужно после завершения посмотреть logs, inspect или exit code, --rm мешает: контейнер уже удалён. Для диагностики сначала запускай без --rm, а после проверки удаляй вручную.

Интерактивные контейнеры

docker run -it --name debug alpine sh

-i держит stdin открытым, -t выделяет pseudo-terminal. Без них shell внутри контейнера может сразу завершиться или работать неудобно.

Полезные фильтры

docker ps -a --filter name=web
docker ps -a --filter status=exited
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'

--filter name=web оставляет контейнеры, у которых имя совпадает с фильтром. --filter status=exited показывает завершённые контейнеры. --format управляет колонками вывода: {{.Names}}, {{.Status}}, {{.Image}} — поля Docker-объекта, а table добавляет заголовок таблицы.

Фильтры особенно удобны, когда на учебной VM много контейнеров от разных лабораторий.

Foreground и detached режим

Без -d контейнер привязан к текущему терминалу:

docker run nginx:alpine

Ты увидишь логи nginx прямо в терминале. Для выхода часто используют Ctrl+C, и это остановит процесс. С -d контейнер уходит в фон:

docker run -d --name web nginx:alpine
docker logs -f web

Для серверных процессов в лабах почти всегда нужен detached режим, потому что дальше нужно выполнять проверочные команды.

Attach

docker attach web

attach подключается к stdout/stdin главного процесса контейнера. Это не то же самое, что exec. Для диагностики чаще безопаснее logs и exec, потому что attach может перехватить текущую сессию процесса.

Restart policy через Docker CLI

docker run -d --restart unless-stopped --name web nginx:alpine

Restart policy говорит daemon перезапускать контейнер при сбое или рестарте Docker. Но если приложение внутри контейнера падает из-за конфигурации, policy может создать бесконечный цикл перезапусков. Тогда смотри docker logs и docker inspect.

Мини-runbook для упавшего контейнера

docker ps -a
docker logs <container> --tail=100
docker inspect -f '{{.State.ExitCode}} {{.State.Error}}' <container>
docker inspect -f '{{.Config.Cmd}}' <container>

Если exit code ненулевой, причина часто уже есть в logs. Если logs пустые, проверь command/CMD: возможно, процесс завершился штатно или не стартовал из-за неправильной команды.

Не удаляй контейнер до просмотра logs и inspect, если хочешь понять причину. После docker rm диагностический контекст исчезнет.

Материал изучен?

Войдите, чтобы сохранить прогресс и перейти к практической лаборатории.

Войти и продолжить