Перейти к основному содержимому

Объекты Docker

Разработчику Архитектору Инженеру

Объекты Docker

Материал ниже раскрывает ключевые сущности Docker как единую систему. Когда связи между объектами понятны, запуск и отладка контейнеров становятся предсказуемыми.

Как клиент, демон, реестр и команды build / pull / run / push связаны между собой — схема в главе Docker.


Образ

Образ (Image) — неизменяемый шаблон, в котором лежит всё для запуска приложения — код, библиотеки, зависимости, переменные среды и конфигурация. Удобно представлять образ как "чертёж", по которому создаются контейнеры.

Образ является неизменяемым (immutable), состоит из слоёв (layers), каждый из которых представляет собой изменение в файловой системе.

Образ может быть загружен из реестра или создан локально с помощью Dockerfile.

Образ — это "снимок" (snapshot) файловой системы, который содержит:

  • операционную систему (например, Linux);
  • установленные зависимости (библиотеки, пакеты);
  • исходный код приложения;
  • команды для запуска приложения.

Неизменяемость (иммутабельность) означает, что после создания образ нельзя изменить напрямую. Любые изменения требуют создания нового образа на основе старого.


Слой образа

Слои образа организованы в виде стека (stack), где каждый новый слой добавляется поверх предыдущего.

Каждый слой создаётся на основе инструкции в Dockerfile (например, RUN, COPY, ADD).

Слои сохраняются в кэше Docker, что позволяет повторно использовать их при создании новых образов.

При запуске контейнера Docker монтирует все слои в единое дерево файловой системы с помощью технологии Union File System.

Предположим, у нас есть Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3
COPY app.py /app/app.py
CMD ["python3", "/app/app.py"]

Разбор:

  • FROM ubuntu:20.04 — нижний read-only слой с базовой ОС.
  • RUN apt-get ... python3 — слой с установленными пакетами после выполнения команды в образе.
  • COPY app.py /app/app.py — слой с файлом приложения в /app/.
  • CMD [...] — метаданные образа (команда по умолчанию), отдельного файлового слоя не создаёт.

Слои файловой системы (от RUN, COPY, ADD и базового FROM) выглядят так:

  • Базовый образ (ubuntu:20.04) — нижние read-only слои.
  • Установка Python через apt-get — ещё один слой.
  • Копирование app.py — ещё один слой.

Инструкция CMD не добавляет слой с файлами — она записывает метаданные образа (команда по умолчанию при docker run).

Каждый слой хранится отдельно, и если вы измените только последнюю строку в Dockerfile, то пересоздастся только последний слой, а остальные останутся без изменений. Это делает сборку образов быстрой и эффективной.

Образ также включает манифест - JSON-файл, который описывает метаданные образа:

  • Архитектура (например, amd64, arm64).
  • Версию Docker.
  • Список слоёв с их хешами.
  • Информацию о конфигурации (например, переменные окружения, порты, команды).

Манифест позволяет Docker понять, как собрать слои в единый образ и как запустить контейнер.

Образ получают двумя путями: собирают локально через Dockerfile (docker build) или загружают из реестра (docker pull).

Размер образа зависит от состава слоёв и зависимостей. Для оптимизации размера обычно используют:

  • минимизация базового образа (использование легковесного образа в FROM);
  • удаление ненужных файлов, к примеру, очистка кэша пакетного менеджера:
RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/*

Разбор:

  • Одна инструкция RUN — один слой (меньше слоёв, чем отдельные update и install).

  • rm -rf /var/lib/apt/lists/* — удаляет кэш индексов apt из образа, уменьшая размер слоя.

  • Без очистки списков пакетов слой раздувается на десятки мегабайт.

  • Паттерн "установили и подчистили в том же RUN" — стандарт оптимизации Dockerfile.

    • многоэтапная сборка - использование нескольких этапов для минимизации финального образа.

Чтобы исследовать содержимое образа:

docker history <image>
docker inspect <image>
docker run --rm -it <image> sh

Разбор:

  • docker history <image> — слои образа, размер, команда, создавшая слой, использован ли кэш.
  • docker inspect <image> — манифест, Env, Cmd, RootFS, Id, RepoTags.
  • docker run --rm -it <image> sh — временный контейнер с shell для просмотра файлов; --rm удалит контейнер после exit.
  • Три команды закрывают "состав", "метаданные" и "что внутри FS".

Структура образа:

image-5.png

Слои:

image-6.png

Манифест:

image-7.png

Метаданные:

image-8.png


Контейнер

Контейнер (Container) - экземпляр образа. Это "живой" объект, который можно запустить, остановить, изменить и удалить. Это "коробка", собранная по чертежу.

Контейнер является изменяемым (mutable) - внутри него можно создавать, изменять и удалять файлы. Контейнер изолирован, работает в собственной среде, изолированной от хостовой системы (используются namespaces и cgroups).

Практически контейнер ведёт себя как обычный процесс Linux с изолированным набором ресурсов. Управлять им удобнее как процессом — запускать, читать логи, проверять healthcheck, пересоздавать из образа при обновлении.


Хранилища и тома

Хранилища (Volumes) - механизм для сохранения данных вне контейнера. Они позволяют данные сделать постоянными, даже если контейнер удаляется. Данные сохраняются на хостовой системе. Хранилища можно использовать для баз данных, кэширования или общих файлов между контейнерами.

Тома Docker играют ключевую роль для хранения и управления данными. Они позволяют сохранять данные между перезапусками контейнеров, обмениваться данными между контейнерами и управлять данными независимо от жизненного цикла контейнера.

Тома находятся в специальном каталоге на хосте (например, /var/lib/docker/volumes в Linux). Docker автоматически управляет этим каталогом.

Как работать с томами?

Создание тома:

docker volume create my-volume

Разбор:

  • Создаёт именованный том my-volume в /var/lib/docker/volumes/ (на Linux).
  • Том живёт независимо от контейнеров — данные сохраняются после docker rm.
  • Имя используют в -v my-volume:... при docker run или в Compose volumes:.

Это создаст новый том с именем my-volume.

Монтирование тома в контейнер (подключение):

docker run -d --name my-container -v my-volume:/app/data nginx

Разбор:

  • -v my-volume:/app/data — монтирование именованного тома в путь внутри контейнера.
  • Данные пишутся в том Docker, а не в writable-слой контейнера.
  • --name my-container — стабильное имя для повторного run и volume reuse.
  • nginx — пример сервиса; путь /app/data — каталог приложения для постоянных файлов.

Здесь -v my-volume:/app/data монтирует том my-volume в директорию /app/data внутри контейнера.

Просмотр существующих томов:

docker volume ls

Разбор:

  • Список всех локальных томов: DRIVER, VOLUME NAME.
  • Помогает найти "осиротевшие" тома после удаления контейнеров.

Инспектирование тома:

docker volume inspect my-volume

Разбор:

  • JSON с Mountpoint — фактический путь на хосте, где лежат файлы тома.
  • Поле Labels, драйвер, scope — для отладки и автоматизации.
  • По Mountpoint можно смотреть файлы БД с хоста (осторожно в проде).

Это покажет метаданные тома, включая его расположение на хосте.

Удаление тома:

docker volume rm my-volume

Разбор:

  • Удаляет том my-volume, если ни один контейнер его не использует.
  • При активном mount команда вернёт ошибку — сначала остановите и удалите контейнеры.
  • Данные на диске тома уничтожаются безвозвратно.

Очистка неиспользуемых томов:

docker volume prune

Разбор:

  • Удаляет все тома, не привязанные ни к одному контейнеру (включая остановленным).
  • Запрашивает подтверждение, если не указан -f.
  • Полезно для освобождения диска на dev-машине; в проде — только осознанно.

Эта команда удаляет все тома, которые не используются ни одним контейнером.

Том (volume) и bind mount. Именованный том (docker volume create) управляется Docker и удобен для БД и постоянных данных. Bind mount (-v /host/path:/container/path) привязывает каталог хоста напрямую — удобно в разработке, когда код на диске должен сразу попадать в контейнер.

docker volume create

Разбор:

  • Сокращённый пример команды; без имени Docker сгенерирует случайное имя тома.
  • В практике почти всегда указывают явное имя: docker volume create my-data.
  • Синтаксис напоминает, что том — отдельный объект CLI, как образ и контейнер.

Объекты и жизненный цикл приложения

Связь объектов полезно держать как короткую модель:

  1. Dockerfile задаёт шаги сборки.
  2. Из Dockerfile получается образ с наборами слоёв.
  3. Образ публикуется в реестр и версионируется тегами.
  4. Контейнер запускается как runtime-экземпляр образа.
  5. Томы сохраняют состояние между перезапусками контейнера.

Когда цепочка выстроена именно так, релизы и откаты становятся технической рутиной.


Файловая система контейнера

Когда запускается контейнер, Docker создаёт изолированную файловую систему на основе образа. Файловая система контейнера основана на слоях образа и может быть исследована с помощью команды docker exec. Пример структуры:

  • корневая директория (/) Содержит стандартные папки UNIX, такие как /bin, /etc, /usr, /var.
  • /bin: Бинарные файлы (команды).
  • /etc: Конфигурационные файлы.
  • /usr: Программы и библиотеки.
  • /var: Переменные данные (логи, базы данных и т.д.).
  • /tmp: Временные файлы.
  • /proc: Виртуальная файловая система, предоставляющая информацию о процессах и системе.
  • /sys: Информация о ядре и устройствах.
  • монтированные тома.

Проверить монтированные тома можно с командой mount.

Таким образом, файловая система контейнера содержит стандартные UNIX-директории.

image-9.png

Docker использует стандартные возможности ядра Linux (namespaces, cgroups, chroot, seccomp, overlayfs), оборачивая их в удобный интерфейс. Как мы ранее упомянули, контейнеры используют Namespaces.

Namespaces - механизм изоляции в Linux, создающий для контейнера:

  • PID namespace - свой процессный ID;
  • MNT namespace - файловую систему;
  • NET namespace - сеть;
  • UTS namespace - имя хоста (hostname);
  • IPC namespace - межпроцессное взаимодействие.

Таким образом, благодаря Namespace, контейнер видит некую "мини-ОС".

Cgroups ограничивают и учитывают CPU, память и I/O. При превышении лимита памяти ядро обычно завершает процесс в контейнере (OOM), а не "мягко останавливает" весь контейнер — задавайте лимиты и следите за нагрузкой:

docker run --memory=512m --cpus=1.0 myapp:1.0
docker stats

Разбор:

  • --memory=512m — лимит RAM cgroup для контейнера.
  • --cpus=1.0 — до одного ядра CPU в терминах Docker quota.
  • myapp:1.0 — образ с фиксированным тегом.
  • docker stats — проверка фактического потребления относительно лимитов.

UFS (Union File System) представляет собой многослойную файловую систему. Каждый шаг Dockerfile создаёт новый слой. UFS позволяет объединить несколько слоёв (layers) в единое дерево файловой системы, обеспечивая эффективное использование дискового пространства и быстрое создание новых образов.

Основные принципы UFS:

  • Слои (layers): Каждый слой представляет собой набор изменений в файловой системе.
  • Объединение (union): Слои накладываются друг на друга, формируя единое представление файловой системы.
  • Изменяемость: Только верхний слой может быть изменяемым (writable), остальные слои доступны только для чтения (read-only).

Типы слоёв:

  • Read-only layers — Слои, которые создаются при сборке образа (например, базовый образ, установленные зависимости). Эти слои неизменяемы.
  • Writable layer: Верхний слой, который создаётся при запуске контейнера. Все изменения (например, создание файлов, запись данных) происходят именно в этом слое.

Пример:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3
COPY app.py /app/app.py
CMD ["python3", "/app/app.py"]

Разбор:

  • Повтор примера для связки с OverlayFS: три read-only слоя из Dockerfile + writable при run.
  • Изменение только app.py инвалидирует кэш начиная с COPY, нижние слои остаются.
  • CMD снова только задаёт процесс по умолчанию при старте контейнера.

Слои здесь будут выглядеть так:

  • Базовый образ (ubuntu:20.04) — read-only.
  • Установка Python через apt-get — read-only.
  • Копирование файла app.py — read-only.
  • Верхний слой (writable) — используется для работы контейнера.

На хосте слои хранятся в каталоге /var/lib/docker. Docker использует одну из реализаций UFS (например, OverlayFS, AUFS) для управления этими слоями.

OverlayFS это современная реализация UFS, используемая по умолчанию в Docker. Она включает в себя следующие особенности:

  • Lowerdir: Read-only слои (нижние уровни).
  • Upperdir: Writable слой (верхний уровень).
  • Workdir: Временный каталог для операций слияния.
  • Merged: Объединённое представление файловой системы.

Другие реализации UFS - AUFS (Advanced Multi-Layered Unification Filesystem, используется в старых версиях Docker) и Btrfs/ZFS (альтернативные файловые системы с поддержкой UFS).

Когда образ загружается в реестр, каждый слой сжимается и отправляется отдельно. Это позволяет повторно использовать слои между образами. Слои используют кэширование. Если два образа имеют общие слои, они будут использоваться совместно. Например, если два образа основаны на одном базовом образе, то этот базовый образ будет храниться только один раз. При изменении одного шага в Dockerfile пересоздаётся только соответствующий слой, а остальные слои остаются без изменений. Каждый контейнер получает свой собственный writable слой. Это гарантирует, что изменения в одном контейнере не влияют на другие.

У каждого контейнера есть своя среда - Container Runtime, это ПО, которое отвечает за запуск и управление контейнерами, обеспечивая изоляцию процессов, файловой системы и сетевых ресурсов.

Примеры популярных Container Runtime:

  • runc: Стандартный runtime, используемый Docker.
  • containerd: Уровень абстракции над runc, который управляет контейнерами.
  • CRI-O: Альтернатива containerd, часто используется в Kubernetes.

Контейнер живёт, пока жив его процесс (основной процесс, PID 1). Если этот процесс завершается, контейнер также останавливается.

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

docker top my-container

Разбор:

  • Показывает процессы внутри контейнера my-container с PID на хосте.
  • Помогает найти PID 1 (главный процесс) — от его жизни зависит статус контейнера.
  • Если список пуст — контейнер не запущен или имя неверное.

Чтобы контейнер автоматически перезапускался при завершении процесса, используйте флаг --restart:

docker run -d --name my-container --restart always nginx

Разбор:

  • --restart always — демон Docker перезапустит контейнер после падения процесса или перезагрузки хоста (если Engine включён).
  • Другие политики — unless-stopped, on-failure, no.
  • -d + nginx — типичный долгоживущий сервис в фоне.
  • Политика restart не заменяет healthcheck и оркестратор в кластере.

Частые анти-паттерны в работе с объектами

  • Хранить БД внутри контейнера без volume — данные теряются при пересоздании.
  • Логиниться в контейнер и "чинить руками" — изменения не попадают в образ и теряются после релиза.
  • Использовать один тег для всех релизов — невозможно быстро понять, что именно запущено.
  • Игнорировать inspect/history — сложнее диагностировать состав слоёв и параметры запуска.

Рабочий подход — фиксированные теги, инфраструктурный код и пересоздание контейнера из новой версии образа.


Связанные статьи