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

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-агент в контейнере - об этом напишем отдельно, если опыт окажется интересным.

Контакт

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

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