DevOps

Docker Compose и multi-container

ML-стек: FastAPI + Redis + PostgreSQL + Celery + Flower. Вручную - 5 терминалов, 5 команд, строгий порядок запуска, половина команды не помнит нужные флаги. Compose: один файл, `docker compose up`. 30 секунд - весь стек работает. `docker compose down` - весь стек остановлен. Это не деплой инструмент - это инструмент для того чтобы окружение разработки было воспроизводимым.

  • **ML-стек**: FastAPI + Celery + Redis + PostgreSQL поднять за 30 секунд для любого члена команды без 'как это установить'
  • **CI/CD**: GitHub Actions запускает тесты против настоящей PostgreSQL через Compose - не mock, не SQLite
  • **Команда из 5 человек**: одинаковое окружение без 'у меня работает'. README содержит одну команду: `docker compose up`
  • **LLM inference**: vLLM + Redis (rate limiting) + Nginx одной командой - воспроизводимо на любом сервере с Docker

Compose-файл

**2014 год.** Компания Orchard выпустила Fig - инструмент для запуска multi-container приложений через один YAML-файл. Docker купил Fig и переименовал в Compose. Один файл заменил десятки страниц документации по запуску стека. `docker-compose.yml` описывает сервисы, сети и volumes декларативно: не «как запускать», а «что должно быть запущено». Compose читает файл и сам разбирается с порядком, сетями и монтированием.

**Секреты в .env**: файл `.env` рядом с `docker-compose.yml` подхватывается автоматически. Переменные внутри файла подставляются через `${VAR_NAME}` в compose-файле. `.env` добавляется в `.gitignore` - ключи и пароли не попадают в репозиторий.

**Bind mount vs Named volume**: `./models:/models` - bind mount, папка хоста проброшена внутрь (для разработки, hot reload). `redis_data:/data` - named volume, Docker управляет им сам (для production данных). Смешивать можно: development окружение часто использует оба типа одновременно.

В docker-compose.yml описан сервис `api` с `ports: ["8000:8000"]` и сервис `redis` без `ports`. Что это означает?

Сети в Compose

Compose автоматически создаёт bridge-сеть для проекта и подключает все сервисы. Внутри этой сети работает **встроенный DNS**: контейнер `backend` обращается к базе данных просто как `postgres`, к кэшу - просто как `redis`. Имя сервиса в compose-файле и есть hostname. Нет IP, нет `--link`, нет ручного `/etc/hosts`. Это то, почему `localhost` внутри контейнера - неправильный адрес для другого сервиса.

**localhost - ловушка**: внутри контейнера `localhost` или `127.0.0.1` указывает на loopback самого контейнера, не на хост-машину и не на другой сервис. Типичная ошибка при переносе `.env` с локальной разработки в Docker - `DATABASE_URL=localhost:5432` перестаёт работать. Правило: в Docker - имя сервиса, вне Docker - localhost.

Сервис `api` в Compose пытается подключиться к PostgreSQL через `localhost:5432`. PostgreSQL запущен как сервис `db` в том же Compose-файле. Что произойдёт?

depends_on и порядок

**depends_on не ждёт готовности сервиса.** Он ждёт когда контейнер запустился - это принципиально разные события. PostgreSQL процесс стартует за 0.1 секунды, но принимает соединения через 2-3 секунды после инициализации WAL и recovery. Приложение с `depends_on: [db]` запустится когда контейнер db перешёл в Running, но подключиться к базе ещё не сможет. Без healthcheck нужен retry в самом приложении.

**condition: service_completed_successfully** - специальный вариант для одноразовых сервисов (миграции, seed данных). Ждёт exit code 0. Если миграция завершилась с ошибкой - зависящие сервисы не запустятся.

Сервис `api` имеет `depends_on: [db]` без condition. PostgreSQL занимает 3 секунды на полный старт. Что произойдёт?

Health checks

**HEALTHCHECK** - директива которая периодически проверяет: сервис работает или завис. Docker выполняет test-команду внутри контейнера и отслеживает exit code: 0 - healthy, 1 - unhealthy, 2 - reserved. Статус влияет на depends_on с `condition: service_healthy`, на restart policies и на оркестраторы (Swarm, Kubernetes). Проверки пишутся под конкретный сервис: PostgreSQL - `pg_isready`, Redis - `redis-cli ping`, HTTP API - `curl`.

**start_period**: время после старта контейнера в которое неудачные проверки не считаются - контейнер остаётся в starting state, не переходит в unhealthy. Критично для сервисов с долгой инициализацией: загрузка ML модели, прогрев JVM, первый migration run.

**Compose vs Dockerfile HEALTHCHECK**: healthcheck можно задать как в Dockerfile (`HEALTHCHECK CMD ...`), так и в compose-файле. Compose-версия перекрывает Dockerfile-версию. Для разработки удобно переопределить в compose менее строгие параметры (interval: 2s вместо 30s).

depends_on + healthcheck гарантирует что сервисы запустятся строго по порядку: db, потом migrator, потом api

depends_on + healthcheck гарантирует только что зависимый сервис не стартует до healthy-статуса зависимости. Независимые сервисы запускаются параллельно - порядок между ними не определён

Compose строит граф зависимостей и запускает независимые ветки параллельно для скорости. Если worker-a и worker-b оба зависят от db, они оба дождутся db healthy, затем запустятся одновременно. Для строгого последовательного порядка нужна явная цепочка: a depends_on b depends_on c.

Два независимых сервиса - `worker-a` и `worker-b` - оба имеют `depends_on: db: condition: service_healthy`. Что гарантирует эта конфигурация?

Ключевые идеи

  • **Compose-файл** - декларативное описание стека: services/networks/volumes в одном YAML. `.env` для секретов, bind mounts для dev, named volumes для production данных
  • **Docker DNS**: сервисы обращаются друг к другу по имени сервиса (`redis:6379`, не `localhost:6379`). localhost внутри контейнера = loopback самого контейнера
  • **depends_on** без condition - только порядок старта контейнеров, не readiness. Для ожидания готовности - healthcheck + `condition: service_healthy`
  • **HEALTHCHECK** - периодическая проверка через exit code: pg_isready, redis-cli ping, curl. start_period защищает от false unhealthy при медленной инициализации

Связанные темы

Docker Compose - мост между локальной разработкой и production инфраструктурой:

  • Docker: основы — Compose оперирует Docker-примитивами: images, volumes, networks
  • Kubernetes — Production-grade оркестрация поверх тех же концепций что и Compose
  • Serverless / Lambda — Альтернативная модель упаковки без постоянно работающих контейнеров
  • Репликация баз данных — Классический multi-container сценарий: primary + replica в одном Compose
  • LLM API integration — vLLM + Redis + backend - типовой ML Compose-стек с rate limiting

Вопросы для размышления

  • В стартапе все разработчики используют docker compose up для локальной разработки. Тимлид предлагает использовать тот же Compose-файл для деплоя на продакшн сервер. Какие конкретные проблемы создаёт такой подход?
  • Сервис `trainer` стартует только после того как db и migrator завершились успешно. Как описать эту зависимость в Compose-файле и почему простой depends_on: [db, migrator] не достаточен?
  • Команда жалуется что `docker compose up` у разных разработчиков даёт разное поведение - у одних база инициализируется, у других нет. Как воспроизводимо решить проблему порядка инициализации?

Связанные уроки

  • devops-04
  • devops-01
  • devops-02
  • devops-03
  • cloud-05
  • ds-05-replication
  • aie-05-api-integration
  • net-47-container-networking
  • ds-12-service-discovery
Docker Compose и multi-container

0

1

Войти