序章

多くの場合、データベースはインフラストラクチャに最も価値のある情報の一部を格納します。 このため、事故やハードウェア障害が発生した場合のデータ損失を防ぐために、信頼性の高いバックアップを用意することが重要です。

Percona XtraBackupバックアップツールは、システムの実行中にMySQLデータの「ホット」バックアップを実行する方法を提供します。 これを行うには、ファイルシステムレベルでデータファイルをコピーしてから、クラッシュリカバリを実行して、データセット内の一貫性を実現します。

以前のガイドでは、Perconaのバックアップユーティリティをインストールし、ローテーションローカルバックアップを実行するための一連のスクリプトを作成しました。 これは、データベースマシンの問題を処理するために、データを別のドライブまたはネットワークにマウントされたボリュームにバックアップする場合に適しています。 ただし、ほとんどの場合、データはオフサイトにバックアップして、簡単に保守および復元できるようにする必要があります。 このガイドでは、以前のバックアップシステムを拡張して、圧縮され暗号化されたバックアップファイルをオブジェクトストレージサービスにアップロードします。 このガイドでは例としてDigitalOceanSpaces を使用しますが、基本的な手順は他のS3互換のオブジェクトストレージソリューションにも適用できる可能性があります。

前提条件

このガイドを開始する前に、前のガイドで概説したローカルのPerconaバックアップソリューションで構成されたMySQLデータベースサーバーが必要です。 従う必要のあるガイドの完全なセットは次のとおりです。

上記のチュートリアルに加えて、APIを使用してオブジェクトストレージアカウントとやり取りするためのアクセスキーとシークレットキーも生成する必要があります。 DigitalOcean Spacesを使用している場合は、DigitalOceanSpaceとAPIキーの作成方法ガイドに従ってこれらの資格情報を生成する方法を確認できます。 APIアクセスキーとAPIシークレット値の両方を保存する必要があります。

前のガイドを終了したら、サーバーにログインし直します。 sudo 開始するユーザー。

依存関係をインストールします

いくつかのPythonおよびBashスクリプトを使用してバックアップを作成し、保管のためにリモートオブジェクトストレージにアップロードします。 必要になります boto3 オブジェクトストレージAPIと対話するPythonライブラリ。 これをダウンロードできます pip、Pythonのパッケージマネージャー。

ローカルパッケージインデックスを更新してから、Python3バージョンの pip Ubuntuのデフォルトリポジトリから apt-get 次のように入力します。

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

Ubuntuは独自のパッケージライフサイクルを維持しているため、 pip Ubuntuのリポジトリでは、最近のリリースとの同期が保たれていません。 ただし、新しいバージョンに更新することはできます pip ツール自体を使用します。 我々は使用するだろう sudo グローバルにインストールし、 -H フラグを設定して $HOME 値への変数 pip 期待する:

  1. sudo -H pip3 install --upgrade pip

その後、インストールできます boto3 一緒に pytz モジュール。オブジェクトストレージAPIが返すオフセット対応形式を使用して時間を正確に比較するために使用します。

  1. sudo -H pip3 install boto3 pytz

これで、オブジェクトストレージAPIとやり取りするために必要なすべてのPythonモジュールができました。

オブジェクトストレージ構成ファイルを作成する

バックアップとダウンロードのスクリプトは、ファイルをアップロードし、復元する必要があるときに古いバックアップアーティファクトをダウンロードするために、オブジェクトストレージAPIと対話する必要があります。 前提条件のセクションで生成したアクセスキーを使用する必要があります。 これらの値をスクリプト自体に保持するのではなく、スクリプトで読み取ることができる専用ファイルに配置します。 このようにして、クレデンシャルを公開することを恐れずにスクリプトを共有でき、スクリプト自体よりもクレデンシャルを厳しくロックダウンできます。

最後のガイドでは、 /backups/mysql バックアップと暗号化キーを保存するディレクトリ。 ここに、他のアセットと一緒に構成ファイルを配置します。 というファイルを作成します object_storage_config.sh:

  1. sudo nano /backups/mysql/object_storage_config.sh

内部に次の内容を貼り付け、アクセスキーとシークレットキーをオブジェクトストレージアカウントから取得した値に変更し、バケット名を一意の値に変更します。 エンドポイントURLとリージョン名をオブジェクトストレージサービスによって提供される値に設定します(ここでは、スペースにDigitalOceanのNYC3リージョンに関連付けられた値を使用します)。

/backups/mysql/object_storage_config.sh
#!/bin/bash

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

