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

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

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

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

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

Примечание: Для работы подойдет любое S3-совместимое хранилище объектов.

Требования

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

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

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

Обновите индекс локальных пакетов и установите версию pip для Python 3 из стандартного репозитория системы.

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

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

sudo -H pip3 install --upgrade pip

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

sudo -H pip3 install boto3 pytz

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

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

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

sudo nano /backups/mysql/object_storage_config.sh

Вставьте в файл следующие строки, укажите access key и secret key своего хранилища объектов. Значение MYBUCKETNAME должно быть уникальным. Задайте URL-адрес конечной точки и регион (эти данные предоставляются сервисом хранения объектов):

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

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

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

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

Любой, кто имеет доступ к ключам API, имеет полный доступ к учетной записи хранилища объектов. Потому очень важно ограничить доступ к конфигурационному файлу, он должен быть только у пользователя backup. Нужно предоставить пользователю и группе  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: сценарий позволяет загружать все резервные копии за текущий день. Поскольку сценарий резервного копирования создает полную резервную копию каждое утро, а затем несколько инкрементных копий в течение дня, этот скрипт может загружать все активы, необходимые для восстановления данных за каждый часовой блок.

Вместе с новыми сценариями нужно использовать сценарии 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:

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):
elf.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.py вам нужно убедиться, что эти переменные заполнены из файла object_storage_config.sh.

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

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

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

Создание сценария remote-backup-mysql.sh

Затем нужно создать сценарий remote-backup-mysql.sh. Он будет выполнять многие из функций, которые выполняет исходный сценарий 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 [ "$USER" != "$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"$(echo "${now}" | cut -d' ' -f 1-3)" +%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

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

Создайте файл:

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 [ "$USER" != "$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: Тестирование сценариев

Теперь сценарии нужно протестировать.

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

Для начала создайте полную копию данных MySQL, запустив сценарий remote-mysql-backup.sh как пользователь backup.

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

Для начала нужно переместить текущие данные в другой каталог.

Остановите 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  |
+----+---------+-------+--------+

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

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

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

5: Настройка cron для ежечасного резервного копирования

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

Создайте файл remote-backup-mysql в каталоге /etc/cron.hourly.

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

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

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

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

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: , ,