在Ubuntu 16.04如何使用Percona将MySQL类别的数据库备份到指定的对象存储上呢?

介绍

数据库通常会在您的基础架构中存储一些最有价值的信息。因此,在发生事故或硬件故障时,必须具有可靠的备份以防止数据丢失。

Percona的XtraBackup备份工具提供了系统运行时执行MySQL数据的热备份的方法。他们通过在文件系统级别复制数据文件然后执行崩溃恢复来实现此目的,以实现数据集内的一致性。

首先,我们要安装Percona的备份实用程序,并创建一系列脚本来执行旋转本地备份。这有助于将数据备份到其他驱动器或网络安装卷以处理数据库计算机的问题。但是,在大多数情况下,数据应在异地备份,以便维护和恢复。在本教程中,我们将扩展先前的备份系统,将压缩的加密备份文件上载到对象存储服务。

准备

在开始本教程之前,您需要一个配置了本地Percona备份解决方案的MySQL数据库服务器。您还需要:

在您的服务器上启用防火墙,如果您使用的是腾讯云的CVM服务器,您可以直接在腾讯云控制台中的安全组进行设置。

完成之前的教程后,请以sudo用户身份重新登录服务器以开始使用。

安装依赖项

我们将使用一些Python和Bash脚本来创建备份并将它们上传到远程对象存储以便妥善保管。我们需要cos-python-sdk-v5Python库与对象存储API进行交互。我们可以使用pip,Python的包管理器下载它。

使用apt-get update刷新我们的本地包索引,然后输入apt-get install命令从Ubuntu的默认存储库安装Python 3-pip版本:

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

由于Ubuntu需要维护自己的包生命周期,因此Ubuntu存储库中的版本不会与最新版本保持同步。但是,我们可以使用pip3工具更新到新版本。

$ sudo -H pip3 install --upgrade pip

之后,我们可以把cos-python-sdk-v5pytz模块一起安装,我们将使用该模块使用对象存储API返回的偏移感知格式来准确地比较时间:

$ sudo -H pip3 install cos-python-sdk-v5 pytz

我们现在应该已经拥有了与对象存储API交互所需的所有Python模块。

创建对象存储配置文件

我们的备份和下载脚本需要与对象存储API进行交互,以便在需要还原时上载文件并下载较旧的备份工件。他们需要使用我们在准备部分中生成的访问密钥。我们将这些值放在脚本本身中,而不是将它们放在一个可由我们的脚本读取的专用文件中。这样,我们可以共享我们的脚本,而不必担心暴露我们的凭据,我们可以比脚本本身更严格地锁定凭据。

我们已经创建了用于存储备份和加密密钥的目录/backups/mysql。我们将配置文件放在我们的其他资源旁边。创建一个名为remote-backup-mysql.py的文件:

sudo nano /backups/mysql/remote-backup-mysql.py

在内部,粘贴以下内容,将访问密钥和密钥更改为从对象存储帐户获取的值,并将存储桶名称更改为唯一值。将端点URL和区域名称设置为对象存储服务提供的值:

from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import sys
import logging
​
secret_id = 'xxxxxxxx'      # 替换为用户的 secretId
secret_key = 'xxxxxxx'      # 替换为用户的 secretKey
region = 'ap-beijing-1'     # 替换为用户的 Region
token = ''                  # 使用临时密钥需要传入 Token,默认为空,可不填
scheme = 'https'            # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)

这些行定义了两个环境变量MYACCESSKEYMYSECRETKEY,用于保存访问密钥和安全密钥。该MYBUCKETNAME变量定义了我们要用于存储备份文件的对象存储桶。存储桶名称必须是唯一的,因此您必须选择其他用户未选择的名称。我们的脚本将检查存储桶值以查看它是否已被其他用户声明,并在可用时自动创建。我们使用export定义的变量使得我们在脚本中调用的任何进程都可以访问这些值。

MYENDPOINTURLMYREGIONNAME变量包含API端点和你的对象存储提供商提供的特定区域的标识符。

完成后保存并关闭文件。

任何可以访问我们API密钥的人都可以完全访问我们的对象存储帐户,因此限制对backup用户访问配置文件非常重要。我们可以为该文件的backup用户和组提供所有权,然后输入以下内容以撤消所有其他访问权限:

sudo chown backup:backup /backups/mysql/remote-backup-mysql.py
sudo chmod 600 /backups/mysql/remote-backup-mysql.py

