介绍
数据库通常会存储您基础设施中最有价值的信息,因此,在发生事故或硬件故障时,要有可靠的备份,以防止数据丢失。
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] (https://andsky.com/tech/tutorials/initial-server-setup-with-ubuntu-16-04): 此指南将帮助您配置具有
sudo
权限的用户账户并配置一个基本的防火墙 。 - 联合国 下列 MySQL 安装指南之一:
- 如何在Ubuntu 16.04上安装MySQL: 使用由Ubuntu团队提供和维护的默认包.
- 如何在Ubuntu 16.04上安装最新的MySQL: 使用 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_checkpoints
和xtrabackup_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数据库进行每小时备份,并自动将其上传到远程对象存储空间。系统将每天早上进行完整备份,然后每小时进行增量备份,以便能够恢复到任何小时检查点。