Данные и тома глубже: драйверы, бэкап, права

Рейтинг: 64.6% · 12 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Данные и тома глубже: драйверы, бэкап, права

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права (вы здесь)
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Боль, с которой ты столкнёшься

Контейнеры одноразовы. Это их суперсила и одновременно ловушка. Ты поднял Postgres, налил туда данные, пересоздал контейнер с новым флагом - и база пустая. Или хуже: данные есть, но процесс внутри пишет с ошибкой "permission denied", хотя на хосте каталог явно доступен на запись. Или ты сделал docker volume rm и понял, что только что грохнул прод. Всё это - не баги Docker, а следствие того, что про docker данные многие думают поверхностно: примонтировал каталог и забыл.

Этот урок - про то, как хранение устроено под капотом. Разберём драйверы томов, разницу named volume vs bind vs tmpfs по производительности, почему возникает рассинхрон UID/GID и docker volume права, как делается надёжный бэкап docker volume через tar, как мигрировать данные между хостами и где данные держать нельзя категорически. Это продвинутая часть, поэтому идём вглубь механики, а не по верхам.

Изображение

Три способа дать контейнеру состояние

У Docker есть три типа монтирования, и путать их - классическая ошибка.

Named volume (именованный том) - управляемая Docker сущность. Данные живут в /var/lib/docker/volumes/<имя>/_data на хосте, но ты обращаешься к ним по имени, а не по пути. Docker сам создаёт каталог, ведёт метаданные, отслеживает связи с контейнерами. Это дефолтный выбор для любого состояния, которое должно пережить контейнер: базы, загруженные пользователями файлы, индексы.

Bind mount (привязка) - ты руками прокидываешь конкретный путь хоста внутрь контейнера. Docker тут ничего не "владеет", просто пробрасывает. Идеально для разработки (исходники проекта внутрь контейнера), для конфигов, для сокетов вроде /var/run/docker.sock. Минус: ты завязан на структуру файловой системы хоста, и переносимость страдает.

tmpfs - данные в оперативной памяти, на диск не попадают вообще. Живут пока жив контейнер. Это для секретов, временных файлов, кэшей, которые не должны касаться диска. Работает только на Linux-хостах.

Под капотом разница принципиальная. Named volume и bind - это, по сути, точки монтирования поверх верхнего слоя контейнера. Когда процесс пишет в примонтированный путь, запись идёт напрямую в файловую систему хоста, минуя storage driver (overlay2). А вот запись в обычный путь внутри контейнера проходит через copy-on-write слой overlay2 - и это медленно для интенсивного I/O.

Код: Выделить всё

docker run -d --name db \
  --mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
  --mount type=bind,source=/etc/myapp,target=/etc/myapp,readonly \
  --mount type=tmpfs,target=/tmp,tmpfs-size=64m \
  postgres:16
Я намеренно использую длинный синтаксис --mount, а не короткий -v. Короткий -v src:dst:ro компактнее, но молчаливо создаёт несуществующий путь и легко спутать порядок. --mount явный, валидирует ключи и ругается, если что-то не так. На проде используй --mount.

Производительность: что реально быстрее

Главный практический вопрос: что выбрать для нагруженной БД. Ответ - named volume, и вот почему.
  • Named volume на Linux пишет прямо в нативную ФС хоста (обычно ext4/xfs). Накладных расходов почти ноль - это та же скорость, что и запись на диск напрямую.
  • Bind mount на Linux тоже быстр, по сути эквивалент named volume по скорости. Но на Docker Desktop (Mac/Windows) bind mount идёт через виртуализованную ФС (gRPC-FUSE или virtiofs), и это драматически медленнее - на тысячах мелких файлов разница может быть в разы. Поэтому на Mac/Windows для зависимостей (node_modules, vendor) держи named volume, а не bind с хоста.
  • tmpfs - быстрее всех, это RAM. Но платишь оперативкой и теряешь данные при остановке.
Запомни ключевое: никогда не клади активно пишущую БД в writable-слой контейнера (то есть без тома). Overlay2 при изменении файла копирует его целиком в верхний слой (copy-up). Для БД, где постоянно меняются страницы данных, это убивает производительность и раздувает слой. Postgres и MySQL в writable-слое - гарантированная деградация.

Драйверы томов: local и не только

У каждого тома есть docker volume драйвер. По умолчанию - local. Но local умеет куда больше, чем "каталог на диске": через driver_opts он пробрасывает опции в обычный системный mount(8). Это значит, что NFS не требует отдельного плагина - его монтирует штатный local-драйвер.

Код: Выделить всё

docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=10.0.0.5,rw,nfsvers=4.1,soft,timeo=300 \
  --opt device=:/exports/shared \
  shared_nfs
Поле o - это ровно те опции, что ты передал бы mount -o. type=nfs, device - путь экспорта на NFS-сервере. soft и timeo важны: при недоступности сервера hard-монтирование подвесит процесс намертво, soft вернёт ошибку. То же самое в compose:

Код: Выделить всё

volumes:
  shared:
    driver: local
    driver_opts:
      type: nfs
      o: addr=10.0.0.5,rw,nfsvers=4.1,soft,timeo=300
      device: ":/exports/shared"
Для SSH-удалёнки нужен внешний плагин - vieux/sshfs:

Код: Выделить всё

docker plugin install vieux/sshfs
docker volume create -d vieux/sshfs \
  -o sshcmd=user@10.0.0.7:/data \
  -o IdentityFile=/root/.ssh/id_rsa \
  remote_ssh
Плагины томов (CSI-подобные решения, облачные драйверы под AWS EBS, GCE PD и т.п.) подключаются так же - через docker plugin install и --driver. В RU-реалиях для облачных дисков смотри на драйверы конкретного провайдера; для шаринга между хостами на своём железе NFS через local - самый предсказуемый вариант. sshfs удобен, но FUSE поверх SSH медленный и капризный под нагрузкой - для эпизодического доступа ок, для горячей БД нет.

Права, владельцы и боль UID/GID

Самая частая загадка новичка: docker volume права. Создал том, контейнер пишет - "permission denied". Причина в том, что внутри контейнера и на хосте процессы идентифицируются числовыми UID/GID, а не именами. Имя пользователя - это просто запись в /etc/passwd, которая внутри образа своя. Ядро же видит только числа.

Пример. Образ Postgres запускает процесс под пользователем postgres, у которого внутри UID 999. Когда Postgres пишет в named volume, файлы на хосте получают владельца с числовым UID 999 - и на хосте это будет какой-то случайный системный пользователь или вообще никто. А теперь ты монтируешь bind с хоста, где каталог принадлежит твоему юзеру (UID 1000), а процесс в контейнере бежит под UID 999 - запись запрещена. Имена ни при чём, дело в несовпадении чисел.

Что делать:
  • Пустой named volume Docker инициализирует владельцем, который владеет точкой монтирования в образе. То есть для официального Postgres первый запуск сам выставит UID 999 на _data. Это работает само, пока том пустой.
  • Для bind mount магии нет - права наследуются от хоста. Приводи их сам: chown -R 999:999 /path или запускай контейнер под нужным UID через --user.
  • Флаг --user 1000:1000 (или поле user: в compose) заставляет процесс бежать под заданными числами. Удобно сматчить с владельцем каталога на хосте.
Промышленный приём - чинить права в entrypoint. Контейнер стартует как root, скрипт делает chown на смонтированный каталог, затем сбрасывает привилегии через gosu или su-exec и запускает основной процесс уже под непривилегированным юзером:

Код: Выделить всё

#!/bin/sh
set -e
# каталог тома мог приехать с чужим владельцем - выравниваем
chown -R app:app /data
# дальше работаем без root
exec gosu app "$@"
User namespace remapping - более глубокий механизм. Включается в /etc/docker/daemon.json через "userns-remap": "default". Тогда root внутри контейнера (UID 0) маппится на непривилегированный диапазон на хосте (например, начиная с 165536). Это серьёзно повышает безопасность: даже если злоумышленник станет root в контейнере, на хосте он останется бесправным. Но за это надо платить: файлы на томах теперь принадлежат смещённым UID, и при миграции на хост с другим маппингом или без remap'а права поедут. Учитывай это для бэкапов и переноса. Rootless-режим Docker решает похожую задачу радикальнее, целиком гоняя демон без root.

Бэкап docker volume: tar через временный контейнер

У named volume нет "пути, который можно просто заархивировать" в переносимом виде - лезть руками в /var/lib/docker/volumes плохая идея (а при userns-remap ещё и права не те). Канонический способ сделать бэкап docker volume - примонтировать том в одноразовый контейнер и выгрузить tar:

Код: Выделить всё

docker run --rm \
  -v pgdata:/data:ro \
  -v "$(pwd)":/backup \
  alpine \
  tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
Разбор по частям: --rm убивает контейнер после выхода; том pgdata монтируется только на чтение (:ro), чтобы бэкап был консистентным и tar ничего не испортил; текущий каталог хоста проброшен как /backup для приёма архива; -C /data . архивирует содержимое тома, а не сам каталог /data - так при распаковке не появится лишний уровень вложенности. Этот подход работает на уровне абстракции Docker и не зависит от реального расположения данных и драйвера.

Важный нюанс про БД: для Postgres/MySQL файловый tar "на горячую" может поймать несогласованное состояние, если идёт запись. Либо останови контейнер перед бэкапом, либо для баз используй штатный логический дамп (pg_dump, mysqldump) - он консистентен по дизайну. tar тома хорош для файлов приложения, очередей, загрузок; для активной БД предпочитай нативные дампы.

Восстановление - зеркально: создаём пустой том и распаковываем в него.

Код: Выделить всё

docker volume create pgdata_restored
docker run --rm \
  -v pgdata_restored:/data \
  -v "$(pwd)":/backup \
  alpine \
  sh -c "tar xzf /backup/pgdata-2026-06-15.tar.gz -C /data"
Каталог с бэкапами держи на chmod 700 - в архивах БД лежат креды, токены сессий, пользовательские данные.

Миграция данных между хостами

Перенос - это тот же бэкап плюс передача архива. Собрал tar.gz на старом хосте, перекинул scp/rsync, развернул в новый том на целевом:

Код: Выделить всё

# на старом хосте
docker run --rm -v pgdata:/data:ro -v "$(pwd)":/b alpine \
  tar czf /b/pgdata.tar.gz -C /data .
scp pgdata.tar.gz user@newhost:/tmp/

# на новом хосте
docker volume create pgdata
docker run --rm -v pgdata:/data -v /tmp:/b alpine \
  tar xzf /b/pgdata.tar.gz -C /data
Грабли при миграции: если на исходном хосте был включён userns-remap, а на целевом нет (или диапазон другой), владельцы файлов в архиве не совпадут с тем, что ждёт процесс. tar по умолчанию восстанавливает числовые UID/GID из архива. Решение - либо привести права в новом окружении (chown в entrypoint), либо распаковывать с --no-same-owner, чтобы владельцем стал текущий пользователь распаковки, а дальше уже выровнять под нужный UID.

Где данные держать нельзя

Главный антипаттерн - класть данные в слой образа. Всё, что ты пишешь в Dockerfile (COPY, RUN, генерация файлов), вмерзает в неизменяемые слои. Образ - это шаблон, он должен быть stateless. Если положить туда базу или пользовательские загрузки:
  • Данные теряются при любом пересоздании контейнера - слой образа read-only, изменения уходят в эфемерный writable-слой и умирают с контейнером.
  • Образ раздувается, кэш слоёв ломается, пуш/пул в Yandex или VK Container Registry становится медленным и дорогим.
  • Секреты, случайно зашитые в слой, остаются в истории образа навсегда, даже если "удалить" их следующей инструкцией - предыдущий слой никуда не делся.
Правило простое: образ - код и зависимости, том - состояние. Всё изменяемое выноси в volume.

Осиротевшие тома и уборка

Named volume переживает свой контейнер - это фича, но и источник мусора. Удалил контейнер без --rm и без удаления тома - том осиротел, занимает место, висит безымянным хэшем (anonymous volume, их плодят образы с инструкцией VOLUME без явного имени).

Код: Выделить всё

docker volume ls -f dangling=true     # показать осиротевшие
docker volume prune                   # удалить неиспользуемые
docker volume prune -a                # включая именованные, на которые нет ссылок
docker volume prune без раздумий на проде - это инцидент. Он удаляет всё, на что прямо сейчас не ссылается ни один контейнер. Если БД временно остановлена для обслуживания, её том попадает под раздачу. Перед prune всегда смотри docker volume ls и думай. Именуй важные тома явно - анонимные легче снести случайно.

Мини-лаба (повтори руками)
  • Создай том data1, запусти alpine, запиши в него файл: docker run --rm -v data1:/d alpine sh -c "echo hello > /d/test.txt". Проверь, что файл пережил контейнер, перечитав его новым контейнером.
  • Сделай бэкап data1 в tar.gz через одноразовый alpine (по образцу выше). Грохни том, создай заново и восстанови из архива. Убедись, что test.txt на месте.
  • Запусти контейнер с --user 1000:1000 и попробуй записать в том, инициализированный под другим UID. Поймай permission denied и почини через chown в контейнере под root.
  • Создай анонимный том через образ с VOLUME, удали контейнер, найди осиротевший том через ls -f dangling=true.
Контрольные вопросы
  • Почему запись в named volume быстрее, чем в обычный путь внутри контейнера, и при чём тут copy-on-write overlay2?
  • Откуда берётся permission denied при bind mount и почему имена пользователей тут не важны - объясни через UID/GID.
  • Зачем для бэкапа тома нужен временный контейнер с tar, а не прямое копирование из /var/lib/docker/volumes?
  • Что произойдёт с владельцами файлов при миграции тома между хостами с разным userns-remap и как это починить?
Итог

Образ - неизменяемый код, том - живое состояние, и смешивать их нельзя. Named volume - дефолт для любой персистентности и самый быстрый вариант на Linux; bind - для разработки и конфигов; tmpfs - для секретов в памяти. local-драйвер тянет NFS без плагинов, sshfs и облако - через плагины. Права упираются в числовые UID/GID, а не имена - лечится chown в entrypoint, флагом --user и userns-remap. Бэкап и миграция - всегда через временный контейнер и tar, для БД лучше нативный дамп. И помни про docker volume prune: на проде это команда с предохранителем.
👍3 ❤️ 🔥1 😄 🤔1
Аватара пользователя
pythondev
Сообщения: 2
Зарегистрирован: 26 май 2026, 03:28

Re: Данные и тома глубже: драйверы, бэкап, права

Сообщение pythondev »

Вот про -C /data точка наконец дошло почему у меня при распаковке вечно лишний каталог вылезал. Спасибо, поправил свой скрипт бэкапа.
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
lxcan2
Сообщения: 1
Зарегистрирован: 02 июн 2026, 22:14

Re: Данные и тома глубже: драйверы, бэкап, права

Сообщение lxcan2 »

А если БД в томе и надо бэкап без остановки - всегда pg_dump лучше tar тома или есть кейсы где tar норм? У меня прод нельзя гасить.
👍2 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Docker Compose глубже: profiles, healthcheck-зависимости, override
Следующая глава →
Секреты и конфигурация: как не светить пароли

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: как сделать бэкап docker volume и восстановить данныеdocker volume и bind mount в чем разница и где хранятся данные

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость