Docker создание образа. Понимая Docker. Главные компоненты Docker

Docker — самая распространенная система контейнеризации, позволяющая запускать необходимое для разработки ПО в контейнерах не устанавливая его на локальную систему. В рамках данного материала разберем docker управление контейнерами.

Docker состоит из нескольких компонентов:
  1. Образ — сконфигурированный разработчиками набор ПО, который скачивается с официального сайта
  2. Контейнер — имплементация образа — сущность на сервере, созданная из него, контейнер не должен быть точной копией и может быть скорректирован используя Dockerfile
  3. Volume — область на диске, которую использует контейнер и в которую сохраняются данные. После удаления контейнера ПО не остается, данные же могут использоваться в будущем

Над всей структурой выстроена особым образом сеть, что позволяет пробрасывать желаемым образом порты и делать контейнер доступным снаружи (по умолчанию он работает на локальном IP адресе) через виртуальный бридж. Контейнер при этом может быть доступен как миру, так и одному адресу.

Docker управление контейнерами: базовые возможности

Установим Docker на Ubuntu или Debian сервер если он еще не установлен по инструкции . Лучше выполнять команды от имени непривилегированного пользователя через sudo

Запуск самого простого контейнера покажет, что все работает

Базовые комагды для управления контейнерами

Вывести все активные контейнеры можно так

С ключем -a будут выведены все контейнеры, в том числе неактивные

Dicker назначает имена контейнерам случайным образом, при необходимости можно указать имя непосредственно

docker run —name hello-world

Запускаем контейнер с именем my-linux-container на основе образа ubuntu и переходим в консоль контейнера указывая оболчку bash

docker run -it —name my-linux-container ubuntu bash

Чтобы выйти из контейнера и вновь оказаться на хост системе нужно выполнить

Все образы, на основе которых создаются контейнеры скачиваются с hub.docker.com автоматически при первом создании контейнера, те, что уже существуют локально можно увидеть выполнив docker images

Создание контейнера из уже скачанного образа будет происходить значительно быстрее (практически мгновенно)

При выходе из контейнера с exit он останавливается, чтобы этого не происходило выходить можно сочетанием клавиш CTRL + A + P

Можно убрать все контейнеры, не являющиеся активными

docker rm $(docker ps -a -f status=exited -q)

Или удалять их по одному

Вместо идентификатора в последней команде можно указать имя

В docker управление контейнерами производится за счет небольшого количества интуативно понятных команд:

docker container start ID

docker container stop ID

docker container restart ID

docker container inspect ID

Последняя особенно полезна, она выводит всю информацию о контейнере, конфигурационных файлах и используемых разделах диска. Весь список команд можно легко найти в помощи или на официальном сайте Docker-а

Создание своего образа Docker и использование Dockerfile

Образы обычно создаются из уже существующих за счет использования дополнительных опций, указанных в Dockerfile

FROM ubuntu
CMD echo «hello world»

Сейчас создается новый образ на основе стандартного с ubuntu

Собираем образ дав ему имя (точка в конце команды означает, что используется текущий каталог, а значит и Dockerfile в нем)

docker build -t my-ubuntu .

docker images теперь покажет и созданный только что образ my-ubuntu

Его можно запустить, в консоль при этом будет выведено hello world и это единственное отличие от дефолтного образа

Обычно нужны более сложные правила, например в образ нам нужно включить python3 — перйдем в новый каталог и создадим Dockerfile

FROM ubuntu
CMD apt-get update && apt-get install python3

Все инструкции записываются в одну строку

docker build -t my-ubuntu-with-python3 .

Запускаем контейнер переходя внутрь

docker run -it my-ubuntu-with-python3 bash

Внутри от имени root нужно выполнить dpkg -l | grep python3 , команда покажет, что пакет присутствие в системе, что означает успех

В том, что Docker - это действительно must have инструмент для разработчика и администратора сколько-нибудь крупного проекта. Но даже если это не так, Docker все равно нужно знать: уже в самом ближайшем будущем он будет везде, начиная от десктопного Linux-дистрибутива и заканчивая пулом серверов на AWS. А самое приятное, что разобраться с Docker довольно легко, если, конечно, правильно понимать принцип его работы.

Apt-get в мире виртуальных окружений

Docker базируется на технологиях namespaces и cgroups (первая обеспечивает изоляцию, вторая - группировку процессов и ограничение ресурсов), поэтому в плане виртуализации он мало чем отличается от привычных нам LXC/OpenVZ, и рассказывать тут особо не о чем. Та же нативная скорость работы, те же методы изоляции, основанные на механизмах ядра Linux. Однако уровнем выше начинается совсем другая история. Изюминка Docker в том, что он позволяет развернуть полноценное виртуальное окружение и запустить в нем приложение так же просто, как, например, перезапустить веб-сервер.

