How Container Filesystem Works: Building a Docker-Like Container from Scratch
Как работает файловая система контейнеров: создание Docker-подобного контейнера с нуля
Одна из суперспособностей контейнеров — их изолированное представление файловой системы. Изнутри контейнера она может выглядеть как полноценный дистрибутив Linux, часто отличающийся от хостового. Запустите docker run nginx, и Nginx окажется в своём привычном пользовательском пространстве Debian независимо от того, какую версию Linux использует хост. Но как создаётся эта иллюзия?
В этой статье мы соберём небольшой, но реалистичный Docker-подобный контейнер, используя только стандартные инструменты Linux: unshare, mount и pivot_root. Без магии рантайма и (почти) без упрощений. По пути вы узнаете, почему пространство имён монтирования — это основа изоляции контейнеров, в то время как другие пространства имён, такие как PID, cgroup, UTS и даже сетевое, играют скорее вспомогательную роль.
К концу — особенно если совместить это с руководством по сетям контейнеров — вы сможете запускать полнофункциональные Docker-подобные контейнеры, используя только стандартные команды Linux. Это конечная цель каждого aspiring container guru.
Предварительные требования
- Базовое знакомство с Docker (или Podman и подобными) контейнерами
- Основные знания Linux (скрипты shell, общее понимание пространств имён)
- Фундаментальные принципы файловых систем (единая иерархия каталогов, таблица монтирования, bind mount и т.д.)
Визуализация конечного результата
На диаграмме ниже показано, как выглядит изоляция файловой системы при создании Docker нового контейнера. Нормально, если рисунок кажется сложным. С помощью практических упражнений в этом руководстве мы построим комплексную ментальную модель работы контейнеров, так что когда мы вернёмся к диаграмме в заключительном разделе, она будет выглядеть гораздо понятнее.
Кликните для увеличения
Что именно изолирует Mount Namespace?
Проведём быстрый эксперимент. В Терминале 1 запустим новую сессию shell в собственном пространстве имён монтирования:
sudo unshare --mount bash
Теперь в Терминале 2 создадим файл где-нибудь в файловой системе хоста:
echo "Hello from host's mount namespace" | sudo tee /opt/marker.txt
Удивительно или нет, но при попытке найти этот файл в новом пространстве имён монтирования с помощью Терминала 1 он окажется там:
cat /opt/marker.txt
Так что же мы изолировали с помощью unshare --mount? 🤔
Ответ — таблицу монтирования. Вот как это проверить. Из Терминала 1 смонтируем что-нибудь:
sudo mount --bind /tmp /mnt
💡 Эта команда использует bind mount для простоты, но подойдёт и обычное монтирование (блочного устройства).
Теперь, если вывести содержимое папки /mnt в Терминале 1, должны отобразиться файлы из /tmp:
ls -l /mnt
Но в то же время папка /mnt осталась пустой в пространстве имён монтирования хоста. Если запустить ту же команду ls из Терминала 2, файлов не будет:
ls -lah /mnt
Наконец, «представления» файловой системы начали расходиться между пространствами имён. Однако мы смогли достичь этого только создав новую точку монтирования.
Пространства имён монтирования, визуализировано
Из man страницы mount namespace:
Пространства имён монтирования обеспечивают изоляцию списка монтирований, видимых процессами в каждом экземпляре пространства имён. Таким образом, процессы в каждом из экземпляров пространства имён монтирования будут видеть distinct single directory hierarchies.
Сравните таблицы монтирования, запустив findmnt из Терминала 1 и Терминала 2:
Пространство имён хоста
Новое пространство имён