我们的remote-backup-mysql.py文件现在只能由backup用户访问。

创建远程备份脚本

现在我们有了一个对象存储配置文件,我们可以继续开始创建脚本。我们将创建以下脚本:

  • bject_storage.py:此脚本负责与对象存储API交互,创建存储桶,上载文件,下载内容和修剪旧备份。我们的其他脚本将在需要与远程对象存储帐户交互时调用此脚本。
  • remote-backup-mysql.sh:此脚本通过将文件加密并压缩为单个工件,然后将其上载到远程对象存储库来备份MySQL数据库。它每天开始时创建完整备份,然后每小时创建一次增量备份。它会自动修剪远程存储桶中超过30天的所有文件。
  • download-day.sh:此脚本允许我们下载与给定日期关联的所有备份。由于我们的备份脚本每天早上创建一个完整备份,然后在一天内进行增量备份,因此该脚本可以下载恢复到任何每小时检查点所需的所有资产。

接下来,我们将设置每个脚本并更详细地讨论它们。

创建object_storage.py脚本

如果您没有从GitHub 下载脚本object_storage.py,请在名为的/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 cos-python-sdk-v5
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 = cos-python-sdk-v5.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在呼叫时指定选项来进行调整。
  • get_day:使用标准日期格式作为参数下载(如果日期中包含空格,则使用引号),该工具将尝试解析它并从该日期下载所有文件。

该脚本尝试从环境变量中读取对象存储凭据和存储桶名称,因此我们需要确保remote-backup-mysql.py在调用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

在里面,粘贴以下脚本:

#!/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}/.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"$(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/remote-backup-mysql.py"
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备份和下载脚本

现在我们已经有了脚本,我们应该测试以确保它们按预期运行。

执行完全备份

首先通过backup用户调用remote-mysql-backup.sh脚本。由于这是我们第一次运行此命令,因此应该创建MySQL数据库的完整备份。

$ sudo -u backup remote-backup-mysql.sh

注意: 如果收到错误,指示您选择的存储桶名称已在使用中,则必须选择其他名称。更改/backups/mysql/remote-backup-mysql.py文件中的MYBUCKETNAME值,并删除本地备份目录(sudo rm -rf /backups/mysql/working),使得脚本可以尝试用新桶名称的完整备份。准备好后,重新运行上面的命令再试一次。

如果一切顺利,您将看到类似于以下内容的输出:

Output
​
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目录中创建完整备份。它还使用remote-backup-mysql.py文件中定义的存储桶上载到远程对象存储。

如果我们查看/backups/mysql/working目录,我们可以看到类似于backup-mysql.sh上一个教程中脚本生成的文件:

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

backup-progress.log文件包含来自xtrabackup命令,而xtrabackup\_checkpointsxtrabackup\_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 &quot;your\_bucket\_name&quot;
​
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

接下来,以backup用户身份调用脚本download-day.sh。通过您要下载的档案当天。日期格式相当灵活,但最好是明确无误:

$ 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

压缩的加密存档现在又重新出现在服务器上。

提取并准备备份

收集文件后,我们可以像处理本地备份一样处理它们。

首先,使用backup用户将.xbstream文件传递给extract-mysql.sh脚本:

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

这会将存档解密并解压缩到一个名为restore的目录中。输入该目录并使用prepare-mysql.sh脚本准备文件:

$ cd restore
$ sudo -u backup prepare-mysql.sh
Output
​
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-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=/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

下次我们需要备份目录的干净副本时,我们可以从备份存档文件中再次提取它们。

创建一个Cron作业,每小时运行备份

我们在上一篇cron教程中创建了一个自动备份数据库的工作。我们将设置一个新cron作业来进行远程备份,然后禁用本地备份作业。我们可以根据需要通过启用或禁用cron脚本轻松切换本地和远程备份。

首先,创建一个在/etc/cron.hourly目录中调用的文件remote-backup-mysql

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

在里面,我们将通过命令remote-backup-mysql.shbackup用户调用我们的脚本systemd-cat,这允许我们将输出记录到journald

#!/bin/bash
sudo -u 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

一旦提示出现,我们可以检查日志条目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 "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

请在几个小时后再回来查看是否按计划进行了其他备份。

备份提取密钥

最后一个考虑因素是如何备份加密密钥(可在以下位置找到/backups/mysql/encryption\_key)。

恢复使用此过程备份的任何文件都需要加密密钥,但将加密密钥存储在与数据库文件相同的位置会消除加密提供的保护。因此,将加密密钥的副本保存在单独的位置非常重要,这样,如果数据库服务器出现故障或需要重建,您仍可以使用备份存档。

虽然非数据库文件的完整备份解决方案超出了本文的范围,但您可以将密钥复制到本地计算机以便妥善保管。为此,请输入以下内容查看文件的内容:

$ sudo less /backups/mysql/encryption\_key

在本地计算机上打开文本文件并将值粘贴到其中。如果您需要将备份还原到其他服务器上,请将该文件的内容复制到/backups/mysql/encryption\_key新计算机上,设置本教程中概述的系统,然后使用提供的脚本进行还原。

结论

在本教程中,我们介绍了如何每小时备份MySQL数据库并将其自动上传到远程对象存储空间。系统将每天早上进行完整备份,然后每小时进行一次增量备份,以便能够恢复到任何时间点。每次运行备份脚本时,它都会检查对象存储中超过30天的备份并将其删除。

如果您在生产环境使用,我还是建议您直接使用云关系型数据库,云关系型数据库让您在云中轻松部署、管理和扩展的关系型数据库,提供安全可靠、伸缩灵活的按需云数据库服务。腾讯云关系型数据库提供 MySQL、SQL Server、MariaDB、PostgreSQL 数据库引擎,并针对数据库引擎的性能进行了优化。云关系型数据库是一种高度可用的托管服务,提供容灾、备份、恢复、监控、迁移等数据库运维全套解决方案,可将您从耗时的数据库管理任务中解放出来,让您有更多时间专注于您的应用和业务。

更多Linux教程请前往腾讯云+社区学习更多知识。


参考文献 《How To Back Up MySQL Databases to Object Storage with Percona on Ubuntu 16.04》

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

在Linux上通过可写文件获取root权限的多种方式

在Linux中,一切都可以看做文件,包括所有允许/禁止读写执行权限的目录和设备。当管理员为任何文件设置权限时,都应清楚并合理为每个Linux用户分配应有的读写执...

58800
来自专栏北京马哥教育

linux下用tar进行数据备份

豌豆贴心提醒,本文阅读时间7分钟 本机上数据的手工备份 Linux系统上配有功能强大的tar命令,可以灵活地备份数据。 tar最初是为了制作磁带备份而设计的把...

33780
来自专栏魏豪的专栏

【腾讯云的1001种玩法】Nginx网站服务器学习与入门

本文详细介绍了Nginx网站服务器。从Nginx的基本概念,基本应用,高级应用。每种应用都有详细的案例与之对应。可快速帮助Linux爱好者学习Nginx来技术入...

2.2K00
来自专栏云计算教程系列

如何在Ubuntu 14.04上安装和配置Naxsi

Naxsi是第三方Nginx模块,提供Web应用程序防火墙功能。它为您的Web服务器带来了额外的安全性,并保护您的服务器免受各种Web攻击,如XSS和SQL的注...

20900
来自专栏散尽浮华

nginx的web缓存服务环境部署记录

web缓存位于内容源Web服务器和客户端之间,当用户访问一个URL时,Web缓存服务器会去后端Web源服务器取回要输出的内容,然后,当下一个请求到来时,如果访问...

48370
来自专栏我的博客

php+ftp

$ftp_server = “122.207.221.101”;//主机ip或者域名 $conn_id = ftp_connect($ftp_server) o...

36950
来自专栏惨绿少年

MongoDB的备份与恢复

1.1 MongoDB的常用命令 mongoexport / mongoimport mongodump / mongorestore      有以上两组命...

1.2K60
来自专栏九彩拼盘的叨叨叨

nodejs概要

nodejs是由Ryan Dahl写的。他做nodejs的初衷是为了做一个高性能是web服务器。 为了实现高性能服务器,实现要点是:

22330
来自专栏IMWeb前端团队

HTML5离线存储——manifest简介

离线存储的作用 1、用户可离线访问应用,这对于无法随时保持联网状态的移动终端用户来说尤其重要 2、用户访问本地的缓存文件,通常意味着更快的访问速度 3、仅仅加载...

32750
来自专栏好好学java的技术栈

手把手从零开始带你学git和GitHub

版本: 想想你平时用的软件,在软件升级之后,你用的就是新版本的软件。你应该见过这样的版本号:v2.0 或者 1511(表示发布时为15年11月),如下图:

11820

扫码关注云+社区

领取腾讯云代金券