MAATRIX / Блог / Скопировали 2 ТБ и потеряли права доступа: rsync без одного флага

Скопировали 2 ТБ и потеряли права доступа: rsync без одного флага

MAATRIX

Перенос отработал без единой ошибки: rsync прогнал 2 терабайта, вывод чистый, все файлы на месте, содержимое совпадает байт в байт. А через полчаса приложение на новом сервере начинает сыпать «Permission denied» на часть тех же файлов, которые вы только что скопировали. Ниже — разбор именно такого случая: почему успешное копирование не значит успешный перенос, и что конкретно в команде rsync привело к потере прав доступа.

Что случилось: перенос прошёл, доступ пропал

Сценарий стандартный для миграции: старый сервер выводится из эксплуатации, данные (в этом случае — каталог с пользовательскими файлами и медиа, порядка 2 ТБ) нужно перенести на новый до отключения старого. Команда для переноса выбрана самая привычная — rsync поверх SSH, без изучения man-страницы в деталях, потому что «rsync же для этого и существует, просто скопирует».

Копирование заняло несколько часов, лог отработал без единой строки с ошибкой, счётчик файлов на входе и на выходе совпал. По всем формальным признакам перенос данных — успешен: файлы существуют, их количество совпадает, содержимое при выборочной проверке (diff, контрольные суммы) идентично исходному. На этом этапе перенос данных обычно и объявляют завершённым.

Проблема проявилась не сразу, а когда на новом сервере запустили приложение, которое с этими файлами работает — веб-сервис, читающий и записывающий часть каталога от имени отдельного системного пользователя. Часть запросов начала падать с ошибками доступа: не «файл не найден» (это была бы совсем другая проблема), а именно отказ в праве на чтение или запись — при том что сам файл физически присутствовал на диске и открывался, например, под root.

Первая гипотеза: ищем проблему не там

Первая реакция в такой ситуации почти всегда — не про права доступа, а про целостность данных. Это логично: raз приложение не может нормально поработать с файлом, естественно предположить, что файл повреждён или скопирован не полностью.

Проверили в таком порядке:

  • Контрольные суммы — сравнили md5sum (либо sha256sum) нескольких проблемных файлов на старом и новом сервере. Суммы совпали — данные внутри файлов побайтово идентичны, повреждения при передаче нет.
  • Размер и количество файловdu -sh на каталоге и find /data -type f | wc -l на старом и новом сервере дали одинаковые цифры. Ничего не потерялось и не задвоилось.
  • Версию и конфиг приложения — проверили, не изменилась ли версия сервиса при развёртывании на новом сервере, не другой ли путь к каталогу в конфиге, нет ли опечатки в пути монтирования диска. Конфиг оказался идентичен — путь к данным тот же, версия приложения та же.

Все эти проверки заняли заметную часть времени расследования и ожидаемо ничего не дали — потому что искали не в той плоскости. Данные были целы. Проблема была не в содержимом файлов, а в метаданных: кто ими владеет и кому разрешено их читать и писать.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

ls -la находит виновника за одну команду

Переломный момент — когда вместо содержимого файла начали сравнивать его атрибуты. Команда, которая сразу расставила всё по местам:

# на старом сервере
ls -la /data/uploads/user_42/ | head -20

# на новом сервере
ls -la /data/uploads/user_42/ | head -20

Вывод на старом сервере:

-rw-r----- 1 appuser appgroup 245678 авг 15 09:12 report.pdf
-rw-r----- 1 appuser appgroup  81234 авг 15 09:14 image.jpg

Вывод на новом сервере, для тех же самых файлов:

-rw-r----- 1 deployuser deployuser 245678 авг 20 14:03 report.pdf
-rw-r----- 1 deployuser deployuser  81234 авг 20 14:03 image.jpg

Разница видна невооружённым глазом: владелец appuser:appgroup на старом сервере превратился в deployuser:deployuser на новом. Режим доступа (rw-r-----) формально тот же самый набор бит, но применяется он теперь относительно другого владельца и другой группы — то есть фактические права для процесса, работающего от appuser, изменились полностью. Раньше appuser был владельцем файла и имел право на чтение и запись. Теперь appuser владельцем не является, а группа deployuser ему тоже не подходит — единственное, что ему остаётся, это права «остальных», которых в режиме rw-r----- попросту нет.

Это заняло одну команду и несколько секунд, в отличие от часа проверки контрольных сумм. Мораль в такой ситуации звучит просто: если приложение не может поработать с файлом, который физически существует и не повреждён, сравнение ls -la для одного и того же пути на источнике и приёмнике должно идти в диагностике одним из первых шагов, а не последним.

Почему rsync меняет владельца файлов «по умолчанию»

Здесь важно понять механику, а не просто запомнить «добавляйте -a». У rsync нет единого «поведения по умолчанию» для владельца и группы — оно зависит от того, какие флаги переданы и от чьего имени запущен процесс на стороне приёмника.

Короткие команды вида rsync -rtvz или rsync -rvz (рекурсия, времена, verbose, сжатие) не содержат флагов -o (сохранить владельца) и -g (сохранить группу). Rsync в этом случае не пытается воспроизвести исходного владельца — файл на приёмнике создаётся с владельцем и группой того процесса, который его туда записывает, то есть с владельцем того пользователя, от имени которого запущен rsync на стороне назначения. Не с сервера-источника, а именно на приёмнике.

Именно это и произошло в разборе выше: перенос запускали от пользователя deployuser (обычная учётная запись для деплоя, без root), команда переноса не включала явного сохранения владельца — и каждый файл на новом сервере получил владельца deployuser, потому что это и есть пользователь, создававший файлы при записи.

Отдельная и важная тонкость: даже если явно добавить флаги -o и -g (или собрать полноценный архивный режим), они реально сработают только если сохранение владельца в принципе возможно для процесса на приёмнике. Присвоить файлу владельца, отличного от текущего пользователя, может только root (или процесс с соответствующей привилегией) — обычный непривилегированный пользователь физически не способен назначить файлу чужого владельца, у него просто нет на это прав в самой ОС. Если rsync на приёмнике запущен не от root, флаги -o/-g либо будут проигнорированы (для несовпадающих пользователей), либо потребуют дополнительной настройки (например, через --usermap/--groupmap с соответствующими привилегиями). Так что дело не только в наборе флагов команды — дело ещё и в том, от чьего имени вообще запущен сам перенос.

Архивный режим и что именно он сохраняет

Флаг -a (архивный режим, --archive) — это не какая-то отдельная magic-опция, а сокращение сразу для целого набора флагов:

-a  =  -rlptgoD
ФлагЧто сохраняет
-rрекурсивный обход подкаталогов
-lсимволические ссылки копируются как ссылки, а не разыменовываются
-pрежим доступа (права chmod)
-tвремя модификации файла
-gгруппа-владелец
-oвладелец
-Dфайлы устройств и специальные файлы

Для сравнения — чем короткие команды отличаются от архивного режима:

# так копировали в разбираемом инциденте — владелец и группа не сохраняются
rsync -rtvz /data/ deployuser@newserver:/data/

# так нужно было — архивный режим сохраняет владельца, группу, права, время, ссылки
rsync -avz /data/ deployuser@newserver:/data/

# для переноса между серверами и сохранения владельца обязательно нужен root
sudo rsync -avz -e ssh /data/ root@newserver:/data/

Обратите внимание: -avz в примере тоже сработает не полностью, если сессия SSH идёт от непривилегированного пользователя — владелец и группа сохранятся только для тех файлов, где целевой UID/GID существует на приёмнике и процесс имеет право его назначить. При переносе от root к root проблем обычно не возникает, поэтому многие инструкции «просто добавьте -a» подразумевают перенос именно от root, и не проговаривают эту часть явно.

Как исправили: массовый chown и повторная проверка

Восстанавливать права доступа заново перекачиванием 2 ТБ данных было избыточно — сами данные были целы, требовалось поправить только метаданные. Порядок действий:

  1. Зафиксировали правильное соответствие владелец/группа для каждого подкаталога — сверили со старым сервером через ls -la и с документацией по учётным записям приложения (какой пользователь и группа должны быть у каких путей).
  1. Массово поправили владельца и группу через chown, рекурсивно, от root:
sudo chown -R appuser:appgroup /data/uploads/
  1. Проверили режим доступа отдельно от владельца — в этом случае rw-r----- совпадал с исходным, но если бы отличался и он, права поправили бы через chmod:
sudo chmod -R u=rw,g=r,o= /data/uploads/
  1. Явно проверили доступ от имени самого приложения, а не просто визуально сравнили вывод ls -la. Права «выглядят одинаково» и права «приложение реально может ими пользоваться» — не всегда одно и то же, особенно если есть ACL или SELinux/AppArmor поверх обычных unix-прав. Проверка через sudo -u appuser test -r /data/uploads/user_42/report.pdf && echo OK (или прямой тестовый запрос к приложению) — минимальный, но обязательный шаг перед объявлением переноса завершённым.
  1. Только после того как приложение реально прочитало и записало тестовый файл под своим рабочим пользователем, перенос считали закрытым — а не в момент, когда rsync вывел итоговую статистику без ошибок.

Как не наступить на те же грабли в следующий раз

Практические выводы, которые стоит зашить в чек-лист для любого следующего переноса между серверами:

  • Всегда используйте -a при переносе, где важна точность атрибутов, а не собирайте короткие команды из отдельных флагов «на глаз». Архивный режим — это не про лень, это про то, что он покрывает весь типовой набор метаданных, который легко забыть перечислить вручную.
  • Если перенос делается между серверами с разными системами пользователей (разные UID/GID для одних и тех же логических ролей, разные имена учётных записей), сохранение владельца через -o -g без дополнительной мысли может привести не к отсутствию владельца, а к неправильному владельцу — потому что rsync сопоставляет по UID/GID, а не по имени, если не указано иное. Здесь нужно осознанно решить: либо явно сопоставить UID/GID между серверами заранее, либо использовать --usermap/--groupmap для переноса с явным переназначением, либо перенести и сознательно поправить владельца отдельным chown после, как в разборе выше — но именно осознанно, а не случайно, как получилось в этом инциденте.
  • Перенос больших объёмов лучше делать от root (или через rsync в режиме демона с соответствующими правами) именно тогда, когда сохранение владельца и группы критично для работы приложений — иначе флаги -o/-g в команде есть, а фактического эффекта от них может не быть.
  • Проверка «файлы скопировались» недостаточна. Обязательный отдельный шаг сразу после любого крупного переноса — сравнение атрибутов (ls -la на выборке путей с обеих сторон) и, что важнее, реальная проверка того, что зависящее от этих файлов приложение может их читать и писать от своего рабочего пользователя. Это на порядок дешевле по времени, чем расследование постфактум, когда сервис уже отказывает пользователям.
  • Если структура каталогов и владельцев на новом сервере в принципе сложная (несколько сервисов, несколько владельцев, ACL), стоит заранее задокументировать список путей и требуемых владельцев/прав до переноса — тогда сверка после переноса превращается в формальную проверку по списку, а не в повторное расследование с нуля.

Если перенос данных — это не разовое действие, а часть регулярного процесса миграции файлового хранилища, отдельно стоит посмотреть на миграцию файлового хранилища на терабайты — там разобраны вопросы параллелизации самого переноса, а базовые принципы работы с пользователями и группами в Linux, которые здесь были ключевыми для диагностики, подробно разложены в статье про управление пользователями и группами Linux.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Почему rsync вообще не сохраняет владельца по умолчанию, ведь это логично?

Потому что у rsync исторически нет единого «поведения по умолчанию» — есть набор независимых флагов, и то, что копируется, определяется явно переданными опциями. Без -o/-g (или без -a, который их включает) rsync не пытается воспроизвести исходного владельца — файл создаётся с владельцем того процесса, который его записывает на приёмнике.

Если я добавлю -a, этого точно достаточно?

Флаги в команде — необходимое, но не достаточное условие. Сохранение владельца и группы реально сработает только если процесс rsync на приёмнике имеет право назначать такого владельца — то есть, как правило, если он запущен от root. От обычного непривилегированного пользователя -o/-g может либо не сработать для всех файлов, либо потребовать дополнительной настройки через --usermap/--groupmap.

Как быстро проверить, что права после переноса в порядке, не перечитывая руками каждый файл?

Сравните вывод ls -la на нескольких характерных путях на старом и новом сервере — совпадение владельца, группы и режима сразу видно. Для массовой проверки можно пройтись find /data -printf '%u %g %m %p\n' по обеим сторонам и сравнить через diff, но главное — не остановиться на этом, а ещё явно проверить доступ от имени рабочего пользователя приложения (sudo -u appuser test -r <путь>).

Можно ли восстановить права без повторного копирования всех файлов?

Да, если сами данные скопированы верно и повреждены только метаданные — массовый chown -R и, при необходимости, chmod -R по всему дереву каталогов чинят это за минуты, без повторной передачи 2 ТБ по сети.

А если структуры пользователей на старом и новом сервере вообще разные (разные UID)?

Тогда простое сохранение владельца через rsync может привести не к правильному, а к неверному владельцу, потому что сопоставление идёт по числовому UID/GID. В этом случае перед переносом стоит явно свести таблицу соответствия старых и новых UID/GID и либо создать пользователей с теми же UID на новом сервере заранее, либо использовать --usermap/--groupmap, либо сознательно исправлять владельца отдельным шагом после переноса.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →