如何在 Ubuntu 16.04 上使用 Percona 将 MySQL 数据库备份到对象存储中

介绍

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

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

在[上个指南] (https://andsky.com/tech/tutorials/how-to-configure-mysql-backups-with-percona-xtrabackup-on-ubuntu-16-04)中,我们安装了Percona的备份公用事业,并创建了一系列脚本来进行旋转本地备份. 这很适合备份数据到不同的驱动器或网络挂载的音量 来处理您数据库机器的问题 。 然而,在多数情况下,数据应备份在可以方便地保存和恢复的地方。 在本指南中,我们将扩展之前的备份系统,将压缩,加密的备份文件上传到对象存储服务. 我们将在本指南中以数字海洋空间为例,但基本程序也可能适用于其他S3兼容对象存储解决方案.

前提条件

在启动本指南之前,您将需要一个配置了本地Percona备份解决方案的MySQL数据库服务器。

  • [如何在Ubuntu 16.04上用 Percona XtraBackup 配置 MySQL 备份(https://andsky.com/tech/tutorials/how-to-configure-mysql-backups-with-percona-xtrabackup-on-ubuntu-16-04): 本指南使用Percona XtraBackup工具设置了本地的MySQL备份解决方案. .

除了上述教程之外,您还需要生成一个访问密钥和秘密密密钥以使用API与您的对象存储帐户进行交互。如果您正在使用DigitalOcean Spaces,您可以通过遵循我们的 如何创建DigitalOcean空间和API密钥指南来生成这些凭证。您将需要保存API访问密钥和API秘密值。

当你完成了之前的指南时,重新登录到你的服务器作为你的sudo用户开始。

安装依赖

我们将使用一些Python和Bash脚本来创建我们的备份,并将其上传到远程对象存储中以进行保存。我们将需要Python的)进行交互(https://developers.digitalocean.com/documentation/spaces/)。

更新我们的本地包索引,然后从Ubuntu的默认存储库中安装Python 3版本的pip,使用apt-get键入:

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

由于Ubuntu保持了自己的包生命周期,Ubuntu存储库中的pip版本不会与最近的版本保持同步,但是,我们可以使用该工具更新到更新的pip版本,我们将使用sudo在全球范围内安装,并包括-H旗帜来将$HOME变量设置为一个预期值pip:

1sudo -H pip3 install --upgrade pip

之后,我们可以与pytz模块一起安装boto3,我们将使用它来准确地使用对象存储API返回的离散意识格式来比较时间:

1sudo -H pip3 install boto3 pytz

我们现在应该拥有我们需要与对象存储API互动的所有Python模块。

创建一个对象存储配置文件

我们的备份和下载脚本需要与对象存储 API 交互,以便在我们需要恢复时上传文件并下载更旧的备份文物. 他们需要使用我们在先决条件部分产生的访问密钥。 与其将这些值保留在脚本中,不如把它们放入一个可以由我们的脚本读取的专用文件中. 这样,我们可以分享我们的脚本,而不用担心暴露我们的证书,我们可以比脚本本身更严厉地锁定证书.

最后的指南,我们创建了 /backups/mysql 目录来存储我们的备份和我们的加密密钥。我们将把配置文件放在这里与我们的其他资产一起。创建一个名为 object_storage_config.sh的文件:

1sudo nano /backups/mysql/object_storage_config.sh

将下列内容粘贴到内部,将访问密钥和秘密密钥更改为您从对象存储帐户和桶名中获取的值,并将终端 URL 和区域名设置为您对象存储服务提供的值(我们将在这里使用与 DigitalOcean 的 NYC3 区域用于空间相关的值):

1[label /backups/mysql/object_storage_config.sh]
2#!/bin/bash
3
4export MYACCESSKEY="my_access_key"
5export MYSECRETKEY="my_secret_key"
6export MYBUCKETNAME="your_unique_bucket_name"
7export MYENDPOINTURL="https://nyc3.digitaloceanspaces.com"
8export MYREGIONNAME="nyc3"

这两行界定了两个环境变量,即MYACCESSKEY'和MYSECRETKEY',分别持有我们的访问权和秘密钥匙。 MYBKKETNAME' 变量定义了我们要用来存储备份文件的对象存储桶 。 Bucket 名称必须具有普遍的独特性,所以您必须选择一个没有其他用户选择的名称. 我们的脚本将检查桶值, 看看它是否已被另一个用户所声称, 如果可用则自动创建 。 我们输出 ' 我们定义的变量,以便我们从脚本中调用的任何进程都能够获取这些价值.

)。

保存并关闭文件,当你完成。

任何可以访问我们的API密钥的人都可以完全访问我们的对象存储帐户,所以限制对配置文件的访问至备份用户很重要。

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

我们的object_storage_config.sh文件现在只能被备份用户访问。

创建远程备份脚本

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

  • `物体-储存.py': 此脚本负责与对象存储 API 进行交互,以创建桶,上传文件,下载内容,以及更古老的备份. 我们其他的脚本在需要与远程对象存储账户交互时都会调用这个脚本.
  • `远程备份-mysql.sh': 这个脚本通过加密和将文件压缩成单个文物后再上传到远程对象商店来备份MySQL数据库. 它在每天起步时会形成一个完整的备份,然后在之后每小时会形成一个递增的备份. 它会自动浏览远程桶中超过30天的所有文件.
  • " 每日下载 " : 此脚本允许我们下载所有与某一天相关的备份. 因为我们的备份脚本每天早上建立完整的备份,然后全天递增的备份,这个脚本可以下载所有必要的资产,以恢复到任何每小时的检查站. .

除了上面的新脚本外,我们还将从上面的指南中利用)中的脚本。如果您不想复制和粘贴下面的内容,您可以通过键入直接从GitHub下载新文件:

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

请确保在下载后检查脚本,以确保它们被成功检索,并您批准他们将执行的操作. 如果您满意,请将脚本标记为可执行,然后通过键入将其移动到 /usr/local/bin 目录:

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

接下来,我们将设置这些脚本,并详细讨论它们。

创建 object_storage.py 脚本

如果您没有从 GitHub 下载object_storage.py脚本,请在/usr/local/bin目录中创建一个名为object_storage.py的新文件:

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

将脚本内容复制并粘贴到文件中:

  1[label /usr/local/bin/object_storage.py]
  2#!/usr/bin/env python3
  3
  4import argparse
  5import os
  6import sys
  7from datetime import datetime, timedelta
  8
  9import boto3
 10import pytz
 11from botocore.client import ClientError, Config
 12from dateutil.parser import parse
 13
 14# "backup_bucket" must be a universally unique name, so choose something
 15# specific to your setup.
 16# The bucket will be created in your account if it does not already exist
 17backup_bucket = os.environ['MYBUCKETNAME']
 18access_key = os.environ['MYACCESSKEY']
 19secret_key = os.environ['MYSECRETKEY']
 20endpoint_url = os.environ['MYENDPOINTURL']
 21region_name = os.environ['MYREGIONNAME']
 22
 23class Space():
 24    def __init__(self, bucket):
 25        self.session = boto3.session.Session()
 26        self.client = self.session.client('s3',
 27                                          region_name=region_name,
 28                                          endpoint_url=endpoint_url,
 29                                          aws_access_key_id=access_key,
 30                                          aws_secret_access_key=secret_key,
 31                                          config=Config(signature_version='s3')
 32                                          )
 33        self.bucket = bucket
 34        self.paginator = self.client.get_paginator('list_objects')
 35
 36    def create_bucket(self):
 37        try:
 38            self.client.head_bucket(Bucket=self.bucket)
 39        except ClientError as e:
 40            if e.response['Error']['Code'] == '404':
 41                self.client.create_bucket(Bucket=self.bucket)
 42            elif e.response['Error']['Code'] == '403':
 43                print("The bucket name \"{}\" is already being used by "
 44                      "someone. Please try using a different bucket "
 45                      "name.".format(self.bucket))
 46                sys.exit(1)
 47            else:
 48                print("Unexpected error: {}".format(e))
 49                sys.exit(1)
 50
 51    def upload_files(self, files):
 52        for filename in files:
 53            self.client.upload_file(Filename=filename, Bucket=self.bucket,
 54                                    Key=os.path.basename(filename))
 55            print("Uploaded {} to \"{}\"".format(filename, self.bucket))
 56
 57    def remove_file(self, filename):
 58        self.client.delete_object(Bucket=self.bucket,
 59                                  Key=os.path.basename(filename))
 60
 61    def prune_backups(self, days_to_keep):
 62        oldest_day = datetime.now(pytz.utc) - timedelta(days=int(days_to_keep))
 63        try:
 64            # Create an iterator to page through results
 65            page_iterator = self.paginator.paginate(Bucket=self.bucket)
 66            # Collect objects older than the specified date
 67            objects_to_prune = [filename['Key'] for page in page_iterator
 68                                for filename in page['Contents']
 69                                if filename['LastModified'] < oldest_day]
 70        except KeyError:
 71            # If the bucket is empty
 72            sys.exit()
 73        for object in objects_to_prune:
 74            print("Removing \"{}\" from {}".format(object, self.bucket))
 75            self.remove_file(object)
 76
 77    def download_file(self, filename):
 78        self.client.download_file(Bucket=self.bucket,
 79                                  Key=filename, Filename=filename)
 80
 81    def get_day(self, day_to_get):
 82        try:
 83            # Attempt to parse the date format the user provided
 84            input_date = parse(day_to_get)
 85        except ValueError:
 86            print("Cannot parse the provided date: {}".format(day_to_get))
 87            sys.exit(1)
 88        day_string = input_date.strftime("-%m-%d-%Y_")
 89        print_date = input_date.strftime("%A, %b. %d %Y")
 90        print("Looking for objects from {}".format(print_date))
 91        try:
 92            # create an iterator to page through results
 93            page_iterator = self.paginator.paginate(Bucket=self.bucket)
 94            objects_to_grab = [filename['Key'] for page in page_iterator
 95                               for filename in page['Contents']
 96                               if day_string in filename['Key']]
 97        except KeyError:
 98            print("No objects currently in bucket")
 99            sys.exit()
100        if objects_to_grab:
101            for object in objects_to_grab:
102                print("Downloading \"{}\" from {}".format(object, self.bucket))
103                self.download_file(object)
104        else:
105            print("No objects found from: {}".format(print_date))
106            sys.exit()
107
108def is_valid_file(filename):
109    if os.path.isfile(filename):
110        return filename
111    else:
112        raise argparse.ArgumentTypeError("File \"{}\" does not exist."
113                                         .format(filename))
114
115def parse_arguments():
116    parser = argparse.ArgumentParser(
117        description='''Client to perform backup-related tasks with
118                     object storage.''')
119    subparsers = parser.add_subparsers()
120
121    # parse arguments for the "upload" command
122    parser_upload = subparsers.add_parser('upload')
123    parser_upload.add_argument('files', type=is_valid_file, nargs='+')
124    parser_upload.set_defaults(func=upload)
125
126    # parse arguments for the "prune" command
127    parser_prune = subparsers.add_parser('prune')
128    parser_prune.add_argument('--days-to-keep', default=30)
129    parser_prune.set_defaults(func=prune)
130
131    # parse arguments for the "download" command
132    parser_download = subparsers.add_parser('download')
133    parser_download.add_argument('filename')
134    parser_download.set_defaults(func=download)
135
136    # parse arguments for the "get_day" command
137    parser_get_day = subparsers.add_parser('get_day')
138    parser_get_day.add_argument('day')
139    parser_get_day.set_defaults(func=get_day)
140
141    return parser.parse_args()
142
143def upload(space, args):
144    space.upload_files(args.files)
145
146def prune(space, args):
147    space.prune_backups(args.days_to_keep)
148
149def download(space, args):
150    space.download_file(args.filename)
151
152def get_day(space, args):
153    space.get_day(args.day)
154
155def main():
156    args = parse_arguments()
157    space = Space(bucket=backup_bucket)
158    space.create_bucket()
159    args.func(space, args)
160
161if __name__ == '__main__':
162    main()

该脚本负责管理您的对象存储帐户中的备份。它可以上传文件,删除文件,切割旧备份,并从对象存储中下载文件。而不是直接与对象存储API进行交互,我们的其他脚本将使用这里定义的功能与远程资源进行交互。

  • " 上载 " : 上传到对象存储每个作为参数传递的文件。 可指定多个文件 。
  • " 下载 " : 从远程对象存储中下载一个单一文件,该文件作为参数传递。
  • `prune': 从对象存储位置上删除每个超过一定年龄的文件。 默认此删除超过30天的文件 。 您可以在调用 " prune " 时,通过指定 " 从天到地 " 选项来调整这一点。
  • `get_day':在当天通过,使用标准日期格式作为参数下载(如果日期有白空的话使用引文),工具将试图从该日期开始分析并下载所有文件。 .

该脚本试图从环境变量中读取对象存储凭据和桶名称,因此我们需要确保这些变量从object_storage_config.sh文件中填充,然后再调用object_storage.py脚本。

完成后,保存并关闭文件。

接下来,如果您还没有这样做,请通过键入执行脚本:

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

现在,object_storage.py 脚本可与 API 互动,我们可以创建使用它来备份和下载文件的 Bash 脚本。

创建远程备份-mysql.sh 脚本

接下来,我们将创建),以及一些额外的步骤来上传到对象存储。

如果您没有从存储库下载脚本,请在/usr/local/bin目录中创建并打开名为remote-backup-mysql.sh的文件:

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

在内部,插入以下脚本:

  1[label /usr/local/bin/remote-backup-mysql.sh]
  2#!/bin/bash
  3
  4export LC_ALL=C
  5
  6days_to_keep=30
  7backup_owner="backup"
  8parent_dir="/backups/mysql"
  9defaults_file="/etc/mysql/backup.cnf"
 10working_dir="${parent_dir}/working"
 11log_file="${working_dir}/backup-progress.log"
 12encryption_key_file="${parent_dir}/encryption_key"
 13storage_configuration_file="${parent_dir}/object_storage_config.sh"
 14now="$(date)"
 15now_string="$(date -d"${now}" +%m-%d-%Y_%H-%M-%S)"
 16processors="$(nproc --all)"
 17
 18# Use this to echo to standard error
 19error () {
 20    printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
 21    exit 1
 22}
 23
 24trap 'error "An unexpected error occurred."' ERR
 25
 26sanity_check () {
 27    # Check user running the script
 28    if [ "$(id --user --name)" != "$backup_owner" ]; then
 29        error "Script can only be run as the \"$backup_owner\" user"
 30    fi
 31
 32    # Check whether the encryption key file is available
 33    if [ ! -r "${encryption_key_file}" ]; then
 34        error "Cannot read encryption key at ${encryption_key_file}"
 35    fi
 36
 37    # Check whether the object storage configuration file is available
 38    if [ ! -r "${storage_configuration_file}" ]; then
 39        error "Cannot read object storage configuration from ${storage_configuration_file}"
 40    fi
 41
 42    # Check whether the object storage configuration is set in the file
 43    source "${storage_configuration_file}"
 44    if [ -z "${MYACCESSKEY}" ] || [ -z "${MYSECRETKEY}" ] || [ -z "${MYBUCKETNAME}" ]; then
 45        error "Object storage configuration are not set properly in ${storage_configuration_file}"
 46    fi
 47}
 48
 49set_backup_type () {
 50    backup_type="full"
 51
 52    # Grab date of the last backup if available
 53    if [ -r "${working_dir}/xtrabackup_info" ]; then
 54        last_backup_date="$(date -d"$(grep start_time "${working_dir}/xtrabackup_info" | cut -d' ' -f3)" +%s)"
 55    else
 56            last_backup_date=0
 57    fi
 58
 59    # Grab today's date, in the same format
 60    todays_date="$(date -d "$(date -d "${now}" "+%D")" +%s)"
 61
 62    # Compare the two dates
 63    (( $last_backup_date == $todays_date ))
 64    same_day="${?}"
 65
 66    # The first backup each new day will be a full backup
 67    # If today's date is the same as the last backup, take an incremental backup instead
 68    if [ "$same_day" -eq "0" ]; then
 69        backup_type="incremental"
 70    fi
 71}
 72
 73set_options () {
 74    # List the xtrabackup arguments
 75    xtrabackup_args=(
 76        "--defaults-file=${defaults_file}"
 77        "--backup"
 78        "--extra-lsndir=${working_dir}"
 79        "--compress"
 80        "--stream=xbstream"
 81        "--encrypt=AES256"
 82        "--encrypt-key-file=${encryption_key_file}"
 83        "--parallel=${processors}"
 84        "--compress-threads=${processors}"
 85        "--encrypt-threads=${processors}"
 86        "--slave-info"
 87    )
 88
 89    set_backup_type
 90
 91    # Add option to read LSN (log sequence number) if taking an incremental backup
 92    if [ "$backup_type" == "incremental" ]; then
 93        lsn=$(awk '/to_lsn/ {print $3;}' "${working_dir}/xtrabackup_checkpoints")
 94        xtrabackup_args+=( "--incremental-lsn=${lsn}" )
 95    fi
 96}
 97
 98rotate_old () {
 99    # Remove previous backup artifacts
100    find "${working_dir}" -name "*.xbstream" -type f -delete
101
102    # Remove any backups from object storage older than 30 days
103    /usr/local/bin/object_storage.py prune --days-to-keep "${days_to_keep}"
104}
105
106take_backup () {
107    find "${working_dir}" -type f -name "*.incomplete" -delete
108    xtrabackup "${xtrabackup_args[@]}" --target-dir="${working_dir}" > "${working_dir}/${backup_type}-${now_string}.xbstream.incomplete" 2> "${log_file}"
109
110    mv "${working_dir}/${backup_type}-${now_string}.xbstream.incomplete" "${working_dir}/${backup_type}-${now_string}.xbstream"
111}
112
113upload_backup () {
114    /usr/local/bin/object_storage.py upload "${working_dir}/${backup_type}-${now_string}.xbstream"
115}
116
117main () {
118    mkdir -p "${working_dir}"
119    sanity_check && set_options && rotate_old && take_backup && upload_backup
120
121    # Check success and print message
122    if tail -1 "${log_file}" | grep -q "completed OK"; then
123        printf "Backup successful!\n"
124        printf "Backup created at %s/%s-%s.xbstream\n" "${working_dir}" "${backup_type}" "${now_string}"
125    else
126        error "Backup failure! If available, check ${log_file} for more information"
127    fi
128}
129
130main

