Про наглого клиента, или мониторинг borg backup в prometheus на коленке

30896cb5663b8dfa788d8740716ea0bb.jpg

Есть у меня один сервер в облаке hetzner, с него нужно было делать бекап на storage box, есть у хетцнера такое онлайн-хранилище.

Storage box поддерживает соединение по 22 и 23 портам (это важно для дальнейшего повествования)

a630972e98d5f599968ef7a14bde450d.png

Сам хетцнер рекомендует использовать для моей задачи borg, ну, а почему бы и нет. Попробовал, работает, что-то там дедублицирует, сжимает, есть не просит.

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

При этом Borg подключается по протоколу ssh на 23 порт (Хетцнер называет это extended ssh service). А вот sftp клиент по умолчанию использует 22 порт.

Ssh ключ для подключений на оба порта один и тот же. Публичный ключик заранее загружен специальным образом в storage box. Приватный ключик положили тут ~/.ssh/id_rsa на сервере.

Если делать все ручками, то после первого подключения к storage box потребуется подтверждение отпечатков пальцев:

Are you sure you want to continue connecting (yes/no/[fingerprint])?

Когда мы согласимся, в файл ~/.ssh/known_hosts будет добавлена строчка для storage box, и дальше подключения будут проходить без лишних вопросов.

Если же хочется установку скрипта для бекапов автоматизировать, то можно заранее добавить нужную строчку в known_hosts. (Я это, как и всю установку и настройку, делаю ролью ansible)

А теперь внимание, вопрос знатокам! Могут ли у одного хоста быть разные отпечатки пальцев для разных портов? И если да, то как это будет выглядет в файле known_hosts?

Но, поехали дальше. Итак, я заранее добавил строчку в known_hosts:

myuser.your-backup.de ssh-rsa AAAA%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%...

и быстро убедился, что borg все равно запрашивает подтверждение отпечатков пальцев. При чем отпечатки для ssh-ed25519.

Вопрос знатокам номер два. Если мы подключаемся к хосту с ключом одного типа (ssh-rsa), может ли клиент запрашивать подтверждение отпечатков для другого типа ключа (ssh-ed25519 например) ?

Признаюсь, что сначала я не удосужился разобраться, почему это происходит, просто добавил вторую строчку в known_hosts:

myuser.your-backup.de ssh-rsa AAAA%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%...
myuser.your-backup.de ssh-ed25519 AAAA%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%...

И все заработало.

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

Некоторое время потребовалось для того, чтобы обнаружить, что вторая строчка магическим образом пропала из файла known_hosts.

Кто-то заботливо создавал резервную копию моего файла — known_hosts.old, появлялся новый known_hosts, уже без ssh-ed25519 ключа.

Потыкав, как это бывает, в разных направлениях, я нашел виновника. Повысив log-level sftp клиента я увидел следующее:

$ sftp -4 -vvv -i ~/.ssh/id_rsa myuser@myuser.your-backup.de
...
debug3: hostkeys_foreach: reading file "~/.ssh/known_hosts"
debug3: hostkeys_find: found ssh-rsa key at ~/.ssh/known_hosts:1
debug3: hostkeys_find: deprecated ssh-ed25519 key at ~/.ssh/known_hosts:2
..
debug3: client_input_hostkeys: 1 server keys: 0 new, 1 retained, 0 incomplete match. 1 to remove
debug2: check_old_keys_othernames: checking for 1 deprecated keys
debug3: check_old_keys_othernames: searching ~/.ssh/known_hosts for myuser.your-backup.de / (none)
debug3: hostkeys_foreach: reading file "~/.ssh/known_hosts"
...
Deprecating obsolete hostkey: ED25519 SHA256:%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5
debug3: hostkeys_foreach: reading file "~/.ssh/known_hosts"
debug3: host_delete: RSA key already at ~/.ssh/known_hosts:1
~/.ssh/known_hosts:2: Removed ED25519 key for host myuser.your-backup.de
...

Я признаюсь, и подумать не мог, что клиент так беспардонно может начать удалять отпечатки из known_hosts.

Подключаясь к хосту по порту 22, sftp клиент не увидел соответствия отпечаткам для ssh-ed25519, посчитал их неактуальными и удалил. И ему пофиг, что для другого порта этого же хоста запись нужна!

Теперь внимание, вопрос знатокам номер 3. Почему какое-то время скрипт мог работать? (Я сам не знаю ответа на этот вопрос)

Ладно, поехали дальше. Проблему решил добавлением в файл ~/.ssh/config двух строк:

Host myuser.your-backup.de
    UpdateHostKeys no

Теперь пришло время того, что должно было быть сделано сразу — мониторинг.

Пишем экспортер для мониторинга бекапа в prometheus

Я решил сделать максимально простое, но в то же время функциональное решение для мониторинга бекапов. А именно написать простейший exporter для prometheus, тем более сервер рабочий уже есть.

Погнали. Вот код на bash:

#!/bin/bash
while true; do
    {
        echo -e "HTTP/1.1 200 OK\nContent-Type: text/plain\n"
        cat /path/to/file_with_metrics
    } | nc -l -p 9999 -q 1
done

Что мы тут имеем? Бесконечный цикл, который прослушивает порт 9999 и отправляет туда текст из нашего файла с метриками, снабдив HTTP-заголовками.

Сохраняем скрипт например в /usr/local/bin/borg_exporter.sh Создаем systemd unit который будет его запускать:

[Unit]
Description=Borgbackup prometheus exporter
After=network-online.target

[Service]
ExecStart=/usr/local/bin/borg_exporter.sh
Type=simple
Restart=always

Сохраняем в /etc/systemd/system/borg_exporter.service.

Активируем наш новый сервис:

sudo systemctl daemon-reload
sudo systemctl enable borg_exporter
sudo systemctl start borg_exporter

Да, надо не забыть сделать скрипт запускаемым, да надо бы запускать все это не от root, и да, надо открыть порт на фаерволе и т.д. …

Но это простое решение реально может работать с прометеусом!

Добавляем в его настройки (в prometheus.yml, в scrape_configs):

- job_name: borg
  static_configs:
    - targets:
      - 1.2.3.4:9999 # IP of your server
  scheme: http

где 1.2.3.4 — IP адрес сервера (если prometheus работает на том же сервере, то заменить на localhost)

Перезагружаем promehteus, и, если в файле /path/to/file_with_metrics есть хотя бы одна строка в формате

metric_name value

, то эта метрика будет им считываться. Value может быть только целым числом или числом с плавающей точкой.

Остается нам настроить обновление нужных метрик в файле, а также алерты.

Возвращаемся к моему основному скрипту, который запускает бекап, и да, код которого я тут не приводил.

Давайте я отброшу лишнее, и оставлю только то, что касается мониторинга.

#!/bin/bash
...
borg create ssh://myuser@myuser.your-backup.de:23/./folder::bckp_name /target/
borg_last_backup_error_code=$?
echo "borg_last_backup_error_code $borg_last_backup_error_code" > /path/to/file_with_metrics

echo "borg_last_run_timestamp $(date +%s)" >> /path/to/file_with_metrics

borg_storage_free_space=$((ssh myuser@myuser.your-backup.de 'df') | tail -n 1 | awk '{print $4}')
echo "borg_storage_free_space $borg_storage_free_space" >> /path/to/file_with_metrics

borg_storage_disk_space=$((ssh myuser@myuser.your-backup.de 'df') | tail -n 1 | awk '{print $2}')
echo "borg_storage_disk_space $borg_storage_disk_space" >> /path/to/file_with_metrics

Что тут происходит?

borg create … — запускается создание бекапа и сохранение его на storage box. Для работы команды нужны кое-какие дополнительные env-переменные, но я это опускаю.

Далее мы записываем код ошибки для команды borg create в файл метрик. 0 — если все ок.

Следующее — добавляем в файл метрик текущее время в формате unix.

И последние два — получаем сводное место на storage box (в блоках), добавляем третью строку с метрикой, получаем полный размер доступного диска, добавляем 4ю строку.

После запуска скрипта файл /path/to/file_with_metrics должен приобрести вид:

borg_last_backup_error_code 0
borg_last_run_timestamp 1728745782
borg_storage_free_space 444444444
borg_storage_disk_space 555555555

И эти метрики будет считывать prometheus.

Можно было бы обойтись и тремя метриками, но чтобы не заморачиваться размером блоков, будем считать оставшееся место в процентах.

С какой частотой запускать скрипт бекапа — дело ваше. В моем случае он раз в день запускается по cron.

Ну и последнее, алерты.

- name: borgbackup_alerts
  interval: 30s
	rules:
    - alert: InstanceDown
      expr: borg_last_backup_error_code > 0
      for: 60s
	  annotations:
		description: 'Oh, no, borg backup failed!'
		summary: 'Borg error code not equal zero'
      labels:
        severity: warning

Выше пример алерта, настраивается в конфиге prometheus. Всего предлагаю таких создать 4, т.е. создать 4 блока alert, вот с такими условиями (expr):

  1. up == 0 — если наш экспортер станет недоступен

  2. borg_last_backup_error_code > 0 — если команда borg create вернет ошибку (например не сможет подключиться к storage box)

  3. time () — borg_last_run_timestamp > 2×24 * 3600 — если последний запуск срипта был более 2х дней назад.

  4. (borg_storage_disk_space — borg_storage_free_space) / borg_storage_disk_space * 100 > 80 — если занято более 80% места storage box

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

И очередной вопрос знатокам. Возможна ли ситуация, что с бекапом что-то не так, но никакие алерты не сработают?)

Ну конечно. Как минимум стоило бы добавить периодический запуск команды borg check (и мониторинг ее результата), которая может проверять как состояние отдельного архива, так и всей репы.

Ну, а уж если идти дальше, то можно написать такую автоматизацию: создание временного сервера, восстановление на него бекапа, проверка функциональности (с мониторингом результата в prometheus), и удалением сервера после проверки.

Если вы никогда не проверяли восстановление из бекапа, считайте что его у вас нет.

Конечно зависит от того, что за данные мы бекапим. А скорее всего, если используются такие решения «на коленке», то это какие-то местячковые бекапы.

По большей части баловство, хоть и интересное)

Всем спасибо за внимание!

© Habrahabr.ru