Comment sauvegarder des bases de données MySQL sur un stockage d’objets avec Percona sous Ubuntu 16.04

introduction

Les bases de données stockent souvent certaines des informations les plus précieuses de votre infrastructure. Pour cette raison, il est important de disposer de sauvegardes fiables pour se prémunir contre la perte de données en cas d’accident ou de défaillance matérielle.

Les outils de sauvegarde Percona XtraBackup fournissent une méthode permettant d’effectuer des sauvegardes «à chaud» des données MySQL pendant le fonctionnement du système. Pour ce faire, ils copient les fichiers de données au niveau du système de fichiers, puis effectuent une récupération sur incident pour assurer la cohérence du jeu de données.

Dans https://www.digitalocean.com/community/tutorials/how-to-configure-mysql-backups-with-percona-xtrabackup-on-ubuntu-16-04 (guide précédent), nous avons installé les utilitaires de sauvegarde de Percona et créé une série de scripts pour effectuer des sauvegardes locales en rotation. Cela fonctionne bien pour la sauvegarde de données sur un autre lecteur ou sur un autre volume monté sur le réseau afin de gérer les problèmes liés à votre ordinateur de base de données. Cependant, dans la plupart des cas, les données doivent être sauvegardées hors site, où elles peuvent être facilement conservées et restaurées. Dans ce guide, nous allons étendre notre système de sauvegarde précédent afin de télécharger nos fichiers de sauvegarde compressés et chiffrés sur un service de stockage d’objets. Nous utiliserons DigitalOcean Spaces à titre d’exemple dans ce guide, mais les procédures de base s’appliquent probablement également à d’autres solutions de stockage d’objets compatibles S3.

Conditions préalables

Avant de commencer ce guide, vous aurez besoin d’un serveur de base de données MySQL configuré avec la solution de sauvegarde Percona locale décrite dans notre guide précédent. L’ensemble des guides à suivre sont les suivants:

Outre les tutoriels ci-dessus, vous devrez également générer une clé d’accès et une clé secrète pour pouvoir interagir avec votre compte de stockage d’objet à l’aide de l’API. Si vous utilisez DigitalOcean Spaces, vous pouvez savoir comment générer ces informations d’identification en suivant notre https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key. Guide [Comment créer un espace DigitalOcean et une clé API]. Vous devrez enregistrer la clé d’accès à l’API et la valeur secrète de l’API.

Lorsque vous avez terminé avec les guides précédents, reconnectez-vous à votre serveur en tant qu’utilisateur + sudo + pour commencer.

Installer les dépendances

Nous allons utiliser des scripts Python et Bash pour créer nos sauvegardes et les télécharger vers un stockage d’objets distant pour les sauvegarder. Nous aurons besoin de la bibliothèque + boto3 + Python pour interagir avec l’API de stockage object. Nous pouvons le télécharger avec + pip +, le gestionnaire de paquets de Python.

Actualisez notre index de paquetage local puis installez la version Python 3 de + pip + à partir des référentiels par défaut d’Ubuntu en utilisant + apt-get + en tapant:

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

Ubuntu conservant son propre cycle de vie de paquet, la version de + pip dans les référentiels Ubuntu n’est pas synchronisée avec les versions récentes. Cependant, nous pouvons mettre à jour une version plus récente de + pip + en utilisant l’outil lui-même. Nous allons utiliser + sudo + pour installer globalement et inclure l’indicateur + -H + pour définir la variable + $ HOME + sur une valeur + pip + attend:

sudo -H pip3 install --upgrade pip

Ensuite, nous pouvons installer + boto3 + avec le module + pytz +, que nous utiliserons pour comparer les temps avec précision en utilisant le format compatible avec l’offset renvoyé par l’API de stockage d’objets:

sudo -H pip3 install boto3 pytz

Nous devrions maintenant avoir tous les modules Python dont nous avons besoin pour interagir avec l’API de stockage d’objets.

Créer un fichier de configuration de stockage d’objets

