
Можно много раз говорить, что резервное копирование — это база и необходимая функция для надежной работы с данными в любой системе. А можно раз показать, как мы в нашей инфраструктуре реализовали механизм бэкапов. С учетом специфики виртуальных машин, OpenStack и современных инструментов, обеспечивающих сжатие, дедупликацию и безопасное хранение данных.
Привет, Хабр! Меня зовут Игорь Шишкин, я руковожу командой R&D в облачном направлении Рег.ру и являюсь архитектором наших сервисов — в этой статье расскажу, как мы создавали бэкапы в облаке. Далее о том, как выбирали инструменты для реализации, строили архитектуру и что мы делаем, когда пользователи устраивают тот еще квест — дружно решают забэкапиться в одно время.
Навигация по тексту
Какие вопросы решали на старте: виртуальные машины, консистентность и архитектура
Резервное копирование — неотъемлемая часть любого решения, которое старается обезопасить себя и своих потребителей от потери данных. Тут важно понимать, что бэкапы — это самостоятельный инструмент со своими целями и методиками применения. В нашем случае ими пользуются клиенты с виртуальными машинами (да, и не только), которые могут периодически менять конфигурацию своих услуг. В какой-то момент может оказаться, что их конфигурация не работоспособна. Еще один пример, если клиент на своем сервере по какой-то причине будет взломан, и его данные окажутся зашифрованными. Конечно, со своей стороны мы предпринимаем все меры, чтобы такого не происходило, но и шаги по восстановлению не исключаем.
Спустя продолжительное время внутреннего тестирования мы решили поделиться тем, как это работает у нас, какие проблемы и особенности успели встретить на своем пути.
Начнем с простого: как сделать бэкап чего-то — просто взять и скопировать. Казалось бы, просто. Но если мы говорим про виртуальные машины, то внутри происходит работа с диском — процессы постоянно что-то читают и что-то пишут. Пока мы дочитаем диск виртуальной машины до конца (а его объем на текущий момент может достигать 960 ГБ), данные в начале диска, скорее всего, уже изменятся — и полученная копия станет неконсистентной.
Параллельно мы решали вопросы архитектуры нашей системы бэкапов: какие операции делать синхронными, какие — асинхронными. Как организовать взаимодействие компонентов, как реализовать механизм троттлинга операций и так далее. Простые задачи, например, узнать о занятом месте под конкретный бэкап, выполняются синхронно — у нас уже есть готовая информация об этом. Но если речь идет о получении данных сразу по тысячам таких объектов, приходится немного заморочиться. Для этих целей мы часто делаем отдельные собственные инструменты. Например, exporter’ы с метриками, чтобы оптимизировать работу с этими данными — получать информацию о 1000 бакетов не в 1000 запросов и выдавать это в необходимом формате. Разобравшись со всеми фундаментальными и подготовительными работами, мы погнали дальше.
Про возможные инструменты и подходы
Скажем сразу: низкоуровневый инструмент, обеспечивающий нам организацию резервных копий, мы взяли готовый — проверенный временем restic. Он предоставляет нам способ хранения (создание резервных копий и операции над ними, политики удаления), дедупликацию (поблоковую) и компрессию, поддерживает несколько разных бэкендов хранения, а также из коробки позволяет обеспечить защиту данных at-rest посредством шифрования. В нашем случае репозитории restic’а хранятся во внутреннем S3, построенном на базе Ceph, как и наш публичный сервис S3.
Про restic написано достаточно много в его документации, но в двух словах расскажу о нем. Это single-binary инструмент, который умеет делать резервные копии файлов на диске (или из STDIN’а) в свой, специальным образом сформированный репозиторий, находящийся практически где угодно: от отдельного диска до публичного или приватного облака или облачного сервиса. Он всегда безальтернативно шифрует данные, делает дедупликацию и позволяет выбрать степень компрессии. К его особенностям можно отнести отсутствие механизма планирования операций — действия выполняются через CLI и по команде. Для запуска и совершения операции что-то должно его включить — сам он не умеет запускаться по расписанию. Для использования в масштабах Облака Рег.ру нам нужно было сделать собственный планировщик и оркестратор высокоуровневых операций для резервного копирования.
Почему restic, или как выбирали инструменты
По нашему мнению, в 2k25 практически любая система резервного копирования обязана отвечать трендам безопасности и эффективности хранения. Шифрование данных at-rest — обязательная фича, как дедупликация и компрессия. Таким образом, restic нам идеально подходил. Также мы уже неоднократно использовали его в более компактных решениях — это проверенный временем инструмент.
В целом, софта для резервного копирования много и условно его можно разделить на два вида:
специфический софт для создания бэкапов конкретных данных — понимает и умеет работать с тем, что он бэкапит. Например, базы данных, виртуальные машины или средства для копирования медиафайлов. Инструменты для бэкапа в системах виртуализации — отдельный класс, который создает резервные копии виртуальных машин в различных системах виртуализации;
content-agnostic — тот, которому без разницы что бэкапить, его задача только организовать хранение и обеспечить способ поступления данных. Вся специфика конкретного источника этой информации на стороне потребителя.
Думая над созданием этой системы, мы рассмотрели некое количество готовых решений вокруг OpenStack и в целом систем резервного копирования виртуализации. Поняли, что все они либо недостаточно эффективны, либо чересчур «энтерпрайзны» — нацелены на большое, но не бесконечное количество потребителей, плюс сложны в масштабировании. У них приятный интерфейс, но нам — API подавай. Также платные инструменты биллятся по количеству гипервизоров или виртуальных машин — вроде, логично и легко перекладывается на клиентов, но общая сумма при нашем масштабе выходит просто космическая. Поэтому и решились создать собственную систему резервного копирования.
Конкретно в нашем кейсе специфика бэкапов заключается в создании резервных копий виртуальных машин, поэтому все созданные обертки вокруг restic’а нацелены на эту функциональность и масштабирование под порядки нашего IaaS.
Специфика виртуальных машин в OpenStack
Итак, у нас есть OpenStack — это наша платформа IaaS. Под капотом там используется libvirt и QEMU-KVM. Первый дает API для управления виртуальными машинами в рамках гипервизора. Второй — это сама система виртуализации. В первой (и текущей, но это пока секрет) реализации виртуальных машин их диски живут локально на гипервизорах. Так, для создания резервной копии нам достаточно скопировать этот диск. Остается только два вопроса: как гарантировать или хотя бы резко увеличить вероятность, что:
диск не изменится в процессе его копирования;
виртуальная машина запишет консистентные данные.
Ответ на первый вопрос прост: в libvirt’е предусмотрен механизм, позволяющий разделить диск виртуальной машины на два: один остался бы базовым (текущим), второй — содержал бы все последующие записи. Метод стар как мир и является общей практикой для виртуальных машин, использующих QCOW2 c QEMU-KVM. Работает это за счет использования qemu-guest-agent, который предварительно производит необходимые действия внутри виртуальной машины. Затем QEMU делает кратковременный остановку IO, исчисляемую сотнями микросекунд и создает отдельный QCOW2-файл (его еще называют sidecar). Базовый файл с диском виртуальной машины получается целостный и консистентный, в который гарантированно никто не пишет в данный момент, а все изменения происходят в другом месте. Когда процедура резервного копирования завершается, записанные в соседний файл данные мержатся с оригинальным диском. Файл снова становится один, а IO переключается назад.
А вот ответ на второй вопрос может быть нетривиальным. Дело в том, что для создания консистентного бэкапа — относительно процессов внутри виртуальной машины — необходимо самим процессам об этом сообщить. Такую механику нам предоставляет как раз qemu-guest-agent — для этого в нашей истории он и нужен. У него есть даже собственные хуки, чтобы выполнить осмысленные команды, когда происходит бэкап или снэпшот. Кажется, это отличный момент призвать читателей не выключать qemu-guest-agent и подумать над тем, как для их рабочей нагрузки он может быть полезен.
Как это работает на практике: сначала делаем domfsfreeze → снимаем копию → натравливаем на нее restic → domfsthaw → и, по классике, blockcommit. На выходе получается консистентный снэпшот (в терминах restic’а) в репозитории restic’а в S3. Далее встает вопрос, какие операции должны быть синхронными, а какие асинхронными в самой системе резервного копирования. Ведь делать синхронные операции зачастую неэффективно. Со стороны restic’а получение списка снэпшотов означает множество запросов в S3 и может занимать ощутимое время. Для таких случаев мы поддерживаем кэш данных о репозитории restic’а, чтобы пользователи могли быстро получать эту информацию. Мутирующие операции, вроде создания бэкапов, применения retention policy — асинхронные и как раз триггерят обновление кэша.
Для конечного клиента образуется две опции, как производить резервное копирование своих виртуальных машин в облаке:
1. Технические бэкапы — это наши внутренние резервные копии, которые создаются для увеличения катастрофоустойчивости системы. На случай, если с инфраструктурой что-то пойдет не так или клиент придет в службу поддержки с соответствующим обращением. У нас бывали случаи, когда пользователи случайно удаляли свои виртуальные машины или пропускали все уведомления о недостатке средств в грейс-период. Результат: их виртуальные машины удалялись из-за неоплаты. Для этих бэкапов существует свой собственный retention-период, который позволяет нам обеспечить дополнительную (но не лишнюю) надежность нашего сервиса для клиентов.
2. Услуга резервного копирования — внутри команды мы называем их «бэкапы как сервис». В этом случае пользователи сами определяют, сколько бэкапов им нужно хранить. Например, можно хранить копии за несколько лет.
Если пользователь решит отказаться от услуги — она выключается, но наши технические бэкапы остаются в любом случае. Когда клиент включает услугу, он платит за место, используемое его бэкапами. И здесь в игру вступают бенефиты, которые дает нам restic — бэкапы хранятся максимально компактно и эффективно. Каждая последующая копия — это только изменения данных внутри виртуальной машины. Например, если за всё время мы записали 10 байт на диск, то забэкапятся только эти 10 байт и после этого случится компрессия.
Здесь стоит упомянуть момент, что описанный механизм хорошо работает в ситуации, когда на диске виртуальной машины лежат просто файлы. В случае, если ее диск полностью зашифрован, это, конечно же, не сработает. Тоже самое касается криптоконтейнеров и других подобных механизмов. В этом случае пользователю будет эффективнее и дешевле делать резервное копирование самостоятельно, с учетом устройства его мер защиты данных.
Архитектура сервиса
Что касается нашей архитектуры, она состоит из трех собственных компонентов: API, планировщик и обработчик на гипервизорах. Они связаны между собой через очередь и обмениваются состояниями. Каждый гипервизор — отдельная очередь, что позволяет нарезать задачи без конкуренции и не злоупотреблять приоритетами. Эту схему, которую можно назвать классической — как CQRS на максималках, примененный к распределенной системе. Что внутри: API, получающий запросы потребителей (например, из личного кабинета облака). Сервис-планировщик, обрабатывающий расписания и ставящий бэкапы для конкретных исполнителей — воркеров. Сами воркеры — компоненты, которые живут на гипервизоре и выполняют основную работу.
Кроме этого, существуют база данных и кэш. Они позволяют хранить собственное представление о том, в каком состоянии находится каждая отдельная виртуальная машина в OpenStack с точки зрения системы резервного копирования. Например, чтобы не ходить в OpenStack API лишний раз, мы храним полную информацию о виртуальных машинах в системе резервного копирования. Точно так же для restic-репозиториев — для минимизации обращения к нему синхронно.

Наши маленькие боли
Как это часто бывает, идеального win-win-решения достичь крайне сложно и практически невозможно. Всплыл ряд нюансов. Например, наша маленькая боль, когда клиенты отключают у себя qemu-guest-agent. При создании резервной копии он дает понять гостевой операционной системе, что надо записать всё на диск, чтобы мы могли сделать бэкап. Если пользователь отключает qemu-guest-agent, то такую возможность он нам обрывает.
Это означает, что бэкапы могут быть не полностью консистентными. Например, если у клиента есть база данных, она может не успеть сохранить свое состояние на диск — а у нас не будет возможности это забэкапить. Мы создадим копию диска, но будет ли записано всё состояние — вопрос. Поэтому еще раз призываю не отключать qemu-guest-agent, который помогает системе вовремя записать данные на диск. Кстати, это влияет не только на бэкапы, но и на некоторые операции с виртуальными машинами. Например, без qemu-guest-agent не работает смена пароля.
Второй кейс связан с тем, что пока идет бэкап, пользователь может захотеть что-то сделать со своей виртуальной машиной, допустим, поменять тариф. Это способно привести к потере данных, ведь OpenStack не знает ничего о происходящих в данный момент изменениях. К счастью, для этой проблемы есть системное решение — блокировка виртуальной машины в Nova. Остается только решить условно классическую проблему stale lock’ов…
Что делать, когда пользователи начинают дружно бэкапиться
Как и в любой другой распределенной системе, вопрос масштаба и нагрузки открыт всегда. Облако Рег.ру большое: в продакшене — это сотни гипервизоров и десятки тысяч клиентов только в одном регионе. Если всем дружно позволить бэкапиться одновременно, то у нас банально закончится сеть, PPS станет диким, S3 тоже загрустит. Потому мы применяем несколько превентивных механизмов. Прежде всего, «размазываем» бэкапы по времени и периодически ребалансируем расписания, чтобы исключить образование горячих точек и непрогнозируемого роста нагрузки.
Также мы любим рейт-лимиты, они спасают сервисы — в большинстве случаев есть квоты по RPS. В некоторых случаях — квоты по пропускной способности. Что особенно примечательно, этот механизм работает корректно, ведь restic умеет обрабатывать троттлинг от S3. Кроме того, каждому сервису выделяется своя квота по занимаемому месту. Это особенно важно в контексте использования Ceph в нашем S3 — он крайне не любит, когда заканчивается место.
Последнее на очереди, но не по значению — в системе мы можем управлять параллелизмом выполнения бэкапов (сколько виртуальных машин бэкапятся одновременно) и отдельно количеством запросов (сколько потоков для работы с S3 используется) с клиентской стороны. Мы достаточно долго выбирали эффективные значения и нам удалось найти оптимальный баланс скорости создания резервных копий и восстановления с учетом запаса по нагрузке.
Финальная мысль
Резервное копирование — штука сложная, но необходимая для любой инфраструктуры и нагруженного сервиса. Как и многие, мы прошли через пробы, ошибки и тестирование разных вариантов, чтобы найти свое, подходящее именно нам решение.
Для себя мы вывели такую формулу: дифференциальные бэкапы и рационально подобранные инструменты помогли нам справиться с высокими нагрузками, обеспечить надежное и эффективное хранение бэкапов на внутреннем хранилище S3. Про стабильность и масштабируемость тоже помним, учитываем, следим, прогнозируем тренды и регулярно добавляем места.
Спасибо, что дочитали! Если у вас появились вопросы — велкам, обсудим в комментариях.