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

Вступление

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

Https://www.percona.com/software/mysql-database/percona-xtrabackup[Percona XtraBackup инструменты резервного копирования] предоставляют метод выполнения «горячих» резервных копий данных MySQL во время работы системы. Они делают это, копируя файлы данных на уровне файловой системы, а затем выполняя аварийное восстановление для достижения согласованности в наборе данных.

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

Предпосылки

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

В дополнение к вышеприведенным учебникам вам также потребуется сгенерировать ключ доступа и секретный ключ для взаимодействия с вашей учетной записью хранилища объектов с помощью API. Если вы используете DigitalOcean Spaces, вы можете узнать, как создавать эти учетные данные, следуя нашим https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key [Как создать DigitalOcean Space и API Key] руководство. Вам нужно будет сохранить как ключ доступа API, так и секретное значение API.

Когда вы закончите работу с предыдущими руководствами, войдите на свой сервер как пользователь + sudo +, чтобы начать.

Установите зависимости

Мы будем использовать некоторые скрипты Python и Bash для создания наших резервных копий и загрузки их в хранилище удаленных объектов для безопасного хранения. Нам понадобится библиотека Python + boto3 + для взаимодействия с API хранилища object. Мы можем скачать это с + pip +, менеджером пакетов Python.

Обновите наш локальный индекс пакета, а затем установите версию Python 3 + pip + из репозиториев 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 хранилища объектов.

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

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

В last guide мы создали + / каталог backups / mysql + `для хранения наших резервных копий и нашего ключа шифрования. Мы разместим файл конфигурации здесь вместе с другими нашими активами. Создайте файл с именем `+ object_storage_config.sh +:

sudo nano /backups/mysql/object_storage_config.sh

Внутри вставьте следующее содержимое, изменив ключ доступа и секретный ключ на значения, полученные из учетной записи хранилища объектов, а имя корзины на уникальное значение. Задайте URL-адрес конечной точки и имя региона в соответствии со значениями, предоставленными вашей службой хранения объектов (здесь мы будем использовать значения, связанные с регионом DigitalOcean NYC3 для пробелов):

/backups/mysql/object_storage_config.sh

#!/bin/bash

export MYACCESSKEY=""
export MYSECRETKEY=""
export MYBUCKETNAME=""
export MYENDPOINTURL="https://nyc3.digitaloceanspaces.com"
export MYREGIONNAME="nyc3"

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

Переменные + MYENDPOINTURL + и + MYREGIONNAME + содержат конечную точку API и идентификатор конкретной области, предлагаемый вашим поставщиком хранилища объектов. Для пространств DigitalOcean конечной точкой будет + https: //. Digitaloceanspaces.com +. Вы можете найти доступные области для Пробелов в Панели управления DigitalOcean (на момент написания этой статьи доступен только «nyc3»).

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

Любой, кто может получить доступ к нашим ключам 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 +.

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

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

  • + object_storage.py +: этот скрипт отвечает за взаимодействие с API хранилища объектов для создания сегментов, загрузки файлов, загрузки контента и удаления старых резервных копий. Другие наши скрипты будут вызывать этот скрипт каждый раз, когда им нужно взаимодействовать с учетной записью хранилища удаленных объектов.

  • + remote-backup-mysql.sh +: этот сценарий выполняет резервное копирование баз данных MySQL путем шифрования и сжатия файлов в один артефакт, а затем загружает его в хранилище удаленных объектов. Он создает полную резервную копию в начале каждого дня, а затем добавочную резервную копию каждый час после этого. Он автоматически удаляет все файлы из удаленного сегмента старше 30 дней.

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

Наряду с новыми сценариями, приведенными выше, мы будем использовать сценарии + extract-mysql.sh + и + prepare-mysql.sh + из предыдущего руководства, чтобы помочь восстановить наши файлы. Вы можете в любое время просмотреть скрипты в repository для этого руководства на 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/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 + для взаимодействия с API, мы можем создать сценарии 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

Внутри вставьте следующий скрипт:

/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

Внутри вставьте следующее содержимое:

/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 + для чтения и интерпретации строки даты, представленной в качестве аргумента. Эта функция довольно гибкая и может интерпретировать даты в различных форматах, включая относительные строки, например «пятница». Однако, чтобы избежать двусмысленности, лучше использовать более четко определенные даты. Обязательно заключайте в кавычки даты, если формат, который вы хотите использовать, содержит пробелы.

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

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

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

Тестирование удаленного резервного копирования MySQL и загрузки сценариев

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

Выполните полное резервное копирование

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

sudo -u backup remote-backup-mysql.sh

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

OutputUploaded /backups/mysql/working/full-10-17-2017_19-09-30.xbstream to ""
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
Outputbackup-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
OutputUploaded /backups/mysql/working/incremental-10-17-2017_19-19-20.xbstream to ""
Backup successful!
Backup created at /backups/mysql/working/incremental-10-17-2017_19-19-20.xbstream

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

ls /backups/mysql/working
Outputbackup-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 ""

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

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

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

ls
Outputfull-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
OutputBackup 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 + restore, которую предоставила команда + prepare-mysql.sh +, чтобы скопировать файлы резервных копий в каталог + / var / lib / mysql +:

sudo xtrabackup --copy-back --target-dir=

После завершения процесса измените права доступа к каталогу и владельца, чтобы обеспечить доступ к процессу 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;'
Output+----+---------+-------+--------+
| id | type    | quant | color  |
+----+---------+-------+--------+
|  1 | slide   |     2 | blue   |
|  2 | swing   |    10 | yellow |
|  3 | sandbox |     4 | brown  |
+----+---------+-------+--------+

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

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

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

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

Создание задания 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 +:

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

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

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

Мы включим нашу новую работу + cron + и отключим старую, манипулируя битом разрешения + executetable + в обоих файлах:

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

Протестируйте новое задание удаленного резервного копирования, выполнив сценарий вручную:

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

Как только приглашение вернется, мы можем проверить записи в журнале с помощью + journalctl +:

sudo journalctl -t remote-backup-mysql
[seconary_label Output]
-- 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 ""
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

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

Резервное копирование ключа извлечения

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

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

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

sudo less /backups/mysql/encryption_key

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

Заключение

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

Related