Резервное копирование базы данных MySQL в хранилище объектов с помощью Percona XtraBackup в Ubuntu 16.04

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

Инструменты резервного копирования Percona XtraBackup предоставляют средства «горячего» резервного копирования данных MySQL во время работы системы. Для этого они копируют файлы данных на уровне файловой системы, а затем выполняют аварийное восстановление для достижения согласованности в наборе данных.

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

Примечание: Основные процедуры мануала применимы к разным S3-совместимым хранилищам объектов.

Требования

Войдите на сервер как пользователь sudo и приступайте к работе.

1: Установка зависимостей

Для создания резервных копий и загрузки их в удаленное хранилище объектов мы будем использовать скрипты Python и Bash. Для работы вам понадобится библиотека Python boto3 (для взаимодействия с API хранилища объектов). Вы можете загрузить ее с помощью pip, менеджера пакетов Python.

Обновите индекс локальных пакетов, а затем установите pip для Python 3 из репозиториев Ubuntu по умолчанию, используя apt-get:

sudo apt-get update
sudo apt-get install python3-pip

Поскольку Ubuntu поддерживает собственный жизненный цикл этого пакета, версия pip в репозиториях Ubuntu не синхронизируется с последними релизами. Однако мы можем обновиться до более новой версии pip, используя сам инструмент. Добавьте sudo для глобальной установки и включите флаг -H, чтобы установить для переменной $HOME значение, необходимое pip:

sudo -H pip3 install —upgrade pip

После этого можно установить boto3 вместе с модулем pytz, его мы будем использовать для точного сравнения времени в формате с учетом смещения, который возвращает API хранилища объектов:

sudo -H pip3 install boto3 pytz

Теперь у вас есть все модули Python, необходимые для взаимодействия с API хранилища объектов.

2: Создание конфигурационного файла хранилища объектов

Скрипты резервного копирования и загрузки должны будут взаимодействовать с API хранилища объектов, чтобы подгружать файлы и загружать более старые артефакты резервного копирования для восстановления. Для этого скриптам нужно использовать ключи доступа, которые вы должны были сгенерировать заранее (согласно требованиям мануала). Вместо того чтобы хранить эти значения в самих скриптах, мы поместим их в специальный файл, который могут прочитать только скрипты. Таким образом вы сможете делиться сценариями, не опасаясь раскрыть свои учетные данные.

В предыдущем мануале вы создали каталог /backups/mysql для хранения бэкапов и ключа шифрования. Поместите сюда же и конфигурационный файл. Создайте файл object_storage_config.sh:

sudo nano /backups/mysql/object_storage_config.sh

Вставьте в файл такие параметры (укажите свои ключи access key и secret key). Выберите уникальное имя корзины. Укажите URL конечной точки и название региона – эти данные предоставляет ваш сервис хранения объектов.

#!/bin/bash
export MYACCESSKEY="my_access_key"
export MYSECRETKEY="my_secret_key"
export MYBUCKETNAME="your_unique_bucket_name"
export MYENDPOINTURL="https://nyc3.myobjectstorage.com"
export MYREGIONNAME="nyc3"

Эти строки определяют две переменные окружения, MYACCESSKEY и MYSECRETKEY, в них хранятся ваши ключи. Переменная MYBUCKETNAME определяет корзину в хранилище объектов, которую вы хотите использовать для файлов резервных копий. Имена корзин должны быть универсально уникальными, поэтому вы должны выбрать имя, которое не выбрал ни один другой пользователь. Скрипт проверит значение корзины, чтобы определить, было ли оно заявлено другим пользователем, и автоматически создаст ее, если имя доступно. Экспортируйте определяемые переменные, чтобы процессы, которые мы вызываем в скриптах, имели доступ к этим значениям.

Переменные MYENDPOINTURL и MYREGIONNAME содержат конечную точку API и идентификатор конкретного региона, которые предоставляет ваш провайдер хранилища объектов.

Сохраните и закройте файл.

Любой, кто может получить доступ к ключам API, получает полный доступ к учетной записи хранилища объектов, поэтому доступ к этому файлу конфигурации важно ограничить только пользователем backup. Чтобы предоставить пользователю и группе права на файл, а затем отозвать все остальные права доступа, наберите:

sudo chown backup:backup /backups/mysql/object_storage_config.sh
sudo chmod 600 /backups/mysql/object_storage_config.sh

Теперь доступ к файлу object_storage_config.sh есть только у пользователя backup.

3: Создание сценариев для удаленного резервного копирования

Теперь, когда у вас есть конфигурация хранилища, можно приступать к созданию сценариев. Мы создадим следующие скрипты:

  • object_storage.py: этот скрипт отвечает за взаимодействие с API хранилища объектов для создания корзин, загрузки файлов, скачивания контента и удаления старых резервных копий. Другие скрипты будут вызывать этот скрипт каждый раз, когда им нужно взаимодействовать с учетной записью удаленного хранилища.
  • remote-backup-mysql.sh: этот сценарий выполняет резервное копирование баз данных MySQL, зашифровывая и сжимая файлы в один артефакт, а затем загружая его в хранилище объектов. Он создает полную резервную копию в начале каждого дня, а затем – инкрементную резервную копию каждый час. Он автоматически удаляет из корзины все файлы старше 30 дней.
  • download-day.sh: этот скрипт позволяет скачивать все резервные копии за указанный день. Сценарий remote-backup-mysql создает полную копию каждое утро, а затем добавляет инкрементные копии в течение дня; а этот сценарий может загрузить все ресурсы, необходимые для восстановления в любой контрольной точке.

Вместе с перечисленными выше новыми сценариями мы будем использовать сценарии extract-mysql.sh и prepare-mysql.sh из предыдущего мануала (чтобы помочь восстановить наши файлы). Вы можете в любое время просмотреть скрипты в репозитории на GitHub. Если вы не хотите копировать и вставлять содержимое скриптов, как показано ниже, вы можете скачать файлы прямо с GitHub:

cd /tmp
curl -LO https://raw.githubusercontent.com/do-community/ubuntu-1604-mysql-backup/master/object_storage.py
curl -LO https://raw.githubusercontent.com/do-community/ubuntu-1604-mysql-backup/master/remote-backup-mysql.sh
curl -LO https://raw.githubusercontent.com/do-community/ubuntu-1604-mysql-backup/master/download-day.sh

Обязательно проверьте скрипты после загрузки, чтобы убедиться, что они были загружены успешно и что вы одобряете действия, которые они будут выполнять. Если все в порядке, отметьте сценарии как исполняемые и затем переместите их в каталог /usr/local/bin:

chmod +x /tmp/{remote-backup-mysql.sh,download-day.sh,object_storage.py}
sudo mv /tmp/{remote-backup-mysql.sh,download-day.sh,object_storage.py} /usr/local/bin

Теперь можно создать все сценарии и обсудить их подробнее.

Создание скрипта object_storage.py

Если вы не загрузили скрипт object_storage.py с GitHub, создайте новый файл в каталоге /usr/local/bin по имени object_storage.py:

sudo nano /usr/local/bin/object_storage.py

Скопируйте и вставьте следующие параметры:

