Лекция 7. Docker Compose и оркестрация
1. Проблема: запуск нескольких контейнеров вручную
Современные приложения редко существуют в виде единственного процесса. Типичный веб-сервис включает как минимум три компонента: сам сервер приложений, базу данных и кэш (или брокер сообщений). Каждый из них удобно запускать в отдельном контейнере, однако при ручном подходе это порождает ряд проблем:
- Длинные команды: для каждого контейнера приходится указывать
docker runс десятками флагов (-p,-v,-e,--network,--nameи т.д.). - Порядок запуска: база данных должна быть доступна до старта приложения; при ручном запуске за этим необходимо следить самостоятельно.
- Воспроизводимость: если коллега клонирует проект, ему нужно повторить те же команды с теми же аргументами, что чревато ошибками.
- Управление жизненным циклом: остановка, перезапуск и удаление нескольких контейнеров превращается в рутину.
Docker Compose решает все эти задачи: он позволяет декларативно описать весь стек в одном YAML-файле и управлять им единственной командой.
Декларативный подход означает, что мы описываем желаемое состояние системы («должны быть запущены три сервиса с такими-то параметрами»), а не последовательность действий для его достижения. Compose сам определяет, какие контейнеры создать, пересоздать или оставить без изменений.
2. Формат docker-compose.yml
2.1. Общая структура файла
Файл docker-compose.yml (или compose.yml — оба имени поддерживаются) состоит из нескольких секций верхнего уровня:
# Необязательно в современных версиях (Docker Compose V2)# version: "3.8"
services: # Описание контейнеров (сервисов) web: ... db: ...
networks: # Пользовательские сети (необязательно) backend: ...
volumes: # Именованные тома (необязательно) db-data: ...2.2. Версии формата: v2 vs v3
Исторически Compose использовал поле version для указания формата файла:
| Версия | Особенности |
|---|---|
| v2 | Ориентирована на локальную разработку. Поддерживает mem_limit, cpu_shares, depends_on с condition. |
| v3 | Создавалась для совместимости с Docker Swarm. Убраны некоторые параметры v2 (например, mem_limit заменён на deploy.resources). |
Начиная с Docker Compose V2 (CLI-плагин docker compose, без дефиса), поле version больше не требуется. Compose автоматически определяет возможности по установленному движку. В новых проектах рекомендуется не указывать version вовсе.
3. Описание сервиса: основные директивы
Каждый сервис — это шаблон для запуска контейнера. Ниже приведён пример с подробными комментариями:
services: web: # Откуда берётся образ: build из Dockerfile или готовый image build: context: . # Каталог с Dockerfile dockerfile: Dockerfile # Имя файла (по умолчанию Dockerfile) # image: myapp:latest # Альтернатива: использовать готовый образ
# Проброс портов: HOST:CONTAINER ports: - "8080:5000"
# Монтирование томов volumes: - ./src:/app/src # bind mount: локальная папка → контейнер - static-data:/app/static # named volume
# Переменные окружения (явно) environment: - FLASK_ENV=development - DATABASE_URL=postgresql://user:pass@db:5432/mydb
# Переменные окружения из файла env_file: - .env
# Переопределение команды запуска command: ["python", "app.py", "--reload"]
# Зависимости: web запустится после db и redis depends_on: db: condition: service_healthy redis: condition: service_started
# Политика перезапуска restart: unless-stopped # Варианты: no | always | on-failure | unless-stoppedКлючевые директивы:
| Директива | Назначение |
|---|---|
image | Использовать готовый образ из реестра |
build | Собрать образ из Dockerfile |
ports | Проброс портов (HOST:CONTAINER) |
volumes | Монтирование томов и каталогов |
environment | Переменные окружения (список или словарь) |
env_file | Файлы с переменными окружения |
command | Переопределение CMD из Dockerfile |
depends_on | Порядок запуска сервисов |
restart | Политика автоматического перезапуска |
4. Сети в Compose
4.1. Автоматическая default-сеть
При запуске docker compose up Compose автоматически создаёт bridge-сеть с именем <имя_проекта>_default. Все сервисы подключаются к ней и могут обращаться друг к другу по имени сервиса как по DNS-имени:
# Внутри контейнера web можно обратиться к базе данных так:import psycopg2conn = psycopg2.connect(host="db", port=5432, dbname="mydb")# "db" — это имя сервиса в docker-compose.yml4.2. Пользовательские сети
Для изоляции групп сервисов создают отдельные сети:
services: web: networks: - frontend - backend db: networks: - backend # db недоступен из frontend-сети nginx: networks: - frontend
networks: frontend: driver: bridge backend: driver: bridgeВ данном примере nginx может обращаться к web, но не может напрямую обратиться к db, потому что они находятся в разных сетях.
4.3. DNS между сервисами
Docker Compose настраивает встроенный DNS-сервер. Каждый контейнер получает DNS-запись, соответствующую имени сервиса. Это означает, что вместо IP-адресов в конфигурации приложения всегда используют имена сервисов:
redis://redis:6379 # вместо redis://172.18.0.3:6379postgresql://db:5432/mydb # вместо postgresql://172.18.0.2:5432/mydb5. Volumes в Compose
5.1. Bind mounts
Монтирование каталога хост-машины внутрь контейнера. Удобно для разработки — изменения в коде сразу видны в контейнере:
volumes: - ./src:/app/src # относительный путь к каталогу хоста - /var/log/app:/app/logs # абсолютный путь5.2. Named volumes
Именованные тома управляются Docker и сохраняются при пересоздании контейнеров:
services: db: image: postgres:16 volumes: - db-data:/var/lib/postgresql/data
# Объявление именованного тома на верхнем уровнеvolumes: db-data: driver: local # драйвер по умолчаниюВажно: named volumes не удаляются командой docker compose down. Для их удаления необходимо использовать флаг -v:
docker compose down -v # Удаляет контейнеры, сети И именованные тома6. Переменные окружения
6.1. Файл .env
Docker Compose автоматически загружает файл .env из каталога проекта и подставляет переменные в docker-compose.yml:
POSTGRES_USER=adminPOSTGRES_PASSWORD=secret123POSTGRES_DB=productionAPP_PORT=8080services: db: image: postgres:16 environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} web: ports: - "${APP_PORT}:5000"6.2. environment vs env_file
| Способ | Описание |
|---|---|
environment | Переменные задаются явно в compose-файле. Подходит для небольшого числа переменных. |
env_file | Переменные загружаются из файла. Удобно для большого числа переменных и разделения конфигурации. |
services: web: env_file: - ./config/app.env # загрузить переменные из файла environment: - LOG_LEVEL=debug # эта переменная переопределит значение из app.env6.3. Приоритет переменных окружения
Переменные применяются в следующем порядке (от высшего приоритета к низшему):
- Переменные, заданные в команде:
APP_PORT=9090 docker compose up - Переменные из shell-окружения
- Значения из файла
.env - Значения по умолчанию в compose-файле:
${VAR:-default_value}
7. Healthchecks
7.1. Зачем нужны проверки здоровья
Контейнер может быть запущен (status: running), но приложение внутри — ещё не готово принимать подключения (например, база данных инициализирует данные). Healthcheck позволяет Docker определить, когда сервис действительно готов.
7.2. Синтаксис
services: db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s # как часто проверять timeout: 5s # максимальное время ожидания ответа retries: 5 # сколько неудачных попыток до статуса unhealthy start_period: 30s # время на начальную инициализацию (проваленные # проверки в этот период не считаются)7.3. Примеры healthcheck
PostgreSQL:
healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 start_period: 30sRedis:
healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3HTTP-сервис:
healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"] interval: 15s timeout: 5s retries: 3 start_period: 10s8. Зависимости между сервисами
8.1. Простой depends_on
services: web: depends_on: - db - redisCompose запустит db и redis до web, но не будет ждать их готовности — только факт запуска контейнера.
8.2. depends_on с condition
Для ожидания реальной готовности сервиса используют condition:
services: web: depends_on: db: condition: service_healthy # ждать, пока healthcheck не пройдёт redis: condition: service_started # ждать только запуска контейнераДоступные условия:
| Условие | Описание |
|---|---|
service_started | Контейнер запущен (поведение по умолчанию) |
service_healthy | Healthcheck вернул статус healthy |
service_completed_successfully | Контейнер завершился с кодом 0 (для init-контейнеров) |
8.3. Порядок запуска и остановки
- Запуск: Compose запускает сервисы в порядке зависимостей (сначала
db, потомweb). - Остановка: в обратном порядке (сначала
web, потомdb), что позволяет приложению корректно закрыть соединения.
9. Практический пример: многоконтейнерное приложение
Рассмотрим полный docker-compose.yml для приложения, состоящего из веб-сервера на Python (Flask), PostgreSQL и Redis:
# docker-compose.yml — полный пример многоконтейнерного приложения
services: # === Веб-сервер (Python / Flask) === web: build: context: . # Контекст сборки — текущий каталог dockerfile: Dockerfile # Имя Dockerfile ports: - "${APP_PORT:-8080}:5000" # Проброс порта (по умолчанию 8080) volumes: - ./src:/app/src # Bind mount для hot-reload при разработке environment: FLASK_ENV: development DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} REDIS_URL: redis://redis:6379/0 env_file: - .env # Общие переменные из .env файла depends_on: db: condition: service_healthy # Ждём готовности PostgreSQL redis: condition: service_healthy # Ждём готовности Redis restart: unless-stopped # Перезапуск при сбоях networks: - app-network
# === База данных PostgreSQL === db: image: postgres:16-alpine # Легковесный образ environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - db-data:/var/lib/postgresql/data # Persistant storage - ./init.sql:/docker-entrypoint-initdb.d/init.sql # SQL при первом запуске healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 start_period: 30s restart: unless-stopped networks: - app-network
# === Кэш Redis === redis: image: redis:7-alpine command: ["redis-server", "--maxmemory", "128mb", "--maxmemory-policy", "allkeys-lru"] volumes: - redis-data:/data # Persistant storage для RDB/AOF healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3 restart: unless-stopped networks: - app-network
# === Именованные тома ===volumes: db-data: # Данные PostgreSQL сохраняются между перезапусками redis-data: # Данные Redis сохраняются между перезапусками
# === Пользовательская сеть ===networks: app-network: driver: bridgeСоответствующий .env файл:
POSTGRES_USER=appuserPOSTGRES_PASSWORD=supersecretPOSTGRES_DB=appdbAPP_PORT=808010. Команды Docker Compose
10.1. Основные команды
# Запуск всех сервисовdocker compose up # В интерактивном режиме (логи в терминале)docker compose up -d # В фоновом режиме (detached)docker compose up --build # Пересобрать образы перед запускомdocker compose up -d --build # Фоновый режим + пересборка
# Остановка и удалениеdocker compose down # Остановить и удалить контейнеры, сетиdocker compose down -v # + удалить именованные томаdocker compose down --rmi all # + удалить образы
# Просмотр логовdocker compose logs # Логи всех сервисовdocker compose logs -f # Следить за логами в реальном времениdocker compose logs -f web # Логи только сервиса web
# Статус сервисовdocker compose ps # Список запущенных контейнеров
# Выполнение команды в контейнереdocker compose exec web bash # Открыть shell в контейнере webdocker compose exec db psql -U appuser appdb # Подключиться к PostgreSQL
# Сборка образовdocker compose build # Собрать все образыdocker compose build --no-cache # Собрать без кэшаdocker compose build web # Собрать только web
# Перезапускdocker compose restart # Перезапустить все сервисыdocker compose restart web # Перезапустить только web
# Остановка без удаленияdocker compose stop # Остановить все контейнеры (без удаления)10.2. Полезные флаги
| Флаг | Назначение |
|---|---|
--project-name NAME | Задать имя проекта (по умолчанию — имя каталога) |
--file FILE | Указать альтернативный compose-файл |
--env-file FILE | Указать альтернативный .env файл |
--profile PROFILE | Запустить только сервисы из указанного профиля |
11. Масштабирование сервисов
11.1. docker compose up —scale
Docker Compose позволяет запустить несколько экземпляров одного сервиса:
docker compose up -d --scale web=3Это создаст три контейнера web. Однако есть ограничения:
- Нельзя использовать фиксированный проброс портов (
ports: "8080:5000"), так как порт 8080 займёт только первый контейнер. Решение — указать только порт контейнера:
services: web: ports: - "5000" # Docker назначит случайный порт на хосте- Масштабированные контейнеры не имеют встроенного балансировщика нагрузки. Для распределения трафика необходим reverse proxy:
services: nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - web web: build: . # ports не пробрасываем наружу — трафик идёт через nginx11.2. Ограничения масштабирования Compose
Docker Compose предназначен для локальной разработки и тестирования. Для продуктового масштабирования (десятки/сотни экземпляров на нескольких серверах) необходимы полноценные оркестраторы, такие как Kubernetes.
12. Реестры образов
12.1. Docker Hub
Крупнейший публичный реестр Docker-образов. Содержит официальные образы (postgres, redis, nginx, python) и образы от сообщества.
# Загрузить образ с Docker Hubdocker pull python:3.12-slim
# Тегировать свой образ для публикацииdocker tag myapp:latest myusername/myapp:1.0.0
# Опубликовать образdocker push myusername/myapp:1.0.012.2. GitHub Container Registry (ghcr.io)
Реестр, встроенный в экосистему GitHub. Образы привязаны к репозиторию и поддерживают управление доступом через GitHub:
# Авторизацияecho $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
# Тегирование и публикацияdocker tag myapp:latest ghcr.io/myusername/myapp:1.0.0docker push ghcr.io/myusername/myapp:1.0.012.3. Приватные реестры
Организации часто разворачивают собственные реестры для хранения внутренних образов:
- Docker Registry — open-source решение от Docker.
- Harbor — enterprise-решение с поддержкой сканирования уязвимостей.
- AWS ECR, Google GCR, Azure ACR — облачные реестры от провайдеров.
13. Введение в оркестрацию
13.1. Зачем нужна оркестрация
Docker Compose управляет контейнерами на одной машине. В продуктовой среде возникают задачи, которые Compose решить не может:
- Масштабирование: запуск сотен контейнеров на десятках серверов.
- Отказоустойчивость: автоматический перезапуск упавших контейнеров на другом узле.
- Service discovery: контейнеры должны находить друг друга в кластере без ручной настройки.
- Балансировка нагрузки: распределение трафика между экземплярами сервиса.
- Rolling updates: обновление приложения без простоя.
13.2. Kubernetes — основные концепции
Kubernetes (K8s) — стандарт де-факто для оркестрации контейнеров. Ключевые абстракции:
| Концепция | Описание |
|---|---|
| Pod | Минимальная единица развёртывания. Группа из одного или нескольких контейнеров, разделяющих сеть и хранилище. |
| Service | Сетевая абстракция для доступа к группе Pod’ов. Обеспечивает стабильный DNS-имя и балансировку. |
| Deployment | Декларативное описание желаемого состояния: сколько реплик Pod’а должно быть запущено, какой образ использовать. |
| Node | Рабочая машина (физическая или виртуальная), на которой запускаются Pod’ы. |
| Cluster | Совокупность Node’ов, управляемых Control Plane (мастер-компонентами). |
13.3. Отличие от Compose
| Характеристика | Docker Compose | Kubernetes |
|---|---|---|
| Масштаб | Одна машина | Кластер из множества машин |
| Назначение | Разработка, тестирование | Продуктовая среда |
| Отказоустойчивость | Нет (только restart) | Автоматическое перемещение Pod’ов |
| Балансировка | Нет встроенной | Service + Ingress |
| Сложность | Низкая | Высокая |
14. Docker в CI/CD
14.1. Контейнеры для сборки и тестирования
Docker обеспечивает изолированную и воспроизводимую среду для CI/CD:
- Каждая сборка запускается в чистом контейнере — нет «грязного» состояния от предыдущих сборок.
- Зависимости зафиксированы в образе — результат не зависит от конфигурации CI-сервера.
- Тесты можно запускать с реальными зависимостями (PostgreSQL, Redis) через Compose.
14.2. Docker-in-Docker vs Docker Socket Mount
Два подхода к использованию Docker внутри CI-контейнера:
Docker-in-Docker (DinD):
# Контейнер с полноценным Docker-демоном внутриservices: ci: image: docker:dind privileged: true # Требуется привилегированный режим- Полная изоляция.
- Требуется
--privileged, что снижает безопасность. - Кэш слоёв не разделяется между сборками.
Docker Socket Mount:
# Монтирование сокета Docker-хостаservices: ci: image: docker:cli volumes: - /var/run/docker.sock:/var/run/docker.sock- Быстрее, разделяется кэш слоёв.
- Менее изолирован — CI-контейнер имеет доступ ко всем контейнерам хоста.
В продуктовых CI/CD-системах (GitHub Actions, GitLab CI) Docker обычно уже настроен в runner’ах, и инженеру остаётся только использовать команды docker build и docker compose.
15. Вопросы для самопроверки
- Какую проблему решает Docker Compose по сравнению с ручным запуском контейнеров через
docker run? - Из каких основных секций верхнего уровня состоит файл
docker-compose.yml? - Чем отличается директива
imageотbuildв описании сервиса? - Как сервисы внутри Docker Compose обращаются друг к другу по сети? Почему можно использовать имя сервиса вместо IP-адреса?
- В чём разница между bind mount и named volume? Когда предпочтительнее использовать каждый из них?
- Объясните приоритет переменных окружения в Docker Compose: откуда они могут поступать и какое значение «побеждает»?
- Зачем нужны healthchecks? Чем
depends_onсcondition: service_healthyотличается от простогоdepends_on? - Что произойдёт, если выполнить
docker compose downбез флага-v? А с ним? - Какие ограничения у масштабирования через
--scaleв Docker Compose и как их можно решить? - Назовите ключевые отличия Docker Compose от Kubernetes. В каких сценариях каждый инструмент предпочтительнее?