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

Лекция 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 psycopg2
conn = psycopg2.connect(host="db", port=5432, dbname="mydb")
# "db" — это имя сервиса в docker-compose.yml

4.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:6379
postgresql://db:5432/mydb # вместо postgresql://172.18.0.2:5432/mydb

5. 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:

.env
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
POSTGRES_DB=production
APP_PORT=8080
docker-compose.yml
services:
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.env

6.3. Приоритет переменных окружения

Переменные применяются в следующем порядке (от высшего приоритета к низшему):

  1. Переменные, заданные в команде: APP_PORT=9090 docker compose up
  2. Переменные из shell-окружения
  3. Значения из файла .env
  4. Значения по умолчанию в 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: 30s

Redis:

healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3

HTTP-сервис:

healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s

8. Зависимости между сервисами

8.1. Простой depends_on

services:
web:
depends_on:
- db
- redis

Compose запустит 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_healthyHealthcheck вернул статус 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 файл:

.env
POSTGRES_USER=appuser
POSTGRES_PASSWORD=supersecret
POSTGRES_DB=appdb
APP_PORT=8080

10. Команды 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 в контейнере web
docker 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 не пробрасываем наружу — трафик идёт через nginx

11.2. Ограничения масштабирования Compose

Docker Compose предназначен для локальной разработки и тестирования. Для продуктового масштабирования (десятки/сотни экземпляров на нескольких серверах) необходимы полноценные оркестраторы, такие как Kubernetes.


12. Реестры образов

12.1. Docker Hub

Крупнейший публичный реестр Docker-образов. Содержит официальные образы (postgres, redis, nginx, python) и образы от сообщества.

Окно терминала
# Загрузить образ с Docker Hub
docker pull python:3.12-slim
# Тегировать свой образ для публикации
docker tag myapp:latest myusername/myapp:1.0.0
# Опубликовать образ
docker push myusername/myapp:1.0.0

12.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.0
docker push ghcr.io/myusername/myapp:1.0.0

12.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 ComposeKubernetes
МасштабОдна машинаКластер из множества машин
НазначениеРазработка, тестированиеПродуктовая среда
ОтказоустойчивостьНет (только 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. Вопросы для самопроверки

  1. Какую проблему решает Docker Compose по сравнению с ручным запуском контейнеров через docker run?
  2. Из каких основных секций верхнего уровня состоит файл docker-compose.yml?
  3. Чем отличается директива image от build в описании сервиса?
  4. Как сервисы внутри Docker Compose обращаются друг к другу по сети? Почему можно использовать имя сервиса вместо IP-адреса?
  5. В чём разница между bind mount и named volume? Когда предпочтительнее использовать каждый из них?
  6. Объясните приоритет переменных окружения в Docker Compose: откуда они могут поступать и какое значение «побеждает»?
  7. Зачем нужны healthchecks? Чем depends_on с condition: service_healthy отличается от простого depends_on?
  8. Что произойдёт, если выполнить docker compose down без флага -v? А с ним?
  9. Какие ограничения у масштабирования через --scale в Docker Compose и как их можно решить?
  10. Назовите ключевые отличия Docker Compose от Kubernetes. В каких сценариях каждый инструмент предпочтительнее?