Проблема эфемерности: Почему данные исчезают вместе с контейнером
Проблема эфемерности: Почему данные исчезают вместе с контейнером
Представьте классическую ситуацию: вы развернули базу данных PostgreSQL в Docker. Приложение работает, пользователи регистрируются, таблицы наполняются данными. Спустя месяц выходит критическое обновление безопасности PostgreSQL. Вы скачиваете новый образ, удаляете старый контейнер, запускаете новый и... обнаруживаете абсолютно пустую базу данных. Все пользователи и записи исчезли.
Чтобы понять, куда испарились данные, нам нужно разобраться с фундаментальным свойством Docker-контейнеров — их эфемерностью.
Иллюзия виртуальной машины
Главная ошибка начинающих инженеров — воспринимать контейнер как легковесную виртуальную машину. В виртуальной машине есть виртуальный жесткий диск: выключили машину, удалили ее из гипервизора — диск (если его не удалить явно) может остаться лежать в хранилище.
Контейнер работает иначе. По своей природе он эфемерен (от греч. ephemeros — однодневный, мимолетный). Это означает, что контейнер задуман как временная, одноразовая сущность. Его жизненный цикл: запустился, выполнил задачу, остановился, был уничтожен.
Контейнеры не предназначены для того, чтобы их «чинили» или «обновляли» изнутри. Если нужна новая версия библиотеки или конфигурации, мы не заходим в запущенный контейнер через терминал, а пересобираем образ и запускаем новый контейнер. Но как эта философия влияет на файлы, которые приложение создает во время работы?
Анатомия файловой системы: Слой контейнера
Вспомним, как устроен образ: это набор неизменяемых (Read-Only) слоев. Когда вы выполняете команду docker run, демон Docker берет эти слои и добавляет поверх них еще один, финальный слой — слой контейнера (Container Layer).
В отличие от слоев образа, слой контейнера доступен для записи (Writable Layer).
Именно здесь происходит вся магия файловых операций во время работы приложения:
- Создание: Если ваше приложение пишет лог-файл или сохраняет загруженную пользователем картинку, этот файл физически создается в Writable-слое.
- Изменение: Благодаря механизму Copy-on-Write (CoW), если приложение решает изменить конфигурационный файл, изначально лежащий в базовом образе, Docker копирует этот файл из нижнего Read-Only слоя наверх, в Writable-слой, и уже там изменяет его.
- Удаление: Если приложение удаляет файл, Docker не может удалить его из неизменяемого образа. Вместо этого в Writable-слое создается специальная пометка (whiteout), которая скрывает файл от приложения.
Слой контейнера (Container Layer)
Тонкий слой файловой системы, доступный для чтения и записи, который создается поверх неизменяемых слоев образа в момент запуска контейнера. Все изменения файловой системы в рантайме происходят исключительно в нем.
Жизненный цикл данных
Ключевая проблема кроется в том, что слой контейнера жестко привязан к жизненному циклу самого контейнера.
Если вы остановите контейнер командой docker stop, слой контейнера сохранится на диске хост-машины. Вы можете запустить его снова (docker start), и приложение увидит все созданные ранее файлы.
Но как только вы выполните команду docker rm (или контейнер завершится сам при запуске с флагом --rm), Docker безвозвратно удалит слой контейнера. Вместе с ним в цифровую бездну отправятся логи, загруженные файлы, кэши и, что самое страшное, файлы баз данных.
Баг или фича?
Кажется, что потеря данных при пересоздании контейнера — это серьезный архитектурный недостаток. На самом деле, это одна из главных причин успеха Docker.
Эфемерность дает невероятную предсказуемость. Если вы запустите 100 контейнеров из одного образа, вы гарантированно получите 100 абсолютно идентичных сред. Если бы контейнеры накапливали мусор, кэши и изменения конфигураций между пересозданиями, мы вернулись бы к проблеме «на моем компьютере это работает», от которой Docker призван нас избавить.
Чтобы система работала надежно, инженерам пришлось разделить приложения на два архитектурных типа:
- Stateless (без состояния): Приложения, которые не сохраняют данные между сессиями. Например, веб-сервер Nginx или микросервис на Python, который просто принимает запрос, вычисляет результат и отдает ответ. Такие приложения идеально ложатся на философию Docker. Их можно убивать и запускать тысячами, они полностью эфемерны.
- Stateful (с состоянием): Приложения, чья главная задача — хранить и накапливать данные. Базы данных (PostgreSQL, MongoDB), брокеры сообщений (Kafka), хранилища логов.
Правило профессионального управления инфраструктурой гласит: контейнер должен быть stateless, даже если внутри работает stateful-приложение.
Чтобы добиться этого, нам нужно «пробить» изоляцию файловой системы Docker и вынести хранение состояния за пределы эфемерного слоя контейнера. Данные должны пережить смерть контейнера. Для этого в Docker существуют специальные механизмы монтирования, которые мы начнем разбирать в следующих главах.