Драйверы хранилища: overlay2, гранулярность copy-up и ловушка inode
Драйвер решает, как стекаются слои образа и как записываемый слой делает copy-up. overlay2 — дефолт; его файловый copy-up, давление на inode и разделяемый page cache — это то, что позволяет диагностировать узел, чей диск читается полным, а du — пустым.
Парк CI начал валить сборки с no space left on device — но df -h показывал /var/lib/docker на 38% использования. Дежурный инженер час гонялся за фантомным «диск полон», пока кто-то не запустил df -i: inode были на 100%. Образы сборки несли дерево node_modules со 180 000 крошечных файлов, каждый CI-контейнер копировал свою часть наверх в записываемый слой, и пара сотен параллельных сборок исчерпали таблицу inode ФС, оставив 60% блоков свободными. На диске было полно места для байт и ноль для файлов. Драйвер хранилища — overlay2, работающий на файловой гранулярности, — был механизмом, а единственным надёжным лечением было понимание того, что драйвер делает под нагрузкой, вместо доверия одному df -h, измерявшему не тот ресурс.
Что такое драйвер хранилища и почему победил overlay2
Выбор драйвера хранилища — почти не-решение: overlay2 выбирается за тебя по умолчанию. Но когда дисковый инцидент падает на твой дежурный стол, понимание механизма — разница между одноминутной диагностикой и часом охоты за призраком. Драйвер хранилища — подсистема движка, реализующая слоистую ФС: как стекаются read-only слои образа, как материализуется записываемый слой и что делает copy-on-write при записи. Docker за годы поставлял несколько — aufs, devicemapper, btrfs, zfs, vfs — но ответ практически для любого современного Linux-хоста — overlay2, дефолт. Он построен на ядерном OverlayFS, не требует особой настройки блочного устройства, работает на ext4 и xfs (xfs нужен ftype=1) и единственный активно рекомендуется. Остальные — легаси: devicemapper удалён, aufs ушёл из современных ядер, а vfs существует лишь как no-CoW запасной вариант, копирующий каждый слой целиком (используется внутри контейнеров или для отладки, никогда ради производительности).
$ docker info --format '{{ .Driver }}'
overlay2
$ docker info --format '{{ json .DriverStatus }}'
[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"]]Выбор драйвера сегодня в основном не-решение — берите overlay2 — но знать, какой у вас, важно при диагностике, потому что у каждого драйвера своя единица copy-up и накладные расходы. У vfs нет цены copy-up, потому что нет разделения (он дублирует всё, так что 10-слойный образ занимает 10-кратный диск). btrfs и zfs делают copy-up на блочной гранулярности и предлагают снапшоты, но требуют выделенной ФС и несут свой операционный вес. Компромисс overlay2 — файловый copy-up, разделяемый page cache, отсутствие особой настройки — это то, что делает его прагматичным универсальным дефолтом.
Гранулярность overlay2: обоюдоострый файловый copy-up
overlay2 работает на уровне файла, а не блока. Этот единственный факт определяет и лучшее, и худшее его поведение. С хорошей стороны он включает разделяемый page cache: когда десять контейнеров из одного образа читают один файл из общего lowerdir, ядро держит одну запись page cache, а не десять — так что узел с 200 копиями образа не платит 200-кратной памятью за общие страницы. Поэтому overlay2 — рекомендуемый драйвер для высокоплотных PaaS-хостов.
С плохой стороны файловая гранулярность означает, что единица copy-up — весь файл. Измените один байт большого файла — и весь файл сперва копируется в upperdir (разобрано в уроке о записываемом слое), но более тонкая цена — давление на inode и метаданные. Inode (запись в таблице ФС, хранящая метаданные файла: тип, размер, владелец, указатели на блоки) — конечный ресурс: ФС имеет фиксированное их количество, не зависящее от свободного места на диске. Каждый скопированный наверх файл потребляет свежий inode в записываемом слое; образ с сотнями тысяч мелких файлов (node_modules, питоновский site-packages, разросшееся дерево ассетов) может исчерпать таблицу inode ФС хоста задолго до исчерпания блоков. df -h измеряет блоки и читается нормально; df -i измеряет inode и читается на 100%. Это отказ из истории, и он невидим тому, кто смотрит только на байтовое использование диска.
CI-хост сообщает 'no space left on device', пока df -h показывает /var/lib/docker на 38%. Образы несут огромные деревья node_modules. Какова наиболее вероятная причина?
Диагностика и обход проблем уровня драйвера
Senior-ходы следуют из механизма. Первое, измеряй оба ресурса: алерт узла про диск должен проверять df -h и df -i; инцидент исчерпания inode на уровне приложения выглядит идентично инциденту переполнения блоков, но лечится иначе. Второе, держи большие изменяемые файлы и оборот множества мелких файлов вне записываемого слоя — тома полностью обходят драйвер, так что база на томе не платит copy-up и не жжёт inode overlay2. Третье, реклейми агрессивно: остановленные контейнеры, висячие образы и кэш сборки накапливают слои и inode; docker system df показывает разбивку, а docker system prune реклеймит, но на нагруженном CI-хосте это надо планировать, а не надеяться. Четвёртое, на xfs-бэкенде подтверди ftype=1 (overlay2 без него отказывается стартовать) и помни, что у некоторых xfs-настроек фиксированная аллокация inode, которую файлоёмкая нагрузка может перерасти.
Вместе эти четыре хода закрывают оба сценария отказа: трату блоков на copy-up большого файла и невидимый дрейф inode на мелкофайловой нагрузке. Пропусти второй — починишь байты, а не исчерпание файлов; пропусти третий — проблема вернётся за несколько дней на нагруженном CI-флоте.
Внутреннее, что надо усвоить: драйвер хранилища — не ручка для кручения (overlay2 — это ответ), а механизм, поведение которого надо уметь объяснять. Большинство инцидентов «Docker-диск» — это на самом деле файловый copy-up overlay2, встретивший либо большой файл (трата блоков, затыки copy-up), либо множество мелких файлов (исчерпание inode), и том — escape-люк для обоих, потому что он полностью обходит драйвер.
Почему запуск 200 контейнеров из одного образа на overlay2-хосте не стоит 200-кратной памяти за файлы, которые они делят?
▸Почему это работает
Почему экосистема сошлась на файловом драйвере, когда блочный CoW (btrfs, zfs) тоньше по гранулярности и избегает копирования целого файла? Потому что overlay2 не нуждается в особой настройке хранилища — он работает на обычном ext4- или xfs-корне с модулем в ядре, — а блочные драйверы требуют выделенной ФС, управления снапшотами и своей операционной экспертизы. Для подавляюще частого случая (read-mostly слои образа, состояние вытолкнуто в тома) файловый copy-up редко на горячем пути, а разделяемый page cache, который он включает, стоит больше блочной точности, которую вы бы выиграли. Правильный ответ на «какой драйвер» почти всегда «overlay2, и положи записи в том».
- 01Что делает драйвер хранилища, почему overlay2 — дефолт и чем отличаются альтернативы?
- 02Объясни файловую гранулярность overlay2 как компромисс: что она покупает и что стоит, с отказом по inode.
Драйвер хранилища — подсистема движка, превращающая слои образа в рабочую корневую ФС: он стекает read-only слои, материализует записываемый слой и определяет, что делает copy-on-write. За десятилетие драйверов — aufs, devicemapper, btrfs, zfs, vfs — современный ответ это overlay2, дефолт и единственный активно рекомендуемый. Он работает на обычном ext4- или xfs-корне (xfs нужен ftype=1) через ядерный OverlayFS без выделенного блочного устройства, а альтернативы — легаси (devicemapper удалён, aufs ушёл), запасной вариант без разделения для отладки (vfs дублирует каждый слой целиком) или более тяжёлые блочные опции (btrfs, zfs), требующие своей ФС. Единственный факт, объясняющий весь характер overlay2, — что он делает copy-up на файловой, а не блочной гранулярности. Эта гранулярность покупает разделяемый page cache: множество контейнеров, читающих один файл из общего lowerdir, делят одну запись page cache, так что узел может крутить сотни копий образа без умножения памяти — причина, почему overlay2 подходит высокоплотным хостам. Та же гранулярность — это цена: изменение одного байта большого файла копирует весь файл наверх, а каждый скопированный файл жжёт свежий inode, так что образ с множеством мелких файлов может исчерпать таблицу inode при почти свободных блоках. Это классический фантомный «диск полон» — df -h читается нормально, df -i на 100% — и единственный способ это увидеть — измерять оба. Senior-позиция — не тюнить драйвер, а уметь его объяснять: overlay2 — ответ, большие изменяемые файлы и файлоёмкий оборот живут на томах, которые его полностью обходят, а дисковые инциденты на Docker-хостах обычно — файловый copy-up, встретивший либо один большой файл, либо миллион мелких. Теперь, когда видишь «no space left on device» на Docker-хосте, проверь df -i раньше, чем df -h: inode — невидимый ресурс, который overlay2 тихо сжигает.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.