Модуль 1 / 18
Контейнерная модель и архитектура Docker Engine
Содержание
1. Цели и результат модуля
После модуля вы сможете объяснить контейнер как изолированный процесс Linux, проследить запрос от Docker CLI до OCI runtime, различать image и container и диагностировать завершившийся контейнер по фактам.
- контейнер и виртуальная машина — разные модели изоляции
- Docker Engine, containerd и runc решают разные задачи
- namespaces ограничивают видимость, cgroups — потребление ресурсов
- writable layer не является постоянным хранилищем
- жизненный цикл контейнера определяется главным процессом
2. Теория: контейнер — это процесс, а не виртуальная машина
Контейнер не эмулирует отдельный компьютер и обычно не загружает собственное ядро. На Linux его процессы выполняются общим ядром хоста, но получают изолированное представление части системных ресурсов. Image добавляет файловую систему и метаданные запуска; runtime создаёт процесс с нужными namespaces, cgroups и security-настройками.
Виртуальная машина имеет гостевую ОС и собственное ядро. Контейнер запускается быстрее и имеет меньший overhead, но разделяет kernel attack surface с хостом. Поэтому контейнеризация сама по себе не равна полноценной границе безопасности.
- image — неизменяемый шаблон слоёв и конфигурации
- container — экземпляр image с runtime-конфигурацией и writable layer
- process — реальная единица выполнения для ядра хоста
- volume — данные с жизненным циклом, независимым от writable layer
3. Теория: архитектура Docker Engine
Docker CLI отправляет запрос в Docker API. Docker daemon управляет images, containers, networks и volumes и передаёт управление жизненным циклом в containerd. По умолчанию containerd использует runc как OCI runtime для создания контейнерного процесса.
После запуска runc завершается. containerd shim сохраняет связь с работающим контейнером, его вводом-выводом и exit status. Встроенный containerd Docker Engine принадлежит Engine: его внутренний socket и namespace не являются стабильным общим API.
- Docker CLI — клиент API
- dockerd — control plane одного Engine
- containerd — lifecycle и runtime content
- runc — реализация OCI Runtime Specification
- shim — сопровождение запущенного контейнера
4. Теория: image и файловая система контейнера
Image состоит из адресуемых по содержимому read-only слоёв и конфигурации. При create Docker монтирует root filesystem из слоёв и добавляет writable layer конкретного контейнера. Изменение файла не переписывает image.
docker stop сохраняет writable layer, а docker rm удаляет его вместе с контейнером. Важные данные размещают в volume или внешнем хранилище. Ручной docker commit не заменяет воспроизводимый Dockerfile и pipeline сборки.
5. Теория: namespaces, cgroups и security
PID namespace меняет видимое дерево процессов, mount — точки монтирования, network — интерфейсы и маршруты, UTS — hostname, IPC — объекты обмена, user namespace — отображение UID/GID. Namespace ограничивает видимость, но не задаёт ресурсный лимит.
Cgroups учитывают и ограничивают CPU, memory и PIDs. Дополнительную защиту дают capabilities, seccomp, AppArmor или SELinux, read-only filesystem и запуск без root. --pid=host, --network=host и особенно --privileged ослабляют изоляцию и требуют отдельного обоснования.
docker stats показывает удобные runtime-метрики. На Linux CLI вычитает page cache из memory usage, поэтому его число может отличаться от сырого Docker API или cgroup-файлов.
6. Теория: жизненный цикл и PID 1
docker run концептуально объединяет pull при необходимости, create и start. Контейнер работает, пока работает главный процесс. Завершение PID 1 останавливает контейнер, даже если приложение пыталось отправить дочерний процесс в фон.
docker stop посылает stop signal, обычно SIGTERM, ждёт timeout, затем использует SIGKILL. Приложение должно перестать принимать работу и завершиться в пределах бюджета. Exec-form запуска и корректный init делают доставку сигналов и сбор zombie-процессов предсказуемыми.
- created — конфигурация есть, процесс не запущен
- running — главный процесс выполняется
- paused — процессы заморожены
- exited — процесс завершён, metadata сохранены
- dead — daemon не смог корректно завершить удаление
7. Методические указания
- Начните с docker context show: клиент может быть подключён не к локальному Engine
- Перед командой сформулируйте ожидаемую смену state
- До docker rm собирайте logs, inspect и events
- В автоматизации используйте --format или JSON, а не табличный вывод
- Не применяйте --privileged и не монтируйте docker.sock в этой лаборатории
- Cleanup выполняйте только после проверки точных имён
Лаборатория рассчитана на Linux Engine. Docker Desktop размещает Engine внутри управляемой VM, поэтому host PID и пути файловой системы относятся к этой VM, а не напрямую к macOS или Windows.
8. Лабораторная работа: lifecycle и изоляция
Зафиксируйте версии клиента и сервера, context, storage driver и cgroup version. Выполните create и start отдельно, затем исследуйте процессы, ресурсы и writable layer.
docker version
docker info
docker context show
docker system df
docker container ls --alldocker pull alpine:3.21
docker create --name docker-m1 alpine:3.21 sh -c 'trap "exit 0" TERM; while :; do sleep 1; done'
docker start docker-m1
docker container ls --filter name=docker-m1
docker inspect docker-m1
docker top docker-m1
docker exec docker-m1 sh -c 'printf "pid=%s host=%s\n" "$$" "$(hostname)"'
docker stats docker-m1 --no-stream
docker stop --timeout 10 docker-m1
docker inspect --format '{{json .State}}' docker-m1docker start docker-m1
docker inspect --format 'container_pid={{.State.Pid}}' docker-m1
docker exec docker-m1 ps
docker exec docker-m1 cat /proc/1/status
docker exec docker-m1 sh -c 'printf "created in writable layer\n" > /lesson.txt'
docker diff docker-m1
docker stop docker-m1
docker start docker-m1
docker exec docker-m1 cat /lesson.txt
docker rm --force docker-m1Критерий: вы наблюдали created → running → exited, получили exitCode 0, увидели разные host/container PID и доказали, почему файл переживает stop/start, но исчезает вместе с container.
9. Диагностика: контейнер сразу завершился
Процесс упражнения пишет причину в stderr и возвращает код 42. Не запускайте его повторно вслепую: восстановите timeline и отделите ошибку приложения от ошибки runtime.
docker run --name docker-m1-failure alpine:3.21 sh -c 'printf "fatal: configuration rejected\n" >&2; exit 42'
docker container ls --all --filter name=docker-m1-failure
docker logs docker-m1-failure
docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}} finished={{.State.FinishedAt}}' docker-m1-failure
docker events --since 10m --until 0s --filter container=docker-m1-failure
docker rm docker-m1-failure- docker ps -a показывает существование и грубый state
- inspect .State содержит exit code, timestamps, OOMKilled и runtime error
- logs читает stdout/stderr, если logging driver это поддерживает
- events связывает create, start, die и destroy
- exit code 42 задан приложением, а не Docker daemon
10. Контроль, критерии освоения и шпаргалка
- Почему контейнер не является маленькой VM?
- Что разделяют dockerd, containerd и runc?
- Чем image layers отличаются от writable layer?
- Почему Running не означает готовность сервиса?
- Что будет после завершения PID 1 с кодом 0?
- Какие evidence нужны до удаления failed container?
- Почему --privileged не исправление Permission denied?
docker container ls --all
docker image ls alpine:3.21
docker system df
docker container prune --filter until=24hМодуль освоен, если вы объясняете путь CLI → API → dockerd → containerd → OCI runtime, прогнозируете жизненный цикл процесса и данных и собираете evidence до cleanup.