これらの行は、と呼ばれる2つの環境変数を定義します MYACCESSKEYMYSECRETKEY アクセスキーと秘密キーをそれぞれ保持します。 The MYBUCKETNAME 変数は、バックアップファイルの保存に使用するオブジェクトストレージバケットを定義します。 バケット名は普遍的に一意である必要があるため、他のユーザーが選択していない名前を選択する必要があります。 このスクリプトは、バケット値をチェックして、別のユーザーによって既に要求されているかどうかを確認し、使用可能な場合は自動的に作成します。 私達 export スクリプト内から呼び出すプロセスがこれらの値にアクセスできるように定義する変数。

The MYENDPOINTURLMYREGIONNAME 変数には、APIエンドポイントと、オブジェクトストレージプロバイダーによって提供される特定のリージョン識別子が含まれます。 DigitalOceanスペースの場合、エンドポイントは https://region_name.digitaloceanspaces.com. スペースに使用できる領域は、DigitalOceanコントロールパネルにあります(この記事の執筆時点では、「nyc3」のみが使用可能です)。

終了したら、ファイルを保存して閉じます。

APIキーにアクセスできる人は誰でも、オブジェクトストレージアカウントに完全にアクセスできるため、構成ファイルへのアクセスをに制限することが重要です。 backup ユーザー。 私たちは与えることができます backup ファイルのユーザーとグループの所有権を確認してから、次のように入力して他のすべてのアクセスを取り消します。

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

私たちの object_storage_config.sh これで、ファイルにアクセスできるのは backup ユーザー。

リモートバックアップスクリプトの作成

オブジェクトストレージ構成ファイルができたので、先に進んでスクリプトの作成を開始できます。 次のスクリプトを作成します。

  • object_storage.py:このスクリプトは、オブジェクトストレージAPIとやり取りして、バケットの作成、ファイルのアップロード、コンテンツのダウンロード、古いバックアップの整理を行います。 他のスクリプトは、リモートオブジェクトストレージアカウントと対話する必要があるときはいつでもこのスクリプトを呼び出します。
  • remote-backup-mysql.sh:このスクリプトは、ファイルを暗号化して単一のアーティファクトに圧縮し、それをリモートオブジェクトストアにアップロードすることで、MySQLデータベースをバックアップします。 毎日の初めに完全バックアップを作成し、その後は1時間ごとに増分バックアップを作成します。 30日より古いリモートバケットからすべてのファイルを自動的に削除します。
  • download-day.sh:このスクリプトを使用すると、特定の日に関連付けられているすべてのバックアップをダウンロードできます。 バックアップスクリプトは毎朝完全バックアップを作成し、その後1日を通して増分バックアップを作成するため、このスクリプトは1時間ごとのチェックポイントに復元するために必要なすべてのアセットをダウンロードできます。

上記の新しいスクリプトに加えて、 extract-mysql.shprepare-mysql.sh ファイルの復元に役立つ前のガイドのスクリプト。 このチュートリアルのリポジトリにあるスクリプトは、GitHubでいつでも表示できます。 以下の内容をコピーして貼り付けたくない場合は、次のように入力して、GitHubから新しいファイルを直接ダウンロードできます。

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

ダウンロード後にスクリプトを調べて、スクリプトが正常に取得されたこと、およびスクリプトが実行するアクションを承認していることを確認してください。 満足している場合は、スクリプトを実行可能としてマークしてから、スクリプトをに移動します /usr/local/bin 次のように入力してディレクトリを作成します。

  1. chmod +x /tmp/{remote-backup-mysql.sh,download-day.sh,object_storage.py}
  2. 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:

  1. 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 脚本。

終了したら、ファイルを保存して閉じます。

次に、まだ実行していない場合は、次のように入力してスクリプトを実行可能にします。

  1. 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 ディレクトリ:

  1. 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 前回の記事で使用したスクリプトは、毎日のバックアップ用に個別のディレクトリを維持していました。 バックアップはリモートで保存するため、バックアップ専用のディスク容量を最小限に抑えるために、最新のバックアップのみをローカルに保存します。 以前のバックアップは、復元の必要に応じてオブジェクトストレージからダウンロードできます。

前のスクリプトと同様に、いくつかの基本的な要件が満たされていることを確認し、実行するバックアップの種類を構成した後、各バックアップを暗号化して1つのファイルアーカイブに圧縮します。 以前のバックアップファイルは、ローカルファイルシステムおよびで定義された値よりも古いリモートバックアップから削除されます。 days_to_keep 削除されます。

