Есть у меня один сервер в облаке 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):

  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), и удалением сервера после проверки.

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

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

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

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

Комментарии (9)


  1. cliver
    14.10.2024 10:30

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

    Файл known_hosts, хранит только согласно докам marker (optional), hostnames, keytype, base64-encoded key, comment. Никакой информации о портах подключения там нет. Я не очень понимаю, что вы имеете ввиду, но если вы скажем, извратитесь и запустите два разных sshd (то есть ssh-cервера) на одном хосте и привяжете их к разным портам, а также пропишите путь к разным host keys в конфигах, то при подключении к нему поочередно по этим разным портам у вас в known_hosts файле появятся две разные строчки с соответствующими ключами. Но это очень нестандартный случай.

    И если да, то как это будет выглядет в файле known_hosts?

    Зависит от того, что вы имеете ввиду.


  1. cliver
    14.10.2024 10:30

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

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

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

    Вы понимаете, что в known_hosts нужно заносить не pubkey для аутентификации, а публичный ключ хоста? Откуда вы изначально взяли строки, которые добавили known_hosts?


    1. PATRI0T
      14.10.2024 10:30

      возможно он действительно перепутал known_hosts и authorized_keys


      1. chushev Автор
        14.10.2024 10:30

        вы шутите?


  1. 13werwolf13
    14.10.2024 10:30

    заготовил чай с бутербродиком ожидая чтение интересной статьи, а получил пару абзацев от человека не умеющего читать маны и несколько абзацев переизобретённого велосипеда коему 100500 версий на гитхабе((

    алсо
    а кто нибудь знает аналог сторейджбокса который можно нормально оплачивать нынче? увы я не нашёл нормальной замены, по соотношению цены к объёму хранимых данных аналогов просто нет, всякие s3/ftp хранилки сильно проигрывают по удобству (да и по цене).
    временно как замену юзаю сидбокс от pulsedmedia, но это как вместо мерседеса сесть на какой нибудь лихяо ли 100500, вроде куча функций, но они не нужны, а основная функция работает печально...


    1. Cloud4Y
      14.10.2024 10:30

      Если вдруг будет полезно: У нас есть корпоративное файловое хранилище, связка Nextcloud + ftp. https://www.cloud4y.ru/cloud-services/corp-drive/

      Делали специально, как аналог Storagebox. Доступно автоматическое создание пользователей, совместная работа с файлами, можно расшаривать папки. Миграция на Nextcloud с других платформ — без проблем. Будем рады, если пригодится :)


      1. 13werwolf13
        14.10.2024 10:30

        Вы что прикалываетесь?

        Сторейджбокс это

        1) копеечная стоимость хранения

        2) доступ по sftp

        На этом фоне ваше решение

        1) дикий костыль

        2) дикий оверинженеринг

        3) не настолько секурно

        4) тупо дороже

        P.S.: хватит делить сервисы на корпоративные и личные, это прошлый век и фуфуфу


  1. MiDeT
    14.10.2024 10:30

    Я ждал "наглого клиента", прям клиента в виде человечка.

    А тут совсем о другом, о своих костылях.

    Ох уж эти кликбейтовые заголовки.


  1. overslepter
    14.10.2024 10:30

    Хм, я всегда думал что правильно "бэкап".