Лекция 6. Docker: контейнеризация
1. Проблема окружений
1.1. «У меня работает»
Одна из самых распространённых проблем в разработке ПО — несовместимость окружений. Код, прекрасно работающий на машине разработчика, может не запуститься на сервере тестирования или в продакшене.
Типичные причины:
- Разные версии языка/рантайма (Python 3.10 vs 3.12).
- Отсутствие системных библиотек (libssl, libc разных версий).
- Различия в ОС (macOS для разработки, Linux в продакшене).
- Разные переменные окружения, конфигурации, пути к файлам.
- «Забытые» зависимости, установленные вручную и не отражённые в документации.
1.2. Цепочка зависимостей
Любое приложение зависит от целой цепочки компонентов:
Код приложения └── Зависимости (библиотеки, пакеты) └── Рантайм (Python, Node.js, JVM) └── Системные библиотеки (glibc, openssl) └── Операционная система └── Инфраструктура (железо / облако)Контейнеризация решает эту проблему: приложение упаковывается вместе со всеми зависимостями и окружением в единый переносимый артефакт — контейнер. Это гарантирует, что приложение будет работать одинаково в любой среде.
2. Виртуализация vs контейнеризация
2.1. Виртуальные машины (VM)
Виртуальная машина — это программная эмуляция полноценного компьютера. Каждая VM содержит собственную операционную систему (гостевую ОС), работающую поверх гипервизора.
Типы гипервизоров:
| Тип | Описание | Примеры |
|---|---|---|
| Type 1 (bare-metal) | Устанавливается непосредственно на аппаратуру. Нет хостовой ОС. Высокая производительность | VMware ESXi, KVM, Microsoft Hyper-V, Xen |
| Type 2 (hosted) | Работает как приложение поверх обычной ОС. Удобно для разработки и тестирования | VirtualBox, VMware Workstation, Parallels |
Type 1 (bare-metal) Type 2 (hosted)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ VM 1 │ │ VM 2 │ │ VM 3 │ │ VM 1 │ │ VM 2 │ │ Гост.│ │ Гост.│ │ Гост.│ │ Гост.│ │ Гост.│ │ ОС │ │ ОС │ │ ОС │ │ ОС │ │ ОС │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ ┌─────────────────────────┐ ┌─────────────────┐ │ Гипервизор │ │ Гипервизор │ └─────────────────────────┘ └─────────────────┘ ┌─────────────────────────┐ ┌─────────────────┐ │ Аппаратура │ │ Хостовая ОС │ └─────────────────────────┘ └─────────────────┘ ┌─────────────────┐ │ Аппаратура │ └─────────────────┘2.2. Контейнеры
Контейнер — это изолированный процесс (или группа процессов), использующий ядро хостовой ОС. В отличие от VM, контейнер не содержит собственной ОС — только приложение и его зависимости.
┌────────┐ ┌────────┐ ┌────────┐ │Контейн.│ │Контейн.│ │Контейн.│ │ App A │ │ App B │ │ App C │ │ + deps│ │ + deps│ │ + deps│ └────────┘ └────────┘ └────────┘ ┌────────────────────────────────┐ │ Container Runtime │ │ (Docker Engine) │ └────────────────────────────────┘ ┌────────────────────────────────┐ │ Операционная система │ │ (ядро Linux) │ └────────────────────────────────┘ ┌────────────────────────────────┐ │ Аппаратура │ └────────────────────────────────┘2.3. Сравнение VM и контейнеров
| Характеристика | Виртуальная машина | Контейнер |
|---|---|---|
| Изоляция | Полная (отдельное ядро ОС) | На уровне процессов (общее ядро) |
| Размер образа | Гигабайты (включает гостевую ОС) | Мегабайты (только приложение + deps) |
| Время запуска | Минуты | Секунды (или доли секунды) |
| Overhead | Высокий (гипервизор + гостевая ОС) | Минимальный |
| Плотность | Десятки VM на сервер | Сотни/тысячи контейнеров на сервер |
| Переносимость | Ограничена (зависит от гипервизора) | Высокая (работает везде, где есть Docker) |
| Безопасность | Более сильная изоляция | Изоляция слабее (общее ядро) |
| Применение | Разные ОС, строгая изоляция, legacy | Микросервисы, CI/CD, масштабирование |
3. Технологии ядра Linux для контейнеризации
Контейнеры — это не «магия Docker». Они построены на фундаментальных механизмах ядра Linux.
3.1. Namespaces (пространства имён)
Namespaces изолируют различные аспекты системы, создавая для каждого контейнера иллюзию собственной среды.
| Namespace | Что изолирует | Описание |
|---|---|---|
| PID | Процессы | Контейнер видит только свои процессы. PID 1 в контейнере — не init хоста |
| NET | Сетевой стек | Собственные сетевые интерфейсы, IP-адреса, таблицы маршрутизации, порты |
| MNT | Файловая система | Собственное дерево точек монтирования |
| UTS | Hostname | Контейнер имеет собственное имя хоста |
| IPC | Межпроцессное взаимодействие | Изоляция очередей сообщений, семафоров, разделяемой памяти |
| USER | Пользователи | Маппинг UID/GID: root в контейнере ≠ root на хосте |
# Просмотр namespaces процессаls -la /proc/$$/ns/
# Запуск процесса в новом namespace (пример)unshare --pid --fork --mount-proc bash3.2. cgroups (Control Groups)
cgroups — механизм ядра Linux для ограничения и учёта ресурсов, потребляемых группой процессов.
| Ресурс | Описание |
|---|---|
| CPU | Ограничение времени процессора (cpu.shares, cpu.cfs_quota_us) |
| Memory | Лимит оперативной памяти (memory.limit_in_bytes) |
| Block I/O | Ограничение скорости чтения/записи на диск |
| Network | Приоритизация и ограничение сетевого трафика |
| PIDs | Максимальное количество процессов |
# Запуск контейнера с ограничением ресурсовdocker run --memory=512m --cpus=1.5 nginx3.3. Union FS (OverlayFS)
Union File System — слоёная (layered) файловая система, позволяющая объединять несколько каталогов в один. Docker использует OverlayFS для создания файловых систем контейнеров.
┌──────────────────────────────┐│ Writable layer │ ← Контейнер (изменения)│ (container layer) │├──────────────────────────────┤│ Layer 3: COPY app.py │ ← Только для чтения├──────────────────────────────┤│ Layer 2: RUN pip install │ ← Только для чтения├──────────────────────────────┤│ Layer 1: FROM python:3.12│ ← Базовый образ (только для чтения)└──────────────────────────────┘Преимущества слоёной ФС:
- Слои разделяются между образами и контейнерами — экономия дискового пространства.
- Слои кэшируются — повторная сборка быстрее, если предыдущие слои не изменились.
- Принцип copy-on-write — данные копируются в верхний слой только при модификации.
4. Docker: архитектура
4.1. Компоненты Docker
Docker использует клиент-серверную архитектуру:
┌──────────────┐ REST API ┌──────────────────┐│ Docker CLI │ ◀────────────────────▶ │ Docker Daemon ││ (docker) │ (Unix socket / │ (dockerd) │└──────────────┘ TCP) │ │ │ ┌────────────┐ │┌──────────────┐ │ │ Контейнеры │ ││ Docker │ ◀── pull / push ──▶ │ ├────────────┤ ││ Compose │ │ │ Образы │ │└──────────────┘ │ ├────────────┤ │ │ │ Сети │ │ │ ├────────────┤ │ │ │ Volumes │ │ │ └────────────┘ │ └────────┬─────────┘ │ ┌────────▼─────────┐ │ Docker Registry │ │ (Docker Hub) │ └──────────────────┘| Компонент | Описание |
|---|---|
Docker CLI (docker) | Командная строка для взаимодействия с Docker. Отправляет команды демону через REST API |
Docker Daemon (dockerd) | Фоновый процесс, управляющий контейнерами, образами, сетями, томами |
| Docker Registry | Хранилище образов. Docker Hub — публичный реестр по умолчанию. Существуют и приватные реестры (GitHub Container Registry, GitLab CR, AWS ECR) |
4.2. Взаимодействие компонентов
- Пользователь вводит команду:
docker run nginx. - Docker CLI отправляет запрос Docker Daemon.
- Daemon проверяет, есть ли образ
nginxлокально. Если нет — скачивает из Docker Hub (docker pull). - Daemon создаёт контейнер из образа и запускает его.
5. Образы (Images)
5.1. Что такое Docker-образ
Docker-образ (image) — это неизменяемый шаблон, содержащий файловую систему и метаданные, необходимые для запуска контейнера. Образ состоит из слоёв (layers), каждый из которых представляет собой результат выполнения одной инструкции Dockerfile.
5.2. Слои и кэширование
docker build: FROM ubuntu:22.04 → Layer 1 (скачивается из Hub, кэшируется) RUN apt-get update → Layer 2 (кэшируется) RUN apt-get install -y python3 → Layer 3 (кэшируется) COPY app.py /app/ → Layer 4 (пересобирается при изменении app.py) CMD ["python3", "/app/app.py"] → Layer 5При повторной сборке Docker использует кэш: если инструкция и контекст не изменились, слой берётся из кэша. Поэтому порядок инструкций в Dockerfile влияет на скорость сборки: часто меняющиеся инструкции размещайте в конце.
5.3. Базовые образы
| Образ | Размер | Описание |
|---|---|---|
ubuntu | ~78 MB | Полноценная Ubuntu (apt, bash, стандартные утилиты) |
alpine | ~7 MB | Минимальный дистрибутив Linux (musl, busybox). Популярен для продакшена |
python:3.12 | ~1 GB | Python с полным набором инструментов |
python:3.12-slim | ~150 MB | Python без лишних пакетов |
python:3.12-alpine | ~50 MB | Python на базе Alpine |
node:20 | ~1 GB | Node.js с полным набором |
node:20-alpine | ~130 MB | Node.js на базе Alpine |
nginx:alpine | ~40 MB | Web-сервер Nginx на базе Alpine |
5.4. Docker Hub
Docker Hub (hub.docker.com) — крупнейший публичный реестр Docker-образов.
# Поиск образовdocker search python
# Скачивание образаdocker pull python:3.12-slim
# Просмотр локальных образовdocker imagesИменование образов: [registry/]repository[:tag]
python:3.12— образpythonс тегом3.12из Docker Hub.ghcr.io/owner/app:v1.0— образ из GitHub Container Registry.- Без тега используется
latest(не рекомендуется в продакшене — неопределённая версия).
6. Контейнеры
6.1. Что такое контейнер
Контейнер — это запущенный экземпляр образа. Поверх read-only слоёв образа добавляется тонкий writable layer (запись-при-копировании), в который попадают все изменения, произведённые во время работы контейнера.
Образ (read-only) Контейнер┌────────────────┐ ┌────────────────┐│ Layer 3 │ │ Writable layer │ ← изменения├────────────────┤ ├────────────────┤│ Layer 2 │ │ Layer 3 │├────────────────┤ ├────────────────┤│ Layer 1 │ │ Layer 2 │└────────────────┘ ├────────────────┤ │ Layer 1 │ └────────────────┘6.2. Жизненный цикл контейнера
docker create docker start docker pause ┌──────────┐ ┌───────────┐ ┌──────────┐ │ Created │ ────▶ │ Running │ ────▶ │ Paused │ └──────────┘ └─────┬─────┘ └────┬─────┘ │ │ docker stop docker unpause │ │ ┌─────▼─────┐ │ │ Stopped │ ◀───────────┘ └─────┬─────┘ │ docker rm │ ┌─────▼─────┐ │ Removed │ └───────────┘| Состояние | Описание |
|---|---|
| Created | Контейнер создан, но не запущен |
| Running | Контейнер работает, процессы выполняются |
| Paused | Процессы приостановлены (SIGSTOP через cgroups freezer) |
| Stopped | Контейнер остановлен, процессы завершены, writable layer сохранён |
| Removed | Контейнер удалён, все данные (writable layer) потеряны |
7. Dockerfile
Dockerfile — текстовый файл с инструкциями для сборки Docker-образа. Каждая инструкция создаёт новый слой.
7.1. Основные инструкции
FROM — базовый образ
# Всегда первая инструкция (кроме ARG)FROM python:3.12-slimОпределяет базовый образ, на основе которого строится новый. FROM scratch — пустой образ (для статически скомпилированных бинарников).
RUN — выполнение команд при сборке
# Каждый RUN создаёт новый слойRUN apt-get update && apt-get install -y \ gcc \ libpq-dev \ && rm -rf /var/lib/apt/lists/*Выполняет команду в процессе сборки. Результат фиксируется как новый слой. Объединяйте команды через && для минимизации слоёв.
COPY / ADD — копирование файлов
# COPY — копирование файлов из контекста сборкиCOPY requirements.txt .COPY src/ /app/src/
# ADD — то же, но с дополнительными возможностями:# - распаковка архивов (.tar, .gz)# - загрузка по URL (не рекомендуется)ADD archive.tar.gz /app/Рекомендация: используйте COPY в большинстве случаев. ADD — только когда нужна автораспаковка архивов.
WORKDIR — рабочая директория
WORKDIR /app# Все последующие команды выполняются в /app# Если директория не существует — она будет созданаENV — переменные окружения
ENV PYTHONUNBUFFERED=1ENV APP_PORT=8000Переменные окружения доступны и при сборке, и при запуске контейнера.
EXPOSE — документирование портов
EXPOSE 8000Не открывает порт! Это лишь документация — указание, что приложение в контейнере слушает данный порт. Для фактического проброса порта используется флаг -p при docker run.
CMD vs ENTRYPOINT
# CMD — команда по умолчанию (можно переопределить при запуске)CMD ["python", "app.py"]
# ENTRYPOINT — фиксированная команда (аргументы добавляются к ней)ENTRYPOINT ["python", "app.py"]
# Комбинация: ENTRYPOINT + CMD (CMD задаёт аргументы по умолчанию)ENTRYPOINT ["python"]CMD ["app.py"]| Аспект | CMD | ENTRYPOINT |
|---|---|---|
| Назначение | Команда по умолчанию | Основная команда контейнера |
| Переопределение | docker run image <cmd> заменяет CMD | docker run image <args> добавляет аргументы к ENTRYPOINT |
| Типичное использование | Запуск приложения с возможностью замены | Контейнер как исполняемый файл |
ARG — build-time переменные
ARG PYTHON_VERSION=3.12FROM python:${PYTHON_VERSION}-slim
ARG APP_VERSION=1.0.0RUN echo "Building version ${APP_VERSION}"ARG доступны только при сборке (в отличие от ENV, которые доступны и при запуске).
7.2. Пример: Dockerfile для Python Flask приложения
# Базовый образFROM python:3.12-slim
# МетаданныеLABEL maintainer="dev@example.com"LABEL version="1.0"
# Переменные окруженияENV PYTHONUNBUFFERED=1ENV FLASK_APP=app.py
# Рабочая директорияWORKDIR /app
# Сначала копируем зависимости (для кэширования слоёв)COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# Затем копируем исходный кодCOPY . .
# Документируем портEXPOSE 5000
# Создаём непривилегированного пользователяRUN adduser --disabled-password --gecos "" appuserUSER appuser
# Команда запускаCMD ["flask", "run", "--host=0.0.0.0"]8. Лучшие практики Dockerfile
8.1. Минимизация слоёв
Объединяйте связанные команды RUN в одну инструкцию:
# Плохо — 3 слояRUN apt-get updateRUN apt-get install -y curlRUN rm -rf /var/lib/apt/lists/*
# Хорошо — 1 слойRUN apt-get update && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/*8.2. Использование .dockerignore
Файл .dockerignore исключает файлы из контекста сборки (аналогично .gitignore):
.git.gitignorenode_modules__pycache__*.pyc.env.venvdocker-compose*.ymlREADME.md8.3. Multi-stage builds
Многоэтапная сборка позволяет использовать один образ для компиляции и другой (минимальный) для запуска. Финальный образ не содержит инструментов сборки.
Пример: сборка Go-приложения:
# === Этап 1: сборка ===FROM golang:1.22 AS builderWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -o server .
# === Этап 2: финальный образ ===FROM alpine:3.19RUN apk --no-cache add ca-certificatesWORKDIR /appCOPY --from=builder /app/server .EXPOSE 8080CMD ["./server"]Результат: финальный образ — десятки МБ вместо сотен (не содержит Go SDK).
8.4. Другие рекомендации
- Не запускать от root — используйте
USERдля переключения на непривилегированного пользователя. - Фиксировать версии —
FROM python:3.12.3-slim, а неFROM python:latest. - Располагать редко меняющиеся инструкции выше — для эффективного использования кэша.
- Использовать minimal base images —
alpine,slim,distroless. - Не хранить секреты в образе — не использовать
ENVдля паролей, передавать через--envили Docker secrets.
9. Основные команды Docker
9.1. Сборка образа
# Сборка из текущей директории (ищет Dockerfile)docker build -t myapp:1.0 .
# Сборка с указанием Dockerfiledocker build -f Dockerfile.prod -t myapp:prod .
# Сборка без кэшаdocker build --no-cache -t myapp:1.0 .9.2. Запуск контейнера — docker run
docker run [OPTIONS] IMAGE [COMMAND]| Флаг | Описание | Пример |
|---|---|---|
-d | Запуск в фоновом режиме (detached) | docker run -d nginx |
-p | Проброс портов (хост:контейнер) | docker run -p 8080:80 nginx |
-v | Монтирование тома/директории | docker run -v ./data:/app/data nginx |
--name | Имя контейнера | docker run --name web nginx |
--rm | Удалить контейнер после остановки | docker run --rm nginx |
-it | Интерактивный режим + терминал | docker run -it ubuntu bash |
-e | Переменная окружения | docker run -e DB_HOST=db nginx |
--network | Подключение к сети | docker run --network mynet nginx |
Примеры:
# Запуск Nginx с пробросом портаdocker run -d --name web -p 8080:80 nginx
# Запуск PostgreSQL с переменными окружения и томомdocker run -d --name postgres \ -e POSTGRES_USER=admin \ -e POSTGRES_PASSWORD=secret \ -e POSTGRES_DB=mydb \ -v pgdata:/var/lib/postgresql/data \ -p 5432:5432 \ postgres:16
# Интерактивный запуск Ubuntudocker run -it --rm ubuntu:22.04 bash9.3. Управление контейнерами
# Список запущенных контейнеровdocker ps
# Все контейнеры (включая остановленные)docker ps -a
# Логи контейнераdocker logs webdocker logs -f web # следить в реальном времени (follow)
# Выполнить команду в работающем контейнереdocker exec -it web bashdocker exec web cat /etc/nginx/nginx.conf
# Остановить / запустить / перезапуститьdocker stop webdocker start webdocker restart web
# Удалить контейнерdocker rm webdocker rm -f web # принудительное удаление (даже запущенного)9.4. Управление образами
# Список локальных образовdocker images
# Удалить образdocker rmi nginx:latest
# Скачать образdocker pull python:3.12-slim
# Отправить образ в реестрdocker push myregistry.com/myapp:1.0
# Удалить неиспользуемые образыdocker image prune10. Volumes и bind mounts
10.1. Проблема: данные в контейнере эфемерны
При удалении контейнера все данные в его writable layer теряются. Для сохранения данных между перезапусками используется персистентное хранение.
10.2. Типы монтирования
| Тип | Описание | Команда |
|---|---|---|
| Volume | Управляется Docker. Хранится в /var/lib/docker/volumes/. Рекомендуемый способ | -v myvolume:/data |
| Bind mount | Монтирование конкретной директории хоста в контейнер | -v /host/path:/container/path |
| tmpfs mount | Хранение в оперативной памяти. Данные не сохраняются при остановке | --tmpfs /tmp |
10.3. Работа с Docker Volumes
# Создать томdocker volume create mydata
# Список томовdocker volume ls
# Информация о томеdocker volume inspect mydata
# Удалить томdocker volume rm mydata
# Удалить неиспользуемые томаdocker volume prune10.4. Сравнение типов монтирования
| Характеристика | Volume | Bind mount | tmpfs |
|---|---|---|---|
| Управление | Docker | Пользователь | Ядро ОС |
| Расположение | /var/lib/docker/volumes/ | Любой путь на хосте | RAM |
| Персистентность | Да | Да | Нет (до остановки) |
| Портируемость | Высокая (не зависит от структуры хоста) | Низкая (привязка к пути) | — |
| Производительность | Хорошая | Зависит от хост-ФС | Максимальная |
| Применение | Базы данных, данные приложений | Исходный код при разработке, конфиги | Секреты, кэш |
Пример: разработка с bind mount:
# Исходный код монтируется в контейнер — изменения видны сразуdocker run -d -p 5000:5000 \ -v $(pwd)/src:/app/src \ myapp:dev11. Сети Docker
11.1. Типы сетей
| Тип сети | Описание |
|---|---|
| bridge | Сеть по умолчанию. Контейнеры могут общаться между собой по IP. Изолированы от хоста |
| host | Контейнер использует сетевой стек хоста напрямую. Нет изоляции сети, но максимальная производительность |
| none | Контейнер полностью изолирован от сети |
11.2. Пользовательские сети (user-defined bridge)
По умолчанию контейнеры подключаются к сети bridge. Однако пользовательские сети предоставляют важное преимущество — автоматический DNS: контейнеры могут обращаться друг к другу по имени.
# Создать пользовательскую сетьdocker network create mynet
# Запустить контейнеры в одной сетиdocker run -d --name db --network mynet postgres:16docker run -d --name app --network mynet \ -e DB_HOST=db \ # обращение по имени контейнера! myapp:1.0
# Список сетейdocker network ls
# Информация о сетиdocker network inspect mynet
# Удалить сетьdocker network rm mynet11.3. DNS внутри Docker-сети
В пользовательской bridge-сети Docker запускает встроенный DNS-сервер (127.0.0.11). Контейнеры могут обращаться друг к другу по имени:
┌──────────────────── mynet ────────────────────────┐│ ││ ┌──────────┐ ┌──────────────┐ ││ │ db │ ◀──────── │ app │ ││ │ postgres │ db:5432 │ "DB_HOST=db"│ ││ │ :5432 │ │ :8000 │ ││ └──────────┘ └──────────────┘ ││ ││ Docker DNS: db → 172.18.0.2 │└────────────────────────────────────────────────────┘11.4. Port mapping (проброс портов)
Контейнеры по умолчанию не доступны с хоста. Для доступа используется проброс портов флагом -p:
# Формат: -p <хост_порт>:<контейнер_порт>docker run -d -p 8080:80 nginx # localhost:8080 → контейнер:80docker run -d -p 127.0.0.1:3000:3000 app # только с localhostdocker run -d -p 80 nginx # случайный порт хоста → 80
# Просмотр маппинга портовdocker port <container_name>12. Вопросы для самопроверки
- В чём суть проблемы окружений и как контейнеризация её решает?
- Чем контейнеры отличаются от виртуальных машин? Назовите преимущества и недостатки каждого подхода.
- Какова роль namespaces и cgroups в контейнеризации? Что изолирует каждый namespace?
- Опишите архитектуру Docker. Как взаимодействуют Docker CLI, Docker Daemon и Docker Registry?
- Что такое Docker-образ? Как устроена слоёная файловая система и зачем нужно кэширование слоёв?
- Опишите жизненный цикл Docker-контейнера. Какие состояния он проходит?
- Объясните назначение каждой инструкции Dockerfile: FROM, RUN, COPY, WORKDIR, ENV, EXPOSE, CMD, ENTRYPOINT. В чём разница между CMD и ENTRYPOINT?
- Что такое multi-stage build? Приведите пример, когда он полезен.
- Чем отличаются Docker volumes, bind mounts и tmpfs? Когда использовать каждый тип?
- Какие типы сетей поддерживает Docker? Почему пользовательские bridge-сети предпочтительнее сети по умолчанию?