Nos scripts de sauvegarde et de téléchargement devront interagir avec l’API de stockage d’objets afin de télécharger des fichiers et de télécharger d’anciens artefacts de sauvegarde lorsque nous aurons besoin de restaurer. Ils devront utiliser les clés d’accès que nous avons générées dans la section des prérequis. Plutôt que de conserver ces valeurs dans les scripts eux-mêmes, nous les placerons dans un fichier dédié lisible par nos scripts. De cette façon, nous pouvons partager nos scripts sans craindre d’exposer nos informations d’identification et nous pouvons les verrouiller plus lourdement que le script lui-même.

Dans le guide https://www.digitalocean.com/community/tutorials/how-to-configure-mysql-backups-with-percona-xtrabackup-on-ubuntu-16-04], nous avons créé le + / backups / mysql + `répertoire pour stocker nos sauvegardes et notre clé de cryptage. Nous allons placer le fichier de configuration ici à côté de nos autres actifs. Créez un fichier nommé `+ object_storage_config.sh +:

sudo nano /backups/mysql/object_storage_config.sh

A l’intérieur, collez le contenu suivant, en modifiant la clé d’accès et la clé secrète pour les valeurs obtenues à partir de votre compte de stockage d’objet et le nom du compartiment pour une valeur unique. Définissez l’URL du terminal et le nom de la région sur les valeurs fournies par votre service de stockage d’objets (nous utiliserons ici les valeurs associées à la région NYC3 de DigitalOcean pour les espaces):

/backups/mysql/object_storage_config.sh

#!/bin/bash

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

Ces lignes définissent deux variables d’environnement appelées + MYACCESSKEY + et + MYSECRETKEY + pour contenir nos clés d’accès et nos clés secrètes respectivement. La variable + MYBUCKETNAME + définit le compartiment de stockage d’objets que nous souhaitons utiliser pour stocker nos fichiers de sauvegarde. Les noms de compartiment doivent être universellement uniques. Vous devez donc choisir un nom qu’aucun autre utilisateur n’a sélectionné. Notre script vérifie la valeur du compartiment pour voir si elle est déjà réclamée par un autre utilisateur et la créera automatiquement si elle est disponible. Nous + exportons + les variables que nous définissons afin que tous les processus que nous appelons depuis nos scripts aient accès à ces valeurs.

Les variables + MYENDPOINTURL + et + + MYREGIONNAME + contiennent le point de terminaison de l’API et l’identificateur de région spécifique proposé par votre fournisseur de stockage d’objets. Pour les espaces DigitalOcean, le point de terminaison sera + https: //. Digitaloceanspaces.com +. Vous pouvez trouver les régions disponibles pour les espaces dans le panneau de configuration de DigitalOcean (au moment de la rédaction de cet article, seul «nyc3» est disponible).

Enregistrez et fermez le fichier lorsque vous avez terminé.

Toute personne pouvant accéder à nos clés d’API a un accès complet à notre compte de stockage d’objets. Il est donc important de limiter l’accès au fichier de configuration à l’utilisateur + backup +. Nous pouvons donner à l’utilisateur + backup + et au groupe la propriété du fichier, puis révoquer tout autre accès en tapant:

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

Notre fichier + object_storage_config.sh + devrait maintenant être accessible uniquement à l’utilisateur + backup +.

Création des scripts de sauvegarde à distance

Maintenant que nous avons un fichier de configuration de stockage d’objets, nous pouvons commencer et créer nos scripts. Nous allons créer les scripts suivants:

  • + object_storage.py +: ce script est chargé de l’interaction avec l’API de stockage d’objets pour créer des compartiments, télécharger des fichiers, télécharger du contenu et élaguer des sauvegardes plus anciennes. Nos autres scripts appellent ce script chaque fois qu’ils ont besoin d’interagir avec le compte de stockage d’objets distant.

  • + remote-backup-mysql.sh +: Ce script sauvegarde les bases de données MySQL en chiffrant et en compressant les fichiers dans un seul artefact, puis en le téléchargeant dans le magasin d’objets distant. Il crée une sauvegarde complète au début de chaque journée, puis une sauvegarde incrémentielle toutes les heures après. Il élimine automatiquement tous les fichiers du panier distant âgés de plus de 30 jours.

  • + download-day.sh +: Ce script nous permet de télécharger toutes les sauvegardes associées à un jour donné. Comme notre script de sauvegarde crée une sauvegarde complète chaque matin, puis des sauvegardes incrémentielles tout au long de la journée, ce script peut télécharger tous les actifs nécessaires à la restauration sur un point de contrôle horaire.

