Файловая система образа: слои и overlay
Откуда берётся «слоёная» файловая система образа и почему контейнеры такие лёгкие на диске.
OverlayFS — это union-файловая система Linux: она «склеивает» несколько каталогов в один, где верхний слой перекрывает нижние. На ней Docker строит образы из read-only слоёв и тонкий writable-слой контейнера.
Зачем это знать на практике
Понимание слоёв напрямую экономит время и место. Оно объясняет, почему изменение одной строки в Dockerfile ломает кеш и пересобирает половину образа; почему десять контейнеров из одного образа занимают на диске почти столько же, сколько один; и почему данные, записанные внутрь контейнера, исчезают после его удаления. Без этой модели оптимизация Dockerfile превращается в шаманство.
Образ — это стопка слоёв
Каждая инструкция в Dockerfile, меняющая файловую систему (RUN, COPY, ADD), порождает новый слой — архив с разницей: какие файлы добавлены, изменены или удалены относительно предыдущего слоя. Образ — это упорядоченная стопка таких слоёв плюс конфигурация. Слои read-only и переиспользуются между образами: если два образа основаны на ubuntu:22.04, базовые слои хранятся на диске один раз.
FROM ubuntu:22.04 # базовые слои
RUN apt-get update # слой со списком пакетов
RUN apt-get install -y curl # слой с установленным curl
COPY app.py /app/ # слой с вашим файлом
Историю слоёв и их размер показывает docker history:
docker history nginx:latest
# IMAGE CREATED CREATED BY SIZE
# <...> ... /bin/sh -c apt-get install ... 54MB
# <...> ... /bin/sh -c #(nop) COPY ... 1.1kB
OverlayFS: как слои становятся одной ФС
Контейнеру нужна обычная единая файловая система, а не стопка архивов. Их объединяет драйвер хранилища overlay2 поверх OverlayFS. В терминах overlay есть нижние слои (lowerdir, read-only слои образа), верхний слой (upperdir, writable-слой контейнера) и результат их объединения (merged) — то, что контейнер видит как /.
merged (то, что видит контейнер как «/»)
▲ объединение
┌────────┴─────────┐
upperdir (writable-слой контейнера, copy-on-write)
lowerdir (read-only слои образа: ubuntu + apt + curl + app.py)
Если файл есть только в нижнем слое — контейнер читает его оттуда. Если файл создан или изменён в контейнере — он лежит в upperdir и перекрывает версию из нижних слоёв. Удаление файла из read-only слоя реализуется специальным «whiteout»-файлом в верхнем слое, который прячет нижний.
Copy-on-write: почему запись «дешёвая», но не бесплатная
Главный приём слоистой ФС — copy-on-write (CoW). Пока контейнер только читает файл из образа, никакого копирования нет: данные берутся из общего read-only слоя. Но как только контейнер впервые изменяет файл, overlay копирует его целиком из нижнего слоя в upperdir и правит уже копию.
# большой файл лежит в образе (read-only слой)
docker run -it --name demo someimage bash
echo " " >> /var/big.log # дозапись 1 байта → весь big.log скопируется в upperdir
Отсюда два практических вывода. Во-первых, запуск десяти контейнеров из одного образа почти ничего не стоит по диску: у каждого лишь свой пустой upperdir, а тяжёлые слои общие. Во-вторых, активная запись и изменение больших файлов внутри контейнера раздувает его writable-слой и медленнее, чем работа на обычной ФС, — поэтому базы данных и часто меняющиеся данные держат в томах (volumes), которые минуют слоистую ФС.
Тот же механизм объясняет и поведение кеша при сборке. Каждый слой адресуется по содержимому, поэтому при повторном docker build Docker переиспользует ранее собранные слои, пока инструкция и её входные данные не изменились. Стоит измениться файлу, который копирует COPY, — и этот слой, а вместе с ним все последующие, пересобираются заново. Поэтому порядок инструкций в Dockerfile — это не косметика, а прямое управление тем, что попадёт в кеш, а что будет пересчитано при каждой правке кода.
Writable-слой и эфемерность данных
Всё, что контейнер пишет в свою ФС, оседает в его writable-слое. Этот слой принадлежит конкретному контейнеру и удаляется вместе с ним. Два контейнера из одного образа имеют независимые writable-слои и не видят изменений друг друга. Что именно изменилось относительно образа, показывает docker diff:
docker diff demo
# A /app/cache (Added — добавлено)
# C /etc/nginx.conf (Changed — изменено)
# D /tmp/old.txt (Deleted — удалено)
Как это работает под капотом
OverlayFS — это модуль ядра Linux, а не выдумка Docker. Драйвер overlay2 при старте контейнера монтирует overlay-точку, указывая ядру список lowerdir (слои образа сверху вниз) и upperdir (writable-слой). Ядро само разрешает, из какого слоя брать каждый файл. Слои на диске лежат в /var/lib/docker/overlay2/ и адресуются по содержимому (хеш), поэтому одинаковые слои дедуплицируются автоматически.
Частые ошибки
- Хранить важные данные в writable-слое. После
docker rmони пропадают. Для постоянных данных — тома. - Ломать кеш слоёв сборки. Если
COPY . .идёт до установки зависимостей, любая правка кода инвалидирует слой и переустанавливает пакеты заново. Копируйте сначала файл зависимостей, ставьте их, потом копируйте остальной код. - Удалять файлы отдельной инструкцией RUN. Удаление в новом слое не уменьшает образ: данные остаются в нижнем слое, а сверху лишь whiteout. Создание и удаление надо делать в одной инструкции.
- Активно писать большие файлы в CoW-слой. Каждая первая запись копирует файл целиком; для нагруженного I/O используйте том.
Итоги
- Образ — стопка read-only слоёв; каждая меняющая ФС инструкция Dockerfile добавляет слой.
- OverlayFS объединяет read-only слои (
lowerdir) и writable-слой контейнера (upperdir) в единую ФС. - Copy-on-write делает чтение бесплатным, а первую запись файла — копированием его в верхний слой.
- Writable-слой эфемерен и удаляется с контейнером;
docker historyпоказывает слои,docker diff— изменения относительно образа.