Бекап в облако Яндекс или Мейлру на лету в ограниченном дисковом пространстве

Сама по себе задача тривиальная, пока объемы бэкапа не начинают достигать десятков гигабайт. Особенно задача становится сложной, когда нет возмоэжности создать архив с данными на том же дисковом пространстве. Обычно такая необходимость возникает на VPS/VDS, где если бэкап и есть, то всей машины. Плюс как правило количество копий ограничено или обходится дороже чем место в облаке.

Поскольку у многих людей есть халявные гиги на облако мейло ру или яндекс диске, то логичным становится задействовать эти гиги для хранения бекапов. В свое время получил за что-то 100 гигов в облако мейл ру. Долгое время это облако простаивало. Недавно я про него вспомнил и решил использовать его для хранения бэкапов. В статье будет описан один алгоритм создания бэкапа, который подходит для Мейлру и Яндекс.Поскольку мне больше всего нравится Python, то скрипт будет на этом языке программирования.

Содержание

Монтирование облака по WebDAV

Для монтирования нам нужно установить davfs2. Поскольку я работаю в Ubuntu Server, то приводить буду команды для этой системы. После установки davfs2, нам необходимо создать файл с паролями.

nano /etc/davfs2/secrets

Формат довольно прост: url login password

Для мейлру это выглоядит так: https://webdav.cloud.mail.ru login@mail.ru 8XE3kRarADA0rG

Созраняем файл. Теперь для монтирования нам достаточно выолнить команду:

mount -t davfs https://webdav.cloud.mail.ru /path/to/dir

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

Создание и копирование архива на лету

Чтобы не создавать архив, отнимая место, копировать его в облако, а потом удалять. Можно выполнить все одной командой, без манипуляций с этим архивом. Архив будет создаваться и потоком отправляться сразу в облако. Вот собственно сама команда:

zip -r -0 - ./dir | curl -T- -sK /etc/davfs2/curl https://webdav.cloud.mail.ru/backup.zip

По параметрам:

  • ./dir — Папка с файлами, для которых необходимо создать бэкап
  • /etc/davfs2/curl — файл с параметрами авторизации. Его содержимое: —user login@mail.ru:password

Проверка монтирования WebDAV

Папка, к которой мы монтируем облако, существует вне зависимости от того, примонтировано облако или нет. По этой причине нам необходимо перед созданием бэкапа убедится что облако примонтировано. для этого в Python есть специальный модуль, точнее метод ismount, у модуля os.path.

if not os.path.ismount( '/path/to/dir' ):
        os.system( "mount -t davfs https://webdav.cloud.mail.ru path/to/dir" )

Как видите, все просто. Сначала проверяем, если не примонтирован, то монтируем. Но у нас может возникнуть ошибка, значит нужно проверить ещё раз после попытки монтирования.

if not os.path.ismount( '/path/to/dir' ):
        sys.exit()

Не знаю как вы, а я не люблю молчаливые скрипты. Приятно когда скрипт присылает уведомления о ходе выполнения задачи. Для этого можно добавить команду отправки уведомления себе в телеграм. Для этого потребуется создать своего бота, благо это легко и быстро делается. Затем воспользоваться любой удобной библиотекой для работы с API телеграма. Я использую pyTelegramBotAPI.

if not os.path.ismount( '/path/to/dir' ):
        bot.send_message( ADMIN_ID, f'[{hostname}] Не монтируется облако' )
        sys.exit()

Таким образом, если по каким-то причинам облако не примонтируется, мы от бота получим соответствующее уведомление.

Проверка создания бэкапа

Поскольку архив потоком отправляется по сети, то во время отправки может возникнуть ошибка. Логично сделать несколько попыток создания бэкапа. Код получится примерно такой:

backup_check = True

backup_attempt = 0
while not os.path.exists( f"/path/to/dir/backup.zip" ):
        backup_attempt += 1
        os.system( f"zip -r -0 - ./dir | curl -T- -sK /etc/davfs2/curl https://webdav.cloud.mail.ru/backup.zip" )
        if backup_attempt >= 5:
                bot.send_message( ADMIN_ID, f'[{hostname}] Бекап файлов сайта не создан' )
                backup_check = False
                break

Если все 5 попыток не увенчаются успехом, мы получим уведомление об этом в телеграм.

Удаление старых бэкапов

Чтобы наше хранилище не забилось до отказа, нам необходимо хранить несколько бэкапов и удалять старые. Для этого нужно предусмотреть алгоритм, который бы отслеживал бы количество бэкапов и удалял устаревшие.

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

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

Автоматизация создания бэкапа

