Docker. Немного теории и практики.

В последнее время набирает обороты использование контейнерной виртуализации для изоляции приложений. Зачем это нужно? Во-первых — безопасность. Приложение не может получить доступ к системе за пределы выделенного контейнера. Во-вторых — управление зависимостями. Все необходимые библиотеки устанавливаются в контейнер, а не на хост-систему и версии данных библиотек могут быть любыми, а не только теми, что доступны в хост-системе. В-третьих управление доступом к файловым системам и не только. Раздел или каталог могут быть примонтированы к разным контейнерам с разными опциями (например, каталог, в который пользователи сайта загружают файлы имеет смысл монтировать с опцией noexec, для того, чтобы из него было невозможно запустить исполняемые файлы).

Дальше всех в данном вопросе продвинулся инструментарий Docker. Попробуем в нем немного разобраться для того, чтобы вывести возможные области применения.

Начиная с версии 0.9 Docker больше не опирается на LXC для создания контейнеров, а использует (причем, по умолчанию) собственный API libcontainer, позволяющий абстрагироваться от конкретной системы виртуализации. libcontainer может создавать контейнеры как напрямую, общаясь с интерфейсами ядра, так и с существующим гипервизором, например LXC или Jail во FreeBSD.

Возникает вопрос, как libcontainer создает свои контейнеры и в чем их преимущества/недостатки по сравнению с уже существующими решениями? Как можно видеть здесь, libcontainer использует для создания и управления контейнерами cgroups (в. ч. подсистему ns aka «namespaces»), apparmor/SElinux и др. Отсюда вытекает важный момент:
С точки зрения технической реализации контейнеры Docker идентичны контейнерам LXC.
Таким образом, каких либо отличий с точки зрения производительности или системных ограничений от LXC у Docker нет, ибо «под капотом» у них прячутся одинаковые технологии.

Поднимемся уровнем выше. О том, что происходит внутри контейнера. LXC, OpenVZ, FreeBSD Jail представляют собой полноценные среды виртуализации. В виртуальных машинах на их основе работает полноценное окружение, ограниченное только возможностями ядра хост-машины. В Docker все происходит несколько иначе. Внутри любого Docker-контейнера (в идеале) может исполниться только один процесс указанный пользователем при создании, причем не демонизированный (демонизируется сам контейнер). По сути, можно провести параллель с системным загрузчиком, который загружает ядро в оперативную память, подготавливает окружение и запускает процесс /sbin/init, только в Docker вместо init запускается пользовательский процесс, указанный в Dockerfile (о нем чуть позже), причем как только он завершается (а если быть точным отдает exit status), умирает и сам контейнер. Отсюда вытекают следующие моменты:

1. Технически в Docker, можно запустить несколько сервисов, однако это нарушает философию проекта, поэтому не рекомендуется к применению.
2. Запускать приложение в контейнере необходимо не в качестве демона, а интерактивно.

Теперь поднимемся еще выше. Как запустить приложение в Docker?

В Docker-контейнерах нет процесса init и подключиться к нему невозможно, однако можно войти в его namespace. Тем не менее, делать что-либо в контейнере после его создания считается дурным тоном. Docker рассчитан на использование «поставил, настроил и забыл». Если необходимо что-либо изменить, то необходимо пересоздать образ и на его основе создать новый контейнер.
Для описания и создания среды контейнера используется так называемый Dockerfile. Это файл содержащий декларативное описание свойств шаблона, который используется для создания рабочих контейнеров. Помимо свойств непосредственно контейнера, данный файл содержит консольные команды для установки ПО и запуска необходимых действий. В качестве интерпретатора для консольных команд используется /bin/sh. Если необходим bash, то его следует указывать явно, например /bin/bash -c <команда>.

Итак, вооружившись данными знаниями, попробуем создать свой Docker-контейнер, который будет просто отображать страницу веб-сервера Apache по умолчанию.

1. Создадим директорию с настройками образа:

mkdir /var/lib/docker/images/apachetest

2. Создаем Dockerfile:

nano /var/lib/docker/images/apachetest/Dockerfile

FROM centos
RUN yum -y update
RUN yum -y install httpd
ENTRYPOINT httpd -D FOREGROUND

Где:
FROM обозначает на базе какого дистрибутива (точнее, образа дистрибутива) мы создаем свой образ;
RUN исполняет команду при создании образа. В данном случае, мы ставим Apache из репозиториев.
ENTRYPOINT команда, исполняемая при запуске контейнера. В данном случае, мы запускаем апач в интерактивном режиме (это важно).

3. Собираем образ:

docker build -t apachetest /var/lib/docker/images/apachetest/

Ключ -t задает имя образа, будем его использовать для создания контейнеров.

4. Запускаем контейнер:

docker run -d -p 80:80 apachetest

-d запускает контейнер как демона
-p проброс порта 80 хост машины на 80 порт контейнера.

5. Проверяем.
Наберите в адресной строке браузера http://127.0.0.1 и вы увидите перед собой страницу веб-сервера по умолчанию.

6. Просматриваем список контейнеров:

Активных: docker ps
Всех: docker ps -a

7. Останавливаем и удаляем контейнер:

docker stop [container]
docker rm [container]

Подведем небольшой итог. Зачем может пригодиться Docker в первую очередь?

1. Для формирования установочных snap-пакетов, работающих идентично, независимо от конфигурации хост машины. Docker обеспичивает повторяемость и идентичность среды исполнения приложения.
2. Для отладки приложений. За счет идентичности среды исполнения, раработчику не нужно волноваться о том, с какими версиями библиотек/заголовков работает приложение, что облегчает отладку.
3. Sandboxing (запуск в песочнице) сервисов, не зависящих от других демонов. То есть, если вам необходимо, чтобы в онтейнере работали одновременно, например Apache, Nagios, Rsyslog и Exim, то Docker вам не подойдет.

Добавить комментарий