#!/usr/bin/env python3
import argparse
import os
import sys
from datetime import datetime, timedelta
import boto3
import pytz
from botocore.client import ClientError, Config
from dateutil.parser import parse
# "backup_bucket" must be a universally unique name, so choose something
# specific to your setup.
# The bucket will be created in your account if it does not already exist
backup_bucket = os.environ['MYBUCKETNAME']
access_key = os.environ['MYACCESSKEY']
secret_key = os.environ['MYSECRETKEY']
endpoint_url = os.environ['MYENDPOINTURL']
region_name = os.environ['MYREGIONNAME']
class Space():
def __init__(self, bucket):
self.session = boto3.session.Session()
self.client = self.session.client('s3',
region_name=region_name,
endpoint_url=endpoint_url,
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
config=Config(signature_version='s3')
)
self.bucket = bucket
self.paginator = self.client.get_paginator('list_objects')
def create_bucket(self):
try:
self.client.head_bucket(Bucket=self.bucket)
except ClientError as e:
if e.response['Error']['Code'] == '404':
self.client.create_bucket(Bucket=self.bucket)
elif e.response['Error']['Code'] == '403':
print("The bucket name \"{}\" is already being used by "
"someone.  Please try using a different bucket "
"name.".format(self.bucket))
sys.exit(1)
else:
print("Unexpected error: {}".format(e))
sys.exit(1)
def upload_files(self, files):
for filename in files:
self.client.upload_file(Filename=filename, Bucket=self.bucket,
Key=os.path.basename(filename))
print("Uploaded {} to \"{}\"".format(filename, self.bucket))
def remove_file(self, filename):
self.client.delete_object(Bucket=self.bucket,
Key=os.path.basename(filename))
def prune_backups(self, days_to_keep):
oldest_day = datetime.now(pytz.utc) - timedelta(days=int(days_to_keep))
try:
# Create an iterator to page through results
page_iterator = self.paginator.paginate(Bucket=self.bucket)
# Collect objects older than the specified date
objects_to_prune = [filename['Key'] for page in page_iterator
for filename in page['Contents']
if filename['LastModified'] < oldest_day]
except KeyError:
# If the bucket is empty
sys.exit()
for object in objects_to_prune:
print("Removing \"{}\" from {}".format(object, self.bucket))
self.remove_file(object)
def download_file(self, filename):
self.client.download_file(Bucket=self.bucket,
Key=filename, Filename=filename)
def get_day(self, day_to_get):
try:
# Attempt to parse the date format the user provided
input_date = parse(day_to_get)
except ValueError:
print("Cannot parse the provided date: {}".format(day_to_get))
sys.exit(1)
day_string = input_date.strftime("-%m-%d-%Y_")
print_date = input_date.strftime("%A, %b. %d %Y")
print("Looking for objects from {}".format(print_date))
try:
# create an iterator to page through results
page_iterator = self.paginator.paginate(Bucket=self.bucket)
objects_to_grab = [filename['Key'] for page in page_iterator
for filename in page['Contents']
if day_string in filename['Key']]
except KeyError:
print("No objects currently in bucket")
sys.exit()
if objects_to_grab:
for object in objects_to_grab:
print("Downloading \"{}\" from {}".format(object, self.bucket))
self.download_file(object)
else:
print("No objects found from: {}".format(print_date))
sys.exit()
def is_valid_file(filename):
if os.path.isfile(filename):
return filename
else:
raise argparse.ArgumentTypeError("File \"{}\" does not exist."
.format(filename))
def parse_arguments():
parser = argparse.ArgumentParser(
description='''Client to perform backup-related tasks with
object storage.''')
subparsers = parser.add_subparsers()
# parse arguments for the "upload" command
parser_upload = subparsers.add_parser('upload')
parser_upload.add_argument('files', type=is_valid_file, nargs='+')
parser_upload.set_defaults(func=upload)
# parse arguments for the "prune" command
parser_prune = subparsers.add_parser('prune')
parser_prune.add_argument('--days-to-keep', default=30)
parser_prune.set_defaults(func=prune)
# parse arguments for the "download" command
parser_download = subparsers.add_parser('download')
parser_download.add_argument('filename')
parser_download.set_defaults(func=download)
# parse arguments for the "get_day" command
parser_get_day = subparsers.add_parser('get_day')
parser_get_day.add_argument('day')
parser_get_day.set_defaults(func=get_day)
return parser.parse_args()
def upload(space, args):
space.upload_files(args.files)
def prune(space, args):
space.prune_backups(args.days_to_keep)
def download(space, args):
space.download_file(args.filename)
def get_day(space, args):
space.get_day(args.day)
def main():
args = parse_arguments()
space = Space(bucket=backup_bucket)
space.create_bucket()
args.func(space, args)
if __name__ == '__main__':
main()

