Docker: containerd, runC, runV и в общем про архитектуру

Сначала Docker был монолитом. И это преподносилось как преимущество. Например, в знаменитом “Introduction to Docker” на 20-й минуте Соломон Хайкс рассказывает как здорово, что Docker — это один файл. Кладешь его в систему, и готово! Позже, однако, монолитность была названа одной из причин размолвки между Docker и CoreOS и последовавшего за этим бурления на Hacker News:

Docker process model — where everything runs through a central daemon — is fundamentally flawed. To “fix” Docker would essentially mean a rewrite of the project…

Но всё, что ни делается — к лучшему. Резонанс утих, а сообщество разработчиков договорилось о необходимости разрабатывать открытые стандарты и делать декомпозицию. Появился проект ​Open Container Initiative (OCI) и разные интересные названия: containerd, runC, runV, runZ

Если сейчас (речь о версии 1.12) посмотреть на процессы, запущенные на машине с работающим Докером, то будет примерно такая картина:

  • docker — консольный клиент, посредством которого мы управляем Докером.

  • dockerd — демон, обеспечивающий само существование и работу Докера.

  • docker-proxy — так называемый userland proxy, который по умолчанию запускается для каждого контейнера, если он пробрасывает наружу порты.

А что же такое docker-containerd и docker-containerd-shim?

Оказывается, самая важная функциональность (запуск контейнеров) вынесена в отдельные проекты, у которых даже есть свои смешные логотипы и сайты: runC и containerd.

runC — это реализация спецификации OCI на базе средств ядра Linux (namespaces, cgroups, seccomp, capabilities, SELinux и так далее).

runC запускает Linux-процессы в изолированных окружениях, в контейнерах.

В ранних версиях (до 0.9) Docker использовал для этого LXC. Позже написали свою библиотеку libcontainer и пересели на неё. Ну а с началом движухи вокруг Open Container Initiative переупаковали её в как бы самодостаточный инструмент и переименовали в “runC”. Вот так.

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

Зачем понадобилось так много сущностей? Тут и технические причины, и желание разбить сложную систему на более простые компоненты, и стремление следовать принципам философии UNIX. Подробности можно почерпнуть из следующих источников:

Но что тут самое интересное — это то, что containerd и runC можно подменять на другие OCI-совместимые реализации. И уже существует проект runV от компании HyperHQ и продукт HyperContainer, в котором Docker запускает контейнеры в виде виртуальных машин.

Стоп-стоп-стоп! Как же так? Что за нонсенс? С самого начала Docker противопоставлялся виртуальным машинам, а тут такое дело…

Видимо, мы наблюдаем момент, когда термин “контейнер” выходит за свои привычные рамки и начинает обозначать любую технологию изоляции, будь то виртуальные машины, Linux-контейнеры, FreeBSD Jails, Solaris Zones, Windows Containers или что-либо ещё. Есть неподтверждённые слухи, что Oracle и Intel уже работают над своими реализациями OCI-стандарта. Жуть!

А хотите попробовать runV в действии? Это несложно.

Я использовал Ubuntu 16.04, Docker 1.12 и runV 0.6

Сначала установим зависимости:

sudo apt-get install autoconf automake pkg-config make gcc git golang qemu

Скачиваем, собираем и устанавливаем runV:

export GOPATH=$HOME/gopath
git clone https://github.com/hyperhq/runv/ $GOPATH/src/github.com/hyperhq/runv
cd $GOPATH/src/github.com/hyperhq/runv
./autogen.sh
./configure
sudo GOPATH=$GOPATH make install

Убеждаемся, что всё срослось:

runv

NAME:
   runv - Open Container Initiative hypervisor-based runtime
   . . .
runv-containerd --help

NAME:
   runv-containerd - High performance hypervisor based container daemon
   . . .

Теперь надо подготовить ядро и initrd для виртуальных машин:

cd ~
git clone https://github.com/hyperhq/hyperstart
cd hyperstart
./autogen.sh
./configure
make

В директории ~/hyperstart/build должны появиться файлы kernel и hyper-initrd.img.

Что ж, приготовления закончены. Можно запускать.

Сначала останавливаем Docker daemon:

sudo service docker stop

Запускаем реализацию containerd, предназначенную для работы с runV:

sudo runv-containerd --debug \
    --listen /tmp/runv.sock \
    --driver qemu \
    --kernel $HOME/hyperstart/build/kernel \
    --initrd $HOME/hyperstart/build/hyper-initrd.img

Открываем ещё один терминал, и запускаем в нём Docker с привязкой к новому containerd:

sudo docker daemon -D -l debug --containerd=/tmp/runv.sock

В третьем терминале создаём контейнер:

sudo docker run --rm -ti ubuntu

Повиснет пауза в несколько секунд, а runv-containerd начнёт писать в консоль, как он запускает QEMU

В четвёртом терминале можно дать команду ps ax | grep qemu и убедиться, что действительно работает виртуальная машина.

Внутри контейнера мне удалось выполнить apt-get и запустить nginx. Монтирование томов и проброс портов тоже получилось.

Непонятно, конечно, насколько такая супер-универсальность окажется востребована, и доведут ли до ума хотя бы одну реализацию OCI-спецификации помимо runC. Но как будто бы не за горами тот день, когда мы запустим Windows внутри Докера на Линуксе!