Абстрагируемся от деталей конкретных дистрибутивов и представим, что у нас есть чистый CentOS и мы хотим запустить в нем определенную команду в полностью виртуальном окружении без доступа к основной системе. Придется скачивать образы дистрибутивов, разворачивать их в систему и настраивать виртуальное окружение? Вовсе нет, все, что нужно сделать, - это запустить две команды:

$ sudo yum install docker-io $ sudo docker run -t ubuntu:latest /usr/bin/top

И это все. Мы только что запустили утилиту top внутри контейнера с окружением на базе последней доступной на данный момент версии Ubuntu с выводом информации в текущий терминал. И все это с помощью одной простой команды (установка не в счет). Неплохо, не правда ли? В общем-то, мы можем даже «зайти» в этот контейнер и делать все то, что обычно делают со свежеустановленной системой:

$ sudo docker run -t -i ubuntu:latest /bin/bash # apt-get update # apt-get install nginx #

Как видишь, с сетью тоже все ОK, поэтому мы можем обновить систему, установить и настроить любой софт. Немного похоже на магию, но на самом деле все очень просто. Docker - это своего рода apt-get в мире контейнеров, только вместо пакетов здесь образы файловой системы, а вместо официальных Debian/Ubuntu-репозиториев - облачное хранилище, называемое Docker Hub.

Когда мы выполнили «docker run...», система сделала следующее:

  1. Утилита docker связалась с демоном dockerd на нашей локальной машине, передала от нас привет и попросила запустить последнюю версию Ubuntu (об этом говорит тег latest в команде) в изолированном контейнере.
  2. Демон dockerd сверился со своей записной книжкой, сходил в каталог /var/lib/docker и выяснил, что образа файловой системы с последней Ubuntu на нашей машине нет, поэтому он решил обратиться к Docker Hub с целью выяснить, а есть ли такой образ там.
  3. Пообщавшись с Docker Hub, он убедился, что образ все-таки существует, и попросил отправить его нам.
  4. Получив нужный образ, dockerd смонтировал его файловую систему, сделал в нее chroot и запустил указанную в последнем аргументе команду, ограничив ее «область видимости» с помощью namespaces (по сути, отрезал ей доступ к основной ФС, процессам хост-системы, IPC и прочему, заперев в песочнице), но перекинул в нее файлы устройства текущего терминала (флаг -t), чтобы наш top смог отрисовать свой псевдографический интерфейс.

Изюминка такой модели в том, что Docker Hub открыт для всех и любой может подготовить собственный образ (об этом позже) и опубликовать его с целью установки на другую машину и/или другим человеком. На момент написания статьи в Docker Hub было опубликовано более 45 тысяч образов на все случаи жизни, начиная от образов «голых» дистрибутивов и заканчивая образами с преднастроенными серверными и десктопными приложениями, работающими в минималистичном Linux-окружении.

Что, если мы хотим запустить Firefox внутри виртуального окружения? Нет ничего проще, открываем Docker Hub в браузере, нажимаем Browse & Search и вбиваем firefox. На экран вывалится список результатов. Смотрим, kennethkl/firefox вроде вполне подходит. Клацаем по нему и видим инфу, как все это дело запустить. Автор говорит нам выполнить такую команду:

$ sudo docker run -d --name firefox -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix kennethkl/firefox

Пробуем. Да, действительно, после недолгого скачивания образа получаем на экране стандартный Firefox. На этом же примере, кстати, можно ознакомиться с еще четырьмя полезными опциями команды docker run:

  • -d - «демонизирует» контейнер, то есть просто отключает Docker от STDOUT виртуального окружения и позволяет ему работать в фоне;
  • --name - имя контейнера, которое он получит вместо идентификатора;
  • -e - позволяет «пробросить» в виртуалку переменную окружения;
  • -v - пробрасывает указанный файл или каталог (формат /файл/на/хост/системе:/файл/в/виртуалке или просто /файл/на/хост/системе, если пути совпадают).

В данном случае переменная и файл нужны для того, чтобы Firefox смог получить доступ к дисплею локальной машины. Это довольно небезопасно, так как любой процесс в контейнере не только сможет запускать любой софт на твоем десктопе, но и, например, перехватывать нажатия клавиш или передвижения курсора. Но для примера сойдет.

Есть и более простой способ поиска образов Docker, с помощью команды docker search:

$ sudo docker search nginx

INFO

Любой пользователь Docker может запустить свой личный приватный Hub. Он носит название «реестр» и доступен в виде уже готового образа. Все, что нужно сделать, - это просто запустить его: docker run -p 5555:5555 registry.

