Docker Compose v2: именованные сети, volumes и первый взгляд на Swarm
Перевели staging на Compose v2 формат: именованные сети и volumes в одном файле вместо links и магических соглашений. Тестируем деплой через Docker Swarm.
Docker Compose 1.7 и Docker 1.11 вышли с поддержкой формата Compose v2 и сетевых плагинов; Swarm получил совместимость с новым форматом
Docker 1.11 и Compose 1.7 вышли с разницей в несколько дней, и центральная тема у обоих - формат Compose file v2. Мы как раз возились со staging-окружением одного из проектов на сопровождении и решили не откладывать - перевели на новый формат и заодно посмотрели, как это работает через Docker Swarm.
Что было не так с v1
В старом формате docker-compose.yml не было явного понятия сети. Compose создавал сеть автоматически, именовал её по директории, и контейнеры соединялись через links. На небольшом файле это выглядело терпимо, но при трёх-четырёх сервисах с зависимостями friends-of-friends начиналась вермишель из links и volumes_from.
Главная боль - links. Мы уже писали про это в контексте Docker 1.10: link привязывает имя к конкретному запущенному контейнеру, не к абстракции. Перезапустил один сервис - остальные, которые на него ссылаются, могут потерять резолвинг. В v1 Compose это частично обходил, но не устранял.
Volumes в v1 тоже жили в рамках сервиса: либо анонимные, либо через volumes_from от другого контейнера. Никакого явного lifecycle, никакого именования на уровне файла.
Что изменилось в v2
Новый формат добавил четыре секции на верхнем уровне: version, services, networks, volumes. Это принципиальная смена - теперь сети и volumes - граждане первого класса, а не побочный эффект сервисов.
Минимальный docker-compose.yml для типичного web-проекта:
version: "2"
services:
postgres:
image: postgres:9.5
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
app:
image: registry.internal/myapp:2.1.0
environment:
DATABASE_URL: postgres://postgres:5432/myapp
networks:
- backend
depends_on:
- postgres
nginx:
image: nginx:1.9
ports:
- "80:80"
- "443:443"
networks:
- backend
networks:
backend:
driver: bridge
volumes:
db-data:
driver: local
Никаких links. Сервисы резолвятся по имени через встроенный DNS Docker 1.10+. depends_on - это порядок старта, не сетевая зависимость. Volume db-data существует независимо от контейнера postgres: можно удалить и пересоздать контейнер, данные останутся.
Что заметили при миграции
Перевод с v1 на v2 занял около получаса на проект, если не считать тест-прогона.
Первое - links просто удаляешь. DNS-резолвинг по имени сервиса работает из коробки в любой user-defined сети. Переменные окружения, которые раньше пробрасывал link (POSTGRES_PORT_5432_TCP_ADDR и подобные), больше не нужны - подключение через имя сервиса.
Второе - volumes_from уходит. Паттерн с data-container, где один контейнер держал только volume а другой его монтировал через volumes_from, был распространённым в v1. Теперь именованный volume монтируется напрямую в нужный сервис. Проще и явнее.
Третье - изоляция между проектами. В v1 все проекты на хосте попадали в одну сеть bridge, и при желании контейнеры разных проектов могли общаться. В v2 каждый проект получает свою сеть по умолчанию, и между проектами - стена. Для нас это хорошо: клиентские окружения не должны видеть друг друга.
Четвёртое - depends_on не ждёт готовности. Это распространённое заблуждение. depends_on: postgres гарантирует, что контейнер postgres запустится раньше app, но не что postgres будет готов принимать соединения. Если приложение при старте сразу лезет в базу без retry - падает. Нужен или wait-for-it.sh, или retry-логика в самом приложении. Мы это знали, но напомнили себе ещё раз при тесте.
Docker Swarm и Compose v2
Мы смотрим на Kubernetes как на оркестратор для нагруженных проектов, но k8s - это инфраструктура с порогом входа. Для части проектов нужна оркестрация проще, и Swarm - кандидат.
Docker 1.11 с Compose v2 позволяет деплоить через docker-compose на Swarm-кластер почти без изменений в файле. Проверили на двухузловом стенде: Swarm-менеджер и один воркер.
# Создание токена кластера (Swarm Classic)
docker run --rm swarm create
# вывод: <cluster-token>
# На менеджере
docker run -d -p 2375:2375 swarm manage token://<cluster-token>
# На воркере
docker run -d swarm join --addr=10.0.1.11:2375 token://<cluster-token>
# Деплой через compose файл
DOCKER_HOST=tcp://swarm-manager:2375 docker-compose up -d
Swarm распределяет контейнеры по нодам на основе ресурсных ограничений и constraints. Без явных constraints - round-robin по доступным нодам. Для stateless-сервисов это работает. Для postgres с volume - нужен constraint на конкретную ноду, иначе контейнер может переехать на другую машину без данных.
services:
postgres:
image: postgres:9.5
volumes:
- db-data:/var/lib/postgresql/data
environment:
constraint:node: "==node-1"
Это ограничение Swarm-а: shared storage для volumes из коробки нет. Либо NFS, либо volume-плагины (Flocker, Convoy). Мы этот вопрос оставили открытым - для текущего проекта postgres живёт на одной ноде и нас это устраивает.
Где сейчас
Staging-окружение двух проектов работает на Compose v2. По ощущениям - файлы стали читаемее, структура понятнее без знания внутренних соглашений v1.
Swarm попробовали, понял общую механику. Для проектов, где k8s избыточен, это рабочий вариант - но с оговоркой про stateful-сервисы. Пока держим на нём только staging, production ещё не трогали.
Параллельно смотрим на k8s 1.2 - там ingress и улучшенный autoscaler. Выбор между Swarm и k8s для конкретных проектов будет отдельной темой, когда наберём больше наблюдений на обоих.