En plus des nouveaux scripts ci-dessus, nous utiliserons les scripts + extract-mysql.sh + et + prepare-mysql.sh + du guide précédent pour restaurer nos fichiers. Vous pouvez afficher les scripts dans la repository pour ce tutoriel sur GitHub à tout moment. Si vous ne voulez pas copier et coller le contenu ci-dessous, vous pouvez télécharger les nouveaux fichiers directement à partir de GitHub en tapant:

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

Assurez-vous d’inspecter les scripts après le téléchargement pour vous assurer qu’ils ont été récupérés avec succès et que vous approuvez les actions qu’ils vont effectuer. Si vous êtes satisfait, marquez les scripts comme exécutables, puis déplacez-les dans le répertoire + / usr / local / bin + en tapant:

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

Ensuite, nous allons configurer chacun de ces scripts et en discuter plus en détail.

Créez le script object_storage.py

Si vous n’avez pas téléchargé le script + object_storage.py + depuis GitHub, créez un nouveau fichier dans le répertoire + / usr / local / bin + appelé + object_storage.py +:

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

Copiez et collez le contenu du script dans le fichier:

/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()

Ce script est responsable de la gestion des sauvegardes dans votre compte de stockage d’objet. Il peut télécharger des fichiers, supprimer des fichiers, supprimer de vieilles sauvegardes et télécharger des fichiers depuis le stockage d’objets. Plutôt que d’interagir directement avec l’API de stockage d’objets, nos autres scripts utiliseront la fonctionnalité définie ici pour interagir avec des ressources distantes. Les commandes qu’il définit sont:

  • + upload +: télécharge vers le stockage d’objet chacun des fichiers transmis en tant qu’arguments. Plusieurs fichiers peuvent être spécifiés.

  • + download: télécharge un seul fichier à partir du stockage d’objets distant, qui est transmis en tant qu’argument.

  • + prune +: supprime tous les fichiers plus anciens qu’un certain âge de l’emplacement de stockage de l’objet. Par défaut, cela supprime les fichiers de plus de 30 jours. Vous pouvez ajuster cela en spécifiant l’option + - days-to-keep + lorsque vous appelez + prune +.

  • + get_day +: indiquez le jour à télécharger comme argument en utilisant un format de date standard (en utilisant des guillemets si la date contient des espaces) et l’outil tentera de l’analyser et de télécharger tous les fichiers à partir de cette date.

Le script tente de lire les informations d’identification de stockage d’objet et le nom de compartiment à partir de variables d’environnement. Nous devrons donc nous assurer qu’elles sont renseignées à partir du fichier + object_storage_config.sh + avant d’appeler le script + object_storage.py +.

Lorsque vous avez terminé, enregistrez et fermez le fichier.

Ensuite, si vous ne l’avez pas déjà fait, rendez le script exécutable en tapant:

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

Maintenant que le script + object_storage.py + est disponible pour interagir avec l’API, nous pouvons créer les scripts Bash qui l’utilisent pour sauvegarder et télécharger des fichiers.

Créez le script remote-backup-mysql.sh

Ensuite, nous allons créer le script + remote-backup-mysql.sh +. Cela remplira bon nombre des mêmes fonctions que le script de sauvegarde local + backup-mysql.sh + d’origine, avec une structure organisationnelle plus basique (car la maintenance des sauvegardes sur le système de fichiers local n’est pas nécessaire) et quelques étapes supplémentaires pour le téléchargement dans le stockage d’objets. .

Si vous n’avez pas téléchargé le script depuis le référentiel, créez et ouvrez un fichier appelé + remote-backup-mysql.sh + dans le répertoire + / usr / local / bin +:

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

À l’intérieur, collez le script suivant:

/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

Ce script gère la procédure de sauvegarde MySQL, contrôle la planification de la sauvegarde et supprime automatiquement les anciennes sauvegardes du stockage distant. Vous pouvez choisir le nombre de jours de sauvegarde que vous souhaitez garder en main en ajustant la variable + days_to_keep +.

Le script local + backup-mysql.sh + que nous avons utilisé dans le dernier article maintenait des répertoires distincts pour les sauvegardes de chaque jour. Comme nous stockons les sauvegardes à distance, nous ne stockons que la dernière sauvegarde localement afin de minimiser l’espace disque consacré aux sauvegardes. Les sauvegardes précédentes peuvent être téléchargées à partir du stockage d’objets si nécessaire pour la restauration.

Comme pour le script précédent, après vérification de la conformité à quelques exigences de base et configuration du type de sauvegarde à effectuer, nous chiffrons et compressons chaque sauvegarde dans un seul fichier d’archive. Le fichier de sauvegarde précédent est supprimé du système de fichiers local et toutes les sauvegardes distantes antérieures à la valeur définie dans + days_to_keep + sont supprimées.

Enregistrez et fermez le fichier lorsque vous avez terminé. Ensuite, assurez-vous que le script est exécutable en tapant:

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

Ce script peut remplacer le script + backup-mysql.sh + de ce système pour passer des sauvegardes locales aux sauvegardes distantes.

Créez le script download-day.sh

Enfin, téléchargez ou créez le script + download-day.sh + dans le répertoire + / usr / local / bin +. Ce script peut être utilisé pour télécharger toutes les sauvegardes associées à un jour particulier.

Créez le fichier de script dans votre éditeur de texte si vous ne l’avez pas téléchargé auparavant:

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

À l’intérieur, collez le contenu suivant:

/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

Ce script peut être appelé pour télécharger toutes les archives d’un jour spécifique. Étant donné que chaque journée commence par une sauvegarde complète et accumule des sauvegardes incrémentielles tout au long de la journée, tous les fichiers pertinents nécessaires à la restauration sont téléchargés.

Le script prend un seul argument qui est une date ou un jour. Il utilise la fonction Python + dateutil.parser.parse + pour lire et interpréter une chaîne de date fournie comme argument. La fonction est assez flexible et peut interpréter les dates dans divers formats, y compris les chaînes relatives telles que «Vendredi», par exemple. Pour éviter toute ambiguïté, il est préférable d’utiliser des dates mieux définies. Assurez-vous de mettre les dates entre guillemets si le format que vous souhaitez utiliser contient des espaces.

Lorsque vous êtes prêt à continuer, enregistrez et fermez le fichier. Rendre le script exécutable en tapant:

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

Nous avons maintenant la possibilité de télécharger les fichiers de sauvegarde à partir du stockage d’objets à une date précise à laquelle nous souhaitons effectuer la restauration.

Test de la sauvegarde à distance MySQL et des scripts de téléchargement

Maintenant que nos scripts sont en place, nous devrions tester pour nous assurer qu’ils fonctionnent comme prévu.

Effectuer une sauvegarde complète

Commencez par appeler le script + remote-mysql-backup.sh + avec l’utilisateur + backup +. Comme c’est la première fois que nous exécutons cette commande, il devrait créer une sauvegarde complète de notre base de données MySQL.

sudo -u backup remote-backup-mysql.sh

Si tout se passe bien, vous verrez une sortie semblable à celle-ci:

OutputUploaded /backups/mysql/working/full-10-17-2017_19-09-30.xbstream to ""
Backup successful!
Backup created at /backups/mysql/working/full-10-17-2017_19-09-30.xbstream

Cela indique qu’une sauvegarde complète a été créée dans le répertoire + / backups / mysql / working. Il a également été chargé sur un stockage d’objets distant à l’aide du compartiment défini dans le fichier + object_storage_config.sh +.

Si nous regardons dans le répertoire + / backups / mysql / working +, nous pouvons voir des fichiers similaires à ceux produits par le script + backup-mysql.sh + du dernier guide:

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

Le fichier + backup-progress.log + contient le résultat de la commande + xtrabackup +, tandis que + xtrabackup_checkpoints + et + xtrabackup_info + contiennent des informations sur les options utilisées, le type et l’étendue de la sauvegarde, ainsi que d’autres métadonnées.

Effectuer une sauvegarde incrémentielle

Modifions légèrement notre table + equipment afin de créer des données supplémentaires qui ne figuraient pas dans notre première sauvegarde. Nous pouvons entrer une nouvelle ligne dans la table en tapant:

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

Entrez le mot de passe administratif de votre base de données pour ajouter le nouvel enregistrement.

