influunt media

influunt media

Who am I?

Блог разработчика: обзор инструментов, практические примеры, мысли и исследования


Backup с помощью Yandex Disk REST API


В качестве высокодоступной СХД (Системы Хранения Данных) отлично подходит Яндекс Диск. Данный сервис досутпен по протоколу WebDAV, а также REST API. Его возможно использовать как репликатор, что также упрощает доставку резервных копий в места их постоянного хранения.

При выборе протокола загрузки копий на диск выбор пал на REST API. Он более прост в использовании, не требует специальной настройки сервера.

REST API возможно использовать только после регистрации приложения на OAuth сервере Яндекса. О том, как это сделать вы можете прочитать в отдельной статье Регистрация приложения на сервере OAuth.yandex.

Рассмотрим сценарий создания резервных копий приложения:

  1. Создание резервных копии баз данных;
  2. Создание резервной копии значимой части файловой системы;
  3. Загрузка резервных копий на Яндекс Диск;
  4. Ротация резервных копий.
Создание резервных копий баз данных

Резервное копирование баз данных будет рассмотрено на примере PostgreSQL.

В набор инструментов PostgreSQL входит pg_dump и pg_restore - первый из них позволяет создать резервную копию, второй - восстановить из неё базу данных. Всего существует 3 типа резервных копий:

  1. Plain text - запись производится в текстовый файл
  2. Tar - запись производится в сжатый архив
  3. Directory - запись производится в папку (Каждая таблица будет выгружена в отдельный файл)

Воспользуемся форматом Plain text (Формат используемый по умолчанию) и в целях экономии места заархивируем копии:

#!/bin/bash
#Папка в которой будут сохраняться резервные копии:
BACKUP_DIR=/backup/
#SQL запрос для получения списка баз данных кластера PostgreSQL:
DB_NAMES_SELECT_QUERY="select datname from pg_database where datistemplate=false and datname != 'postgres'"
#С помощью встроенной утилиты psql запрашиваем список баз данных:
DB_NAMES=$(psql -U postgres postgres -t -c "${DB_NAMES_SELECT_QUERY}");
for DB_NAME in $DB_NAMES;
do
    echo "Dumping $DB_NAME"
    #Выполняем резервное копирование и архивацию:
    pg_dump -U postgres $DB_NAME|gzip -9 > $BACKUP_DIR/$DB_NAME-$(date +'%Y%m%d-%H%M')-db.gz
done

Разберем команду создания резервной копии pg_dump:
-U postgres - выполнить запрос от имени пользователя postgres
$DB_NAME - переменная в которой хранится название базы данных

И команду архивации gzip:
-9 - Уровень компрессии (от 0 до 9)
> $BACKUP_DIR/$DB_NAME-$(date +'%Y%m%d-%H%M')-db.gz - Полный путь сохранения сжатой копии ($BACKUP_DIR - ссылка на папку, $DB_NAME-$(date +'%Y%m%d-%H%M')-db.gz - название архива. Например, для базы с именем test-db в 04:30 1 января 2016 года получится /backup/test-db-20160101-0430-db.gz)

Создание резервной копии значимой части файловой системы

Значимой частью файловой системы являются все файлы так или иначе используемые приложением. Это могут быть файлы, загруженные на сервер пользователями, конфигурационные файлы, ssl сертификаты, логи и т.д.

Все значимые файлы находятся в одной директории /home/<Название приложения>. Для создания копии воспользуемся архиватором tar:

BACKUP_DIR=/backup/
#С помощью встроенной утилиты psql запрашиваем список баз данных:
DB_NAMES=$(psql -U postgres postgres -t -c "${DB_NAMES_SELECT_QUERY}");
for DB_NAME in $DB_NAMES;
do
    echo "Dumping $DB_NAME"
    #Выполняем архивацию значимых файлов:
    tar -czvf $BACKUP_DIR/${DB_NAME}-$(date +'%Y%m%d-%H%M')-files.tar.gz -C /home/$DB_NAME/ .
done

Разберем команду архивации tar:
-c, --create - создать новый архив
-z, --gzip - применить сжатие gzip
-v, --verbose - Показывать добавленные файлы
-f, --file - Название файла архива
-C, --directory - Изменить кореневую директорию внутри архива
* Для удобства при настройке приложения были использованы одинаковые названия базы данных и папки, в которой будут храниться значимые файлы

Загрузка резервных копий на Яндекс Диск

Чтобы воспользоваться REST API Яндекс Диска необходимо иметь токен авторизации (Выдается приложению в результате акцепта запроса на доступ к аккаунту). В качестве примера вместо токена будет использоваться ключевое слово YOUR_TOKEN.

О том, как получить токен авторизации обратитесь к статье Регистрация приложения на сервере OAuth.yandex.

Пара слов о REST API Яндекс диска:

Загрузка файла осуществляется в два этапа: Запрос URL для загрузки, Загрузка файла на полученный URL.

Формат запроса URL для загрузки:

https://cloud-api.yandex.net/v1/disk/resources/upload ?
   path=<путь, по которому следует загрузить файл>
[& overwrite=<признак перезаписи>]
[& fields=<нужные ключи ответа>]

Пример ответа:

{
  "href": "https://uploader1d.dst.yandex.net:443/upload-target/...",
  "method": "PUT",
  "templated": false
}

Загрузка файла на полученный URL:

https://uploader1d.dst.yandex.net:443/upload-target/20240424T101447.217.utd.52csloukwvq67nab1yc84a3xw-k1d.6625

Пример ответа:

HTTP/1.1 201 Created
Content-Length: 0

Все резервные копии находятся в папке /backup/. Для каждой копии необходимо получить URL для загрузки и послать POST запрос с файлом на этот URL. Воспользуемся для этого утилитой curl:

#Каждый http запрос должен содержать токен авторизации: 
YA_DISK_HEADER='Authorization: OAuth YOUR_TOKEN'
BACKUP_DIR=/backup/
#Все файлы из директории backup, за исключением папки bin, должны быть сохранены на диске:
for FILE in $(ls -d ${BACKUP_DIR}/*|grep -v "bin");
do
    echo "Transfering " $FILE;
    #Получаем базовое имя файла:
    FILE_BASENAME=$(ls $FILE|xargs -n1 basename)
    #Формируем URL для загрузки:
    YA_DISK_REQUEST_URL='https://cloud-api.yandex.net:443/v1/disk/resources/upload?path=app:/'${FILE_BASENAME}'&overwrite=false'
    #Выполняем запрос URL
    REST_OUTPUT=$(curl -s -H "${YA_DISK_HEADER}" ${YA_DISK_REQUEST_URL});
    echo "YA REST_OUTPUT: ${REST_OUTPUT}"
    #Формируем URL для загрузки:
    UPLOAD_URL=$(echo $REST_OUTPUT| sed 's/.*"href":"\|",".*//g');
    echo "UPLOAD_URL: $UPLOAD_URL"
    #Выполняем загрузку файла:
    curl -s -T $FILE -H "{YA_DISK_HEADER}" ${UPLOAD_URL};
done

Разберем команду загрузки файла curl:
-s, --silent - Не отображать прогресс и другие сообщения
-T, --upload-file - Передает указанный файл на сервер. В случае http запроса будет использован PUT запрос
-H, --header - Заголовок который необходимо включить в запрос http при отправке на сервер

Ротация резервных копий

Хранить на сервере все резервные копии достаточно накладно и на практике неоправданно. В целях экономии места на диске лучше всего оставлять последние n копий.

Все резервные копии приложений хранятся внутри одной папки /backup и друг от друга отличаются только названием и датой. Чтобы правильно ротировать копии каждого из них воспользуемся командой:

ls -d /backup/* |xargs -n1 basename|sed -r "s/-[0-9]{8}.*//g"|uniq

ls -d /backup/* - отобразит список файлов, исключая директории;
xargs -n1 basename - получит базовое имя файла;
sed -r "s/-[0-9]{8}.*//g" - уберет из имени файла дату и расширение (Например, для app-20160227.tar.gz получится app);
uniq - уберет из списка дублирующиеся строки.

Результатом выполнения данной команды будет список имен приложений, резервные копии которых храняться в папке /backup/. Таким образом, для каждого приложения, можно удалить все файлы старше n копий (В данном случае старше 3 копий):

#Получаем список приложений:
ROTATE_LIST=$(ls -d /backup/* |xargs -n1 basename|sed -r "s/-[0-9]{8}.*//g"|uniq)
for APP_NAME in $ROTATE_LIST;
do
    echo "Rotating $APP_NAME"
    #Удаляем все копии приложения старше 3:
    ls /backup/${APP_NAME}* |sort -r|tail -n +3 |xargs rm -v
done

Разберем команду ротации:
ls /home/core/backup/${APP_NAME}* - отобразит все файлы, название которых начинается с названия приложения
sort -r - отсортирует список по релевантности tail -n +3 - уберет из списка первые три файла xargs rm -v - удалит все оставшиеся в списке файлы

Полный сценарий создания резевных копий

Полный сценарий достаточно компактен и в то же время выполняет все базовые требования к созданию резервных копий:

#!/bin/bash
YA_DISK_HEADER='Authorization: OAuth YOUR_TOKEN'
BACKUP_DIR=/backup/
DB_NAMES=$(psql -U postgres postgres -t -c "select datname from pg_database where datistemplate=false and datname != 'postgres'");
for DB_NAME in $DB_NAMES;
do
    echo "Dumping $DB_NAME"
    pg_dump -U postgres $DB_NAME|gzip -9 > $BACKUP_DIR/$DB_NAME-$(date +'%Y%m%d-%H%M')-db.gz
    tar -czvf $BACKUP_DIR/${DB_NAME}-$(date +'%Y%m%d-%H%M')-files.tar.gz -C /home/core/$DB_NAME/ .
done

for FILE in $(ls -d ${BACKUP_DIR}/*|grep -v "bin");
do
    echo "Transfering " $FILE;
    FILE_BASENAME=$(ls $FILE|xargs -n1 basename)
    YA_DISK_REQUEST_URL='https://cloud-api.yandex.net:443/v1/disk/resources/upload?path=app:/'${FILE_BASENAME}'&overwrite=false'
    REST_OUTPUT=$(curl -s -H "${YA_DISK_HEADER}" ${YA_DISK_REQUEST_URL});
    echo "YA REST_OUTPUT: ${REST_OUTPUT}"
    UPLOAD_URL=$(echo $REST_OUTPUT| sed 's/.*"href":"\|",".*//g');
    echo "UPLOAD_URL: $UPLOAD_URL"
    curl -s -T $FILE -H "{YA_DISK_HEADER}" ${UPLOAD_URL};
done
ROTATE_LIST=$(ls -d ${BACKUP_DIR}/*|xargs -n1 basename|sed -r "s/-[0-9]{8}.*//g"|uniq)
for APP_NAME in $ROTATE_LIST;
do
    echo "Rotating $APP_NAME"
    ls ${BACKUP_DIR}/${APP_NAME}* |sort -r|tail -n +3 |xargs rm -v
done