Контейнеры не существуют на самом деле
В отличие от виртуальных машин, контейнеры — это просто процессы с применёнными к ним специальными ограничениями. Ядро всё ещё запускает этот процесс! И если знать, где искать, контейнер можно обнаружить в директории /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. Написание собственной реализации инструмента, который используешь ежедневно — это отличный способ понять его устройство!