Этот скрипт отвечает за управление резервными копиями в вашем хранилище объектов. Он может загружать файлы, удалять файлы, удалять старые резервные копии и скачивать файлы из хранилища объектов. Вместо непосредственного взаимодействия с API хранилища объектов все другие сценарии для взаимодействия с удаленными ресурсами будут использовать функции, определенные здесь. Скрипт определяет такие команды:

  • upload: загружает в хранилище объектов все файлы, переданные в качестве аргументов. Можно указать несколько файлов.
  • download: загрузка из хранилища удаленных объектов одного файла, который передается в качестве аргумента.
  • prune: удаляет из хранилища объектов каждый файл старше определенного возраста. По умолчанию она удаляет файлы старше 30 дней. Вы можете изменить это значение, указав опцию —days-to-keep при вызове prune.
  • get_day: укажите в качестве аргумента день, резервные копии которого нужно скачать, используйте стандартный формат даты (с кавычками, если в дате есть пробел), и инструмент попытается проанализировать ее и загрузить все файлы с этой даты.

Сценарий пытается прочитать учетные данные хранилища объектов и имя корзины из переменных среды, поэтому вам нужно убедиться, что они заполняются из файла object_storage_config.sh, прежде чем вызывать сценарий object_storage.py.

Когда вы закончите, сохраните и закройте файл.

Затем сделайте скрипт исполняемым, если вы еще этого не сделали:

sudo chmod +x /usr/local/bin/object_storage.py

Теперь, когда сценарий object_storage.py доступен, можно создать сценарии Bash, которые используют его для резервного копирования и загрузки файлов.

Создание скрипта remote-backup-mysql.sh

Далее мы создадим скрипт remote-backup-mysql.sh.Он будет выполнять многие из тех же функций, что и исходный скрипт backup-mysql.sh, но его структура будет более базовой (так как не требуется поддержка копий в локальной файловой системе) и в нем будут дополнительные действия для загрузки копий в хранилище объектов.

Если вы не загрузили сценарий из репозитория, создайте и откройте файл remote-backup-mysql.sh в каталоге /usr/local/bin.

sudo nano /usr/local/bin/remote-backup-mysql.sh

Вставьте в него такие строки:

#!/bin/bash
export LC_ALL=C
days_to_keep=30
backup_owner="backup"
parent_dir="/backups/mysql"
defaults_file="/etc/mysql/backup.cnf"
working_dir="${parent_dir}/working"
log_file="${working_dir}/backup-progress.log"
encryption_key_file="${parent_dir}/encryption_key"
storage_configuration_file="${parent_dir}/object_storage_config.sh"
now="$(date)"
now_string="$(date -d"${now}" +%m-%d-%Y_%H-%M-%S)"
processors="$(nproc --all)"
# Use this to echo to standard error
error () {
printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
exit 1
}
trap 'error "An unexpected error occurred."' ERR
sanity_check () {
# Check user running the script
if [ "$(id --user --name)" != "$backup_owner" ]; then
error "Script can only be run as the \"$backup_owner\" user"
fi
# Check whether the encryption key file is available
if [ ! -r "${encryption_key_file}" ]; then
error "Cannot read encryption key at ${encryption_key_file}"
fi
# Check whether the object storage configuration file is available
if [ ! -r "${storage_configuration_file}" ]; then
error "Cannot read object storage configuration from ${storage_configuration_file}"
fi
# Check whether the object storage configuration is set in the file
source "${storage_configuration_file}"
if [ -z "${MYACCESSKEY}" ] || [ -z "${MYSECRETKEY}" ] || [ -z "${MYBUCKETNAME}" ]; then
error "Object storage configuration are not set properly in ${storage_configuration_file}"
fi
}
set_backup_type () {
backup_type="full"
# Grab date of the last backup if available
if [ -r "${working_dir}/xtrabackup_info" ]; then
last_backup_date="$(date -d"$(grep start_time "${working_dir}/xtrabackup_info" | cut -d' ' -f3)" +%s)"
else
last_backup_date=0
fi
# Grab today's date, in the same format
todays_date="$(date -d "$(date -d "${now}" "+%D")" +%s)"
# Compare the two dates
(( $last_backup_date == $todays_date ))
same_day="${?}"
# The first backup each new day will be a full backup
# If today's date is the same as the last backup, take an incremental backup instead
if [ "$same_day" -eq "0" ]; then
backup_type="incremental"
fi
}
set_options () {
# List the xtrabackup arguments
xtrabackup_args=(
"--defaults-file=${defaults_file}"
"--backup"
"--extra-lsndir=${working_dir}"
"--compress"
"--stream=xbstream"
"--encrypt=AES256"
"--encrypt-key-file=${encryption_key_file}"
"--parallel=${processors}"
"--compress-threads=${processors}"
"--encrypt-threads=${processors}"
"--slave-info"
)
set_backup_type
# Add option to read LSN (log sequence number) if taking an incremental backup
if [ "$backup_type" == "incremental" ]; then
lsn=$(awk '/to_lsn/ {print $3;}' "${working_dir}/xtrabackup_checkpoints")
xtrabackup_args+=( "--incremental-lsn=${lsn}" )
fi
}
rotate_old () {
# Remove previous backup artifacts
find "${working_dir}" -name "*.xbstream" -type f -delete
# Remove any backups from object storage older than 30 days
/usr/local/bin/object_storage.py prune --days-to-keep "${days_to_keep}"
}
take_backup () {
find "${working_dir}" -type f -name "*.incomplete" -delete
xtrabackup "${xtrabackup_args[@]}" --target-dir="${working_dir}" > "${working_dir}/${backup_type}-${now_string}.xbstream.incomplete" 2> "${log_file}"
mv "${working_dir}/${backup_type}-${now_string}.xbstream.incomplete" "${working_dir}/${backup_type}-${now_string}.xbstream"
}
upload_backup () {
/usr/local/bin/object_storage.py upload "${working_dir}/${backup_type}-${now_string}.xbstream"
}
main () {
mkdir -p "${working_dir}"
sanity_check && set_options && rotate_old && take_backup && upload_backup
# Check success and print message
if tail -1 "${log_file}" | grep -q "completed OK"; then
printf "Backup successful!\n"
printf "Backup created at %s/%s-%s.xbstream\n" "${working_dir}" "${backup_type}" "${now_string}"
else
error "Backup failure! If available, check ${log_file} for more information"
fi
}
main

Этот сценарий обрабатывает саму процедуру резервного копирования MySQL, контролирует расписание резервного копирования и автоматически удаляет старые резервные копии из хранилища. Вы можете выбрать, сколько дней может храниться копия, изменив переменную days_to_keep.

Локальный скрипт backup-mysql.sh, который мы использовали в прошлом мануале, содержал отдельные каталоги для резервных копий на каждый день. Поскольку здесь резервные копии хранятся удаленно, мы будем хранить локально только последнюю резервную копию, чтобы минимизировать дисковое пространство, выделяемое для резервных копий.  Предыдущие резервные копии можно скачать из хранилища объектов и восстановить по мере необходимости.

Как и в предыдущем сценарии, после проверки резервного копирования каждая копия шифруется и сжимается в один файловый архив. Предыдущий файл резервной копии удаляется из локальной файловой системы, а все удаленные резервные копии старше, чем значение days_to_keep, удаляются.

Сохраните и закройте файл. После этого сделайте скрипт исполняемым:

sudo chmod +x /usr/local/bin/remote-backup-mysql.sh

Этот сценарий можно использовать в качестве замены сценария backup-mysql.sh в этой системе, чтобы переключаться с локального резервного копирования на удаленное.

Создание скрипта download-day.sh

Наконец, загрузите или создайте сценарий download-day.sh в каталоге / usr / local / bin. Этот сценарий можно использовать для загрузки всех резервных копий, связанных с конкретным днем.

Создайте файл сценария в текстовом редакторе, если вы не загрузили его из репозитория:

sudo nano /usr/local/bin/download-day.sh

В файл вставьте такой код:

#!/bin/bash
export LC_ALL=C
backup_owner="backup"
storage_configuration_file="/backups/mysql/object_storage_config.sh"
day_to_download="${1}"
# Use this to echo to standard error
error () {
printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
exit 1
}
trap 'error "An unexpected error occurred."' ERR
sanity_check () {
# Check user running the script
if [ "$(id --user --name)" != "$backup_owner" ]; then
error "Script can only be run as the \"$backup_owner\" user"
fi
# Check whether the object storage configuration file is available
if [ ! -r "${storage_configuration_file}" ]; then
error "Cannot read object storage configuration from ${storage_configuration_file}"
fi
# Check whether the object storage configuration is set in the file
source "${storage_configuration_file}"
if [ -z "${MYACCESSKEY}" ] || [ -z "${MYSECRETKEY}" ] || [ -z "${MYBUCKETNAME}" ]; then
error "Object storage configuration are not set properly in ${storage_configuration_file}"
fi
}
main () {
sanity_check
/usr/local/bin/object_storage.py get_day "${day_to_download}"
}
main

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

Сценарий принимает один аргумент – дату или день. Он использует функцию Python dateutil.parser.parse для чтения и интерпретации даты, предоставленной в качестве аргумента. Эта функция довольно гибкая и может интерпретировать даты в различных форматах, включая относительные строки, например Friday. Однако чтобы избежать путаницы, лучше использовать более четко определенные даты. Обязательно заключайте даты в кавычки, если формат, который вы хотите использовать, содержит пробелы.

Сохраните и закройте файл. Сделайте скрипт исполняемым:

sudo chmod +x /usr/local/bin/download-day.sh

Теперь можно скачать из хранилища объектов файлы резервных копий за определенную дату, если их нужно восстановить.

4: Тестирование сценариев

Теперь, когда у нас есть все сценарии, мы должны проверить, как они работают.

Полное резервное копирование

Вызовите сценарий remote-mysql-backup.sh как пользователь backup. Поскольку мы запускаем эту команду впервые, она должна создать полную резервную копию базы данных MySQL.

sudo -u backup remote-backup-mysql.sh

Примечание: Если вы получили сообщение об ошибке, где сказано, что выбранное вами имя корзины уже используется, вам придется выбрать другое имя. Измените значение MYBUCKETNAME в файле /backups/mysql/object_storage_config.sh и удалите локальный каталог (sudo rm -rf /backups/mysql/working), чтобы скрипт мог выполнить полное резервное копирование с новым именем корзины. Когда все будет готово, снова запустите команду, чтобы повторить попытку.

Если все прошло хорошо, вы увидите подобный вывод:

Uploaded /backups/mysql/working/full-10-17-2017_19-09-30.xbstream to "your_bucket_name"
Backup successful!
Backup created at /backups/mysql/working/full-10-17-2017_19-09-30.xbstream

Это указывает на то, что в каталоге /backups/mysql/working создана полная резервная копия. Она была загружена в хранилище объектов, в корзину, которая указана в файле object_storage_config.sh.

Если мы посмотрим в каталог /backups/mysql/working, то увидим такие же файлы, какие создал скрипт backup-mysql.sh из предыдущего руководства:

ls /backups/mysql/working
backup-progress.log  full-10-17-2017_19-09-30.xbstream  xtrabackup_checkpoints  xtrabackup_info

Файл backup-progress.log содержит выходные данные команды xtrabackup, а xtrabackup_checkpoints и xtrabackup_info содержат информацию об используемых параметрах, типе и области действия резервной копии и другие метаданные.

Инкрементное резервное копирование

Давайте внесем небольшое изменение в нашу таблицу equipment, чтобы создать новые данные, которых нет в первой резервной копии. Добавьте новую строку в таблицу:

mysql -u root -p -e 'INSERT INTO playground.equipment (type, quant, color) VALUES ("sandbox", 4, "brown");'

