Содержание статьи

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

Два работающих контейнера: Apache/PHP + MySQL
Два работающих контейнера: Apache/PHP + MySQL
Запущенный WordPress
Запущенный 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-контейнеров с помощью нескольких кликов мышью прямо из графического интерфейса.

Драйвер Docker для Nova и его связь с остальными компонентами OpenStack
Драйвер Docker для Nova и его связь с остальными компонентами OpenStack

Принцип работы плагина Docker для Heat
Принцип работы плагина Docker для Heat

 

CoreOS

CoreOS — еще один крупный проект, имеющий прямое отношение к Docker. Это минималистичный Linux-дистрибутив, предназначенный для запуска контейнеров в больших кластерах и уже поддерживаемый Amazon, Azure, DigitalOcean, Google и Rackspace в публичных облаках. В рамках проекта также развивается система etcd, представляющая собой распределенное хранилище ключ — значение, предназначенное для управления конфигурацией кластера и service discovery, а с недавнего времени и альтернативная система управления контейнерами Rocket.

Главная задача etcd — распределенное хранение информации о местонахождении каждого компонента кластера, а именно информации о контейнерах, такой как IP-адреса, открытые порты и другое. Демон etcd запускается на каждом CoreOS-хосте и поддерживает консистентное состояние между всеми нодами кластерами с возможностью автоматического выбора новой мастер-ноды etcd в случае выхода текущей из строя. Фактически etcd решает обозначенную выше проблему связи между контейнерами, запущенными на разных машинах.

 

Rocket

В 2014 году в рамках проекта CoreOS началась разработка новой системы контейнерной виртуализации Rocket, которая должна прийти на смену Docker. Двумя основными поводами для «изобретения велосипеда» стали:

  1. Безопасность и децентрализация. Среди задач Rocket — решить одну из фундаментальных проблем Docker, проблему централизации (на каждой машине свой никак не связанный с другими демон docker). Rocket должен быть таким же простым, как Docker, но с акцентом на децентрализацию и безопасность.
  2. Открытый формат контейнеров. 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. И именно здесь он показывает всю свою мощь.

13 комментария

  1. dskecse

    16.08.2015 at 20:58

    В CentOS 7 нет утилиты ifconfig, она помечена как obsolete. Вместо нее можно использовать `ip addr show eth0`.

  2. dskecse

    17.08.2015 at 07:01

    Добавляем IP-адрес и имя хоста в /etc/hosts: `echo $’172.17.0.20\texample.com’ >> /etc/hosts `.

  3. dskecse

    17.08.2015 at 16:15

    В первом кейсе в качестве примеров приводятся названия образов www и mysql, в то время как авторы создавали образы example:www и example:mysql. Это видно на скриншоте перед тем, как грепаются названия образов.

  4. dskecse

    17.08.2015 at 16:16

    Редактируем конфиг Apache, указав имя хоста (web root установлен по дефолту в /var/www/html): echo ServerName example.com:80 >> /etc/httpd/conf/httpd.conf.

  5. dskecse

    18.08.2015 at 09:37

    Поскольку авторы, вероятно, используют CentOS 6 в качестве базового Docker образа, то команды будут отличаться по сравнению с CentOS 7, где по дефолту используется fakesystemd вместо sysvinit. Нужно использовать свой менеджер процессов в контейнере. То есть Docker контейнер по большому счету должен использоваться как *nix процесс, а не полноценная виртуальная машина. Следует настроить супервайзор, скажем, runit или monit, чтобы мониторить процессы и запускать их явно, не вызывая старт-скрипты или systemd тулзы. Альтернативой запуску супервайзора является непривилегированный systemd в контейнере. Контейнер с Apache можно было бы поднять следующей командой: docker run -d -P example:www /usr/sbin/httpd -DFOREGROUND.

  6. dskecse

    18.08.2015 at 09:47

    Для тех, кто работает с boot2docker на OSX: единственный воркараунд с пермишнами, который я нашел, — это использовать /tmp в качестве DATADIR. Команда docker run -v /tmp:/var/lib/mysql -ti centos:updated /bin/bash. В остальных случаях невозможно писать данные из контейнера.

  7. dskecse

    18.08.2015 at 09:48

    И да, в CentOS 7 по дефолту можно поставить форк мускула — MariaDB, но не сам MySQL.

  8. dskecse

    18.08.2015 at 10:11

    2 последние команды для первого кейса (в OSX) должны быть следующие:
    docker run -d -P -v /tmp/html:/var/www/html example:www /usr/sbin/httpd -DFOREGROUND
    docker run -d -P -v /tmp/mysql:/var/lib/mysql example:mysql /usr/bin/mysqld_safe —bind-address=0.0.0.0

  9. dskecse

    18.08.2015 at 10:19

    Порты лучше прописать явно:
    -p 80 для apache
    -p 3306 для mysql

  10. AlexPi

    27.10.2015 at 05:54

    Я вижу преимущество использования Docker в сравнении с ручным деплоем.
    Но в чем реальное преимущество в сравнении с деплоем через ansible в продакшене?
    Опишу свой проект, публик облако AWS, прайвет облако CloudStack. Все деплоится через ansible с помощью rpm-ов на машины с CentOS.
    Все конфиги firewall(iptables) и тд тоже настраиваются через Ansible.

    Вопрос, что здесь может дать Docker кроме локального тестирования(оно нам не нужно)?

    • AlexPi

      27.10.2015 at 06:13

      Увидел «Преимущества Docker перед LXC, OpenVZ и другими решениями виртуализации уровня ОС» https://xakep.ru/2015/06/01/docker-usage/, не убедило.
      Может если вы в AWS покупаете SPOT instance которые умирают раз в день и вам надо заново все накатить то Docker удобнее.
      Но если инстансы живут хотя бы по месяцу, то выполнить один скрипт в ansible чтобы создать инстанс и еще один чтобы накатить соответствующий для машины софт(а это обычно 1-2 приложения) не вижу смысла.
      Хотя если у вас какие-то microservice и на каждом боксе по 20-30 абсолютно разных приложений, которые не надо устанавливать на другие боксы, то может это и удобно. Но я думаю это редкий случай.

  11. samodelkin

    23.08.2016 at 21:29

    Большое количество ошибок и опечаток в коде статьи…

  12. bbq

    23.01.2017 at 09:23

    Спасибо за статью. Но есть опечатки.
    $ cd /tmp
    $ wget https://wordpress.org/latest.tar.gz
    $ sudo -s
    # mkdir /root/html
    # cd /root/mysql
    # tar xzvf /tmp/latest.tar.gz
    ШТА? (cd /root/mysql)

Оставить мнение

Check Also

Страдания с ReactOS. Почему в заменителе Windows работают трояны, но не работает Word

Сегодня в нашей кунсткамере демонстрируется необычайный организм — двадцатилетний зародыш …