Про наглого клиента, или мониторинг borg backup в prometheus на коленке
Есть у меня один сервер в облаке hetzner, с него нужно было делать бекап на storage box, есть у хетцнера такое онлайн-хранилище.
Storage box поддерживает соединение по 22 и 23 портам (это важно для дальнейшего повествования)
Сам хетцнер рекомендует использовать для моей задачи 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):
up == 0 — если наш экспортер станет недоступен
borg_last_backup_error_code > 0 — если команда borg create вернет ошибку (например не сможет подключиться к storage box)
time () — borg_last_run_timestamp > 2×24 * 3600 — если последний запуск срипта был более 2х дней назад.
(borg_storage_disk_space — borg_storage_free_space) / borg_storage_disk_space * 100 > 80 — если занято более 80% места storage box
Ну вот вроде бы и все, простой, но достатчно эффективный мониторинг бекапа.
И очередной вопрос знатокам. Возможна ли ситуация, что с бекапом что-то не так, но никакие алерты не сработают?)
Ну конечно. Как минимум стоило бы добавить периодический запуск команды borg check (и мониторинг ее результата), которая может проверять как состояние отдельного архива, так и всей репы.
Ну, а уж если идти дальше, то можно написать такую автоматизацию: создание временного сервера, восстановление на него бекапа, проверка функциональности (с мониторингом результата в prometheus), и удалением сервера после проверки.
Если вы никогда не проверяли восстановление из бекапа, считайте что его у вас нет.
Конечно зависит от того, что за данные мы бекапим. А скорее всего, если используются такие решения «на коленке», то это какие-то местячковые бекапы.
По большей части баловство, хоть и интересное)
Всем спасибо за внимание!