終了したら、ファイルを保存して閉じます。 その後、次のように入力して、スクリプトが実行可能であることを確認します。

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

このスクリプトは、 backup-mysql.sh このシステムのスクリプトを使用して、ローカルバックアップからリモートバックアップに切り替えます。

download-day.shスクリプトを作成します

最後に、ダウンロードまたは作成します download-day.sh 内のスクリプト /usr/local/bin ディレクトリ。 このスクリプトを使用して、特定の日に関連付けられているすべてのバックアップをダウンロードできます。

以前にダウンロードしていない場合は、テキストエディタでスクリプトファイルを作成します。

  1. 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

このスクリプトを呼び出すと、特定の日からすべてのアーカイブをダウンロードできます。 毎日が完全バックアップで始まり、その日の残りの時間に増分バックアップが蓄積されるため、これにより、1時間ごとのスナップショットに復元するために必要なすべての関連ファイルがダウンロードされます。

スクリプトは、日付または日である単一の引数を取ります。 Pythonのdateutil.parser.parse関数を使用して、引数として提供された日付文字列を読み取って解釈します。 この関数はかなり柔軟性があり、たとえば「Friday」などの相対文字列など、さまざまな形式の日付を解釈できます。 ただし、あいまいさを避けるために、より明確に定義された日付を使用するのが最善です。 使用する形式に空白が含まれている場合は、必ず日付を引用符で囲んでください。

続行する準備ができたら、ファイルを保存して閉じます。 次のように入力して、スクリプトを実行可能にします。

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

復元する特定の日付のバックアップファイルをオブジェクトストレージからダウンロードできるようになりました。

リモートMySQLバックアップおよびダウンロードスクリプトのテスト

スクリプトが配置されたので、テストして期待どおりに機能することを確認する必要があります。

フルバックアップを実行する

を呼び出すことから始めます remote-mysql-backup.sh スクリプトと backup ユーザー。 このコマンドを実行するのはこれが初めてなので、MySQLデータベースの完全バックアップを作成する必要があります。

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

