Содержание статьи
Благодаря своей архитектуре и особенностям, о которых мы уже поговорили в предыдущих статьях, Docker может быть использован для решения огромного количества задач, многие из которых простираются далеко за пределы виртуализации в классическом понимании этого слова. Docker удобно использовать для реализации микросервисов, деплоя приложений, упрощения цикла разработки, изоляции приложений и многих других задач.
Есть четыре класса задач, для которых Docker подходит если не идеально, то лучше любого другого инструмента. Это:
- Упрощение процесса разворачивания/сопровождения проектов. Docker позволяет разбить проект на небольшие независимые, удобные в сопровождении компоненты, работать с которыми гораздо комфортнее, чем с реальными сущностями вроде Apache 2.4.12, установленного на хосте 1.2.3.4, работающем под управлением CentOS 6.
- Continous development и zero-downtime deployment. Каждый образ Docker — вещь в себе, включающая сервис (или набор сервисов), окружение для его запуска и необходимые настройки. Поэтому контейнеры можно передавать между членами команды в ходе цикла «разработка -> тестирование -> внедрение» и быстро внедрять изменения, просто переключая настройки на новые контейнеры.
- IaaS/PaaS. Благодаря легковесности контейнеров Docker можно использовать в качестве движка виртуализации в IaaS, а благодаря простоте миграции Docker становится идеальным решением для запуска сервисов в PaaS.
- Запуск небезопасного кода. Docker позволяет запустить любой, в том числе графический софт внутри изолированного контейнера с помощью одной простой команды. Поэтому он идеально подходит для запуска разного рода недоверенного или просто небезопасного кода.
В следующих разделах мы рассмотрим все четыре применения Docker с простыми и не очень примерами.
Кейс номер 1. Поднимаем LAMP
В качестве примера разбиения приложения на составные компоненты рассмотрим пример LAMP + WordPress. Это абсолютно стандартная настройка, за тем исключением, что ее компоненты будут работать в обособленных контейнерах. А это, в свою очередь, позволит нам: а) обезопасить хост-систему и другие контейнеры в случае проникновения в один из них; б) получить возможность легко масштабировать нашу инсталляцию, разнеся ее компоненты по разным машинам; в) выполнить миграцию с помощью пары простых команд; г) упростить обкатку новых версий nginx, PHP или MySQL.
Итак, для начала нам понадобится базовый образ для наших будущих контейнеров. Пусть это будет CentOS. Скачиваем образ, запускаем контейнер и обновляем систему:
$ sudo docker pull centos
$ sudo docker run -ti centos /bin/bash
[root@6b08bed3e0f2 /]# yum update -y
Теперь делаем commit, чтобы создать новый образ. Назовем его centos:updated:
# docker commit 6b08bed3e0f2 centos:updated
На основе нашего обновленного образа создадим два новых. Один — для запуска Apache и PHP (к сожалению, мы не сможем разнести их по разным контейнерам по сугубо техническим причинам), второй — для MySQL. Начнем с Apache:
$ sudo docker run -ti centos:updated /bin/bash
[root@270e938d8026 /]# yum install -y httpd php php-mysql
[root@270e938d8026 /]# ifconfig eth0
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:14
inet addr:172.17.0.20 Bcast:0.0.0.0 Mask:255.255.0.0
Добавляем IP-адрес и имя хоста в /etc/hosts и редактируем конфиг Apache, указав имя хоста и web root:
ServerName example.com:80
DocumentRoot /var/www/html
Перезапускаем Apache:
[root@270e938d8026 /]# service httpd restart
Пробуем открыть адрес 172.17.0.20 в браузере в хост-системе, должна появиться стандартная страничка phpinfo(). Если это так — все ОK, можно закоммитить изменения в новый контейнер:
$ sudo docker commit 270e938d8026 www
Теперь настроим контейнер с MySQL с выносом базы данных на хост-систему (мы же не хотим потерять ее после остановки контейнера):
$ sudo mkdir /root/mysql
$ sudo docker run -v /root/mysql:/var/lib/mysql -ti centos:updated /bin/bash
[root@270e938d8026 /]# yum install -y mysql-server
[root@270e938d8026 /]# service mysqld start
Создаем базу данных и юзера, как показано на скриншоте «Создаем базу данных», после чего — новый контейнер:
$ sudo docker commit 270e938d8026 mysql
Смотрим, что у нас получилось:
$ sudo docker images | grep example
example mysql 3745404f897e 8 minutes ago 493.2 MB
example www 270e938d8026 11 minutes ago 457.5 MB
Теперь нам необходимо установить WordPress, и здесь у нас есть два пути: мы можем вновь запустить контейнер example:www и распаковать архив WordPress в каталог /var/www/html/ внутри него либо можем распаковать его в какой-нибудь каталог на хост-системе и просто смонтировать его внутрь контейнера при запуске. Первый способ позволяет с удобством таскать контейнеры между машинами, второй более удобен при разработке веб-приложений. Для примера пойдем по второму пути. Итак, скачиваем и распаковываем WordPress в каталог /root/html/ на хост-системе:
$ cd /tmp
$ wget https://wordpress.org/latest.tar.gz
$ sudo -s
# mkdir /root/html
# cd /root/mysql
# tar xzvf /tmp/latest.tar.gz
# rm -f /tmp/latest.tar.gz
# exit
Все. Теперь осталось только запустить контейнеры с правильными опциями:
$ sudo docker run -d -v /root/html:/var/www/html \
-v /root/mysql:/var/lib/mysql www \
/usr/sbin/httpd -DFOREGROUND
$ sudo docker run -d mysql \
/usr/bin/mysqld_safe --bind-address=0.0.0.0
Открываем в браузере адрес контейнера www и видим инсталлятор WordPress.
Кейс номер 2. Отлаживем, тестируем, внедряем
В предыдущем примере мы для простоты настройки вынесли WordPress на хост-систему, хотя в крупных продакшен-проектах части веб-приложения (или все приложение целиком) следует запускать в отдельных контейнерах. Это даст возможность просто и быстро выполнять миграцию, обкатывать изменения и оперативно внедрять их с помощью простой замены одного контейнера на другой. Как это сделать, должно быть понятно после прочтения предыдущей статьи.
Однако здесь тебя ждет небольшая проблема, и заключается она в том, что для замены одного контейнера на другой придется вручную перенастраивать все зависимые от него контейнеры. А это, во-первых, не очень-то и удобно, а во-вторых, требует времени. Решить эту проблему можно разными средствами, в том числе встроенным в Docker механизмом линковки (о нем мы поговорим в следующей статье), но он имеет ряд проблем, поэтому лучше будет воспользоваться множество раз обкатанной в продакшене системой DNS discovery на основе связки SkyDNS и Skydock.
Основная идея этого метода состоит в том, чтобы поднять локальный DNS-прокси SkyDNS и приложение Skydock, которые, работая в тандеме, будут автоматически назначать контейнерам локальные хостнеймы, основанные на имени образа и контейнера Docker. Как следствие, контейнер с обновленным образом того же MySQL из примера выше можно будет включить в общую настройку, просто запустив его, а затем остановив «старый» контейнер. И все это без дополнительных настроек.
Чтобы добавить SkyDNS к уже существующей конфигурации (возьмем для примера все тот же LAMP), достаточно выполнить несколько команд. Для начала установим и запустим сам SkyDNS:
$ sudo docker pull crosbymichael/skydns
$ sudo docker run -d -p 172.17.42.1:53:53/udp \
--name skydns crosbymichael/skydns \
-nameserver 8.8.8.8:53 -domain docker
Здесь все довольно просто: с помощью опции -p мы пробрасываем порт 53 с виртуального сетевого моста внутрь контейнера SkyDNS (172.17.42.1 — дефолтовый IP-адрес моста docker0, создаваемого демоном Docker), а с помощью опции -nameserver самого SkyDNS указываем fallback DNS-сервер, который будет использован для резолвинга глобальных хостнеймов. Последняя опция, -domain задает имя домена первого уровня для локальных хостнеймов.
Далее необходимо запустить Skydock. Здесь все еще проще:
$ sudo docker pull crosbymichael/skydock
$ sudo docker run -d -v /var/run/docker.sock:/docker.sock \
--name skydock crosbymichael/skydock -ttl 30 \
-environment dev -s /docker.sock -domain docker -name skydns
Здесь мы с помощью опции -v пробрасываем в контейнер UNIX-сокет Docker, необходимый Skydock для получения уведомлений о запуске и остановке контейнеров и их имен, а далее идет несколько опций самого Skydock:
- -ttl 30 — время жизни DNS-записи в секундах;
- -environment dev — имя домена второго уровня для контейнеров;
- -s /docker.sock — путь до проброшенного ранее сокета;
- -domain docker — домен первого уровня;
- -name skydns — имя контейнера со SkyDNS.
Это все. Теперь, если перезапустить контейнеры с Apache и MySQL из предыдущего примера, то они получат имена www.dev.docker и mysql.dev.docker (а если бы мы дали им имена, то они бы стали доменами четвертого уровня). В ситуации с LAMP это нам ничего не даст, кроме чуть большего удобства управления, но в более сложной конфигурации со множеством различных сервисов позволит на лету менять контейнеры (запускаем новый, завершаем старый, никаких настроек, главное, чтобы имена образов и контейнеров совпадали) и даже балансировать нагрузку — SkyDNS будет отдавать IP-адреса с одинаковым именем хоста по очереди, в режиме round-robin.
Кейс номер 3. IaaS и кластеры
Описанный выше пример отлично работает, но в силу централизованной архитектуры Docker и, как следствие, самого Skydock, работает он только на одной физической (как вариант — виртуальной) машине. Чтобы решить эту проблему, нужна некая облачная платформа, которая позволит запускать контейнеры на кластере машин, выполнять их оркестрацию и балансировку нагрузки. На данный момент существует три ключевых проекта, позволяющих связать Docker и кластеры: OpenStack, CoreOS и Kubernetes (плюс фирменные инструменты от разработчиков Docker, но они до сих пор в стадии альфа/бета).
OpenStack
Поддержка Docker в OpenStack есть в трех формах:
- драйвер гипервизора в Nova;
- плагин и шаблон для системы оркестрации Heat;
- движок в каталоге приложений для Murano.
Поддержка драйвера гипервизора Nova, позволяющего запустить OpenStack поверх Docker вместо классических технологий виртуализации (KVM, vSphere и так далее), появилась в релизе Havana. Сам драйвер достаточно прост в установке и настройке и хорошо интегрируется с Glance (сервис, отвечающий за хранение образов) и Neutron (NaaS). Все, что нужно сделать, — это установить драйвер с помощью pip и внести небольшие изменения в файлы конфигурации Nova и Glance. Весь процесс описан на официальной wiki-странице.
Фреймворк оркестрации OpenStack Heat поддерживает Docker, начиная с релиза IceHouse. Его задача — управление Docker, запущенным поверх виртуальной или реальной машины. С помощью одного конфигурационного файла он позволяет запустить одну или несколько машин, установить в них Docker, а затем запустить контейнеры с нужными сервисами внутри. Пример файла конфигурации для автоматического запуска виртуальной машины, установки в нее Docker и запуска контейнера cirros:
resources:
my_instance:
type: OS::Nova::Server
properties:
key_name: ewindisch_key
image: ubuntu-precise
flavor: m1.large
user_data: #include https://get.docker.io
my_docker_container:
type: DockerInc::Docker::Container
docker_endpoint: { get_attr: [my_instance, first_address] }
image: cirros
Murano, выполняющий роль каталога приложений в OpenStack, поддерживает Docker с января 2014 года. Он позволяет публиковать, искать и разворачивать Docker-контейнеры с приложениями так же просто, как и обычные приложения. Благодаря этой функциональности можно запустить набор сервисов внутри Docker-контейнеров с помощью нескольких кликов мышью прямо из графического интерфейса.
CoreOS
CoreOS — еще один крупный проект, имеющий прямое отношение к Docker. Это минималистичный Linux-дистрибутив, предназначенный для запуска контейнеров в больших кластерах и уже поддерживаемый Amazon, Azure, DigitalOcean, Google и Rackspace в публичных облаках. В рамках проекта также развивается система etcd, представляющая собой распределенное хранилище ключ — значение, предназначенное для управления конфигурацией кластера и service discovery, а с недавнего времени и альтернативная система управления контейнерами Rocket.
Главная задача etcd — распределенное хранение информации о местонахождении каждого компонента кластера, а именно информации о контейнерах, такой как IP-адреса, открытые порты и другое. Демон etcd запускается на каждом CoreOS-хосте и поддерживает консистентное состояние между всеми нодами кластерами с возможностью автоматического выбора новой мастер-ноды etcd в случае выхода текущей из строя. Фактически etcd решает обозначенную выше проблему связи между контейнерами, запущенными на разных машинах.
Rocket
В 2014 году в рамках проекта CoreOS началась разработка новой системы контейнерной виртуализации Rocket, которая должна прийти на смену Docker. Двумя основными поводами для «изобретения велосипеда» стали:
- Безопасность и децентрализация. Среди задач Rocket — решить одну из фундаментальных проблем Docker, проблему централизации (на каждой машине свой никак не связанный с другими демон docker). Rocket должен быть таким же простым, как Docker, но с акцентом на децентрализацию и безопасность.
- Открытый формат контейнеров. Docker — это ориентированная на приложения платформа, и поддержка консистентного стандартизованного формата контейнеров не входит в задачи его разработчиков. В противовес разработчики Rocket предлагают собственный расширяемый формат AppContainer, который должен стать неким стандартом в области контейнеров.
Rocket до сих пор находится в стадии активной разработки, но это многообещающий проект. А самое главное, что разработчики CoreOS вовсе не собираются отказываться от Docker, а обещают обеспечить взаимозаменяемость обеих систем и добавить в Rocket поддержку образов Docker.
Kubernetes
Свою руку к Docker приложила и Google, запустив проект Kubernetes. Компания открыла его код в июне 2014 года, и уже совсем скоро благодаря усилиям Mirantis он появится в каталоге приложений OpenStack.
Если говорить о назначении проекта, то это менеджер для управления кластерами из контейнеров, хотя сама Google предпочитает говорить о нем как об импровизационном инструменте, а не сервисе оркестрации. В этом есть смысл, так как в отличие от систем оркестрации здесь нет «дирижера». Система полностью динамическая в том смысле, что самостоятельно реагирует на события в реальном времени и позволяет поднять сервис, который просто будет работать и масштабироваться по запросу.
Kubernetes отходит от традиционной модели пула контейнеров и использует вместо этого понятие подов (pods). Каждый под — это группа объединенных общей задачей контейнеров, которые могут быть и микросервисом, и массивным приложением, разнесенным на несколько машин. Для запуска подов используется специальный планировщик, который автоматически подбирает наиболее подходящие для разворачивания сервиса ноды. В любой момент поды могут быть горизонтально масштабированы с помощью контроллера репликации.
Кейс номер 4. Запускаем софт в песочнице
Docker — превосходный инструмент для запуска приложений в песочнице. В предыдущей статье мы кратко рассмотрели несколько способов запуска консольных и десктопных приложений с помощью Docker, в том числе Firefox с пробрасыванием иксов внутрь контейнера. Сделать это было действительно очень просто, однако мы заимели ряд проблем, одна из которых связана с безопасностью (контейнер получал полный доступ к иксам), а вторая — с тем, что мы пробросили графику, но не пробросили звук. Что ж, пришло время разобраться с этими проблемами.
Есть два способа сделать то, что нам нужно. Первый — это использовать связку из виртуального X-сервера Xephyr и проброса аудиоустройств в контейнер. Второй — использовать функцию форвардинга протокола X11 в SSH и сервер PulseAudio для передачи звука по сети. Первый способ более прост в настройке, но требует, чтобы графическое приложение в контейнере работало внутри окна фиксированного размера (Xephyr — это X-сервер внутри X-сервера), поэтому мы пойдем по второму пути.
Для наглядности процесса создадим контейнер с Firefox с нуля. Не будем возиться с консолью и коммитами, а просто напишем простенький Dockerfile, который все сделает за нас. Для этого создаем в домашнем каталоге каталог ~/tmp (на самом деле имя может быть любым) и копируем в него наш публичный SSH-ключ:
$ cp ~/.ssh/id_rsa.pub ~/tmp
Далее здесь же создаем файл Dockerfile со следующим содержимым:
# Используем Ubuntu в качестве базы
FROM ubuntu:latest
# Устанавливаем Firefox и PulseAudio
RUN apt-get update &&\
apt-get install -yq &&\
openssh-server libpulse0 pulseaudio xauth firefox
# Добавляем наш публичный ключ в SSH
ADD id_rsa.pub /root/id_rsa.pub
RUN mkdir /root/.ssh &&\
cat /root/id_rsa.pub > /root/.ssh/authorized_keys
# Включаем форвардинг X11 и root-доступ в SSH
RUN echo 'X11Forwarding yes\nPermitRootLogin yes' >> /etc/ssh/sshd_config
# Без этого каталога SSH работать не будет
RUN mkdir /var/run/sshd
И собираем образ на его основе:
$ sudo docker build -t ubuntu-firefox
Теперь нам необходимо настроить PulseAudio на хост-системе так, чтобы он мог принимать звуковые потоки по сети. Для этого используем приложение paprefs:
$ sudo apt-get install paprefs
Запускаем paprefs, переходим на вкладку Network Server и отмечаем галочками опции Enable network access to local sound devices и Don’t requre authentication. Далее запускаем SSH внутри нашего контейнера с Firefox:
$ sudo docker run -ti --name firefox ubuntu-firefox /usr/sbin/sshd -D
Выясняем его IP-адрес:
$ sudo docker inspect firefox| grep IPAddress
И подключаемся по SSH
$ ssh -X -R 4713:localhost:4713 root@172.17.0.29
Опция -X здесь отвечает за проброс X11, а -R 4713:localhost:4713 создает обратный туннель между портом 4713 контейнера и тем же портом хост-системы. Это дефолтовый порт PulseAudio, а туннель нужен для того, чтобы Firefox смог получить к нему доступ. Оказавшись в контейнере, набираем следующую команду:
$ PULSE_SERVER="tcp:localhost:4713" firefox
Это все, теперь у нас есть Firefox, работающий в полностью отрезанной от хост-системы песочнице, и его всегда можно запустить с чистого листа, просто перезапустив контейнер. Интерфейс, конечно, будет несколько медлительным из-за шифрования и отсутствия аппаратного ускорения, но не настолько, чтобы это приносило серьезный дискомфорт. Подняв SkyDNS, данную настройку можно полностью автоматизировать с помощью скриптов и повесить на рабочий стол.
Вместо выводов
Это, конечно же, не все возможные применения Docker. В теории его можно использовать для гораздо большего количества задач, вплоть до распространения десктопного софта, но именно эти четыре задачи обычно становятся поводом установить и начать использовать Docker. И именно здесь он показывает всю свою мощь.