Контейнеры не существуют на самом деле

В отличие от виртуальных машин, контейнеры — это просто процессы с применёнными к ним специальными ограничениями. Ядро всё ещё запускает этот процесс! И если знать, где искать, контейнер можно обнаружить в директории /proc.

Контейнеры действительно не существуют на самом деле

В Linux нет отдельного компонента, посвящённого контейнерам. На самом деле контейнеры — это 5 разных функций ядра Linux, одетых в один плащ.

Namespaces скрывают видимость хоста и контейнера, cgroups ограничивают использование ресурсов контейнером, а весь остальной сетевой стек — это сложная магия, которая позволяет изолировать сеть контейнера.

Образы — это просто папки

Образы Docker — это просто папки, которые Docker помещает куда-то и затем использует chroot (или pivot_root) для того, чтобы сделать эту папку корневой директорией процесса контейнера. Так что когда запускается docker run -it --rm ubuntu /bin/bash, Docker выполняет следующее:

  • Скачивает образ ubuntu с Dockerhub.
  • Помещает образ куда-то в файловую систему.
  • Запускает /some/weird/docker/path/bin/bash.

Go — не лучший язык для container runtime

Container runtime Docker, runc, написан на Go, а в проектировании Go есть решения, которые делают его менее подходящим для container runtime. Go runtime нативно многопоточный, что само по себе неплохо, но это создаёт проблемы в runc.

Многие системные вызовы Linux, необходимые для контейнера, должны выполняться в однопоточной среде. Поэтому runc использует хитрый C-модуль, который запускается до инициализации Go runtime и позволяет выполнять эти системные вызовы.

В Go также нет fork! Fork копирует только основной поток — это опасная функция в рантайме на Go. А контейнеру на самом деле нужно выполнить fork, установить ограничения и затем запустить процесс из docker run. Для обхода этого ограничения runc вызывает /proc/self/exe со специальным аргументом командной строки, который определяет процесс как дочерний и имитирует fork.

Docker требует root!

Легко упустить этот момент, если, как и разработчик, использовать Docker на macOS. Многие API ядра Linux, которые используют container runtime, требуют или требовали запуска с правами root, поэтому демон Docker обычно запускается с правами администратора. Безопасность контейнеров — это явно очень сложная задача.

В последнее время ситуация улучшается. Главное исключение — это сеть. Если запускать rootless container runtime, он должен переимплементировать весь сетевой стек в пространстве пользователя, чтобы предоставить те же возможности.

Учиться — это интересно!

Разработчик только начал писать свой container runtime, но уже узнал столько нового о Docker. Написание собственной реализации инструмента, который используешь ежедневно — это отличный способ понять его устройство!