UNRUIN
документация · v1.0

Как устроено

Что считается успехом

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

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

Три исхода, а не два

Законченный запуск — success, partial или failed. Средний существует потому, что округление в любую сторону врёт: округлить вверх — ущербная копия выглядит здоровой; округлить вниз — оператор привыкает не замечать красное.

Что случилось Запуск
Основная копия не сделалась failed — «Не получилось»
Не дошла обязательная вторая копия failed — «Не получилось»
Основная сделалась, но restic вышел с кодом 3 (часть файлов не прочиталась) partial — «С оговорками»
Не дошла копия по возможности partial — «С оговорками»
Всё чисто success — «Всё в порядке»

Обязательное хранилище или по возможности — решается в задании: это и значит required: true у хранилища. Так вы говорите «вторая площадка желательна, основная — нет». Записаны в историю обе в любом случае; флаг решает только, каким цветом будет запуск.

Статус несёт ещё и форму везде, где показан: круг — всё в порядке, ромб — с оговорками, квадрат — не получилось, пунктирное кольцо — ни разу не шло. Так состояние переживает распечатку и человека с дальтонизмом.

Сломанный источник не может стать точкой восстановления

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

  • Каждая удалённая команда обёрнута в bash -o pipefail -c. Если на той стороне конвейер и умирает любое его звено, весь вызов по SSH завершается ненулевым кодом.
  • Локальный конвейер смотрит на PIPESTATUS. Источник, вышедший ненулевым, делает запуск неудачным, даже если restic backup --stdin был совершенно доволен полученными байтами.
  • mysqldump обязан нести --single-transaction. Без него дамп — не согласованный момент времени, а согласованность нельзя обнаружить потом, глядя на файл.

Правило, которое стоит запомнить: код возврата источника старше кода возврата restic.

Код 3 у restic получает собственный статус

restic выходит с кодом 3, когда точку восстановления он создал, но часть файлов, которые его просили прочитать, прочитать не смог. Это не успех и не провал: копия есть и разворачивается, и чего-то, что вы просили, в ней нет. Unruin записывает это как «с оговорками» и сохраняет список непрочитанных файлов, чтобы у следующего вопроса — «каких именно?» — был ответ.

Контрольное восстановление

Задание с verify: true не заканчивается вместе с копированием. Дальше Unruin:

  1. разворачивает последнюю точку восстановления с меткой этого задания — не просто последнюю в хранилище — во временную папку внутри каталога состояния;
  2. обходит развёрнутое дерево и требует хотя бы один обычный файл ненулевого размера;
  3. удаляет временную папку.

Не прошёл любой шаг — запуск неудачный, и в подробностях написано restore-verify failed: ….

Шаг 2 намеренно обходит всё дерево, а не смотрит на верхний уровень. У источника-пути restic разворачивает данные по исходному абсолютному пути, поэтому на верхнем уровне лежит ровно одна папка, а размер папки всегда ненулевой. Проверка верхнего уровня объявила бы пустое восстановление здоровым — то есть пропустила бы ровно ту поломку, ради которой всё это и делается.

Что контрольное восстановление доказывает, а что нет

Доказывает: хранилище открывается теми ключами, которые есть у Unruin, указатель точки восстановления разрешается, и наружу выходят настоящие байты.

Не доказывает, что ваш дамп — корректный SQL, что приложение его примет и что читаются точки восстановления постарше. Проверяется свежая — та, за которой вы потянетесь первой. Полная проверка хранилища (restic check --read-data) — другая и куда более дорогая работа, и Unruin не делает вид, что выполнил её.

Контрольное восстановление идёт только тогда, когда копия пригодна — «в порядке» или «с оговорками». Разворачивать нечего, если копия не сделалась.

Копия, которая тихо перестала доходить

Хранилище «по возможности» делает запуск всего лишь оговорочным, а оговорку раз в неделю легко перестать читать. Поэтому хранилище по возможности, не дошедшее два запуска подряд, поднимает собственное оповещение — «offsite NAS has failed 2 consecutive runs» — один раз, на том запуске, который перешёл черту, а не каждую ночь после.

Обязательным хранилищам это не нужно: они и так красят запуск в красное.

Метки занятости, и та, которую никогда не снимают сами

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

Собственные метки restic — отдельная история. Упавший запуск оставляет метку и блокирует следующий (restic возвращает код 11, а Unruin переводит это во фразу с предложением снять метку).

  • unruin unlock <config> <job> снимает только те метки, которые restic считает брошенными: процесса, который их поставил, больше нет.
  • unruin unlock <config> <job> --remove-all снимает все, включая живые. Существует для случая, когда вы точно знаете, что тот процесс мёртв, а restic этого понять не может.
  • Метку уборки не снимает автоматически ничто. Уборка переписывает хранилище, и сорвать её метку, пока она действительно идёт, — это способ хранилище уничтожить. Если метка уборки застряла, вам об этом скажут, а решение останется вашим.

Зависшая цель не может держать задание вечно

У каждого запуска есть предел по времени: собственный timeout задания, иначе общий run_timeout, иначе шесть часов. По истечении запуск обрывается и записывается как неудачный — вместо того чтобы вечно висеть в состоянии running и блокировать все будущие запуски этого задания через проверку на наложение.

Запуски, оставшиеся в running после падения или перезапуска, при старте приводятся в порядок и помечаются брошенными — по той же причине.

Пропущенные и наложившиеся запуски — это политика, а не случайность

Две настройки задания решают, что делает планировщик, когда реальность не совпала с расписанием:

  • on_missed: skip (по умолчанию) или run — машина была выключена, когда пришло время. Догонять или ждать следующего окна?
  • on_overlap: skip (по умолчанию) или queue — предыдущий запуск ещё идёт. Пропустить этот или выполнить его следом?

Обе записаны у каждого задания, потому что правильный ответ разный: ночному дампу базы обычно нужен skip, недельной синхронизации на вторую площадку — run.

Где всё это видно

  • Фраза-вердикт панели и блок Требует внимания выстраивают ровно эти состояния, худшее первым, — см. панель.
  • Полный журнал каждого запуска сохраняется и читается по запуску.
  • Оповещения приходят по заданию на те события, на которые оно подписано (success, partial, failed) — блок notify в справочнике jobs.yaml.

Бесплатно, целиком ваше, наружу ничего не уходит. Сайт статический: ни аналитики, ни куки, ни внешних запросов.