Введите пароль вашей базы данных, чтобы добавить новую запись.

Теперь можно сделать дополнительную резервную копию. При новом вызове скрипт создает инкрементную резервную копию, только если в этот день уже была сделана полная копия (по часам сервера).

sudo -u backup remote-backup-mysql.sh
Uploaded /backups/mysql/working/incremental-10-17-2017_19-19-20.xbstream to "your_bucket_name"
Backup successful!
Backup created at /backups/mysql/working/incremental-10-17-2017_19-19-20.xbstream

Приведенный выше вывод сообщает, что резервная копия была создана локально в том же каталоге и снова загружена в хранилище объектов. Если мы проверим каталог /backups/mysql/working, то обнаружим, что новая резервная копия появилась, а предыдущая была удалена:

ls /backups/mysql/working
backup-progress.log  incremental-10-17-2017_19-19-20.xbstream  xtrabackup_checkpoints  xtrabackup_info

Поскольку файлы загружаются удаленно, удаление локальной копии помогает уменьшить объем используемого дискового пространства.

Скачивание резервной копии за определенный день

Поскольку резервные копии хранятся удаленно, вам нужно будет скачать эти файлы, если понадобится восстановить данные. Для этого можно использовать скрипт download-day.sh.

Создайте, а затем перейдите в каталог, который пользователь backup может изменять:

sudo -u backup mkdir /tmp/backup_archives
cd /tmp/backup_archives

Затем вызовите скрипт download-day.sh от имени пользователя backup. Передайте день создания архивов, которые вы хотели бы скачать. Формат даты довольно гибкий, но лучше постараться указать ее конкретно.

sudo -u backup download-day.sh "Oct. 17"

Если в корзине есть архивы, которые соответствуют указанной вами дате, они будут загружены в текущий каталог:

Looking for objects from Tuesday, Oct. 17 2017
Downloading "full-10-17-2017_19-09-30.xbstream" from your_bucket_name
Downloading "incremental-10-17-2017_19-19-20.xbstream" from your_bucket_name

Убедитесь, что файлы были загружены в локальную файловую систему:

ls
full-10-17-2017_19-09-30.xbstream  incremental-10-17-2017_19-19-20.xbstream

Сжатые зашифрованные архивы появятся на вашем сервере.

Извлечение и подготовка копий

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

Сначала передайте файлы .xbstream в сценарий extract-mysql.sh, используя пользователя backup:

sudo -u backup extract-mysql.sh *.xbstream

Это расшифрует и распакует архивы в каталог restore. Перейдите в этот каталог и подготовьте файлы с помощью скрипта prepare-mysql.sh:

cd restore
sudo -u backup prepare-mysql.sh
Backup looks to be fully prepared.  Please check the "prepare-progress.log" file
to verify before continuing.
If everything looks correct, you can apply the restored files.
First, stop MySQL and move or remove the contents of the MySQL data directory:
sudo systemctl stop mysql
sudo mv /var/lib/mysql/ /tmp/
Then, recreate the data directory and  copy the backup files:
sudo mkdir /var/lib/mysql
sudo xtrabackup --copy-back --target-dir=/tmp/backup_archives/restore/full-10-17-2017_19-09-30
Afterward the files are copied, adjust the permissions and restart the service:
sudo chown -R mysql:mysql /var/lib/mysql
sudo find /var/lib/mysql -type d -exec chmod 750 {} \;
sudo systemctl start mysql

Полная резервная копия подготовлена в каталоге /tmp/backup_archives/restore. Следуйте инструкциям в выводе, чтобы восстановить данные MySQL в нашей системе.

Восстановление резервных данных

Прежде чем мы восстановим данные из копии, нужно удалить текущие данные.

Выключите MySQL, чтобы избежать повреждения базы данных или сбоя сервиса при замене файлов.

sudo systemctl stop mysql