Чтобы автоматизировать создание бэкапа, нам необходимо все вышеописанное собрать в скрипт и добавить в crontab команду на его выполнение. Я приведу пример своего скрипта, который заточен под один крупный проект, который торчит на сервере в гордом одиночестве. Для вариантов, когда на сервере много мелких сайтов, этот скрипт можно взять за основу и доработать под бэкап большого количества сайтов.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

BOT_TOKEN = '' # токен нашего бота
ADMIN_ID = '' # идентификатор аккаунта в телеграм, который будет получать уведомления

import os, re, sys, time, telebot, socket, datetime

hostname = socket.gethostname() # получаем имя хоста. Это нужно для понимания с какого сервера пришло уведомление, если этот скрипт работает на нескольких серверах

bot = telebot.TeleBot( BOT_TOKEN ) # создаем объект для отправки уведмлений


if not os.path.ismount( '/path/to/dir' ): 
        os.system( "mount -t davfs https://webdav.cloud.mail.ru /path/to/dir" )

if not os.path.ismount( '/path/to/dir' ):
	bot.send_message( ADMIN_ID, f'[{hostname}] Не монтируется облако' )
	sys.exit()

bot.send_message( ADMIN_ID, f'[{hostname}] Создание бекапа началось' )

try:
        os.makedirs( "/path/to/dir/backups/" )
        # создаем папку для бэкапа, если её не существует
except:
	pass

# тут мы проверяем существует ли бэкап за сегодняшний день. Это нужно делать из-за вероятного создания бэкапа вне расписания.
curr_dir_exists = False
list_dir = os.listdir( "/path/to/dir/backups/" )
for dir in list_dir:
	if dir.startswith( time.strftime( "%Y-%m-%d", time.localtime() ) ):
		curr_dir_exists = True

current_time = time.strftime( "%H%M", time.localtime() )
if not curr_dir_exists and current_time == "2355":
	backup_name = time.strftime( "%Y-%m-%d", time.localtime() )
else:
	backup_name = time.strftime( "%Y-%m-%d_%H-%M", time.localtime() )

backup_dir = f"/backups/{backup_name}/"

try:
 	os.makedirs( f"/path/to/dir/{backup_dir}" )
except:
	pass

backup_check = True

backup_attempt = 0
while not os.path.exists( f"/path/to/dir/{backup_dir}site_db.sql.gz" ):
	backup_attempt += 1
	os.system( f"mysqldump --no-tablespaces -u db_user -p'password' db_name | gzip - | curl -T- -sK /etc/davfs2/curl https://webdav.cloud.mail.ru{backup_dir}site_db.sql.gz" )
	if backup_attempt >= 5:
		bot.send_message( ADMIN_ID, f'[{hostname}] Бекап БД не создан' )
		backup_check = False
		break

os.chdir( "/path/to/dir" )
backup_attempt = 0
while not os.path.exists( f"/path/to/dir/{backup_dir}site_files.zip" ):
	backup_attempt += 1
	os.system( f"zip -r -0 - ./dir | curl -T- -sK /etc/davfs2/curl https://webdav.cloud.mail.ru{backup_dir}site_files.zip" )
	if backup_attempt >= 5:
		bot.send_message( ADMIN_ID, f'[{hostname}] Бекап файлов сайта не создан' )
		backup_check = False
		break



if backup_check:
	bot.send_message( ADMIN_ID, f'[{hostname}] Бекап создан' )
else:
	bot.send_message( ADMIN_ID, f'[{hostname}] Бекап создан частично' )

os.chdir( "/path/to/dir/backups/" )

list_dir = os.listdir( "./" )
list_dir.sort()
list_dir.reverse()

last_backup = list_dir[0]

year = int( last_backup[0:4] )
month = int( last_backup[5:7] )
day = int( last_backup[9:10] )

last_backup_date = datetime.datetime( year, month, day )

date_del = ( last_backup_date - datetime.timedelta( days=5 ) ).date()
date_lts_del = ( last_backup_date - datetime.timedelta( days=35  ) ).date()

count = 0
for x in list_dir:
	count += 1
	if "_" in x:
		exp = "%Y-%m-%d_%H-%M"
	else:
		exp = "%Y-%m-%d"
	bckp_date = datetime.datetime.strptime( x, exp ).date()
	backup_day = x[8:10]
	delete = False
	if backup_day == "01":
		delete = bckp_date < date_lts_del
	else:
		delete = bckp_date < date_del

	if delete:
		os.system( f"rm -r {x}" )
		pass

На сайте отсутствует реклама

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

Номер карты

Заранее спасибо!

Бидюков Денис

Эксперт по сайтам

Занимаюсь продвижением личного бренда с помощью сайта и SEO. Если Вы хотите из обычного сантехника, электрика, врача или фотографа стать востребованным и высокооплачиваемым  специалистом, то я с легкостью Вам помогу.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *