Как жить с Docker, или почему лучше с ним, чем без него?
Эта статья предназначена для тех, кто уже знает про Docker, знает для чего он. А вот что делать с этим дальше не знает. Статья носит рекомендательный характер и не посягает на звание «лучшая практика».Итак, возможно вы прошли docker tutorial, докер кажется простым и полезным, но вы пока не знаете, как он может вам помочь с вашими проектами.
Обычно с деплоем возникает три проблемы:
Как мне доставить код на сервера? Как мне запустить код на серверах? Как мне обеспечить одинаковость окружения, в котором запускается и работает мой код? Как с этим поможет Docker под катом. Итак начнем не с docker, а с того, как мы жили без него.
Как мне доставить код на сервера? Первое, что приходит на ум, это собрать все в пакет. RPM или DEB не играет роли.Пакеты В системе уже есть пакетный менеджер, который может управлять файлами, обычно мы ожидаем от него следующего: Доставить файлы проекта на сервер, из некоторого хранилища Удовлетворить зависимости (библиотеки, интерпретатор и т.д.) Удалить файлы предидущей версии и доставить файлы текущей. На практике это работает, пока… Представим себе ситуацию. Вы DevOps на постоянной основе. Т.е. вы целыми днями занимаетесь автоматизацией рутинной деятельности, которой занимались админы. У вас есть небольшой парк машинок, на котором вы не разводите зоопарк из дистрибутивов. Например, вы любите Centos. И она всюду. Но тут к вам приходит задачка: вам нужно для одного из проектов заменить libyy (который найти под Centos само по себе квест) на libxx. Почему заменять — вам не ведомо. Скорее всего «как обычно криворукий» разработчик написал только для libxx, и вместе с libyy оно не работает. Ну что, берем в руки vim и пишем rpmspecи для libxx, параллельно для новых версий библиотек, которые нужны для сборки. И примерно 2–3 дня упражнений, и вот один из результатов:
libxx собрался, все хорошо, задача выполнена libxx не собрался, не т.к., скажем, в системе установлен libyy, который придется удалить, а от него зависит что-то еще на сервере libxx не собрался, не хватает библиотеки, которая конфиликтует с какой-то еще библиотекой Итого вероятность справиться с задачей примерно 30% (хотя на практике всегда выпадают варианты 2 или 3). И если со второй ситуацией еще можно как-то справиться (виртуалка там или lxc), то третья ситуация или добавит в ваш зверинец еще одного зверя, или собираем ручками в /usr/local привет нулевые/слакваяря и т.д.
В целом, эта ситуация называется dependency hell. Вариантов решения этому масса, от простых chroot до сложных проектов, типа NixOs
К чему это я. Ах, ну да. Как только вы начинаете собирать все в пакеты и гонять их на одной и той же системе, у вас ситуация dependency hell обязательно будет. Решить ее можно по-разному. Или под каждый проект городить новый репозиторий плюс новую виртуалку/hardware, или ввести ограничения. Под ограничениями я имею ввиду некий набор политик компании, вроде: «У нас тут только libyy-vN.N. Кому не нравится, в отдел кадров с заявлением.». Про N.N я тоже не просто так сказал, некоторые библиотеки не могут жить на одном сервере в двух версиях. Ограничения ничего не дают в конечном итоге. Бизнес, да и здравый смысл, быстро разрушат их все.
Подумайте сами, что важнее: сделать фичу в продукте, которая зависит от обновления компонента третьей стороны, или дать девопсу время наслаждаться гетерогенностью среды.
Деплой через GIT Неплохо подходит для интерпретируемых языков. Не годится для чего-то, что нужно скомпилировать. И также не избавляют от dependency-hell.Как мне запустить код на серверах? На этот вопрос придумано много всего: daemontools, runit, supervisor. Считаем, что этот вопрос имеет как минимум один верный ответ.Как мне обеспечить одинаковость окружения, в котором запускается и работает мой код? Представим себе банальную ситуацию, вы все тот же DevOps, к вам приходит задача, нужно развернуть проект «Eniac» (имя взято из генератора имен для проектов) на N серверах, где N больше 20.Вы тащите Eniac из GIT, он на известной вам технологии (Django/RoR/Go/1C), собираете его привычным методом, запускаете и… не работает. Связываетесь с разработчиком, ругаете его по первое число, что он как обычно «криворук». А он вам все обратно, и «криворук» уже вы. У разработчика все работает. С точки зрения вашего начальника, вы не можете запустить проект. Именно вы. Так как тестировщики, проект, развернутый на компьютере разработчика, все приняли.
Понимаете проблему. Идем дальше, вам удается запустить Eniac на одном сервере, и не получается еще на N-1.
Как же Docker поможет мне? Ну, во-первых давайте разберемся, как Docker использовать как средство доставки проектов на «железо».Приватный Docker Registry Что такое Docker Registry? Это то, что хранит ваши данные, подобно WEB серверу, который раздает deb-пакеты и файлы с метаданными для них. Docker Hub вы можете установить на виртуалке в Amazon и хранить данные в Amazon S3, или же поставить на свою инфраструктуру и хранить данные в Ceph или на ФС.Сам docker registry тоже можно запустить dockerом. Это всего лишь приложение на python (видимо, на go не осилили написать библиотеки к хранилищам, коих большое количество поддерживает registry).
А как в это хранилище попадет наш проект, разберем позже.
Как мне запустить код на серверах? Сейчас сравним, как мы доставляем проект обычным менеджером пакетов. Например apt: apt-get update && apt-get install -y myuperproject Эта команда годится для автоматизации. Она сама все тихо сделает, останется перезапустить наше приложение.Теперь пример для Docker:
docker run -d 192.168.1.1:80/reponame/mysuperproject:1.0.5rc1 superproject-run.sh И все. Docker скачает с вашего docker registry, расположенного по адресу 192.168.1.1 и слушающим порт 80 (у меня тут nginx), все что нужно для запуска, и запустит superproject-run.sh внутри образа.Что случится, если в образе 2 программы, которые мы запускаем, и, скажем, в следующем релизе одна из них поломалась, однако другая напротив, набрала фич, которые нужно выкатить. Нет проблем:
docker run -d 192.168.1.1:80/reponame/mysuperproject:1.0.10 superproject-run.sh docker run -d 192.168.1.1:80/reponame/mysuperproject:1.0.5rc1 broken-program.sh И теперь у нас уже 2 разных контейнера, и внутри их может быть совершенно разные версии системных библиотек файлов и т.д. Как мне обеспечить одинаковость окружения в котором запускается и работает мой код? В случае со стандартными виртуальными машинами или вообще реальными системами придется следить за всем самому. Ну или изучать Chef/Ansible/Puppet или что-то еще из подобных решений. Иначе никак не гарантировать что xxx-utils одинаковой версии на всех N серверах.В случае с docker ничего делать не нужно, просто разворачиваем контейнеры из одинаковых образов.
Сборка контейнеров Для того, чтобы ваш код попал в docker registry (ваш или глобальный не имеет значения), вам придется его туда запушить.Можно заставить разработчика собирать контейнер самостоятельно:
Разработчик пишет Dockerfile Делает docker tag $imageId 192.168.1.1:80/reponame/mysuperproject:1.0.5rc1 И пушит docker push 192.168.1.1:80/reponame/mysuperproject:1.0.5rc1 Или же собирать автоматически. Я не нашел ничего, что собирает контейнеры, поэтому вот, есть мною собранный велосипед под названием lumper
Суть в следующем:
Устанавливаем на сервер 3 компонента lumper и RabbitMQ
Слушаем порт для github hooks.
Настраиваем в github web-hook на нашу инсталляцию lumper
Разработчик делает git tag v0.0.1 && git push --tags
Получает email с результатами сборки
Dockerfile
На самом деле нет ничего сложного. Например соберем lumper с помошью Dockerfile.
FROM ubuntu:14.04
MAINTAINER Dmitry Orlov
RUN apt-get update && \ apt-get install -y python-pip python-dev git && \ apt-get clean
ADD. /tmp/build/ RUN pip install --upgrade --pre /tmp/build && rm -fr /tmp/build
ENTRYPOINT [»/usr/local/bin/lumper»] Здесь FROM говорит о том, на каком образе основывается образ. Далее RUN запускает команды подготавливая окружение. ADD помещает код в /tmp/build, далее ENTRYPOINT указывает точку входа в наш контейнер. Т.о при запуске написав docker run -it --rm mosquito/lumper --help увидим вывод --help entrypoint.
Заключение Для того, чтобы запускать контейнеры на серверах есть несколько путей, можно или написать init-script или посмотреть на Fig который может разворачивать целые решения из ваших контейнеров.Также я намеренно обошел стороной темы пробросов портов, и ip маршрутизации контейнеров.
Также, так как технология молодая, у docker горомное community. И прекрасная документация. Спасибо за внимание.