Затем можно переместить текущий каталог данных в каталог /tmp. Таким образом его будет проще переместить назад, если при восстановлении появятся проблемы. Поскольку мы переместили файлы в /tmp/mysql в предыдущем мануале, на этот раз мы можем переместить файлы в /tmp/mysql-remote.

sudo mv /var/lib/mysql/ /tmp/mysql-remote

Затем создайте каталог /var/lib/mysql:

sudo mkdir /var/lib/mysql

Теперь можно ввести команду восстановления xtrabackup, которую предоставляет скрипт prepare-mysql.sh, чтобы скопировать файлы резервных копий в каталог /var/lib/mysql:

sudo xtrabackup --copy-back --target-dir=/tmp/backup_archives/restore/full-10-17-2017_19-09-30

После завершения процесса измените права доступа к каталогу и владельца, чтобы обеспечить доступ к процессу MySQL:

sudo chown -R mysql:mysql /var/lib/mysql
sudo find /var/lib/mysql -type d -exec chmod 750 {} \;

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

sudo systemctl start mysql
mysql -u root -p -e 'SELECT * FROM playground.equipment;'
+----+---------+-------+--------+
| id | type    | quant | color  |
+----+---------+-------+--------+
|  1 | slide   |     2 | blue   |
|  2 | swing   |    10 | yellow |
|  3 | sandbox |     4 | brown  |
+----+---------+-------+--------+

Данные доступны, то есть они были успешно восстановлены.

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

cd ~
sudo rm -rf /tmp/backup_archives/restore

5: Автоматизация резервного копирования с помощью cron

Ранее мы создали задачу cron для автоматического локального резервного копирования базы данных. Теперь нужно создать новую задачу cron для удаленного резервного копирования, а затем отключить задачу для локального. Вы сможете легко переключаться между локальным и удаленным резервным копированием по мере необходимости, включая или отключая сценарии cron.

Для начала создайте файл remote-backup-mysql в каталоге /etc/cron.hourly:

sudo nano /etc/cron.hourly/remote-backup-mysql

Вызовите скрипт remote-backup-mysql.sh  как пользователь backup через команду systemd-cat, которая позволяет записывать вывод в journald:

#!/bin/bash
sudo -u backup systemd-cat --identifier=remote-backup-mysql /usr/local/bin/remote-backup-mysql.sh

Сохраните и закройте файл.

Включите новую задачу cron и отключите старую задачу:

sudo chmod -x /etc/cron.hourly/backup-mysql
sudo chmod +x /etc/cron.hourly/remote-backup-mysql

Протестируйте удаленный бэкап:

sudo /etc/cron.hourly/remote-backup-mysql

Проверьте записи в логах:

sudo journalctl -t remote-backup-mysql
-- Logs begin at Tue 2017-10-17 14:28:01 UTC, end at Tue 2017-10-17 20:11:03 UTC. --
Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Uploaded /backups/mysql/working/incremental-10-17-2017_22-16-09.xbstream to "your_bucket_name"
Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Backup successful!
Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Backup created at /backups/mysql/working/incremental-10-17-2017_20-07-13.xbstream

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

6: Резервное копирование ключа шифрования

И последнее, что нужно сделать – это создать резервную копию ключа шифрования (он находится в /backups/mysql/encryption_key).

Ключ шифрования требуется для восстановления всех копий, полученных в результате этого процесса. Но хранить ключ шифрования в том же месте, что и файлы БД, опасно и непоследовательно. По этой причине важно хранить копию ключа шифрования в отдельном месте, чтобы вы смогли использовать архивы резервных копий в случае сбоя сервера БД или необходимости его восстановления.

Вы можете скопировать ключ на локальный компьютер в безопасное место. Для этого откройте содержимое файла:

sudo less /backups/mysql/encryption_key

Откройте текстовый файл на локальном компьютере и вставьте в него ключ. Если вам когда-нибудь понадобится восстановить резервные копии на другом сервере, скопируйте содержимое файла в файл /backups/mysql/encryption_key на новом компьютере, настройте систему, описанную в этом мануале, а затем восстановите все необходимое с помощью сценариев.

Tags: , , ,