Jacques Foucry bio photo

Jacques Foucry

IT, râleur et faiseur de pain

Email Twitter



Où l’on va voir plusieurs façon d’avoir une sauvegarde des dépôts SVN

Le contexte

Certains développeurs de ma connaissance utilisent encore SVN1. Il est bien évident que les dépôts sont sauvagardés toutes les nuits.

Les dépôts d’une société de 5 ans et de 20 développeurs commencent à être conséquents. La sauvegarde complète à coup de # svnadmin dump n’est plus envisageable dans le courant de la nuit2.

La sauvegarde incrémentale

La première solution mise en place est la sauvegarde incrémentale.

Le principe est simple :

  • on commence par une sauvegarde complète ;
  • avec la commande svnlook youngest on obtient la dernière révision, on met cette information dans un fichier ;
  • la sauvegarde partielle commence à la révision stockée précédemment ;
  • la sauvegarde complète suivante génère un nouveau fichier youngest.

Nous avons donc ces sauvegardes sur une semaine complète :

  • Samedi → complète ;
  • Dimanche → les commits du dimanche ;
  • Lundi → les commits du dimanche plus ceux de lundi ;
  • Mardi → les commits du dimanche plus ceux de lundi et mardi ;
  • Mercredi → les commits du dimanche plus ceux de lundi, mardi et mercredi ;
  • Jeudi → les commits du dimanche plus ceux de lundi, mardi, mercredi et jeudi ;
  • Vendredi → les commits du dimanche plus ceux de lundi, mardi, mercredi, jeudi et vendredi.

La restauration se fait donc avec la dernière sauvegarde complète plus la dernière sauvegarde partielle.

Plongeons dans le script

Le script est assez simple. Je vais vous donner les deux fonctions de sauvegarde partielle et complète et la boucle de gestion, je vous laisserais broder autour pour l’envoi d’un message ou la copie de la sauvegarde sur une autre machine.

Définition de variables

Nous allons définir quelques variables, comme le répertoire d’accueil de nos sauvegardes, la date courante, le chemin vers le fichier de trace :

1
2
3
4
5
6
7
8
BACKUPDIR=/home/backups
DATUM=$(/bin/date +"%Y%m%d")
WEEKDAY=$(/bin/date +"%u")
HOUR=$(/bin/date +"%T")
HOSTNAME=$(hostname)
SVNDIR=/srv/storage/svn/repository
LOG=/var/log/backup_svn.${DATUM}.log
REPOLIST="project1 project2 documentation"

Les noms des variables sont assez parlant pour ne pas avoir besoin d’explication.

Le cœur du script, la boucle

Le cœur du script est une boucle sur les listes des dépôts, définie dans la variable REPOLIST.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for REPO in ${REPOLIST}
do
    CURRENTREPO=${SVNDIR}/${REPO}
    echo "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-" >> "${LOG}"
    echo "Dumping ${CURRENTREPO}..." >> "${LOG}"

    # How many full backups did we already have
    nb=$(ls ${BACKUPDIR}/${REPO}.full.* | wc -l)
    if [ $nb -ne 0 ]; then
        # Folder not empty, we have at least one full backups
        if [ ${WEEKDAY} -ne 6 ]; then
            # Weekday is not Saturday
            incbackup
        else
            fullbackup
        fi
    else
        # Folder is empty, we need o first make a full backup
        fullbackup
    fi
done
  • Nous faisons une boucle sur le contenu de REPOLIST, le nom du dépôt en cours de traitement est stocké dans la variable REPO. La variable CURRENTREPO est le chemin absolu vers le dépôt ;
  • nous comptons le nombre de sauvegardes complètes qui se trouvent dans le répertoire d’accueil des sauvegardes ;
    • si nous avons plu de zéro sauvegarde, il est possible de faire une sauvegarde partielle ;
    • nous vérifions si nous ne sommes pas un dimanche
      • un jour de semaine, nous appelons la fonction de sauvegarde partielle ;
    • nous sommes dimanche, nous appelons la fonction de sauvegarde complète 
  • nous n’avons pas de sauvegarde complète, nous appelons la fonction de sauvegarde complète.

La fonction de sauvegarde complète

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fullbackup()
{
    echo "Full backup for ${CURRENTREPO}"  >> "${LOG}"
    # dump of the repo
    /usr/bin/svnadmin dump  "${CURRENTREPO}" > "${BACKUPDIR}"/"${REPO}.full.${DATUM}".
dump 2>> "${LOG}"
    # save the latest revision of the repo
    /usr/bin/svnlook youngest "${CURRENTREPO}" > "${BACKUPDIR}"/"${REPO}".latest.txt
    # compress r=the backup
    /bin/tar jcf "${BACKUPDIR}"/"${REPO}.full.${DATUM}".dump.tbz "${BACKUPDIR}"/"$
{REPO}.full.${DATUM}".dump 2>> "${LOG}"
    # remove the uncompressed backup
    /bin/rm "${BACKUPDIR}"/"${REPO}.full.${DATUM}".dump 2>> "${LOG}"
}
  • nous faisons une sauvegarde complète du dépôt courant dans le répertoire d’accueil des sauvegardes em mettant dans le nom du fichier de sauvegarde la date courante ;
  • avec la commande svnlook youngest nous obtenons la dernière révision du dépôt que nous stockons dans un fichier au nom de dépot.latest dans le répertoire d’accueil des sauvegardes ;
  • nous compressons (au format bzip2) le fichier de sauvegarde du dépôt ;
  • nous supprimons le fichier de sauvegarde non compressé.

