Каждый инженер, упаковывавший веб-приложение в контейнер, хотя бы раз сталкивался с парадоксальной ситуацией: проект состоит из нескольких простых скриптов, а итоговый Docker-образ весит полтора гигабайта. Попытка исправить ситуацию добавлением команды RUN rm -rf /tmp/build-cache в конец Dockerfile ничего не меняет: размер образа остается прежним или даже слегка увеличивается. В результате замедляется развертывание в Kubernetes, растут счета за облачный реестр, а ноды тратят минуты на скачивание раздутых слоев.
Консольная утилита Dive, созданная инженером Алексом Гудманом на языке Go, помогает найти первопричину лишнего веса. Инструмент предлагает интерактивный аудит каждого слоя контейнера и позволяет встроить жесткий автоматический контроль чистоты сборки прямо в пайплайны непрерывной интеграции (CI/CD).
Ловушка неизменяемых слоев в OverlayFS
Причина раздувания контейнеров кроется в архитектуре многослойных файловых систем UnionFS и OverlayFS, лежащих в основе Docker. Образ представляет собой стопку неизменяемых слоев, накладывающихся друг на друга подобно прозрачным пленкам. Если на втором слое распаковывается архив с зависимостями на 400 МБ, а на третьем слое он удаляется, файл становится невидимым в готовом контейнере, но физически навсегда остается запеченным во втором слое.
Dive анализирует файловые дескрипторы и сопоставляет метаданные inode по всей толще слоев. Утилита вычисляет два ключевых показателя:
- Wasted Space: суммарный объем дискового пространства, занятый файлами, которые были перезаписаны или удалены на более поздних этапах сборки.
- Image Efficiency Score: процент полезного использования объема (соотношение реально видимых файлов к суммарному размеру всех слоев).
В двухпанельном терминальном интерфейсе слева выводится список слоев с точным размером и командами Dockerfile, а справа — дерево файлов результирующей системы с цветовой индикацией: зеленым подсвечиваются новые файлы, желтым — измененные, а красным — скрытые удаленные остатки.
Сквозной сценарий: от терминала до блокировки в CI
Для разового исследования образа достаточно запустить утилиту в командной строке или выполнить проверку через официальный контейнер:
# Интерактивный анализ локального или удаленного образа
dive nginx:alpine
# Сборка проекта и мгновенный переход к исследованию слоев
dive build -t my-company-api:latest .
# Запуск аудита через изолированный Docker-контейнер
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest my-company-api:latest
Чтобы предотвратить случайное попадание мусорных файлов в продакшен, Dive встраивается в автоматический конвейер CI. При установке переменной окружения CI=true утилита отключает интерактивный интерфейс и валидирует образ по правилам файла .dive-ci:
# .dive-ci - пороговые значения для проверки сборки
rules:
# Минимально допустимый коэффициент полезности образа (от 0 до 1)
lowestEfficiency: 0.95
# Максимально допустимый объем потерянных байтов
highestWastedBytes: 20MB
# Допустимая доля потерь в пользовательских слоях (без учета базового образа)
highestUserWastedPercent: 0.15
Если неаккуратный коммит оставит в промежуточных слоях тяжелый кэш пакетов или временный тестовый архив, утилита завершит работу с ненулевым кодом выхода, заблокировав выкатку дефектного билда.
Границы применения и инженерный вердикт
Dive незаменим при оптимизации классических многоэтапных Dockerfile, поиске забытых ключей доступа и отладочных утилит (curl, gcc), случайно оставленных в промежуточных слоях.
Однако утилита теряет смысл в проектах, где контейнеризация изначально построена на минималистичных образах без дистрибутива (Distroless) или инструментах вроде ko и apko, которые генерируют однослойные бинарные артефакты вообще без промежуточных шагов сборщика. Также при запуске в контейнере требуется аккуратный проброс сокета Docker с соответствующими привилегиями.
Для большинства команд Dive остается самым наглядным инструментом оптимизации: сокращение веса образа с гигабайта до сотни мегабайт не только экономит терабайты сетевого трафика, но и в разы снижает потенциальную поверхность атак.
