Предисловие
Этот инструмент появился не как идея “сделать проект”, а как попытка решить очень конкретную и довольно неприятную проблему.
Однажды мне нужно было разобраться с настройкой сети на домашнем сервере. К тому моменту я уже был уставший, но решил всё-таки дожать задачу. В итоге это сыграло против меня — где-то по пути я просто сломал систему.
И самое забавное — в тот момент я этого даже не заметил.
Вечером ничего так и не заработало. Я махнул рукой и пошёл спать. А утром выяснилось, что дело вообще не в сети, а в банальных ошибках в конфиге: лишние символы, опечатки — всё то, что легко пропустить, когда устал.
Исправление заняло несколько часов. И вот это было самое неприятное.
Не сама ошибка — с этим можно жить. А то, что на её исправление ушло несоразмерно много времени.
В какой-то момент я поймал себя на мысли:
Почему у меня нет простого способа откатиться назад?
Система была относительно свежей, всего пару месяцев. Нормальные бэкапы я тогда ещё не настроил. Да и, если честно, не чувствовал острой необходимости.
Но в тот момент стало понятно:
Мне не нужен backup. Мне нужен rollback.
Причём быстрый, простой и локальный.
С этого и начался поиск решения, который в итоге вылился в отдельный инструмент.
Проблема rollback на Linux — неочевидная задача
Сначала кажется, что с откатом всё давно решено. Есть бэкапы, снапшоты, файловые системы с поддержкой моментальных снимков.
Но на практике всё ломается об одну простую вещь: в большинстве реальных машин используется ext4. Это стандартная файловая система по умолчанию, и в продакшене, и в домашних серверах. При этом у неё нет встроенного механизма снапшотов.
И именно здесь начинается проблема.
Backup ≠ Rollback
Резервное копирование и откат — это вообще разные задачи, хотя их часто пытаются использовать как одно и то же.
Backup — это про сохранность данных. Он нужен, когда диск умер, данные потерялись или нужно восстановиться через какое-то время. Обычно это тяжёлые операции с большим объёмом данных и часто с внешними хранилищами.
Rollback — про другое. Это быстрый локальный возврат системы в предыдущее состояние: “как было вчера” или “до последних изменений”.
Формально backup можно использовать и для rollback. Но на практике это неудобно: долго, тяжело и часто просто избыточно для локальных проблем.
Где это начинает болеть
Это особенно хорошо проявляется в самых обычных сценариях.
Любые изменения в системе, от правки конфигурации до обновления пакетов, могут привести к тому, что система перестаёт работать корректно. Это может быть неочевидная ошибка в конфиге, некорректное обновление или просто неудачный эксперимент.
И дальше начинается ручная работа:
Искать, что сломалось
Сравнивать конфиги
Смотреть логи
И всё это — время. А порой буквально десятки часов.
В какой-то момент начинаешь понимать, что не хватает кнопки "вернуть систему в состояние, в котором она точно работала".
Почему готовые решения не всегда подходят
На первый взгляд кажется, что решения есть. Но если копнуть — у каждого свои ограничения.
Файловые системы вроде Btrfs или ZFS действительно дают удобные снапшоты. Но требуют смены файловой системы. А это уже миграция, которая не всегда возможна или просто не хочется этим заниматься.
LVM-снапшоты тоже решают задачу, но предполагают, что система изначально построена на LVM. В реальности это встречается далеко не всегда, особенно в домашних и простых серверах.
Backup-инструменты вроде borg или restic решают другую задачу — хранение данных, а не быстрый откат. Они отлично подходят для резервного копирования, но избыточны для локального rollback.
Можно вспомнить rsnapshot, timeshift, ccollect и другие инструменты. Но у них та же история: либо они заточены под долгосрочное хранение, либо не очень удобны для быстрого выборочного отката.
В итоге получается странная штука: вроде инструменты есть, а нормального rollback — нет.
Чтобы не быть голословным, я попробовал свести это в простое сравнение:
Решение | Требования | Скорость rollback | Простота использования | Гибкость | Подходит под задачу rollback |
|---|---|---|---|---|---|
Btrfs / ZFS | Смена ФС / миграция | Мгновенно | Средняя | Низкая | Не всегда |
LVM snapshot | LVM изначально | Быстро | Средняя | Средняя | Ограниченно |
Borg / Restic | Backup-хранилище | Медленно | Низкая | Высокая | Нет |
Timeshift | Настройки + storage | Средне (зависит от конфигурации) | Высокая | Низкая | Частично |
Rsync + hardlink | Любой ext4 | Быстро | Высокая | Высокая | Да |
Формулировка проблемы
Ситуация сводится к простой мысли: стандартная конфигурация Linux на ext4 не даёт удобного механизма отката, а существующие решения либо требуют изменения инфраструктуры, либо решают другую задачу.
Значит, нужен был способ быстро и локально откатывать систему на ext4 без смены файловой системы и без тяжёлых зависимостей. От этого и родилась идея собрать “снапшотоподобное” поведение на уровне обычных файлов.
Идея решения: rsync и hardlink вместо встроенных снапшотов
Уже после того как становится понятна проблема, следующий логичный шаг — попытаться решить её максимально простыми средствами, не меняя файловую систему и не добавляя тяжёлые зависимости.
Ключевая идея оказалась довольно простой:
Если нельзя использовать встроенные снапшоты, можно собрать их поведение на уровне файлов
В качестве основы были выбраны два механизма:
Rsync — для синхронизации состояния файлов
Hardlink — для экономии дискового пространства
Почему именно rsync
rsync уже давно используется как универсальный инструмент для копирования и синхронизации файлов. Он умеет сравнивать директории, определять изменения и переносить только то, что действительно изменилось.
В контексте задачи rollback это даёт важное свойство:
Можно привести файловую систему к нужному состоянию, не копируя всё целиком
То есть rsync выступает не как “копировщик”, а как механизм приведения состояния.
Где появляется неочевидная проблема
Если просто копировать систему целиком при каждом snapshot, это быстро становится неработающим:
Слишком большой объём данных
Медленная работа
Неэффективное использование диска
Такой подход не работает для регулярных снапшотов.
Hardlink как ключ к эффективности
Решение даёт механизм hardlink.
Hardlink — это не копия файла, а дополнительная ссылка на тот же самый inode в файловой системе. Это означает, что один и тот же файл может “существовать” в нескольких местах, занимая при этом место только один раз.
Именно это позволяет реализовать дедупликацию без дополнительной логики.
Если файл не изменился, он просто переиспользуется через hardlink. Если изменился — создаётся новая физическая копия.
Как это объединяется
Комбинация rsync и hardlink даёт нужный эффект.
Каждый новый snapshot создаётся на основе предыдущего:
rsync -aHAX --numeric-ids \ --delete \ --link-dest="${PREV}" \ "${SRC}" "${DEST_TMP}/"
Отдельно хочу обратить внимание на ключи:
-a — сохраняет структуру файлов и метаданные
-H — позволяет корректно работать с hardlink
-A / -X — сохраняют ACL и extended attributes
--delete — удаляет лишние файлы, приводя точное соответствие состоянию snapshot
--link-dest — ключевой параметр, позволяющий переиспользовать неизменённые файлы
Как это реализовано на практике
После того как определена идея, следующий вопрос — как это выглядит в реальной системе.
Здесь важно было сохранить несколько принципов:
Гибкость инструмента
Прозрачная структура
Предсказуемое поведение
Быстрый запуск и откат
В результате реализация свелась к довольно простой, но аккуратной схеме.
Структура хранения
Снапшоты хранятся как обычные директории:
.infra_snapshots/ ├── system/ │ ├── 2026-03-01_00-30-00/ │ ├── 2026-03-02_00-30-00/ │ └── LATEST -> 2026-03-02_00-30-00/ ├── docker/ │ ├── 2026-03-01_23-30-00/ │ └── LATEST -> 2026-03-01_23-30-00/
LATEST — это симлинк на последний snapshot, используемый по умолчанию. Он избавляет от необходимости вручную искать последний снимок, упрощает restore и автоматизацию.
Каждый снапшот — это отдельная папка с полной структурой файлов.
При этом:
Внешне это выглядит как полноценная копия системы, но фактически хранит только изменения
Выбор именно такой структуры неслучаен.
Разделение на system и docker позволяет не смешивать конфигурацию системы и пользовательские данные
Использование timestamp вместо порядковых номеров упрощает навигацию и отладку
Симлинк LATEST
Это делает структуру максимально прозрачной: любой snapshot можно открыть как обычную директорию и посмотреть его содержимое.
Создание снапшота
Процесс создания снапшота состоит из нескольких шагов.
Сначала создаётся временная директория вида:
.tmp-YYYY-MM-DD_HH-MM-SS
Это нужно для того, чтобы избежать ситуации, когда снапшот создаётся не полностью (например, если процесс прервётся).
mv -T "${TMP_DEST}" "${FINAL_DEST}" ln -sfn "${FINAL_DEST}" "${LATEST_LINK}"
Снапшот сначала собирается во временной директории, и только после успешного завершения атомарно “переключается” в финальное состояние.
Далее выполняется rsync с использованием --link-dest. На этом этапе происходит основная работа:
Файлы сравниваются с предыдущим снапшотом
Неизменённые переиспользуются через hardlink
Изменённые записываются заново
После успешного завершения временная директория переименовывается в финальную.
Для пользователя это выглядит атомарно:
Либо снапшота нет
Либо он полностью готов
Промежуточного состояния не существует.
Разделение system и docker
Отдельно стоит важное решение — разделение снапшотов. Система и Docker должны обрабатываться независимо.
Это сделано по нескольким причинам.
Во-первых, у них разная природа данных. Системные файлы — это конфигурация и окружение, тогда как Docker содержит как инфраструктуру, так и пользовательские данные.
Во-вторых, это позволяет не смешивать rollback и хранение данных. Например, Docker volumes намеренно не входят в снапшоты.
В результате:
System snapshot отвечает за состояние системы
Docker snapshot — за инфраструктурную часть окружения.
Такое разделение даёт более точный и безопасный restore.
Восстановление
Restore реализован через тот же rsync, но в обратную сторону.
Фактически это процесс синхронизации:
Текущее состояние системы приводится к состоянию выбранного снапшота.
Предварительная оценка изменений (dry-run)
При этом используется режим dry-run, который показывает изменения до их применения:
rsync -aHAX --numeric-ids --delete \ --dry-run \ "${SNAP}/" "/"
Он позволяет увидеть:
Какие файлы будут удалены
Какие будут изменены
Какие добавлены
Это критично, так как rollback является потенциально опасной операцией. Фактически dry-run выступает как “план изменений”, который можно проверить перед применением и удостовериться, что ничего важного случайно не удалится.
Ограничения и подводные камни
Несмотря на то что подход с rsync и hardlink даёт удобный механизм отката, нужно понимать его ограничения. Это не “магические снапшоты” уровня файловых систем, а файловая модель со своими особенностями.
Нет атомарности на уровне всей системы
В отличие от Btrfs или ZFS, здесь нет мгновенного снимка всей системы в один момент времени.
Снапшот создаётся в процессе выполнения rsync. Это означает, что данные читаются последовательно, и система в этот момент может изменяться.
На практике это приводит к тому, что snapshot является согласованным на уровне файлов, но не обязательно отражает состояние системы в конкретную секунду.
Это, к слову, одна из причин, почему Docker volumes не входят в снапшоты: для активно изменяемых данных такая модель не всегда даёт корректный результат.
Хотя для большинства задач rollback этого достаточно, но это стоит учитывать.
Проблемы с активно изменяемыми данными
Наиболее чувствительный сценарий — это данные, которые активно изменяются во время создания снапшота.
Например:
Базы данных
Журналы (logs)
Файлы, которые постоянно перезаписываются
В таких случаях возможно, что snapshot зафиксирует “промежуточное” состояние.
Это не обязательно приведёт к проблемам, но в некоторых сценариях может потребовать дополнительного внимания и осторожности.
Практический вывод здесь простой:
Для критичных сервисов лучше либо останавливать их перед snapshot, либо использовать отдельные стратегии резервного копирования
Это не backup
Хочу отдельно подчеркнуть: данный подход не заменяет полноценное резервное копирование.
Он не защищает:
От выхода из строя диска
От удаления самих снапшотов
От внешних проблем (например, повреждения файловой системы)
Snapshot — это инструмент локального отката, а не хранения данных.
Поведение restore
Restore — это операция синхронизации, а не “отмена изменений”.
Это означает, что:
Новые файлы, которых нет в snapshot, будут удалены
Изменённые файлы будут перезаписаны
Текущее состояние системы будет приведено к прошлому
Это мощный механизм, но он требует понимания того, что именно будет изменено.
Именно поэтому в реализации используется обязательный dry-run перед применением.
Ограничения docker snapshot
Отдельный момент касается Docker.
Снапшоты намеренно не включают:
Docker volumes
Runtime-данные контейнеров
Это сделано для того, чтобы:
Не раздувать объём снапшотов
Не замедлять их создание
Не смешивать rollback инфраструктуры с хранением пользовательских данных
В результате docker snapshot позволяет восстановить структуру окружения, но не сами данные внутри контейнеров.
Ограничение по inode
Каждый snapshot создаёт новые ссылки на файлы, что увеличивает общее количество inode.
При большом числе снапшотов это может привести к:
Исчерпанию inode
Деградации производительности при обходе директорий
Это особенно заметно на системах с большим количеством мелких файлов.
Учитывайте этот момент, если решите выставить количество снапшотов больше дефолтного в настройках инструмента, особенно на больших и нагруженных системах.
Ограничения ли?
Все эти ограничения — не недостаток реализации, а следствие выбранного подхода.
Он сознательно ориентирован на:
Простоту
Предсказуемость
Гибкость
Минимальные требования к системе
При понимании этих ограничений инструмент работает именно так, как от него ожидается.
Это не универсальное решение “на все случаи”, а практичный инструмент под конкретный класс задач
Заключение
В итоге задача оказалась не в том, чтобы найти “идеальный инструмент”, а в том, чтобы правильно сформулировать проблему.
Нужен был не backup, не сложная система снапшотов и не новая файловая система. Нужен был простой и предсказуемый механизм, который позволял бы быстро откатить систему в рабочее состояние.
В итоге решение оказалось значительно проще, чем ожидалось. Комбинации rsync и hardlink достаточно, чтобы получить поведение, близкое к снапшотам, без изменения инфраструктуры и без тяжёлых зависимостей.
Важно только помнить его границы: это не замена резервному копированию, а инструмент под конкретную задачу — быстрый локальный rollback.
О проекте
Изначально этот инструмент создавался как локальное решение под конкретную задачу. Он не задумывался как публичный проект, не был рассчитан на гибкость или удобство установки.
Со временем стало понятно, что подобная проблема возникает достаточно часто, а простого решения под ext4 не хватает. Поэтому инструмент был приведён в более универсальный вид и опубликован.
Вместо вывода
Эта статья — не столько про инструмент, сколько про подход, который я неожиданно открыл для себя. Иногда задача решается не внедрением сложных технологий, а переосмыслением того, что уже есть в системе.
В данном случае оказалось, что стандартных инструментов Linux достаточно, чтобы построить удобный и практичный механизм rollback, если правильно их использовать.
GitHub
Если вам интересен сам инструмент или подход, исходный код доступен на GitHub
Upd
Пока готовилась статья, инструмент успел дорасти до первой полноценной версии.
Из “скрипта под себя” он превратился в воспроизводимый rollback-механизм для ext4, которым уже можно пользоваться на практике.
Если коротко: это тот инструмент, которого мне самому не хватало.
Если у вас есть идеи, вопросы или замечания — буду рад обсудить.