La fonction de sauvegarde partielle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
incbackup()
{
    echo "Incremental backup for ${CURRENTREPO}" >> "${LOG}"
    # could we find a 'latest' file for the repo
    if [ -s ${BACKUPDIR}/${REPO}.latest.txt ]; then
        {
            latest=$($CAT ${BACKUPDIR}/"${REPO}.latest.txt")
            # dump from the latest rev to current rev for the repo
            /usr/bin/svnadmin dump -r "${latest}":HEAD --incremental "${CURREN
TREPO}" > "${BACKUPDIR}"/"${REPO}.inc.${DATUM}.dump"
            # compress the backup
            /bin/tar jcf ${BACKUPDIR}/"${REPO}.inc.${DATUM}.dump.tbz" ${BA
CKUPDIR}/"${REPO}.inc.${DATUM}.dump"
            # remove the uncompressed backup
            /bin/rm ${BACKUPDIR}/"${REPO}.inc.${DATUM}.dump"
        } 2>> $"{LOG}"
    else
        echo "Cannot find latest backup revision for ${REPO}." > /dev/stderr
    fi
}
  • nous cherchons un fichier .latest.txt au nom du dépôt dans le répertoire d’accueil des sauvegardes ;
  • si le fichier existe, nous peuplons la variable latest avec son contenu ;
    • nous faisons une sauvegarde incrémentale du dépôt en partant de la révision stockée dans latest jusqu’à la révision courante (nommée HEAD dans SVN) ;
    • nous compressons le fichier de sauvegarde ;
    • nous supprimons le fichier de sauvegarde non compressé ;
  • si le fichier n’existe pas
    • il est impossible de faire la sauvegarde partielle

Il reste à enrober tout cela de tests (vérifier l’existence du répertoire d’accueil des sauvegardes), envoyer le fichier de trace par mail, mais l’essentiel est là.

C’est bien, mais pas satisfaisant

Cela fonctionne et même plutôt bien. La restauration se fait facilement en créant un nouveau dépôt, en important la dernière sauvegarde complète puis en important la dernière sauvegarde partielle 

1
2
3
$ svnadmin create /restore/depots
$ tar jxf depot.full.bz | svnadmin load /restore/depots
$ tar jxf depot.inc.date.bz | svnadmin load /restore/depots 

Le problème n’est pas dans la méthode ou la complexité, mais dans le temps prit pour la restauration. Un petit dépôt de 5000 commits, peut être remis sur pied en 30 minutes. Mais un gros dépôt, avec plus de 30 000 commits, certains fichiers binaires (ou c’est mal, mais cela arrive) peut prendre beaucoup plus de temps (1 ou 2 jours parfois). Pendant ce temps-là, les développeurs cessent de travailler.

Il nous faut une solution plus réactive. Cette solution existe, c’est la synchronisation de dépôt.

La synchronisation de dépôt

Le principe est simple. Sur une autre machine, nous allons créer des dépôts qui seront synchronisés avec le serveur principal. Toutes les heures, nous faisons tourner un script qui met à jour le miroir. En cas de pépin sur la machine principale, il faut basculer sur l’autre machine. Un changement dans le DNS suffit.

Les éléments nécessaires

Il nous faut un serveur SVN principal (normalement, nous l’avons déjà), une autre machine avec suffisamment de place pour avoir une copie des dépôts de la première machine. Ces deux machines doivent pouvoir dialoguer entre elles. Un utilisateur dédié à la tache de synchronisation.

Nous n’aurons pas beaucoup de manipulation à faire sur la machine qui héberge le serveur principal. La création de l’utilisateur de synchro3 et l’autorisation de réécrire dans le dépôt de destination.

Autorisation d’écriture

Nous devons, sur le serveur principal, faire en sorte que notre utilisateur de synchro puisse écrire sur le serveur de secours. Je sais, c’est tordu. Nous allons faire ça pour chaque dépôt (que nous voulons synchroniser). Dans le répertoire depot/hooks nous allons ajouter un script pre-revprop-change dans lequel nous allons mettre ceci :

1
2
3
#!/bin/sh

exit 0

Une fois sauvegardé, faites en sorte que ce script soit exécutable :

1
$ chown u+x pre-revprop-change

