ADG Оставить заявку
Блог DevOps 6 мин чтения

Docker 0.10/0.11: тестируем многоконтейнерный стенд, упираемся в сеть и тома

Разворачиваем тестовый стенд из трёх контейнеров на Docker 0.10/0.11 и разбираемся где заканчивается магия и начинаются реальные ограничения сети и хранения данных.

Контекст момента

Docker 0.10 и 0.11 - предрелизная серия с улучшениями работы с томами и сетевой моделью; проект готовится к выпуску 1.0

В марте мы поставили Docker 0.9 на тестовую виртуалку, упаковали Flask + nginx и остановились на наблюдении «для локальной разработки и CI-стендов - интересно». С тех пор вышли 0.10 и 0.11, и в changelog добавились вещи, которые нас давно беспокоили: работа с томами и сетевая модель. Пришло время проверить, что из этого реально изменилось.

Сразу оговоримся: проект всё ещё пре-релизный, 1.0 не вышел. Мы это помним и продакшн не рассматриваем. Задача - понять где потолок для тестовых стендов и CI-окружений прямо сейчас.

Что нового в 0.10 и 0.11

Changelog обоих релизов достаточно объёмный, но нас интересовало три вещи:

  • Улучшения volumes - Docker 0.10 добавил более предсказуемое поведение именованных томов при пересоздании контейнеров. До этого были случаи потери данных при docker rm.
  • Сеть - 0.11 принёс доработки изоляции и возможность задавать диапазон адресов для docker0-бриджа через конфигурацию демона. Мелочь, но если в сети уже есть подсеть 172.17.x.x - это было настоящей болью.
  • API v1.11 - изменения в /containers/{id}/top, пара новых параметров docker run. Нас как операторов это касается меньше, но значит что скрипты автоматизации придётся переписывать при обновлении.

Стенд: три контейнера вместо двух

Расширили мартовский стенд - добавили базу. Теперь картина такая: PostgreSQL, Flask-приложение, nginx. Три контейнера, которые должны знать друг о друге.

nginx (порт 80 -> хост) -> flask (5000) -> postgres (5432)

Запускали в таком порядке - сначала postgres, потом flask с линкингом на postgres, потом nginx с линкингом на flask:

docker run -d --name pg \
  -v /data/pgdata:/var/lib/postgresql/data \
  postgres:9.3

docker run -d --name flask \
  --link pg:postgres \
  myapp-flask

docker run -d --name nginx \
  --link flask:flask \
  -p 80:80 \
  myapp-nginx

Работает. Линкинг передаёт в каждый контейнер переменные окружения с адресом и портом зависимости. Flask видит POSTGRES_PORT_5432_TCP_ADDR и POSTGRES_PORT_5432_TCP_PORT. Nginx видит координаты Flask. Всё подключилось с первого раза.

Где начинаются проблемы

Первое ощущение - красиво. Второе ощущение, когда начинаешь щупать края, - несколько менее радостное.

Линкинг работает только на одном хосте. Это принципиальное ограничение текущей модели: --link использует IP-адреса из подсети docker0, которые видны только локально. Стоит захотеть разнести postgres и nginx на разные машины - механизм линкинга не помогает. Нужно либо прокидывать порты наружу и указывать IP хоста явно, либо городить что-то поверх. Для тестового стенда на одной VM - нормально. Для чего-то реального с несколькими хостами - уже вопрос.

Порядок запуска имеет значение. Если Flask стартует раньше Postgres и тот ещё не готов принимать соединения - приложение падает, потому что переменные окружения с адресом получены, но сокет не слушает. Docker никак не отслеживает готовность сервиса внутри контейнера - только факт его запуска. Добавили в entrypoint цикл с pg_isready - некрасиво, но работает. Правильного способа декларировать зависимости между контейнерами в самом Docker пока нет.

Тома - лучше чем было, но не без странностей. Монтирование директории с хоста через -v /data/pgdata:/var/lib/postgresql/data работает предсказуемо: данные живут на хосте, контейнер можно убить и пересоздать, данные остаются. Но если забыть указать -v при пересоздании контейнера - данные уходят вместе с контейнерным слоем. Никакого предупреждения, никакого «вы уверены». Просто удалились.

С именованными томами через docker run -v pgdata:/var/lib/postgresql/data (без явного пути на хосте) ситуация интереснее: том создаётся под управлением Docker в /var/lib/docker/volumes/. При docker rm данные не удаляются - это то, что улучшили в 0.10. Но найти их руками без docker inspect - квест. Бекапить такие тома неудобно.

Логи растут без ограничений. stdout каждого контейнера копится в файлах на хосте. Механизма ротации нет - точнее, он есть в системе, но Docker его не использует. За несколько дней тестирования nginx-контейнер накопил несколько сотен мегабайт лога. На тестовом стенде это не критично, но для долгоживущих контейнеров - проблема, о которой надо помнить заранее.

Что с сетью в 0.11

Новая опция --bip при запуске docker daemon позволяет задать подсеть для docker0-бриджа. Казалось бы, мелочь. Но у нас в тестовой среде 172.17.0.0/16 уже занят маршрутами в сторону одного из клиентских сегментов, и Docker до 0.11 просто вставал на этот адрес без разбора. Конфликт был тихим - пакеты уходили не туда, диагностировалось долго. Теперь можно:

docker -d --bip=10.200.0.1/24

И docker0 встаёт на 10.200.0.1, контейнерам раздаётся 10.200.0.0/24. Это реальное улучшение для тех, у кого сеть не пустая.

Изоляция между контейнерами по умолчанию работает так: все контейнеры на одном хосте видят друг друга через docker0 без ограничений. Опция --icc=false при запуске демона закрывает это и оставляет только явно залинкованные пары. Мы включили - и сразу же обнаружили что один из вспомогательных контейнеров рассчитывал на прямой доступ к соседу без линкинга. Нашли не сразу.

Попытка описать зависимости через скрипт

После нескольких итераций запуск стенда у нас теперь выглядит как shell-скрипт примерно на 30 строк: создать том, запустить postgres, подождать готовности, запустить flask, запустить nginx. Это работает, но очевидно не масштабируется. Есть инструмент Fig (fig.sh) - YAML-описание для многоконтейнерных приложений. Мы смотрели на него бегло: идея правильная, один файл описывает всю конфигурацию. Но и Fig сейчас в активной разработке, и Docker меняет API - не хочется зависеть от двух нестабильных вещей одновременно.

Где это полезно прямо сейчас

После двух месяцев экспериментов у нас сложилась достаточно чёткая картина:

  • CI-стенды - разворачивать изолированное окружение для прогона тестов быстро и без следов. Именно это сейчас наиболее зрелый use case.
  • Локальная разработка - разработчик получает окружение, идентичное тестовому, без установки PostgreSQL/Redis/чего-угодно прямо на машину.
  • Разовые задачи - запустить инструмент в изоляции, не засоряя хост.

Для чего это ещё не готово: долгоживущие сервисы, несколько хостов, что-то с нетривиальными требованиями к хранению данных. Не потому что концепция плохая, а потому что инструмент честно предрелизный и часть нужных механизмов в явной доработке.

Следим за Docker. 1.0 обещают скоро.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.