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

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

LUKS container vs Border Patrol Agent. Как уберечь свои данные, пересекая границу

Не секрет, что если ты собрался посетить такие страны как США или Великобританию то, прежд…