Зачем нужен Docker и практика работы с ним

16 февраля 2015

Docker — это инструмент, предоставляющий удобный интерфейс для работы с контейнерами. Процессу, запущенному под Docker, кажется, что он работает в минимальном окружении, где помимо него есть только его дети. Хотя при этом процесс работает в той же операционной системе, что и остальные, нормальные, процессы, он просто их не видит, ровно как не видит файлов и всего остального за пределами своей «песочницы». Можно думать о Docker, как о прокачанном chroot или аналоге FreeBSD Jails.

Зачем нужен Docker, если есть тот же Vagrant? Главным образом, просто потому что виртуализация в Docker дешевле. Используя Vagrant, вы эмулируете работу целой операционной системы, тогда как Docker позволяет изолировать просто один процесс. Соответственно, используя одно и то же железо, с помощью Docker вы можете создать больше виртуальных окружений, чем при помощи Vagrant. Более того, контейнеры запускаются (и останавливаются тоже) практически моментально, так как в них не происходит загрузка отдельной ОС. К тому же, Docker использует «слоеную» файловую систему AuFS, благодаря которой контейнеры могут совместно использовать одинаковые части файловой системы, доступные только на чтение. Если образ файловой системы занимает 5 Гб и вы запускаете 100 виртуальных окружений, то Vagrant потребуется 500 Гб места, а Docker — на порядок меньше.

Ну понятно, то есть, Docker во всем лучше Vagrant? На самом деле — нет. Docker не позволит вам запустить Windows, если вы сидите под Ubuntu. Слои AuFS накапливаются, из-за чего приходится время от времени их «схлопывать», а также прибегать к другим трюкам. Отмечаются проблемы в безопасности и общая сложность Docker, из-за которых даже начались работы над альтернативным проектом Rocket. Наконец, в Docker есть целый ряд косяков, которые приходится либо обходить какими-то хаками, либо находить образы, в которых эти хаки уже расставили за тебя.

В общем и целом, если вас всем устраивает Vagrant, скорее всего, вам нужен Vagrant, а не Docker.

Прежде, чем мы перейдем к основам работы с Docker, нужно отметить еще один момент. Есть как минимум два способа запуска приложений в Docker. Первый — просто запустить с тонной флагов, указывающих, куда писать логи, где брать конфиг, какой порт слушать и так далее (поскольку это просто запуск процесса, а не sudo service ololo start), скрестить пальчики и надеяться, что все будет хорошо, даже при условии, что в окружении нет корректного initd, syslog, crond, и так далее. Второй — использовать специальный образ, например, baseimage, в котором решены эти и многие другие проблемы, тупо зайти в окружение по ssh, настроить все по-человечески и запускать приложение в полном соответствии с ожиданиями его разработчиков.

Я лично всегда был сторонником второго способа. Хотя находятся люди, утверждающие, что второй способ, видите ли, не идиоматичный, и единственным правильным способом является первый.

Дополнение: С момента написания этого поста появился Docker Desktop, позволяющий пользоваться Docker под Windows и MacOS.

Установка Docker. В Ubuntu 14.04 поставить Docker можно, воспользовавшись пакетом docker.io, но Docker вы в результате получите доисторический. Предпочтительнее воспользоваться следующей последовательностью действий:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 \
  --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
sudo sh -c "echo deb https://get.docker.com/ubuntu docker main \
 > /etc/apt/sources.list.d/docker.list"

sudo apt-get update
sudo apt-get install lxc-docker

Аналогами боксов из мира Vagrant в мире Docker являются образы (image). Множество готовых образов с предустановленными MongoDB, WordPress и много чем еще можно найти в официальном реестре. Мы с вами, как уже отмечалось, воспользуемся baseimage:

sudo docker pull phusion/baseimage

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

sudo docker run -i -t phusion/baseimage:latest /sbin/my_init -- bash -l

Прописываем свой ключик в ~/.ssh/authorized_keys. Теперь важный момент. Если остановить контейнер, все сделанные в нем изменения откатятся. Поэтому нужно сохранить состояние контейнера в новый образ. В соседнем терминале смотрим список запущенных контейнеров:

sudo docker ps

Из списка нас сейчас интересует container id. Сохраняем образ и останавливаем контейнер:

sudo docker commit 2fd baseimage-ssh
sudo docker stop 2fd

Теперь запускаем новый образ (-d означает «демонизировать»):

sudo docker run -d -i -t baseimage-ssh /sbin/my_init

По умолчанию контейнеры как бы находятся за NAT — они могут свободно ходить в сеть, но из сети нельзя так просто попасть в контейнеры. При этом с точки зрения хост-системы контейнеры находятся в сети 172.17.42.0/16. Узнать IP конкретного контейнера можно командой:

sudo docker inspect -f "{{ .NetworkSettings.IPAddress }}" 1bb

Насколько я могу судить, на данный момент нет простого способа присвоить контейнеру заранее известный IP адрес. Однако можно забиндить порты контейнера на интерфейсы-порты хост-системы, а также заставить контейнеры видеть друг друга при помощи файлов /etc/hosts. Подробности описаны здесь.

Давайте прибьем контейнер, затем запустим его снова, пробросив порт 22 контейнера на 127.0.0.1:222, и зайдем внутрь контейнера по ssh:

sudo docker stop 1bb
sudo docker run --dns 192.168.0.1 -p 127.0.0.1:222:22 \
  -d -i -t baseimage-ssh /sbin/my_init
ssh -p 222 root@localhost

Адрес DNS-сервера, используемого хост-системой, можно узнать, сказав:

nm-tool | grep DNS

Итак, мы в системе! Теперь можно настраивать все и вся привычными нам средствами. Давайте, например, поднимем Nginx:

apt-get update
apt-get install nginx
service nginx start
curl localhost

Должны увидеть:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Поскольку мы используем образ baseimage, а не совсем честную Ubuntu, для того, чтобы Nginx запускался при старте контейнера, нужно дополнительно отредактировать файл /etc/my_init.d/01_services.sh:

#!/bin/sh

service nginx start

А затем сказать:

chmod a+x /etc/my_init.d/01_services.sh

Теперь открываем терминал в хост-системе и сохраняем образ:

sudo docker ps
sudo docker commit 25f baseimage-nginx
sudo docker stop 25f

Запускаем получившийся образ и проверяем:

sudo docker run \
  --name docker-nginx --dns 192.168.0.1 \
  -p 127.0.0.1:222:22 -p 127.0.0.1:8080:80 \
  -d -i -t baseimage-nginx /sbin/my_init

curl localhost:8080

Заметьте, что здесь мы присвоили контейнеру имя, а то сколько уже можно пользоваться этими хэшами? Остановить контейнер теперь можно так:

sudo docker stop docker-nginx

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

sudo docker run -i -t baseimage-nginx:latest /sbin/my_init \
  --skip-startup-files -- bash -l

Наконец, образы можно экспортировать:

sudo docker save -o baseimage-nginx.img baseimage-nginx

… и импортировать:

sudo docker load -i baseimage-nginx.img

Кстати, экспортированные образы неплохо gzip’уются.

За кадром остался еще целый ряд интересных вопросов, например, ограничение ресурсов, используемых контейнером, монтирование части файловой системы хоста в госте, взаимодействие контейнеров друг с другом или написание Dockerfile. Но во всем этом вы и без меня разберетесь при помощи официальной документации.

А для каких целей вы используете Docker и используете ли его вообще?

Дополнение: Также вас могут заинтересовать посты Быстрое введение в Kubernetes и Тестирование проектов на Go с dockertest.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.