注:選択したバケット名がすでに使用されていることを示すエラーが表示された場合は、別の名前を選択する必要があります。 の値を変更します MYBUCKETNAME の中に /backups/mysql/object_storage_config.sh ファイルを作成し、ローカルバックアップディレクトリを削除します(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 ディレクトリ。 また、で定義されたバケットを使用してリモートオブジェクトストレージにアップロードされています object_storage_config.sh ファイル。

内を見ると /backups/mysql/working ディレクトリ、私たちはによって生成されたものと同様のファイルを見ることができます backup-mysql.sh 最後のガイドのスクリプト:

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

The backup-progress.log ファイルには、 xtrabackup コマンド、 xtrabackup_checkpointsxtrabackup_info 使用されるオプション、バックアップのタイプとスコープ、およびその他のメタデータに関する情報が含まれています。

増分バックアップを実行する

私たちに小さな変更を加えましょう equipment 最初のバックアップで見つからなかった追加のデータを作成するためのテーブル。 次のように入力して、テーブルに新しい行を入力できます。

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

データベースの管理者パスワードを入力して、新しいレコードを追加します。

これで、追加のバックアップを取ることができます。 スクリプトを再度呼び出すときは、(サーバーのクロックに従って)前のバックアップと同じ日である限り、増分バックアップを作成する必要があります。

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

上記の出力は、バックアップがローカルで同じディレクトリ内に作成され、オブジェクトストレージに再度アップロードされたことを示しています。 チェックすると /backups/mysql/working ディレクトリに、新しいバックアップが存在し、前のバックアップが削除されていることがわかります。

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

ファイルはリモートでアップロードされるため、ローカルコピーを削除すると、使用されるディスクスペースの量を減らすのに役立ちます。

特定の日からバックアップをダウンロードする

バックアップはリモートに保存されるため、ファイルを復元する必要がある場合は、リモートファイルをプルダウンする必要があります。 これを行うには、 download-day.sh 脚本。

まず、ディレクトリを作成してから移動します。 backup ユーザーは安全に次の場所に書き込むことができます。

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

次に、 download-day.sh スクリプトとして backup ユーザー。 ダウンロードしたいアーカイブの日を渡します。 日付形式はかなり柔軟ですが、明確にすることをお勧めします。

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

指定した日付と一致するアーカイブがある場合、それらは現在のディレクトリにダウンロードされます。

Output
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

ファイルがローカルファイルシステムにダウンロードされていることを確認します。

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

圧縮され、暗号化されたアーカイブが再びサーバーに戻ります。

バックアップを抽出して準備する

ファイルが収集されると、ローカルバックアップを処理したのと同じ方法でファイルを処理できます。

まず、合格 .xbstream にファイル extract-mysql.sh を使用したスクリプト backup ユーザー:

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

これにより、アーカイブが復号化され、次のディレクトリに解凍されます。 restore. そのディレクトリに入り、ファイルを準備します prepare-mysql.sh 脚本:

  1. cd restore
  2. 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をシャットダウンして、データベースが破損したり、データファイルを置き換えるときにサービスがクラッシュしたりしないようにします。

  1. sudo systemctl stop mysql

次に、現在のデータディレクトリをに移動できます /tmp ディレクトリ。 このようにして、復元に問題が発生した場合に簡単に元に戻すことができます。 ファイルをに移動したので /tmp/mysql 前回の記事では、ファイルを次の場所に移動できます /tmp/mysql-remote この時:

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

次に、空を再作成します /var/lib/mysql ディレクトリ:

  1. sudo mkdir /var/lib/mysql

これで、次のように入力できます xtrabackup 復元コマンド prepare-mysql.sh バックアップファイルをにコピーするために提供されるコマンド /var/lib/mysql ディレクトリ:

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

プロセスが完了したら、ディレクトリのアクセス許可と所有権を変更して、MySQLプロセスがアクセスできるようにします。

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

これが終了したら、MySQLを再起動し、データが適切に復元されていることを確認します。

  1. sudo systemctl start mysql
  2. 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 | +----+---------+-------+--------+

データが利用可能であり、正常に復元されたことを示しています。

データを復元した後、戻って復元ディレクトリを削除することが重要です。 将来の増分バックアップは、準備ができたら完全バックアップに適用できないため、削除する必要があります。 さらに、セキュリティ上の理由から、バックアップディレクトリをディスク上で暗号化せずに放置しないでください。

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

次回バックアップディレクトリのクリーンコピーが必要になったときに、バックアップアーカイブファイルからそれらを再度抽出できます。

バックアップを1時間ごとに実行するためのcronジョブの作成

作成しました cron 最後のガイドでデータベースをローカルに自動的にバックアップするジョブ。 新たに設置します cron リモートバックアップを取り、ローカルバックアップジョブを無効にするジョブ。 必要に応じて、ローカルバックアップとリモートバックアップを簡単に切り替えることができます。 cron スクリプト。

まず、というファイルを作成します remote-backup-mysql の中に /etc/cron.hourly ディレクトリ:

  1. 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 ジョブを操作し、を操作して古いものを無効にします executable 両方のファイルの許可ビット:

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

スクリプトを手動で実行して、新しいリモートバックアップジョブをテストします。

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

プロンプトが戻ったら、次のログエントリを確認できます。 journalctl:

  1. 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

数時間後にもう一度チェックして、追加のバックアップがスケジュールどおりに作成されていることを確認してください。

抽出キーのバックアップ

処理する必要がある最後の考慮事項の1つは、暗号化キーをバックアップする方法です( /backups/mysql/encryption_key).

このプロセスを使用してバックアップされたファイルを復元するには暗号化キーが必要ですが、データベースファイルと同じ場所に暗号化キーを保存すると、暗号化によって提供される保護が失われます。 このため、データベースサーバーに障害が発生した場合や再構築が必要な場合でもバックアップアーカイブを使用できるように、暗号化キーのコピーを別の場所に保持することが重要です。

データベース以外のファイルの完全なバックアップソリューションはこの記事の範囲外ですが、保管のためにキーをローカルコンピューターにコピーできます。 これを行うには、次のように入力してファイルの内容を表示します。

  1. sudo less /backups/mysql/encryption_key

ローカルコンピュータでテキストファイルを開き、その中に値を貼り付けます。 別のサーバーにバックアップを復元する必要がある場合は、ファイルの内容をにコピーします。 /backups/mysql/encryption_key 新しいマシンで、このガイドで概説されているシステムをセットアップしてから、提供されているスクリプトを使用して復元します。

結論

このガイドでは、MySQLデータベースの1時間ごとのバックアップを取り、それらをリモートオブジェクトストレージスペースに自動的にアップロードする方法について説明しました。 システムは毎朝完全バックアップを取り、その後は1時間ごとの増分バックアップを取り、1時間ごとのチェックポイントに復元する機能を提供します。 バックアップスクリプトが実行されるたびに、30日より古いオブジェクトストレージ内のバックアップをチェックし、それらを削除します。