Тома и данные в Docker
Запустил контейнер, записал внутрь 50 мегабайт и файл-метку. Всё на месте, команда docker ps показывает размер контейнера 52,4 мегабайта. Удалил контейнер, поднял новый из того же образа (образ - неизменяемая заготовка, из которой запускают контейнеры), читаю метку: «cat: can't open '/data/marker.txt'». Ничего не сломалось - так и устроено.
Стержень: слой записи умирает вместе с контейнером, монтирование каталога перекрывает содержимое образа, а логи без ограничения съедают диск машины.
// Формулировки: «куда делись данные базы после пересоздания?», «почему пропали файлы из образа?», «чем забился диск под контейнерами?»
Слой записи и тома
Поверх неизменяемых слоёв образа контейнер получает свой тонкий слой записи. Всё, что программа создаёт и правит, ложится туда. Посмотреть его можно командой docker diff: у меня она показала три строки, начинающиеся с буквы A (added, добавлено) - каталог /data и два файла в нём. Команда docker ps с ключом размера показала 52,4 мегабайта записанного при 61,5 мегабайта общего объёма вместе с образом.
Живёт этот слой ровно столько, сколько сам контейнер. Удалили контейнер - слой уничтожен вместе с ним. Поэтому состояние выносят наружу, а контейнер держат одноразовым. Я создал именованный том, записал в него из одного контейнера, тот завершился, и следующий контейнер прочитал ту же строку. Физически том лежит на самой машине по пути /var/lib/docker/volumes/имя/_data, а управляет им демон - постоянно работающая служба docker.
// Одноразовость контейнера - это не про аскезу, а про удобство обновлений. Данные лежат отдельно, поэтому новую версию образа катят поверх тех же данных: удалили старый контейнер, подняли новый, том подключили тот же. Если данные внутри контейнера, то каждое обновление превращается в переезд.
- слой записи
- изменяемый слой поверх образа; уничтожается вместе с контейнером
- именованный том
- хранилище под управлением демона, живущее отдельно от контейнеров
Перекрытие против наполнения, и ловушка одного файла
Два вида монтирования ведут себя по-разному, и это ловит почти каждого. Каталог машины просто накрывает точку монтирования: содержимое образа остаётся в слоях, но становится невидимым. Я собрал образ, где в /app/config лежат два файла, и пробросил туда пустой каталог машины - внутри стало видно 0 файлов. Именованный том наоборот: пустой том при первом использовании наполняется содержимым каталога из образа, и у меня внутри оказались оба файла.
Но дальше том живёт своей жизнью и на обновление образа не смотрит. Я собрал вторую версию образа: там три файла и другой текст в настройках. Новый контейнер без тома видит три файла и новый текст, а тот же контейнер со старым томом - два файла и старый текст. Именно так на проде оказываются настройки годичной давности при свежем образе.
// Отдельная ловушка - монтирование ОДНОГО файла. Оно привязано к записи о файле на диске, а не к имени. Проверил: примонтировал файл, дописал в него строку - контейнер новую строку увидел (номер записи 787053 не изменился). Потом отредактировал тот же файл через sed -i, который создаёт новый файл и переименовывает его поверх старого: номер записи стал 787054, на хосте новый текст, а контейнер продолжает читать старую версию. Тот же опыт с примонтированным КАТАЛОГОМ проходит нормально - контейнер видит правку сразу. Поэтому настройки монтируют каталогом.
- монтирование каталога
- проброс каталога хоста внутрь; накрывает то, что лежало в этой точке в образе
- наполнение тома
- разовое копирование содержимого образа в пустой том при первом использовании
- запись о файле
- внутренний номер файла на диске; монтирование одного файла держится за него, а не за имя
Куда девается место на диске
Драйвер логов по умолчанию складывает вывод контейнера в файл и не ограничивает его ничем. Замер: контейнер напечатал 60 000 строк обычного лога приложения, и это дало 6 408 890 байт, то есть примерно 107 байт на строку. Умножьте на болтливый сервис в проде: сотня строк в секунду даёт около 900 мегабайт в сутки с одного контейнера, и через неделю диск кончается.
Тот же контейнер с ограничением max-size=1m и max-file=2 оставил в журнале 968 457 байт - около мегабайта вместо шести. Цена честная: первые 50 949 строк потеряны, самая ранняя доступная строка теперь про заказ номер 50949. Ограничение задают в настройках демона, чтобы правило действовало на все контейнеры разом, а не на те, где о флаге не забыли.
// Логи - лишь один едок. Разбор начинают с команды docker system df, она показывает занятое по категориям. На этом сервере прямо сейчас: образы 11,68 гигабайта, из них 78% не используется ни одним контейнером; кэш сборки, то есть сохранённые промежуточные шаги прошлых сборок, 3,97 гигабайта; тома 2,78 гигабайта, из них 93% не подключено ни к чему. Чистка неиспользуемого экономит гигабайты, но на боевой машине её запускают осознанно: с ключом --volumes она унесёт и те тома, которые сейчас никем не заняты, а это чьи-то данные.
- max-size и max-file
- предел размера файла логов и число хранимых файлов
- чистка неиспользуемого
- удаление образов, слоёв и кэша без живых ссылок; с ключом томов удаляет и данные
Как отвечать: «Пересоздали контейнер с базой, данные исчезли. Что сделали не так?»
Данные лежали в слое записи контейнера, а он живёт ровно столько же, сколько сам контейнер: удалили контейнер - слой уничтожен. Состояние надо выносить на именованный том или в примонтированный каталог, а контейнер держать одноразовым, тогда его можно спокойно пересоздавать и катить новые версии образа поверх тех же данных. И сразу оговорю два подвоха монтирования, потому что на них ловятся следом. Пустой каталог хоста, примонтированный поверх каталога с содержимым образа, делает файлы образа невидимыми - я проверял, внутри видно ноль файлов. А пустой именованный том, наоборот, наполняется из образа при первом использовании и дальше на обновления образа не смотрит: свежий образ, а настройки старые.
Ответ называет причину, даёт рабочую схему и добавляет две ловушки, которые видно только у того, кто это уже ловил руками.
На чём валятся
- −Держат данные в слое записи и теряют их при первом же пересоздании контейнера.
- −Монтируют пустой каталог поверх содержимого образа и решают, что файлы удалились.
- −Правят примонтированный одиночный файл редактором, который подменяет запись о файле, и не видят изменений внутри.
- −Ждут, что именованный том подхватит новые настройки из свежего образа. Он наполняется один раз.
- −Оставляют логи без ограничения размера: диск машины кончается за неделю.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.
- В образе в /app/plugins лежат файлы. Монтируем туда пустой каталог хоста — и они исчезают. Почему, и как ведёт себя named volume?A)Монтирование сливает содержимое: файлы образа останутся видныB)Bind-mount перекрывает, named volume наполняетсяC)Файлы образа стираются с диска при первом монтированииD)Оба варианта прячут содержимое, разницы между ними нет
показать ответ и разбор
+B)Bind-mount перекрывает, named volume наполняется// разбор: Bind-mount просто накрывает точку монтирования каталогом хоста: то, что было в образе, остаётся в слоях, но становится недоступным. У named volume поведение другое: при первом использовании пустой том инициализируется содержимым каталога из образа, дальше живёт своей жизнью. Отсюда сюрприз с плагинами и конфигами, которые «пропали» после добавления монтирования.
- На ноде кончилось место, /var/lib/docker занимает 200 ГБ. Что съело диск в первую очередь?A)Кэш сборки, который чистится только с перезапуском демонаB)Слои остановленных контейнеров: они дублируют образ целикомC)Файлы томов, которые растут при каждом перезапуске сервисаD)Логи контейнеров: json-драйвер по умолчанию пишет без ограничений
показать ответ и разбор
+D)Логи контейнеров: json-драйвер по умолчанию пишет без ограничений// разбор: Драйвер логов json-file без опций max-size и max-file растит файл, пока жив контейнер: болтливый сервис за неделю даёт десятки гигабайт. Рядом копятся висячие образы и кэш сборки. Лечится настройкой драйвера в daemon.json (или отправкой логов наружу) плюс регулярным docker system prune. Порядок разбора: docker system df, затем самые крупные каталоги контейнеров.
- На CI-ноде за неделю /var/lib/docker распух до сотни гигабайт, хотя активных контейнеров мало. Что чистить?A)Только слои базовых образов — их приходится удалять вручнуюB)Ничего: docker сам подчищает неиспользуемые данные по расписаниюC)Логи ядра хоста — docker к разрастанию тут ни при чёмD)Стопнутые контейнеры, dangling-образы и кэш — docker system prune
показать ответ и разбор
+D)Стопнутые контейнеры, dangling-образы и кэш — docker system prune// разбор: CI без уборки быстро копит: остановленные контейнеры, dangling-образы (<none> от пересборок), старый build cache, неиспользуемые тома. Смотрят docker system df, чистят docker system prune (добавив -a для неиспользуемых образов и --volumes осознанно). На CI это ставят в крон/после джоба. Важно не снести чужие тома с данными — --volumes применяют аккуратно.
- Контейнер пишет в примонтированный named volume, но на хосте файлы принадлежат root, и другой процесс не может их прочитать. Откуда несовпадение uid?A)Docker сам проставляет файлам тома владельца root при монтированииB)uid процесса в контейнере и есть владелец файлов на хостеC)Named volume хранит файлы без владельца, а root дописывает хостD)Причина в том, что этот том зашифрован своим драйвером
показать ответ и разбор
+B)uid процесса в контейнере и есть владелец файлов на хосте// разбор: Права в Linux — по числовому uid/gid, а не по имени, и это число общее для хоста и контейнера. Если в контейнере процесс — root (uid 0), созданные им файлы на томе принадлежат uid 0 и на хосте (root). Другой пользователь их не прочитает. Лечится согласованием uid: USER с нужным числовым uid, --user $(id -u):$(id -g), или выставлением прав/владельца на томе.
- Сервис с named volume отлично работал на одной ноде. В кластере под переехал на другую ноду — и данные исчезли. Почему?A)Данные удалились при остановке пода на прежней нодеB)Том не успел реплицироваться, потому что не хватило местаC)Named volume локален ноде — на новой его нетD)Docker перенёс том, но по дороге потерял индекс файлов
показать ответ и разбор
+C)Named volume локален ноде — на новой его нет// разбор: Named volume с драйвером local лежит на диске конкретной ноды (/var/lib/docker/volumes). Он не следует за контейнером на другую ноду — там его просто нет, приложение стартует с пустым каталогом. Для данных, переживающих переезд, нужен сетевой/распределённый storage: NFS, объектное хранилище, CSI-драйвер (в Kubernetes — PVC поверх сетевого диска), а не локальный том.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.