Демон Docker доступен не только с помощью клиента, но и с использованием RESTful API, причем как локально, так и с удаленной машины. Стандартные порты Docker - tcp/2375 e tcp/2376.

Образ Docker не обязательно запускать сразу после скачивания, можно сначала скачать его на локальную машину с помощью команды docker pull, а лишь затем запустить: docker pull ubuntu.

Слоеный пирог

Docker позволяет сделать работу с виртуальными окружениями максимально удобной, упрощая как процесс разворачивания окружений, так и настройки их взаимодействия с хост-системой (чего стоит только последний пример). Но это не единственная его изюминка.

Если ты уже успел поиграть с образом Ubuntu из первых двух примеров, то наверняка заметил, что каждый новый запуск контейнера происходит «с нуля», а все изменения, сделанные в прошлом сеансе, теряются. Это вовсе не баг, это одна из ключевых особенностей архитектуры Docker, которая делает его еще более интересным и привлекательным решением.

Дело в том, что в подавляющем большинстве случаев «образ Docker» - это вовсе не монолитный образ файловой системы, а своего рода слоеный пирог, состоящий из нескольких образов файловых систем, на основе которых формируется контейнер. При этом отдельно взятые образы ФС вовсе не отвечают за те или иные части структуры каталога (как, например, в случае с разбиением диска под Linux на разделы /home, /var, /boot), а наслаиваются друг на друга с помощью механизма AUFS ядра Linux (также есть поддержка той же функциональности через использование btrfs, device mapper и overlay).

Чтобы разобраться с тем, как это работает, вернемся к нашей контейнерной Ubuntu. Запускаем контейнер и устанавливаем nginx, как показано во втором примере в начале статьи, но не завершаем его. Вместо этого запускаем еще один терминал и смотрим список запущенных контейнеров:

$ sudo docker ps

Эта команда покажет все запущенные контейнеры вместе с их ID, используемым образом, запущенной командой, временем работы и прочим. Нас интересует значение в столбце CONTEINER ID. Копируем его и запускаем следующую команду:

$ sudo docker commit ID-контейнера ubuntu-nginx

После того как она отработает, можно выйти из контейнера, таким образом завершив его работу. А далее просто запускаем контейнер ubuntu-nginx и видим, что nginx никуда не пропал и находится на своем месте:

$ sudo docker run -i -t ubuntu-nginx /bin/bash # which nginx /usr/sbin/nginx

Что же мы сделали? Мы создали еще один слой, то есть дополнительный образ ФС, и сгенерировали новый Docker-образ на основе уже существующего Docker-образа Ubuntu с включением нашего образа ФС, который содержит nginx. Звучит немного путано, правда? На самом деле все довольно просто.

Мы уже выяснили, что каждый Docker-образ состоит из нескольких образов ФС. Когда мы запускаем контейнер, эти образы монтируются и собираются в одну структуру каталога с помощью AUFS. Например, первый образ может содержать только базовую установку Ubuntu, второй добавляет к ней набор стандартных демонов, третий - утилиты администрирования и так далее. Docker монтирует все слои в режиме «только чтение», но, чтобы мы имели возможность изменять содержимое образа, сверху подключается еще один изначально пустой слой в режиме «чтение/запись».


По умолчанию после завершения контейнера (которое происходит после завершения последнего работающего в нем процесса) последний слой стирается и все наши изменения пропадают. Однако, используя команду docker commit, мы можем «зафиксировать» изменения, создав новый Docker-образ на основе уже существующих образов ФС плюс образа ФС с нашими изменениями. Так внесенные нами изменения сохранятся. По желанию мы можем запустить контейнер ubuntu-nginx, внести в него изменения и точно так же сохранить в новый Docker-образ с помощью commit, добавив еще один слой. Чтобы посмотреть список всех получившихся в итоге (и полученных из Docker Hub) образов, можно использовать команду docker images, а для просмотра истории формирования слоев - команду docker history:

$ sudo docker history ubuntu-nginx

Такой подход к формированию образов дает большую гибкость в управлении контейнерами, экономит уйму времени и позволяет с легкостью переносить уже сконфигурированные Docker-образы между машинами (образ можно выложить на Docker Hub и затем развернуть на другой машине). Менее очевидный плюс - экономия дискового пространства. Если мы развернем на машине целый зоопарк контейнеров, каждый из которых будет изначально основан на одном базовом образе (той же Ubuntu, например) - они все будут ссылаться на этот базовый образ и не дублировать его содержимое.


Docker вне Linux

Единственный способ запустить Docker в OS X или Windows - это установить его в виртуальную машину. Не обязательно делать это вручную, можно воспользоваться уже готовым решением, например boot2docker. Это набор скриптов, которые позволяют быстро развернуть виртуальную машину с Linux и Docker внутри VirtualBox и запустить ее с автоматическим открытием доступа по SSH. Инструкцию по его использованию и сам инсталлятор можно найти на официальном сайте Docker .

Настройка сети

Для того чтобы контейнеры могли общаться между собой и с внешним миром, Docker автоматически поднимает виртуальный сетевой мост и настраивает правила маскарадинга (NAT) для внешнего сетевого интерфейса. Это значит, что извне достучаться до контейнеров не получится. Однако мы можем настроить проброс портов, чтобы запрос к определенным портам внешнего сетевого интерфейса машины автоматически перенаправлялся на указанные порты контейнера. Например, в компании Mirantis главный узел Fuel (это такой GUI для деплоя и настройки OpenStack) запускается в Docker и использует функцию проброса портов, чтобы открыть доступ к контейнеру ful/nginx (порт 8000):

$ sudo docker run -d -p 8000:8000 fuel/nginx_6.0:latest /usr/local/bin/start.sh

Мы могли бы пробросить порт 8000 на любой другой порт контейнера, просто изменив второе число в опции -p, но в данной конфигурации это не имеет смысла.

Проброс файлов и Dockerfile

В начале статьи мы уже познакомились с флагом -v, позволяющим пробросить в контейнер любой файл или каталог из хост-системы. Это очень удобная функция, ее можно использовать как для хранения каких-либо временных данных, так и для расшаривания файлов между несколькими контейнерами. В Mirantis эта функция используется для проброса файлов конфигурации сервиса Fuel/astute (/etc/astute) внутрь контейнера:

$ sudo docker run -d -v /etc/astute fuel/astute_6.0:latest /usr/local/bin/start.sh

То же самое можно сделать с помощью команды VOLUME в Dockerfile. Сам по себе Dockerfile - это местный эквивалент Makefile, но если последний предназначен для сборки приложений из исходных текстов, то Dockerfile позволяет собирать образы для Docker. Его назначение - упростить создание новых образов без необходимости запускать контейнер, производить в нем какие-то операции и выполнять коммит. Ты можешь просто написать Dockerfile, и Docker сделает все за тебя. Для примера рассмотрим Dockerfile для сборки Fuel/astute:

FROM fuel/centos MAINTAINER Matthew Mosesohn [email protected] RUN rm -rf /etc/yum.repos.d/*;\ echo -e "\nname=Nailgun Local Repo\nbaseurl=http://$(route -n | awk "/^0.0.0.0/ { print $2 }"):_PORT_/os/x86_64/\ngpgcheck=0" > /etc/yum.repos.d/nailgun.repo;\ yum clean all;\ yum --quiet install -y ruby21-nailgun-mcagents sysstat ADD etc /etc ADD start.sh /usr/local/bin/start.sh RUN puppet apply --detailed-exitcodes -d -v /etc/puppet/modules/nailgun/examples/astute-only.pp; [[ $? == 0 || $? == 2 ]] RUN chmod +x /usr/local/bin/start.sh;\ echo -e "\nname=Nailgun Local Repo\nbaseurl=file:/var/www/nailgun/centos/x86_64\ngpgcheck=0" > /etc/yum.repos.d/nailgun.repo; yum clean all VOLUME /etc/astute CMD /usr/local/bin/start.sh

Нетрудно понять, для чего он предназначен. Он создает образ на базе fuel/centos, запускает несколько команд для подготовки образа, добавляет в образ файлы из текущего каталога, применяет манифест Puppet, меняет права доступа на некоторые файлы, пробрасывает в контейнер каталог /etc/asture/ из хост-системы и запускает контейнер с помощью команды /usr/local/bin/start.sh.

Для сборки контейнера достаточно положить Dockerfile и все файлы, которые будут добавлены в него, в какой-нибудь каталог и выполнить следующую команду:

$ sudo docker build fuel/astute_6.0:latest

В данном случае мы выбрали имя fuel/astute_6.0:latest, хотя оно может быть любым.

Нюансы работы с Docker

Docker построен вокруг идеи о том, что в каждом контейнере должен работать только один сервис. Ты расфасовываешь Apache, MySQL, nginx, Varnish и все, что может понадобится для проекта, по разным контейнерам, а затем используешь Docker для сборки всего этого вместе. Такой подход дает большую гибкость, так как позволяет с легкостью менять конфигурацию, тестировать обновления и выполнять миграцию отдельных сервисов на другие машины.

По этой же причине Docker не принято использовать для запуска полноценных Linux-окружений с демоном init, демонами cron и syslog и другими стандартными компонентами дистрибутива. Вместо этого мы просто запускаем нужный нам сервис, и он работает в виртуальном окружении в полном одиночестве:

$ sudo docker run -d -p 80 ubuntu-nginx /usr/sbin/nginx

Но здесь есть небольшая проблема. Docker завершает работу контейнера сразу после того, как будет завершен запущенный в нем процесс (в данном случае nginx), а так как nginx по умолчанию демонизируется, то есть форкает новый процесс и завершает тот, что мы запустили руками, то Docker сразу после этого завершает и контейнер, прибивая форкнутый Docker.

В случае с nginx обойти эту проблему можно, добавив daemon off; первой строкой в его конфиг. Для других демонов потребуются свои настройки, а некоторым можно запретить демонизироваться прямо из командной строки. Например, в sshd для этого предусмотрен флаг -D:

$ sudo docker run -d -p 22 ubuntu-ssh /usr/sbin/sshd -D

В любой момент к контейнеру можно подключиться с помощью команды docker exec с целью просмотреть логи или изменить настройки (здесь и далее ID-контейнера - это либо ID, которое можно увидеть в выводе docker ps, либо имя, заданное при запуске в опции --name):

$ sudo docker exec -ti ID-контейнера /bin/bash

Но и здесь есть одна небольшая загвоздка. Как мы знаем, вся накопленная во время работы виртуального окружения информация потеряется, если мы завершим работу виртуального окружения, а вместе с ней исчезнут логи и изменения, внесенные в настройки. Бесконечно создавать слои мы тоже не можем (хотя бы потому, что их может быть не больше 127), но мы можем пойти немного другим путем и воспользоваться встроенной в Docker системой агрегации логов. Конечно, Docker не умеет собирать логи отдельных приложений, но умеет накапливать вывод STDOUT, то есть любой консольный вывод. Все, что нам остается, - это изменить конфиг nginx так, чтобы логи сыпались в /dev/stdout, а затем просматривать их с помощью команды docker logs:

$ sudo docker logs ID-контейнера

Другой и более правильный вариант - это просто вынести логи (а если нужно, и настройки) на хост-систему с помощью уже описанной опции -v:

$ sudo mkdir /root/logs $ sudo docker run -d -v /root/logs:/var/logs -p 80 ubuntu-nginx /usr/sbin/nginx

При необходимости контейнер можно остановить корректно, завершив работающий в нем сервис с помощью команды docker stop:

$ sudo docker stop ID-контейнера

А если корректно остановить по какой-то причине не выходит, то можно и прибить его с помощью kill:

$ sudo docker kill ID-контейнера

При этом происходит одна важная вещь, о которой забывают многие новички: Docker сохраняет метаинформацию о контейнере. На деле это значит, что если ты запускаешь, например, nginx, указав с помощью аргументов команды docker run его имя, каталоги, которые нужно пробросить в контейнер, порты, переменные окружения и тому подобное, то вся эта информация будет сохранена при завершении контейнера и, чтобы запустить его в следующий раз, тебе уже не придется ее указывать, а достаточно просто выполнить такую команду (вместо ID можно использовать имя):

$ sudo docker start ID-контейнера

Если в сохранении состояния нет необходимости (например, для тестирования или проверки какой-то функциональности), то можно использовать флаг --rm, который заставит Docker полностью уничтожить контейнер после его завершения (с сохранением образа):

$ sudo docker run --rm -i -t busybox /bin/bash

Уничтожить все ранее сохраненные контейнеры можно с помощью такой команды:

# docker rm $(docker ps -a -q)

Docker умеет самостоятельно перезапускать контейнеры в случае их падения и даже запускать их во время старта системы. Все, что для этого нужно сделать, - просто использовать опцию --restart:

$ sudo docker run --restart=always \ -d -v /root/logs:/var/logs -p 80 \ ubuntu-nginx /usr/sbin/nginx

В любой момент образ можно экспортировать в единый файл и затем импортировать на другой машине. Для этого предусмотрены команды docker save и docker restore. Использовать их очень просто, экспорт выполняется так:

$ sudo docker save -o ubuntu-nginx.img ubuntu-nginx

А импорт так:

$ sudo docker load -i ubuntu-nginx.img

Выводы

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

Преимущества Docker перед LXC, OpenVZ и другими решениями виртуализации уровня ОС

  1. Docker использует переносимый универсальный формат образов. Это означает, что эти образы могут быть без каких-либо проблем перенесены на другую машину и расшарены для использования другими юзерами.
  2. Образ может служить базой для других образов. В Docker считается нормой использовать множество слоев для формирования конечного образа. Ты можешь начать с базового образа Ubuntu, затем добавить Apache 2.4, чтобы создать микросервис Ubuntu + Apache.
  3. При выполнении коммита образ можно версионировать, так же как это делается в GIT.
  4. У Docker большое комьюнити и обширная экосистема, которая включает серьезное количество инструментов масштабирования, группировки, мониторинга, разворачивания и управления контейнерами.

Мы не раз затрагивали тематику и рассматривали множество систем для их построения. Сегодня мы познакомим еще с одной замечательной системой контейнерами Docker.

Начнем с того, что опишем базовый функционал, который пригодится в дальнейших статьях цикла, и кратко напомним об архитектуре Docker. Docker использует клиент-серверную архитектуру и состоит из клиента – утилиты docker, которая обращается к серверу при помощи RESTful API , и демона в операционной системе Linux (см. рис. 1). Хотя Docker работает и в отличных от Linux ОС, в этой статье они не рассматриваются.

Основные компоненты Docker:
    • Контейнеры – изолированные при помощи технологий операционной системы пользовательские окружения, в которых выполняются приложения. Проще всего дать определение контейнеру Docker как запущенному из образа приложению. Кстати, именно этим идеологически и отличается Docker, например, от LXC (Linux Containers ), хотя они используют одни и те же технологии ядра Linux. Разработчики проекта Docker исповедует принцип: один контейнер – это одно приложение.
    • Образы – доступные только для чтения шаблоны приложений. Поверх существующих образов могут добавляться новые уровни, которые совместно представляют файловую систему, изменяя или дополняя предыдущий уровень. Обычно новый образ создается либо при помощи сохранения уже запущенного контейнера в новый образ поверх существующего, либо при помощи специальных инструкций для утилиты . Для разделения различных уровней контейнера на уровне файловой системы могут использоваться AUFS, btrfs, vfs и Device Mapper . Если предполагается использование Docker совместно с SELinux , то требуется Device Mapper.
    • Реестры (registry) , содержащие репозитории (repository ) образов, – сетевые хранилища образов. Могут быть как приватными, так и общедоступными. Самым известным реестром является .

Для изоляции контейнеров в операционных системах GNU/Linux используются стандартные технологии ядра Linux, такие как:
  • Пространства имен (Linux Namespaces ).
  • Контрольные группы (Cgroups ).
  • Средства управления привилегиями (Linux Capabilities ).
  • Дополнительные, мандатные системы обеспечения безопасности, такие как AppArmor или SELinux.

Рассмотрим перечисленные технологии чуть более подробно.

Механизм контрольных групп (Cgroups) предоставляет инструмент для тонкого контроля над распределением, приоритизацией и управлением системными ресурсами. Контрольные группы реализованы в ядре Linux. В современных дистрибутивах управление контрольными группами реализовано через systemd , однако сохраняется возможность управления при помощи библиотеки libcgroup и утилиты cgconfig . Основные иерархии контрольных групп (их также называют контроллерами) перечислены ниже:

  • blkio – задает лимиты на операции ввода-вывода и на доступ к блочным устройствам;
  • cpu – используя планировщик процессов, распределяет процессорное время между задачами;
  • cpuacct – создает автоматические отчеты по использованию ресурсов центрального процессора. Работает совместно с контроллером cpu , описанным выше;
  • cpuset – закрепляет за задачами определенные процессоры и узлы памяти;
  • devices – регулирует доступ задачам к определенным устройствам;
  • freezer – приостанавливает или возобновляет задачи;
  • memory – устанавливает лимиты и генерирует отчеты об использовании памяти задачами контрольной группы;
  • net_cls – осуществляет тегирование сетевых пакеты идентификатором класса (classid ). Это позволяет контроллеру трафика (команда tc ) и брандмауэру (iptables ) учитывать эти тэги при обработке трафика;
  • perf_event – позволяет производить мониторинг контрольных групп при помощи утилиты perf;
  • hugetlb – позволяет использовать виртуальные страницы памяти большого размера и применять к ним лимиты.

Пространства имен, в свою очередь, контролируют не распределение ресурсов, а доступ к структурам данных ядра. Фактически это означает изоляцию процессов друг от друга и возможность иметь параллельно «одинаковые», но не пересекающиеся друг с другом иерархии процессов, пользователей и сетевых интерфейсов. При желании разные сервисы могут иметь даже свои собственные loopback-интерфейсы .

Примеры пространств имен, используемых Docker:
  • PID, Process ID – изоляция иерархии процессов.
  • NET, Networking – изоляция сетевых интерфейсов.
  • PC, InterProcess Communication – управление взаимодействием между процессами.
  • MNT, Mount – управление точками монтирования.
  • UTS, Unix Timesharing System – изоляция ядра и идентификаторов версии.

Механизм под названием Capabilities позволяет разбить привилегии пользователя root на небольшие группы привилегий и назначать их по отдельности. Данный функционал в GNU/Linux появился начиная с версии ядра 2.2. Изначально контейнеры запускаются уже с ограниченным набором привилегий.

При помощи опций команды docker можете разрешать и запрещать:
  • операции монтирования;
  • доступ к сокетам;
  • выполнение части операций с файловой системой, например изменение атрибутов файлов или владельца.

Подробнее ознакомиться с привилегиями можно при помощи man-страницы CAPABILITIES(7) .

Установка Docker

Рассмотрим установку Docker на примере CentOS. При работе с CentOS у вас есть выбор: использовать последнюю версию из upstream или версию, собранную проектом CentOS с дополнениями Red Hat. Описание изменений доступно на странице.

В основном это обратное портирование исправлений из новых версий upstream и изменения, предложенные разработчиками Red Hat, но пока не принятые в основной код. Наиболее заметным различием на момент написания статьи было то, что в новых версиях сервис docker был разделен на три части: демон docker, containerd и runc . Red Hat пока не считает, что это изменение стабильно, и поставляет монолитный исполнимый файл версии 1.10.

Настройки репозитория для установки upstream-версии , как и инструкции для инсталляции в других дистрибутивах и ОС, приведены в руководстве по инсталляции на официальном сайте . В частности, настройки для репозитория CentOS 7:

# cat /etc/yum.repos.d/docker.repo name=Docker Repository baseurl=https://yum.dockerproject.org/repo/main/centos/7 enabled=1 gpgcheck=1 gpgkey=https://yum.dockerproject.org/gpg

# cat /etc/yum.repos.d/docker.repo

name = Repository

baseurl = https : / / yum .dockerproject .org / repo / main / centos / 7

enabled = 1

gpgcheck = 1 gpgkey = https : / / yum .dockerproject .org / gpg

Устанавливаем необходимые пакеты на и запускаем и включаем сервис:

# yum install -y docker-engine # systemctl start docker.service # systemctl enable docker.service

# yum install -y docker-engine

# systemctl start docker.service

# systemctl enable docker.service

Проверяем статус сервиса:

# systemctl status docker.service

# systemctl status docker.service

Также можно посмотреть системную информацию о Docker и окружении:

# docker info

При запуске аналогичной команды в случае установки Docker из репозиториев CentOS увидите незначительные отличия, обусловленные использованием более старой версии программного обеспечения. Из вывода docker info можем узнать, что в качестве драйвера для хранения данных используется Device Mapper , а в качестве хранилища – файл в /var/lib/docker/:

# ls -lh /var/lib/docker/devicemapper/devicemapper/data -rw-------. 1 root root 100G Dec 27 12:00 /var/lib/docker/ devicemapper/devicemapper/data

# ls -lh /var/lib/docker/devicemapper/devicemapper/data

Rw -- -- -- - . 1 root root 100G Dec 27 12 : 00 / var / lib / / devicemapper / devicemapper / data

Опции запуска демона, как это обычно бывает в CentOS, хранятся в /etc/sysconfig/ . В данном случае имя файла docker. Соответствующая строчка /etc/sysconfig/docker , описывающая опции:

OPTIONS="--selinux-enabled --log-driver=journald"

Если бы вы запустили команду docker не пользователем root и не пользователем, входящим в группу docker, вы бы увидели подобную ошибку:

$ docker search mysql

$ search mysql

Warning: failed to get default registry endpoint from daemon (Cannot connect to the Docker daemon. Is the docker daemon running on this host?). Using system default: https://index. docker.io/v1/

Cannot connect to the Docker daemon. Is the docker daemon running on this host?

Обратите внимание, что фактически включение пользователя в группу docker равносильно включению этого пользователя в группу root.

У разработчиков RHEL/CentOS несколько иной подход к безопасности демона Docker, чем у разработчиков самого Docker из upstream. Подробнее о подходе Red Hat написано в статье разработчика дистрибутива RHEL Дэна Уолша .

Если же вы хотите «стандартное» поведение Docker, установленного из репозиториев CentOS (т.е. описанное в официальной документации), то необходимо создать группу docker и добавить в опции запуска демона:

OPTIONS="--selinux-enabled --log-driver=journald ↵ --group=docker"

OPTIONS = "--selinux-enabled --log-driver=journald ↵ --group=docker"

После чего рестартуем сервис и проверяем, что файл сокета docker принадлежит группе docker, а не root:

# ls -l /var/run/docker.sock

Поиск образов и тэги Docker

Попробуем найти контейнер на Docker Hub.

$ docker search haproxy

$ search haproxy


В данном выводе мы получили список ряда образов HA Proxy. Самый верхний элемент списка – это HA Proxy из официального репозитория. Такие образы отличаются тем, что в имени отсутствует символ «/» , отделяющий имя репозитория пользователя от имени самого контейнера. В примере за официальным показаны два образа haproxy из открытых репозиториев пользователей eeacms и million12.

Образы, подобные двум нижним, можете создать сами, зарегистрировавшись на Docker Hub. Официальные же поддерживаются специальной командой, спонсируемой Docker, Inc. Особенности официального репозитория:

  • Это рекомендованные к использованию образы, созданные с учетом лучших рекомендаций и практик.
  • Они представляют собой базовые образы, которые могут стать отправной точкой для более тонкой настройки. Например, базовые образы Ubuntu, CentOS или библиотек и сред разработки.
  • Содержат последние версии программного обеспечения с устраненными уязвимостями.
  • Это официальный канал распространения продуктов. Чтобы искать только официальные образы, можете использовать опцию –filter “is-official=true” команды docker search .

Число звезд в выводе команды docker search соответствует популярности образа. Это аналог кнопки Like в социальных сетях или закладок для других пользователей. Automated означает, что образ собирается автоматически из специального сценария средствами Docker Hub. Обычно следует отдавать предпочтение автоматически собираемым образам вследствие того, что его содержимое может быть проверено знакомством с соответствующим файлом .

Скачаем официальный образ HA Proxy:

$ docker pull haproxy Using default tag: latest

Полное имя образа может выглядеть следующим образом:

[имя пользователя]имя образа[:тэг]

Просмотреть список скаченных образов можно командой docker images:

Запуск контейнеров

Для запуска контейнера не обязательно предварительно скачивать образ. Если он доступен, то будет загружен автоматически. Давайте попробуем запустить контейнер с Ubuntu. Мы не будем указывать репозиторий, и будет скачан последний официальный образ, поддерживаемый Canonical.

$ docker run -it ubuntu root@d7402d1f7c54:/#

$ run - it ubuntu

root @ d7402d1f7c54 : / #

Помимо команды run , мы указали две опции: -i – контейнер должен запуститься в интерактивном режиме и -t – должен быть выделен псевдотерминал. Как видно из вывода, в контейнере мы имеем привилегии пользователя root, а в качестве имени узла отображается идентификатор контейнера. Последнее может быть справедливо не для всех контейнеров и зависит от разработчика контейнера. Проверим, что это действительно окружение Ubuntu:

root@d7402d1f7c54:/# cat /etc/*release | grep DISTRIB_DESCRIPTION DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"

root @ d7402d1f7c54 : / # cat /etc/*release | grep DISTRIB_DESCRIPTION

DISTRIB_DESCRIPTION = "Ubuntu 16.04.1 LTS"

Команду uname -a для подобных целей использовать не получится, поскольку контейнер работает с ядром хоста.

В качестве одной из опций можно было бы задать уникальное имя контейнера, на которое можно для удобства ссылаться, помимо ID-контейнера. Она задается как –name <имя>. В случае если опция опущена, имя генерируется автоматически.

Автоматически генерируемые имена контейнеров не несут смысловой нагрузки, однако как интересный факт можно отметить, что имена генерируются случайным образом из прилагательного и имени известного ученого, изобретателя или хакера. В коде генератора для каждого имени можно найти краткое описание того, чем известен данный деятель.

Посмотреть список запущенных контейнеров можно командой . Для этого откроем второй терминал:

Однако если отдать команду , контейнера, созданного из образа mysql, мы не обнаружим. Воспользуемся опцией -a , которая показывает все контейнеры, а не только запущенные:

Очевидно, что при запуске контейнера не были указаны обязательные параметры. Ознакомиться с описанием переменных среды, необходимых для запуска контейнера, можно, найдя официальный образ MySQL на Docker Hub. Повторим попытку, используя опцию -e , которая задает переменные окружения в контейнере:

$ docker run --name mysql-test ↵ -e MYSQL_ROOT_PASSWORD=docker -d mysql

Последним параметром выступает команда, которую мы хотим исполнить внутри контейнера. В данном случае это командный интерпретатор Bash . Опции -it аналогичны по назначению использованным ранее в команде docker run.

Фактически после запуска этой команды в контейнер mysql-test добавляется еще один процесс – bash . Это можно наглядно увидеть при помощи команды pstree. Сокращенный вывод до команды docker exec: