Sauvegarder automatiquement sa base de données sur Ruby on Rails

9 minutes de lecture

Sauvegarder automatiquement sa base de données sur Ruby on Rails

Introduction

Aujourd’hui, nous allons voir comment effectuer des sauvegardes de sa base de données MySQL sur un site web sous Ruby on Rails. La gestion des tâches récurrentes peut être fastidieuse et source d’erreurs. Pour éviter ces problèmes, de nombreuses solutions existent, comme l’utilisation de cronjobs.

Cependant, ces derniers peuvent être difficiles à gérer et à maintenir, en particulier lorsqu’il s’agit de tâches complexes. C’est là que la gemme Whenever intervient.

Cette bibliothèque Ruby permet de configurer des tâches récurrentes de manière claire et simple, en utilisant une syntaxe facile à comprendre. Dans cet article, nous allons examiner de plus près la gemme Whenever, découvrir comment elle fonctionne et explorer ses fonctionnalités pour créer une tâche de backup récurrente.

Automatiser avec les tâches Cron

Présentation de la gemme Whenever

La gemme Whenever est un outil populaire de planification de tâches pour les applications Ruby on Rails. Elle permet de définir des tâches périodiques qui s’exécutent automatiquement à des intervalles réguliers, ce qui peut être très utile pour automatiser des tâches récurrentes.

La syntaxe de la gemme Whenever est simple et intuitive. Elle utilise un langage DSL (Domain-Specific Language) qui ressemble à du code Ruby mais qui est plus facile à comprendre et à écrire. Les tâches peuvent être planifiées pour s’exécuter à des intervalles réguliers (toutes les heures, tous les jours, toutes les semaines, etc.) ou à des moments précis de la journée ou de la semaine.

La gemme Whenever utilise le système de planification de tâches cron sous-jacent de Linux, qui est très fiable et bien testé. Elle génère automatiquement un fichier cron à partir de votre code.

Installer la gemme

Nous allons installer la gemme nécessaire, à savoir whenever pour permettre de créer des tâches cron sur votre serveur.

Procédons à l’installation de la gemme. Ajoutez cette ligne dans votre Gemfile : 

gem 'whenever', require: false

Maintenant que la gemme est installée, générez le fichier de configuration avec cette commande :

wheneverize .

Remarque

Si la commande ne fonctionne pas sur votre environnement de développement essayez en ajoutant bundle exec devant la commande.

En utilisant cette commande, un fichier nommé schedule.rb a été créé dans le dossier config de votre application. Vous pouvez ouvrir ce fichier avec votre éditeur de code.

Voici le code à insérer :

every :sunday, at: '3am' do
    rake "backup:db RAKE_ENV=production"
end

Ce code va executer un cronjob  tous les dimanches à 3 heures du matin. Concernant le rake, nous y viendrons tout de suite !

Création d’une tâche pour Rails

Maintenant que le cronjob est créé (mais pas encore activé !) nous allons ajouter une tâche avec Rails comme celles utilisées pour créer les bases de données, compiler les assets, etc.

Pour ce faire on va lancer la commande suivante :

rails g task backup db

Une fois la commande exécutée, un fichier backup.rake a été créé dans le dossier lib/tasks à la racine de votre application.

Nous allons ouvrir ce fichier et y insérer le code suivant :

Attention

Le code ci-dessous est fait pour les bases de données MySQL uniquement. À vous de l’adapter si ce n’est pas votre type de base de donnée.

namespace :backup do
    desc "rails backup database"
    task db: :environment do
        settings = Rails.configuration.database_configuration[Rails.env]
        output_name = "#{settings['database']}-#{Time.now.strftime('%Y%m%d-%H:%M')}"
        output_file = Rails.root.join('backups/db', "#{output_name}.sql")

        #mac sql save
        if Rails.env == "development"
            system("/usr/local/mysql/bin/mysqldump -h localhost -u #{settings['username']} -p#{settings['password']} #{settings['database']} > #{output_file}")
        else
        #linux (for production) save
            system("/usr/bin/env mysqldump -h localhost -u #{settings['username']} -p#{settings['password']} #{settings['database']} > #{output_file}")
        end
        #File SQL saved ! Compress with gzip
        system("gzip #{output_file}")
    end
end

Expliquons un peu le code et ce qu’il va faire.

Tout d’abord on va sélectionner en fonction de l’environnement, la base de donnée à utiliser lors de la sauvegarde. Ensuite, on va créer le nom du futur fichier à savoir nom_de_la_base-date_heure_sauvegarde. Et nous lui disons que ce fichier devra être enregistré à la racine de l’application dans un nouveau dossier nommé backups/db.

Pour la suite, j’ai laissé ma configuration afin qu’il utilise mysqldump sous /usr/local sous Mac (l’OS avec lequel je développe) et sinon il utilisera la config /usr/bin/env pour mon serveur sous Linux CentOS.

Pour finir, il va compresser la base de donnée créé afin de minimiser la taille.

Test du code et génération du cronjob

Nous allons commencer par tester le code.

Maintenant créez manuellement le dossier backups et db à la racine de votre application (ça évitera des problèmes si le système n’a pas les droits pour le créer de lui-même).

Et enfin, lancer la commande de sauvegarde manuellement comme ceci :

bundle exec rake backup:db RAILS_ENV=development

Normalement si tout s’est déroulé comme prévu, un fichier est disponible dans le dossier backups/db.

Configurons maintenant le cronjob

Pour ce faire, il vous suffit de taper la ligne de commande suivante :whenever –update-crontab

Et voilà, c’est terminé ! vous avez mis en place votre système de backup pour votre base de donnée en quelques minutes seulement !

Envoyer la sauvegarde par e-mail

Et si on envoyait notre sauvegarde par e-mail ?

Création du template pour l’e-mail

Nous allons utiliser le Mailer de Rails par défaut pour créer ce mail. Pour ce faire, créer un fichier nommé user_mailer.rb dans le dossier app/mailers.

Maintenant vous allez ajouter le code suivant :

class UserMailer < ApplicationMailer
    default from: "votre-mail[at]site.com"
    def send_backup_message(user, file, name)
        @user = User.find(user) #passing to view
        attachments["#{name}.sql.gz"] = File.read(file)
        mail(:to => @user.email, :subject => "Copie de la sauvegarde de la base de donnée !")
    end
end

Passons en revu le code ci-dessus.

Nous allons commencer par sélectionner l’utilisateur en fonction de l’id passé dans la définition. Ensuite, nous allons ajouter le fichier compressé de la sauvegarde à l’email. Pour finir, nous envoyons le mail à l’utilisateur.

Passons à la création de la vue

Créez un fichier nommé send_backup_message.html.erb dans le dossier app/views/user_mailer. Ajoutez le code suivant (ou celui que vous voulez c’est juste un peu de blabla pour expliquer qu’on a fait la sauvegarde).

<h2 style="font-size: 20px;">Bonjour <%= @user.first_name %>,</h2>
<br>
<p>Veuillez trouver ci-joint, le backup de la base de donnée principale de votre site internet.</p><br>
<p>Cette archive est également disponible à la racine de votre site dans le dossier <strong>backups</strong> ainsi que toutes les futurs sauvegardes.</p><br>
<p>Cet e-mail a été généré automatiquement. Vous l'avez reçu car a priori tout a fonctionné correctement.</p>

Ajoutons l’envoi du mail à la tâche cron

Vous devez à nouveau éditer le fichier backup.rake disponible dans lib/tasks et ajouter après system("gzip #{output_file}") le code suivant :

#on vérifie que le fichier est ok
result = system("gzip -t #{output_file}.gz")
if result
 UserMailer.send_backup_message(1, "#{output_file}.gz", output_name).deliver
end

Attention : ici je passe l’id 1 à send_backup_message car je souhaite envoyer l’e-mail à l’utilisateur ayant cet id. Il vous faudra le modifier en fonction de votre ID.

Améliorations

Voilà tout est prêt et fonctionnel. Mais, maintenant il nous reste une possibilité d’optimisation, car en l’état le code va créer tous les dimanches un backup de la base de données.

Supprimer les anciennes sauvegardes

Nous allons ajouter une option qui va supprimer les sauvegardes qui ont plus de 6 mois. Pour ce faire, il vous faut éditer le fichier backup.rake et ajouter ce code :

      #on récupère les dossiers
      folder_path = Rails.root.join('backups/db')
      # Récupérez tous les fichiers du dossier
      files = Dir.entries(folder_path)
      # Obtenez la date actuelle
      current_time = Time.now

      # Parcourez chaque fichier et supprimez ceux qui ont l'extension .gz et qui sont vieux de plus de 6 mois
      files.each do |file|
        if file.end_with?('.gz')
          file_path = File.join(folder_path, file)
          # Vérifiez la date de modification du fichier
          file_time = File.mtime(file_path)
          # Si la date de modification du fichier est antérieure à 6 mois à la date actuelle, supprimez le fichier
          if (current_time - file_time) > 1
            FileUtils.rm(file_path)
          end
        end
      end

Suggestions d’améliorations

Une autre modification intéressante serait d’envoyer le mail à tous les administrateurs. Ceci n’est pas du tout compliqué à mettre en place, dans le mailer il suffirait de supprimer l’id et de remplacer par un where admin: true.

Une dernière pour la route, serait de créer une table et y insérer les backups. Ainsi, ils pourraient être directement gérés depuis votre administration et vous permettraient de les supprimer, télécharger … et pourquoi pas de les restaurer !

Conclusion

Voilà ! On arrive au bout, c’est l’heure de se quitter mais si vous avez des problèmes lors de la réalisation de ce tutoriel, n’hésitez pas à en parler dans les commentaires ci-dessous !