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. Подробности можно почерпнуть из следующих источников:
- runC: The little engine that could…
- containerd — Building a Container Supervisor
- Introducing RunC: A Lightweight Universal Container Runtime
- Docker 1.11: The First Runtime Built On Containerd And Based On OCI Technology
Но что тут самое интересное — это то, что 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 внутри Докера на Линуксе!