À moins d’une erreur, nous n’aurons plus besoin d’intervenir sur le serveur principal, le reste des manipulations se faisant sur la machine de secours.

Création des dépôts de secours

Nous allons maintenant travailler sur la machine de secours. Sur celle-ci nous allons commencer par créer les dépôts que nous allons synchroniser. Faisons cela d’un jet pour nos trois dépôts :

1
2
3
4
$ for i in project1 project2 documentation
> do
> svnadmin create /home/svn/$i
> done

Nos dépôts sont créés.

Initialisation des dépôts

C’est cette étape qui va permettre d’indiquer au dépôt de sauvegarde ou se trouve sa source.

1
$ svnsync init /home/svn/project1 https://machine_principale:/srv/storage/svn/repository/project1

svnsync va vous demander le nom et le mot de passe de l’utilisateur. C’est celui que nous avons créé plus haut qu’il faut indiquer4.

Il faut bien sûr réaliser cette étape pour nos trois dépôts.

Synchronisation des dépôts

Il est temps de synchroniser réellement nos dépôts :

1
$ svnsync --non-interavtive sync /home/svn/project1

Si tout se passe bien, la synchronisation se fait presque magiquement. C’est dément non ? Si vous n’avez pas sauvegardé4 le mot de passe, celui-ci vous sera demandé.

Attention : Si votre dépot est gros, la première synchronisation sera longue. Aussi longue en fait qu’un checkout de dépot.

Un script pour automatiser tout ça

Nous allons réaliser un petit script pour automatiser la synchronisation. C’est un script tout aussi simle que le script de sauvegarde, plus simple même.

Une boucle sur la liste des dépôts et la synchronisation :

1
2
3
4
5
6
7
8
9
SYNCUSER=svnsync
SYNCPASSWD=mot-de-passe
REPOPATH=file:///home/svn
REPOLIST="project1 project2 documentation"

for repo in $REPOLIST; do
    $(/usr/bin/svnsync sync file:///home/svn/repository/$repo --source-usern
ame=$SYNCUSER --source-password=$SYNCPASSWD)
done

Voilà, le script est fini. On en change les droits pour le rendre exécutable et non visible par le commun des mortel :

1
2
$ chmod u+x svn_sync.sh
$ chmod go-rwx svn_sync.sh

Des finitions

J’avais envie de rendre mon script plus sympa. Avec des couleurs et des [OK] ou [FAILED], comme les scripts de démarrage de Linux.

Commençons par définir les couleurs (cette partie se place avant ou après la définition des variables) :

1
2
3
4
5
6
7
8
9
10
11
12
# Colors
ESC_SEQ="\x1b["
COL_RESET="$(tput setaf 9)"
COL_RED="$(tput setaf 1)"
COL_GREEN="$(tput setaf 2)"
COL_YELLOW="$(tput setaf 3)"
COL_BLUE="$(tput setaf 4)"
COL_MAGENTA="$(tput setaf 5)"
COL_CYAN="$(tput setaf 6)"
COL_CYAN="$(tput setaf 7)"
BOLD="$(tput bold)"
RESET="$(tput sgr0)"

Et modifions la boucle pour tester le code retour de la commande de synchronisation :

1
2
3
4
5
6
7
8
9
10
11
# Start loop
for repo in $REPOLIST; do
    $(/usr/bin/svnsync sync file:///home/svn/repository/$repo --source-usern
ame=$SYNCUSER --source-password=$SYNCPASSWD)
    ret=$?
    if [ $ret -ne 0 ]; then
        printf '%-25s %s\n' "Sync ${repo}" "$COL_RED [FAILED] $RESET"
    else
        printf '%-25s %s\n' "Sync ${repo}" "$COL_GREEN [OK] $RESET"
    fi
done

Et voilà le résultat (sauf que l’on de voit pas les couleurs) :

1
2
3
Sync porject1                  [OK]
Sync project2                  [OK]
Sync documentation             [OK]

Reste à mettre tout ça dans un fichier de trace et à l’envoyer par courrier électronique et le lancer régulièrement (toutes les heures par exemple)

  1. non, dans l’entreprise on ne peut pas changer de système de gestion de version d’un coup de tête. Il faut être sûr de conserver l’historique. N’oublions pas le coût de la formation et les réticences d’un passage à git (par exemple). 

  2. si la nuit existe toujours. En effet, une société qui dispose de développeurs à Paris, New York et Tokyo, ne connaît pas de période de non-activité hors du week-end. 

  3. Si vous utilisez LDAP, faites un utilisateur dans votre base LDAP. 

  4. En fonction de votre configuration, il est possible que l’on vous demande si vous voulez stocker le mot de passe de l’utilisateur de synchro en clair. Je n’ai pas encore de solution pour le stocker de façon sure. Je ne l’ai pas sauvegardé, je préfère le mettre dans le script en ayant des droits stricts dessus.  2


Laisser un commentaire

Les commentaires sont soumis à modération.