main

Cross-storage pool'ы в бакуле

Предыстория: в существующей инсталляции бакулы на sd начало заканчиваться место, а увеличить его нет возможности. Было решено поставить рядом ещё один sd, и часть бэкапов перевести на него путём миграции данных средствами самой бакулы. Здесь и начинаются наши приключения.

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

Идём в документацию бакулы, читать про migration job. Вроде всё просто: настройте сам sd, добавьте новый блок StorageDaemon на директоре, добавьте новый блок Job {} с "Type = Migrate" и выполните это задание из консоли. Но вот дальше мы упираемся в кривизну проектирования бакулы. Чтобы понять в чём дело, надо немного погрузиться в логику работы существующего конфига.

У нас есть энное количество Fileset'ов, которые определяют что бэкапить в fs. Есть энное количество Schedule, которые описывают когда бэкапить и по какому алгоритму (full/incr/diff). Там же можно указать куда это бэкапить (pool), и как раз логично указывать его именно в schedule, поскольку время хранения volume с файлами определяется в pool'е (и только там), а для разных уровней бэкапа нужно разное время хранения файлов1. Наконец есть блоки Job, которые связывают всё перечисленное вместе. Там также можно переопределить pool, storage и level.

Вот так это выглядит в реальном конфиге:

Pool {
  Name = default-pool-1m
  Pool Type = Backup
  Volume Retention = 1 month
  Label Format = M1-
  < ... >
}
< ...ещё 4 подобных записи >
Schedule {
  Name = "F1m-I1d"
  # полный бэкап в первое воскресенье каждого месяца (хранить 6 месяцев)
  Run  = Full         Pool = default-pool-6m at 23:25 on 1st sun
  # инкрементальный бэкап во все остальные дни (хранить 1 месяц)
  Run  = Incremental  Pool = default-pool-1m at 23:25 on 2nd-5th sun
  Run  = Incremental  Pool = default-pool-1m at 23:25 on mon-sat
}
< ...ещё 3 записи >

Теперь вернёмся непосредственно к миграции. Если мы хотим, чтобы новые тома (volume) создавались на новом sd, нам в принципе достаточно явно указать (переопределить) параметр "storage" в соответствующем определении задачи (job {}); Но чтобы переместить уже существующие тома, созданные ранее с этого же клиента/задания, то это можно сделать только через указание параметра "next pool" в том pool {} откуда мы собираемся мигрировать (или переопределить его в schedule {} для этой задачи). Если для конкретного задания используется несколько pool'ов, то указывать нужно во всех.

Проще говоря, миграция уже существующих данных сейчас возможна только вместе со сменой pool'а. В моём случае это значит, что придётся продублировать все определения pool {}, просто чтобы дать им новые имена для использования на новом sd и все определения schedule {}, т.к. в них явно указан pool. Вот так мы на ровном месте получаем ненужный срач в конфиге при выполнении в общем-то типовой задачи. И просто так их потом удалить не получится, вы будете с этим потом жить ещё годы.

Происходит это потому, что разработчики до конца не определились для себя, могут ли тома одного pool одновременно храниться на нескольких (однотипных) storage или же строго на одном из них. По изначальной реализованной логике - это сущности разного назначения и вроде как ответ "да, могут", иначе непонятно, зачем pool'ы сделали глобальной сущностью, в конфиге director'а, вместо их определения в конфиге sd или хотя бы обязательного параметра "storage" в определении pool'а. По факту, из изучения позднее реазилованных фич, получается что таки "нет". Некоторые части документации, хоть и не говорят однозначно, но прямо на это намекают:

The Storage directive defines the name of the storage services where you want to backup the FileSet data. The Storage resource may also be specified in the Job resource, but the value, if any, in the Pool resource overrides any value in the Job. This Storage resource definition is not required by either the Job resource or in the Pool, but it must be specified in one or the other. We highly recommend that you define the Storage resource to be used in the Pool rather than elsewhere (job, schedule run, …).

Видимо здесь сказывается тяжелое наследие системы, изначально оориентированной на ленты и большие объёмы данных, которую в дальнейшем переделывали на работу с "томами" в виде файлов.

Ситуацию может спасти введение директивы "Next storage" в storage {} конфига director'а (по аналогии), но когда ещё это будет и будет ли вообще - непонятно.

В сухом остатке я не стал заморачиваться с миграцией в виду её побочных эффектов для моего случая, и просто переопределил storage в job {} для нужных клиентов. Все новые бэкапы с них теперь пойдут на новый storage, а место на старом должно чиститься "естественным путём" по мере устаревания томов (retention) и их переиспользования (recycle).

UPD: В процессе перенастройки всплыл интересный эффект, имеющих отношение к теме: такие cross-storage pool'ы действительно работают, но строго при условии указания 'max volume jobs = 1'. Иначе происходит следующее: при выполнении первой задачи, которая использует storage A, на нём размечается очередной том, который помечается статусом 'Append'. При выполнении любой следующей задачи, которая использует любой другой storage, отличный от A, задачи просто валятся с ошибкой из-за того, что тот просто не может у себя найти указанный том (ведь он физически находится на A), а сам том в базе помечается статусом 'Error'.

Так что, экспериментально выяснено, что pool настоятельно рекомендуется использовать строго с одним storage.


  1. Как правило бэкапы с level=diff/incr не имеет смысла хранить после следующего успешного full или же если не осталось ни одного full раньше них по времени. ↩

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

P.S. В bareos'е, как я погляжу, ничего кардинально менять не стали, но многие мелочи значительно подтянули. Сравните например официальный мануал бакулы с мануалом от bareos'а. Последний читать как-то в разы приятнее.