Maintenant, nous pouvons faire une sauvegarde supplémentaire. Lorsque nous appelons à nouveau le script, une sauvegarde incrémentielle doit être créée tant que le jour précédent est identique à celui de la sauvegarde précédente (en fonction de l’horloge du serveur):

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

La sortie ci-dessus indique que la sauvegarde a été créée localement dans le même répertoire et a de nouveau été téléchargée sur le stockage d’objets. Si nous vérifions le répertoire + / backups / mysql / working +, nous constaterons que la nouvelle sauvegarde est présente et que la sauvegarde précédente a été supprimée:

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

Étant donné que nos fichiers sont téléchargés à distance, la suppression de la copie locale permet de réduire la quantité d’espace disque utilisée.

Télécharger les sauvegardes d’un jour spécifié

Puisque nos sauvegardes sont stockées à distance, nous devrons extraire les fichiers distants si nous devons restaurer nos fichiers. Pour ce faire, nous pouvons utiliser le script + download-day.sh +.

Commencez par créer puis déplacez-vous dans un répertoire dans lequel l’utilisateur + backup + peut écrire en toute sécurité:

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

Ensuite, appelez le script + download-day.sh + en tant qu’utilisateur + backup +. Indiquez le jour des archives que vous souhaitez télécharger. Le format de la date est assez flexible, mais il vaut mieux essayer de ne pas être ambigu:

sudo -u backup download-day.sh ""

Si certaines archives correspondent à la date que vous avez fournie, elles seront téléchargées dans le répertoire actuel:

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

Vérifiez que les fichiers ont été téléchargés sur le système de fichiers local:

ls
Outputfull-10-17-2017_19-09-30.xbstream  incremental-10-17-2017_19-19-20.xbstream

Les archives compressées et cryptées sont à nouveau sur le serveur.

Extraire et préparer les sauvegardes

Une fois les fichiers collectés, nous pouvons les traiter de la même manière que nous avons traité les sauvegardes locales.

Commencez par transmettre les fichiers + .xbstream + au script + extract-mysql.sh + à l’aide de l’utilisateur + backup +:

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

Cela décryptera et décompressera les archives dans un répertoire appelé + restore +. Entrez ce répertoire et préparez les fichiers avec le script + prepare-mysql.sh +:

cd restore
sudo -u backup prepare-mysql.sh
OutputBackup looks to be fully prepared.  Please check the "prepare-progress.log" file
to verify before continuing.

If everything looks correct, you can apply the restored files.

First, stop MySQL and move or remove the contents of the MySQL data directory:

       sudo systemctl stop mysql
       sudo mv /var/lib/mysql/ /tmp/

Then, recreate the data directory and  copy the backup files:

       sudo mkdir /var/lib/mysql
       sudo xtrabackup --copy-back --target-dir=/tmp/backup_archives/restore/full-10-17-2017_19-09-30

Afterward the files are copied, adjust the permissions and restart the service:

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

La sauvegarde complète dans le répertoire + / tmp / backup_archives / restore + devrait maintenant être préparée. Nous pouvons suivre les instructions de la sortie pour restaurer les données MySQL sur notre système.

Restaurer les données de sauvegarde dans le répertoire de données MySQL

Avant de restaurer les données de sauvegarde, nous devons écarter les données actuelles.

Commencez par arrêter MySQL pour éviter de corrompre la base de données ou de faire planter le service lorsque nous remplaçons ses fichiers de données.

sudo systemctl stop mysql

Ensuite, nous pouvons déplacer le répertoire de données actuel dans le répertoire + / tmp +. De cette façon, nous pouvons facilement le ramener si la restauration a des problèmes. Puisque nous avons déplacé les fichiers vers «+ / tmp / mysql » dans le dernier article, nous pouvons déplacer les fichiers vers « / tmp / mysql-remote +» cette fois-ci:

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

Ensuite, créez un répertoire + / var / lib / mysql vide:

sudo mkdir /var/lib/mysql

Maintenant, nous pouvons taper la commande + xtrabackup + restore fournie par la commande + prepare-mysql.sh + pour copier les fichiers de sauvegarde dans le répertoire + / var / lib / mysql +:

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

Une fois le processus terminé, modifiez les autorisations et la propriété du répertoire pour vous assurer que le processus MySQL a accès:

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

Lorsque cela est terminé, redémarrez MySQL et vérifiez que nos données ont été correctement restaurées:

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

Les données sont disponibles, ce qui indique qu’elles ont été restaurées avec succès.

Après la restauration de vos données, il est important de revenir en arrière et de supprimer le répertoire de restauration. Les sauvegardes incrémentielles futures ne peuvent pas être appliquées à la sauvegarde complète une fois celle-ci préparée. Nous devons donc la supprimer. De plus, les répertoires de sauvegarde ne doivent pas rester non chiffrés sur le disque pour des raisons de sécurité:

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

La prochaine fois que nous aurons besoin de copies propres des répertoires de sauvegarde, nous pourrons les extraire à nouveau des fichiers d’archive de sauvegarde.

Création d’un travail périodique pour exécuter les sauvegardes toutes les heures

Nous avons créé un travail + cron pour sauvegarder automatiquement votre base de données localement dans le dernier guide. Nous allons configurer un nouveau travail + cron + pour effectuer des sauvegardes à distance, puis désactiver le travail de sauvegarde local. Nous pouvons facilement basculer entre les sauvegardes locales et distantes selon les besoins en activant ou désactivant les scripts + cron +.

Pour commencer, créez un fichier nommé + remote-backup-mysql dans le répertoire` + / etc / cron.hourly`:

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

À l’intérieur, nous appellerons notre script + remote-backup-mysql.sh + avec l’utilisateur + backup + via la commande + systemd-cat +, ce qui nous permettra de consigner la sortie dans + 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

Enregistrez et fermez le fichier lorsque vous avez terminé.

Nous allons activer notre nouveau travail + cron + et désactiver l’ancien en manipulant le bit de permission + executable + sur les deux fichiers:

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

Testez le nouveau travail de sauvegarde à distance en exécutant le script manuellement:

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

Une fois l’invite renvoyée, nous pouvons vérifier les entrées du journal avec + journalctl +:

sudo journalctl -t remote-backup-mysql
[seconary_label Output]
-- Logs begin at Tue 2017-10-17 14:28:01 UTC, end at Tue 2017-10-17 20:11:03 UTC. --
Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Uploaded /backups/mysql/working/incremental-10-17-2017_22-16-09.xbstream to ""
Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Backup successful!
Oct 17 20:07:17 myserver remote-backup-mysql[31422]: Backup created at /backups/mysql/working/incremental-10-17-2017_20-07-13.xbstream

Revenez dans quelques heures pour vous assurer que des sauvegardes supplémentaires sont effectuées dans les délais.

Sauvegarde de la clé d’extraction

Une dernière considération à prendre en compte est la sauvegarde de la clé de cryptage (disponible dans + / backups / mysql / encryption_key +).

La clé de cryptage est requise pour restaurer les fichiers sauvegardés à l’aide de ce processus, mais le stockage de la clé de cryptage au même emplacement que les fichiers de base de données élimine la protection fournie par le cryptage. Pour cette raison, il est important de conserver une copie de la clé de chiffrement dans un emplacement distinct afin de pouvoir continuer à utiliser les archives de sauvegarde si votre serveur de base de données tombe en panne ou doit être reconstruit.

Bien qu’une solution de sauvegarde complète pour les fichiers hors base de données n’entre pas dans le cadre de cet article, vous pouvez copier la clé sur votre ordinateur local pour plus de sécurité. Pour ce faire, affichez le contenu du fichier en tapant:

sudo less /backups/mysql/encryption_key

Ouvrez un fichier texte sur votre ordinateur local et collez la valeur à l’intérieur. Si vous devez restaurer des sauvegardes sur un autre serveur, copiez le contenu du fichier dans + / backups / mysql / encryption_key + sur la nouvelle machine, configurez le système décrit dans ce guide, puis restaurez-le à l’aide des scripts fournis.

Conclusion

Dans ce guide, nous avons expliqué comment effectuer des sauvegardes d’une base de données MySQL toutes les heures et les télécharger automatiquement vers un espace de stockage d’objets distant. Le système effectuera une sauvegarde complète tous les matins, puis des sauvegardes incrémentielles toutes les heures par la suite, afin de vous permettre de restaurer tous les points de contrôle horaires. Chaque fois que le script de sauvegarde s’exécute, il recherche les sauvegardes dans le stockage d’objets de plus de 30 jours et les supprime.