Docker 0.9 в тестовой среде: упаковываем веб-приложение в контейнер за час
Берём Docker 0.9 на Ubuntu 13.10, пакуем простой Python/nginx стек в контейнеры и смотрим что из этого выходит. Продакшн пока не рассматриваем.
Docker 0.9 (март 2014) - стабилизация API, переход с LXC на libcontainer как execution driver по умолчанию; сообщество активно тестирует инструмент
Docker шумит в интернете уже несколько месяцев, и на этой неделе вышел релиз 0.9. Главное в нём - переход с LXC на собственный execution driver libcontainer, стабилизация API, плюс Docker начинает официально поддерживать не только Ubuntu. Мы давно собирались потрогать это руками, и повод появился.
Сразу скажем честно: продакшн не рассматриваем. Инструмент пре-релизный, API вполне может поменяться, и авторы это явно не скрывают. Задача на сегодня - понять механику, упаковать что-то живое и составить собственное мнение, а не пересказывать туториалы с официального сайта.
Что за Docker и зачем
Если совсем коротко: Docker - это обёртка над cgroups и namespaces Linux, которая позволяет запускать изолированные процессы в контейнерах. В отличие от полноценной виртуализации - никакого гипервизора, никакого гостевого ядра. Один хост, одно ядро, несколько изолированных окружений.
Главная идея, которая цепляет: образ контейнера включает в себя всё нужное для запуска приложения - зависимости, конфиги, бинарники. Собрал образ на одной машине, запустил на другой - поведение одинаковое. Звучит как маркетинг, но на практике это действительно решает конкретную боль: «у меня работает, у тебя нет».
До 0.9 Docker под капотом использовал LXC. Теперь по умолчанию включён libcontainer - собственная реализация на Go, которая напрямую работает с kernel API без зависимости от lxc-инструментов. LXC можно вернуть флагом, если нужно.
Стенд и что устанавливали
Тестовая виртуалка: Ubuntu 13.10 (Saucy), 2 vCPU, 4 ГБ RAM. Установка Docker 0.9 через официальный скрипт:
curl -s https://get.docker.io/ubuntu/ | sudo sh
Скрипт добавляет репозиторий dotCloud и ставит пакет. После установки - docker version, убеждаемся что 0.9 и что execution driver - libcontainer.
Для теста взяли простое Python-приложение: Flask-сервис, отдающий JSON с метриками, и nginx перед ним в качестве обратного прокси. Ничего сложного - ровно то, что позволяет не утонуть в деталях приложения и сосредоточиться на Docker.
Dockerfile и первый образ
Docker работает через Dockerfile - текстовый файл с инструкциями сборки образа. Вот что получилось для Flask-части:
FROM ubuntu:13.10
RUN apt-get update && apt-get install -y python python-pip
RUN pip install flask gunicorn
ADD app/ /app
WORKDIR /app
EXPOSE 5000
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:application"]
Каждая инструкция RUN создаёт новый слой образа. Docker кеширует слои, и при пересборке повторяет только то, что изменилось ниже по файлу. Это работает умно: если поменял код приложения в ADD app/, слои с установкой пакетов не пересобираются.
Сборка:
docker build -t myapp-flask .
Первая сборка заняла несколько минут - качаются пакеты. Вторая, после правки кода, - секунды.
Образ для nginx - похожая история, только в конфиге прописываем проксирование на Flask-контейнер.
Запуск и связывание контейнеров
Запускаем Flask:
docker run -d --name flask myapp-flask
Флаг -d - detached, работает в фоне. --name flask - называем контейнер, чтобы на него можно было ссылаться.
Nginx запускаем с линкингом:
docker run -d --name nginx --link flask:flask -p 80:80 myapp-nginx
--link flask:flask добавляет в контейнер nginx переменные окружения с адресом и портом Flask-контейнера. Внутри nginx-конфига используем FLASK_PORT_5000_TCP_ADDR - Docker подставит IP автоматически.
Это работает, но выглядит немного как магия. Механизм линкинга пока ограничен одним хостом, и документация честно говорит что API линкинга ещё не финальный. Для теста - ок.
Что получилось и что не так
Хорошее. Образ собирается и запускается воспроизводимо. Перенесли образ на другую тестовую машину через docker save / docker load - запустился без установки зависимостей. Именно та история про «работает везде одинаково» - она работает.
Слои образа. После экспериментов образ Flask-приложения вырос примерно до 400 МБ - за счёт базового образа Ubuntu и установленных пакетов. Это много для небольшого сервиса, но в концепции Docker это нормально: слои разделяются между образами, и диск расходуется умнее чем кажется.
Персистентность данных. Контейнер по умолчанию не сохраняет ничего после остановки. Если приложение пишет на диск - нужны volumes. Мы не стали углубляться, но это важный момент для любого реального приложения.
Сеть. Docker создаёт свой bridge docker0 и раздаёт контейнерам адреса из подсети 172.17.x.x. Связь между контейнерами через линкинг работает, но если надо сделать что-то нестандартное в сети - быстро упираешься в ограничения. Документация по сети скудная.
Логи. docker logs flask - и видишь stdout/stderr контейнера. Просто и удобно для разработки. Для чего-то серьёзнее нужно думать отдельно.
Что это меняет в голове
После часа с Docker понятно почему вокруг шум. Идея упаковать приложение вместе с окружением и гарантировать одинаковое поведение на разных машинах - это не новая идея, но реализация через Dockerfile и слоистые образы сделана удобно. Порог входа заметно ниже чем у полноценной виртуализации.
Куда это движется - не очень ясно. Сейчас Docker хорош для локальной разработки и CI-стендов, где нужно быстро поднять изолированное окружение. Для продакшна вопросов больше чем ответов: оркестрация нескольких контейнеров на нескольких хостах, сеть, хранение данных, мониторинг - всё это либо не закрыто, либо закрыто инструментами с таким же пре-релизным статусом.
Но ощущение что это не очередная хайп-технология, а что-то с реальным потенциалом - оно есть. Будем наблюдать.
В рамках нашей работы над CI-пайплайнами Docker интересен как способ изолировать сборочное окружение. Попробуем запустить Jenkins-агент в контейнере - об этом напишем отдельно, если опыт окажется интересным.
- Jenkins + Ansible: автоматический прогон playbooks после каждого коммита · 11 марта 2014
- OpenStack Havana: запускаем пилот частного облака на трёх узлах · 14 февраля 2014