Примечание переводчика: недавно инженер Google Михал Питр написал практический обзор того, как работают файловые системы в контейнерах. Его небольшая статья поможет разобраться, что делает Docker, и вручную создать файловую систему контейнера. Слово автору.
На выходных я сделал игрушечный клон Docker’а. В процессе возник вопрос, откуда у контейнера берётся файловая система? Чтобы ответить на него, давайте разберёмся, что делает Docker, а потом воспроизведём всё это самостоятельно.
Запустим командную оболочку в Docker-контейнере на базе образа Alpine:
michal@michal-lg:~$ docker run -it --entrypoint /bin/sh --rm --name "alpine-container" alpine
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # hostname
a7cbf0aea1ad
/ # cd home && ls
Мы оказались в отдельной файловой системе, и она довольно пустая. Создадим файл:
/ # echo -e "Hello there\nGeneral Kenobi" > /home/hello_there.txt
/ # cat /home/hello_there.txt
Hello there
General Kenobi
Как думаете, можно ли получить доступ к этому файлу с хоста?
…
…
…
Спойлер
Можно — давайте сделаем это!
Docker хранит всё в директории /var/lib/docker
, поэтому запустим второй терминал и поищем:
root@michal-lg:/var/lib/docker# find -name hello_there.txt
./overlay2/1557145fe40a1595d090eeafa72c39a7b54cca4791ae9e3ffafabff06466125c/diff/home/hello_there.txt
./overlay2/1557145fe40a1595d090eeafa72c39a7b54cca4791ae9e3ffafabff06466125c/merged/home/hello_there.txt
root@michal-lg:/var/lib/docker#
Любопытно, что наш файл нашёлся в двух разных директориях. Посмотрим, что ещё есть в diff
и merged
:
root@michal-lg:/var/lib/docker/.../diff# ls
home root
root@michal-lg:/var/lib/docker/.../merged# ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
diff
содержит только пустую root-директорию и домашнюю директорию с созданным нами файлом. Содержимое merged
полностью совпадает с файловой системой контейнера.
OverlayFS
Docker использует файловую систему OverlayFS. Она позволяет объединить два дерева файлов, «нижнее» и «верхнее», в комбинированный вид «merged». В Docker «верхнее» дерево файлов называется «diff», что, пожалуй, более уместно, и я буду обращаться к нему именно так.
Понять, зачем нужны файловые системы union-типа (см. Каскадно-объединённое монтирование), помогает следующий пример: на одном хосте, как правило, запускается несколько контейнеров. Высока вероятность того, что у них будет одинаковый нижний слой, будь то Alpine, Ubuntu или более специализированный, например Golang.
Если этот слой сделать read-only, его можно будет переиспользовать между контейнерами, сильно экономя место. При этом все изменения будут записываться в верхний слой. Давайте посмотрим, что произошло после создания hello_there.txt:
При создании hello_there.txt в /home
тот был записан в diff
, а OverlayFS создала объединённый вид в merged
:
Что произойдёт, если изменить что-нибудь в нижнем слое? Давайте переименуем /bin/echo
в /bin/echo.old
:
/ # cd bin
/bin # mv echo echo.old
/bin # ls
...
chgrp echo.old gzip ln mount printenv setserial umount
...
Как уже говорилось, нижний слой доступен только для чтения, поэтому все правки попадают в diff
.
root@michal-lg:/var/lib/docker/overlay2/.../diff/bin# ls -l
total 0
c--------- 1 root root 0, 0 Nov 11 23:06 echo
lrwxrwxrwx 1 root root 12 Sep 6 13:34 echo.old -> /bin/busybox
Файлов теперь два! Один — для более не существующего echo, другой — для echo.old. OverlayFS использует специальные whiteout-файлы для обозначения того, что файл был удалён в верхнем слое (даже если он существует в нижнем read-only-слое). Когда OverlayFS видит этот файл, она знает, что не нужно включать его в merged-представление.
Второй файл гораздо менее интересен: это просто переименованный echo, который на поверку оказывается символической ссылкой на busybox:
Создаём файловую систему в контейнере
Теперь давайте посмотрим, как Docker создаёт новую файловую систему с помощью OverlayFS.
Сначала создадим временные директории в /tmp/
— они и станут файловой системой нашего контейнера:
michal@michal-lg:/tmp$ mkdir -p /tmp/container-demo/{diff,merged,work}
michal@michal-lg:/tmp$ ls container-demo/
diff merged work
С diff
и merged
мы уже встречались. work
— рабочая директория OverlayFS, и нас она не интересует.
Загрузим Alpine minirootfs для нужной архитектуры процессора и распакуем его в /tmp/container-demo/
. Я переименую извлечённую папку в «alpine»:
michal@michal-lg:/tmp/container-demo$ ls
alpine diff merged work
Всё настроено, можно монтировать файловую систему OverlayFS:
michal@michal-lg:/tmp/container-demo$ sudo mount -t overlay overlay -o lowerdir=alpine,upperdir=diff,workdir=work merged
Если теперь вывести содержимое merged
, увидим файловую систему Alpine:
michal@michal-lg:/tmp/container-demo/merged$ ls
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr
Если создать там файл, он будет записан в diff
:
michal@michal-lg:/tmp/container-demo/merged$ echo hello > hello.txt
michal@michal-lg:/tmp/container-demo/merged$ ls
bin etc home media opt root sbin sys usr
dev hello.txt lib mnt proc run srv tmp var
michal@michal-lg:/tmp/container-demo/merged$ ls ../diff/
hello.txt
В заключение можно запустить новый shell-процесс и установить merged
в качестве его корневой директории. То же самое происходит в контейнерах — так те получают свою собственную файловую систему. Любой процесс, порождённый этим shell-процессом, унаследует корневую директорию и сможет работать со своей файловой системой.
michal@michal-lg:/tmp/container-demo$ sudo chroot merged /bin/sh
/ # ls
bin hello.txt media proc sbin tmp
dev home mnt root srv usr
etc lib opt run sys var
/ # cd ..
/ #
Полноценная реализация отличается от нашей тем, что в ней используются пространства имён для дополнительной изоляции. Подробнее о них можно узнать из man-страниц Linux.
Заключение
Для подавляющего большинства инженеров контейнеры — это чёрный ящик. Надеюсь, теперь у вас появилось понимание того, как они работают.
Ещё у меня есть более полный клон Docker'а примерно из 200 строк кода, написанный на Golang, — заходите, если интересно!
P. S.
Читайте также в нашем блоге:
TimsTims
Спасибо за статью. Если понятие lower изменить нельзя (используется в самой OverlayFS) , то на картинке считаю неудачным изображение этого "нижнего слоя" выше остальных слоев. То есть на картинке нижний слой по по факту оказывается выше. Diff где-то сбоку.
Правильно ли я понял, что OverlayFS создает в Merged ссылки на оригинальные файлы из Lower, и дальше следит за diff, при изменении в котором как-то перекладывает (или линкует?) их в Merged?