此脚本处理了实际的MySQL备份程序,控制了备份时间表,并自动从远程存储中删除旧备份。

我们在上一篇文章中使用的本地backup-mysql.sh脚本为每日备份保留了单独的目录.由于我们正在远程存储备份,我们只会本地存储最新备份,以最大限度地减少用于备份的磁盘空间。

与之前的脚本一样,在检查若干基本要求是否满足并配置应该采取的备份类型后,我们将每个备份加密并压缩到一个单一的文件档案中。

完成后保存并关闭文件,然后通过键入确保脚本可执行:

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

此脚本可以用作这个系统上的backup-mysql.sh脚本的替代品,从本地备份切换到远程备份。

创建 download-day.sh 脚本

最后,下载或创建在 /usr/local/bin 目录中的 download-day.sh 脚本. 此脚本可用于下载与特定日期相关的所有备份。

在文本编辑器中创建脚本文件,如果您之前没有下载它:

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

内部,粘贴下列内容:

 1[label /usr/local/bin/download-day.sh]
 2#!/bin/bash
 3
 4export LC_ALL=C
 5
 6backup_owner="backup"
 7storage_configuration_file="/backups/mysql/object_storage_config.sh"
 8day_to_download="${1}"
 9
10# Use this to echo to standard error
11error () {
12    printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
13    exit 1
14}
15
16trap 'error "An unexpected error occurred."' ERR
17
18sanity_check () {
19    # Check user running the script
20    if [ "$(id --user --name)" != "$backup_owner" ]; then
21        error "Script can only be run as the \"$backup_owner\" user"
22    fi
23
24    # Check whether the object storage configuration file is available
25    if [ ! -r "${storage_configuration_file}" ]; then
26        error "Cannot read object storage configuration from ${storage_configuration_file}"
27    fi
28
29    # Check whether the object storage configuration is set in the file
30    source "${storage_configuration_file}"
31    if [ -z "${MYACCESSKEY}" ] || [ -z "${MYSECRETKEY}" ] || [ -z "${MYBUCKETNAME}" ]; then
32        error "Object storage configuration are not set properly in ${storage_configuration_file}"
33    fi
34}
35
36main () {
37    sanity_check
38    /usr/local/bin/object_storage.py get_day "${day_to_download}"
39}
40
41main

由于每一天都以完整的备份开始,并在白天剩余时间内积累了增量备份,这将下载所有必要的相关文件,以恢复到任何小时快照。

剧本采用单一的参数,即日期或日期。 它使用[Python's `dateutil.parser.parse'函数 (https://dateutil.readthedocs.io/en/stable/parser.html# dateutil.parser.parse)来读取和解释作为参数提供的日期字符串. 该函数相当灵活,可以多种格式解释日期,例如"Friday"等相对字符串. 然而,为了避免含糊不清,最好使用定义更明确的日期。 如果您想要使用的格式包含白空格, 请务必在引文中将日期包裹起来 .

当你准备好继续时,保存并关闭文件. 通过键入将脚本执行:

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

我们现在有能力从对象存储中下载备份文件,在我们想要恢复的特定日期。

测试远程MySQL备份和下载脚本

现在我们有我们的脚本,我们应该测试,以确保它们按预期运作。

完成完整的备份

首先,与备份用户一起调用remote-mysql-backup.sh脚本,因为这是我们第一次运行这个命令,所以它应该创建我们MySQL数据库的完整备份。

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

<$>[注] 注: 如果您收到一个错误,表明您选择的库存名称已经在使用,您将不得不选择不同的名称。在 /backups/mysql/object_storage_config.sh 文件中更改 MYBUCKETNAME 的值,然后删除本地备份目录(sudo rm -rf /backups/mysql/working),以便脚本可以尝试使用新库存名称进行完整备份。

如果一切顺利,你会看到类似于以下的输出:

1[secondary_label Output]
2Uploaded /backups/mysql/working/full-10-17-2017_19-09-30.xbstream to "your_bucket_name"
3Backup successful!
4Backup 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脚本产生的文件:

1ls /backups/mysql/working
1[secondary_label Output]
2backup-progress.log full-10-17-2017_19-09-30.xbstream xtrabackup_checkpoints xtrabackup_info

backup-progress.log文件包含xtrabackup命令的输出,而xtrabackup_checkpointsxtrabackup_info包含有关所使用的选项、备份类型和范围以及其他元数据的信息。

执行增量备份

让我们对我们的设备表进行一个小小的更改,以创建在我们的第一个备份中找不到的额外数据。

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

输入您的数据库的管理密码以添加新记录。

当我们再次调用脚本时,应该创建一个增量备份,只要它仍然与以前的备份相同(根据服务器的时钟):

1sudo -u backup remote-backup-mysql.sh
1[secondary_label Output]
2Uploaded /backups/mysql/working/incremental-10-17-2017_19-19-20.xbstream to "your_bucket_name"
3Backup successful!
4Backup created at /backups/mysql/working/incremental-10-17-2017_19-19-20.xbstream

上面的输出表明,备份是在同一目录中本地创建的,并再次上传到对象存储中。如果我们检查 /backups/mysql/working 目录,我们会发现新的备份存在,并且以前的备份已被删除:

1ls /backups/mysql/working
1[secondary_label Output]
2backup-progress.log incremental-10-17-2017_19-19-20.xbstream xtrabackup_checkpoints xtrabackup_info

由于我们的文件是远程上传的,所以删除本地副本有助于减少使用的磁盘空间。

从指定日期下载备份

由于我们的备份是远程存储的,如果我们需要恢复我们的文件,我们将需要拉下远程文件。

开始创建,然后进入一个目录,用户可以安全地写入备份:

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

接下来,将download-day.sh脚本称为备份用户。 请在您想要下载的档案的日期上传。 日期格式相当灵活,但最好尝试保持明确:

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

如果有与您提供的日期相匹配的档案,则将下载到当前目录:

1[secondary_label Output]
2Looking for objects from Tuesday, Oct. 17 2017
3Downloading "full-10-17-2017_19-09-30.xbstream" from your_bucket_name
4Downloading "incremental-10-17-2017_19-19-20.xbstream" from your_bucket_name

確認檔案已下載到本地檔案系統:

1ls
1[secondary_label Output]
2full-10-17-2017_19-09-30.xbstream incremental-10-17-2017_19-19-20.xbstream

压缩,加密的档案现在又回到了服务器上。

提取和准备备份

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

首先,通过备份用户将.xbstream文件传输到extract-mysql.sh脚本:

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

这将解密和解压缩档案到一个名为恢复的目录. 输入该目录并用prepare-mysql.sh脚本准备文件:

1cd restore
2sudo -u backup prepare-mysql.sh
 1[secondary_label Output]
 2Backup looks to be fully prepared. Please check the "prepare-progress.log" file
 3to verify before continuing.
 4
 5If everything looks correct, you can apply the restored files.
 6
 7First, stop MySQL and move or remove the contents of the MySQL data directory:
 8
 9        sudo systemctl stop mysql
10        sudo mv /var/lib/mysql/ /tmp/
11
12Then, recreate the data directory and copy the backup files:
13
14        sudo mkdir /var/lib/mysql
15        sudo xtrabackup --copy-back --target-dir=/tmp/backup_archives/restore/full-10-17-2017_19-09-30
16
17Afterward the files are copied, adjust the permissions and restart the service:
18
19        sudo chown -R mysql:mysql /var/lib/mysql
20        sudo find /var/lib/mysql -type d -exec chmod 750 {} \;
21        sudo systemctl start mysql

现在应该准备在 /tmp/backup_archives/restore 目录中的完整备份,我们可以按照输出中的说明来恢复我们系统上的 MySQL 数据。

将备份数据恢复到MySQL数据目录

在我们恢复备份数据之前,我们需要将当前数据移除。

首先,关闭 MySQL 以避免损坏数据库或在我们更换数据文件时破坏服务。

1sudo systemctl stop mysql

接下来,我们可以将当前的数据目录移动到 /tmp 目录. 这样,如果恢复有问题,我们可以轻松地将其移动回去. 由于我们在上一篇文章中将文件移动到 /tmp/mysql,我们可以将文件移动到 /tmp/mysql-remote`:

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

然后再创建一个空的 /var/lib/mysql 目录:

1sudo mkdir /var/lib/mysql

现在,我们可以输入xtrabackup恢复命令,即提供的prepare-mysql.sh命令将备份文件复制到/var/lib/mysql目录:

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

一旦进程完成,修改目录权限和所有权,以确保MySQL进程可以访问:

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

当完成时,重新启动MySQL并检查我们的数据是否已正确恢复:

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

数据可用,表明已成功恢复。

恢复数据后,重要的是返回并删除恢复目录. 未来的增量备份无法应用到完整备份后,我们应该删除它。

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

下次我们需要备份目录的清洁副本,我们可以从备份档案文件中再次提取它们。

创建一个Cron工作以运行备份小时

我们在最后一个指南中创建了一个cron工作,以便在本地自动备份我们的数据库。我们将设置一个新的cron工作,以进行远程备份,然后禁用本地备份工作。

要开始,请在 /etc/cron.hourly 目录中创建一个名为 remote-backup-mysql 的文件:

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

内部,我们将通过systemd-cat命令与backup用户调用我们的remote-backup-mysql.sh脚本,这使我们能够将输出登录到journald:

1[label /etc/cron.hourly/remote-backup-mysql]
2#!/bin/bash 
3sudo -u backup systemd-cat --identifier=remote-backup-mysql /usr/local/bin/remote-backup-mysql.sh

保存并关闭文件,当你完成。

我们将启用我们的新的cron任务,并通过操纵两个文件的可执行权限位来禁用旧的任务:

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

通过手动执行脚本来测试新的远程备份任务:

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

一旦提示返回,我们可以用journalctl检查日志条目:

1sudo journalctl -t remote-backup-mysql
1[seconary_label Output]
2-- Logs begin at Tue 2017-10-17 14:28:01 UTC, end at Tue 2017-10-17 20:11:03 UTC. --
3Oct 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"
4Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Backup successful!
5Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Backup created at /backups/mysql/working/incremental-10-17-2017_20-07-13.xbstream

检查几小时后,以确保额外的备份正在按日程进行。

备份提取密钥

您将不得不处理的最后一个考虑是如何备份加密密钥(在)。

需要加密密钥来恢复使用此过程备份的任何文件,但将加密密密钥存储在与数据库文件相同的位置将消除加密提供的保护,因此,重要的是将加密密钥的副本保存在一个单独的位置,以便在数据库服务器失败或需要重建时仍然可以使用备份档案。

虽然对非数据库文件的完整备份解决方案不在本文的范围内,但您可以将密钥复制到本地计算机进行保存。

1sudo less /backups/mysql/encryption_key

如果您需要将备份恢复到另一个服务器上,请将文件的内容复制到新机上的 /backups/mysql/encryption_key,设置本指南所述的系统,然后使用提供的脚本恢复。

结论

在本指南中,我们介绍了如何对MySQL数据库进行每小时备份,并自动将其上传到远程对象存储空间。系统将每天早上进行完整备份,然后每小时进行增量备份,以便能够恢复到任何小时检查点。

Published At
Categories with 技术
comments powered by Disqus