Сбои бэкапа: оповещения до того, как нужно восстановление

Бэкапы неделями ломаются молча. Ловите ошибки restic и borg, устаревшие снапшоты и пропуски запусков — и получайте звонок при сбое.

Обновлено

Содержание

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

Решение — не в лучшем инструменте для бэкапа, а в том, чтобы проблема доходила до человека в тот же час, когда случилась. В этом руководстве показано, как это сделать для restic и borg (и всего, что запускается из shell): три реальных способа, которыми ломаются бэкапы, и скрипты, которые можно скопировать. На последнем шаге алерт уходит на телефон через Echobell, так что сломанный бэкап звонит, а не лежит в почтовом ящике.

Три способа, которыми ломается бэкап

Самая частая ошибка — следить только за первым.

  1. Запустился и упал. Репозиторий заблокирован, диск заполнен, неверный пароль, обрыв сети. Инструмент завершается с ненулевым кодом.
  2. Запустился и выполнился наполовину. restic завершается с кодом 3, если снапшот создан, но часть файлов прочитать не удалось. borg возвращает 1 при предупреждениях. Оба случая легко списать со счетов, и оба могут скрывать проблему с правами доступа, из-за которой молча пропускается самый важный каталог.
  3. Не запустился вообще. Сервер был выключен, таймер отключён, crontab перезаписан. Ошибки нет, потому что нет процесса. Именно этот сбой и убивает.

Для каждого нужен свой детектор, поэтому мы построим все три.

Шаг 1: Создайте канал, способный привлечь внимание

В Echobell создайте канал backups и для важных задач выберите тип уведомления Звонок. Скопируйте его URL вебхука:

https://hook.echobell.one/t/<channel-token>

Задайте шаблоны, понятные спросонья, на основе полей, которые мы будем отправлять ниже:

Заголовок: Бэкап {{status}} на {{host}}
Текст: {{job}}: {{detail}}

Синтаксис описан в справочнике по шаблонам.

Шаг 2: Ловите сбои скриптом-обёрткой

Небольшая обёртка превращает коды завершения в алерты. Сохраните как /usr/local/bin/backup-run:

#!/usr/bin/env bash
set -uo pipefail

HOOK="https://hook.echobell.one/t/<channel-token>"
JOB="nightly-restic"

alert() {
  curl -fsS -m 10 -X POST "$HOOK" \
    -H "Content-Type: application/json" \
    -d "{\"job\":\"$JOB\",\"host\":\"$(hostname)\",\"status\":\"$1\",\"detail\":\"$2\"}" || true
}

restic backup /srv /etc --exclude-caches
code=$?

case $code in
  0)  ;;                                   # успех: молчим
  3)  alert "incomplete" "Snapshot saved, but some files were unreadable" ;;
  11) alert "locked" "Repository is locked by another process" ;;
  12) alert "auth-failed" "Wrong repository password" ;;
  *)  alert "failed" "restic exited with code $code" ;;
esac

exit $code

Почему важны детали:

  • set -e намеренно не используется, чтобы скрипт дошёл до алерта после падения restic.
  • || true у curl означает, что недоступный вебхук никогда не заменит настоящий код завершения.
  • -m 10 не даёт зависшему сетевому вызову заблокировать следующий запланированный запуск.

Коды завершения restic, использованные выше, описаны в руководстве restic: 0 — успех, 1 — фатальная ошибка, 3 — снапшот создан, но часть исходных файлов не читается, 10 — репозиторий не существует, 11 — не удалось заблокировать, 12 — неверный пароль. Сверьтесь с restic version на своей машине: коды добавлялись со временем.

borg и borgmatic

borg возвращает 0 при успехе, 1 при предупреждениях и 2 и выше при ошибках. Изменение файла во время бэкапа даёт 1, что для каталогов с логами обычное дело, поэтому распространённый подход — звонить только при 2 и выше, а для 1 отправлять тихое уведомление:

borg create --stats ::'{hostname}-{now}' /srv /etc
code=$?
if [ "$code" -ge 2 ]; then
  alert "failed" "borg exited with code $code"
elif [ "$code" -eq 1 ]; then
  alert "warning" "borg finished with warnings"
fi

Если вы используете borgmatic, его хук on_error запускает команду, когда ошибка возникает в самом borgmatic, — естественное место для того же curl. Учтите ограничение, которое признаёт и документация borgmatic: если borgmatic вообще не запустился, on_error не сработает. Это сбой номер три, и для него нужен шаг 3.

Шаг 3: Ловите бэкап, который не запускался

Обёртка может сообщить только о запуске, который состоялся. Чтобы поймать тишину, переверните логику: бэкап отправляет ping в монитор при успехе, а монитор оповещает вас, когда ping опаздывает. Это называется dead man's switch («выключатель мертвеца»).

Healthchecks.io (облачный или собственный) делает именно это:

  1. Создайте проверку с расписанием 0 3 * * * и льготным временем в час-два.
  2. Добавьте её ping в конец задачи, чтобы он срабатывал только при успехе.
  3. В интеграциях проверки добавьте Webhook на URL вашего канала Echobell.

Ping ставится в конец обёртки:

# в backup-run прямо перед финальным `exit $code`; пингует только при успехе
if [ $code -eq 0 ]; then
  curl -fsS -m 10 --retry 3 -o /dev/null https://hc-ping.com/<your-uuid>
fi

Healthchecks также поддерживает варианты ping /start и /fail и умеет принимать код завершения в URL, так что одна проверка поймает и зависшие задачи. Итог: умерший сервер, отключённый таймер и удалённая строка crontab приводят к одному результату — звонит телефон. Общую схему и другие мониторы смотрите в статье оповещения о сбоях cron-задач.

Шаг 4: Проверьте, что бэкап свежий и восстановимый

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

Возраст снапшота. Оповещайте, если самый свежий снапшот старше ожидаемого:

latest=$(restic snapshots --latest 1 --json | jq -r '.[0].time')
age_h=$(( ( $(date +%s) - $(date -d "$latest" +%s) ) / 3600 ))
if [ "$age_h" -gt 30 ]; then
  alert "stale" "Newest snapshot is ${age_h}h old"
fi

Целостность репозитория. restic check проверяет структуру репозитория. Запускайте его раз в неделю; для больших репозиториев restic check --read-data-subset=5% проверяет выборку данных, не читая всё. Оповещайте при ненулевом коде.

Ни то ни другое не заменяет настоящего тестового восстановления. Раз в квартал восстанавливайте проверяемый файл во временный каталог. Это единственный тест, который доказывает, что бэкап работает.

Шаг 5: Звоните только когда это важно

Если каждое предупреждение звонит вам на телефон, вы заглушите канал, и он не зазвонит в тот единственный раз, когда это важно. Используйте условия Echobell, чтобы разделить уровни в одном канале:

status == "failed" || status == "stale" || status == "auth-failed"

Отправляйте incomplete и warning во второй канал — Срочное (Time Sensitive) или обычный: они появятся на экране блокировки без звонка. Если хотите, чтобы дневные сбои были тихими, добавьте временное окно с помощью UTC-переменных из справочника по условиям.

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

Как получить уведомление о сбое бэкапа?

Оберните команду бэкапа, прочитайте её код завершения и отправляйте вебхук при любом ненулевом коде. Добавьте dead man's switch вроде Healthchecks.io, чтобы узнавать и о запусках, которые не начались.

На какие коды завершения restic стоит оповещать?

Громко оповещайте на 1, 10, 11 и 12 — это настоящие сбои. Код 3 (снапшот создан, но часть файлов не читается) считайте предупреждением, которое нужно разобрать, поскольку он может скрывать проблему с правами.

Как понять, что бэкап молча перестал запускаться?

Используйте heartbeat: задача отправляет ping в монитор при успехе, а монитор оповещает, когда ping опаздывает. Хук ошибки здесь не поможет: ничего не запускается, значит, и ошибку поднимать некому.

Достаточно ли успешного кода завершения, чтобы доверять бэкапу?

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

Можно ли получать звонок вместо письма?

Да. Письмо легко пропустить. Звонковый канал в Echobell ведёт себя как входящий вызов и проходит через режим фокусирования iOS, поэтому сбой бэкапа в 03:00 не останется незамеченным.

Итог

Три детектора закрывают три режима отказа: обёртка с кодами завершения — для запусков, которые падают; проверка свежести — для запусков, которые «успешны», но бесполезны; heartbeat — для запусков, которых не было. Направьте их в звонковый канал, оставьте предупреждения для более тихого — и сломанный бэкап превратится в пятиминутное исправление вместо потерянных выходных.

Скачайте Echobell для iPhone или возьмите в Google Play, создайте канал backups и запустите обёртку один раз с заведомо неверным паролем, чтобы услышать звонок.

Похожие статьи