Aller au contenu

Moteur de restauration

Un plan est une liste d'étapes, exécutées dans l'ordre, sur le runner. La première qui échoue arrête l'exécution : les suivantes ne sont pas jouées, et les sondes non plus.

Il existe neuf types d'étapes, et il n'y en a pas d'autres.

Type Ce qu'il fait
fetch Rapatrie la sauvegarde dans l'espace de travail.
unpack Décompresse une archive.
start_sandbox Démarre un conteneur (base de données, service).
wait Attend qu'un port, une URL ou une commande réponde.
exec_in_sandbox Exécute une commande dans le conteneur.
restore_postgres Rejoue un dump PostgreSQL.
restore_mysql Rejoue un dump MySQL / MariaDB.
restore_mongo Rejoue un dump MongoDB.
restore_ldap Recharge un LDIF.

L'enchaînement habituel est fetchunpackstart_sandboxwaitrestore_*, puis les sondes.

Comment un fichier est retrouvé

Plusieurs étapes prennent un champ input. Sa résolution est la même partout, et elle est plus tolérante qu'un simple nom de fichier.

  1. Si input ne contient pas de joker et que le fichier existe à la racine de l'espace de travail, c'est celui-là.
  2. Sinon, input est traité comme un motif (*.sql, dump-*.tar.gz).
  3. Si rien ne correspond à la racine, la recherche descend dans les sous-répertoires : le nom est comparé au nom de chaque fichier de l'arborescence.
  4. Si plusieurs fichiers correspondent, le plus gros gagne — une sauvegarde arrive rarement seule, à côté d'une somme de contrôle et d'un manifeste.

C'est ce qui fait qu'une archive construite normalement — tar czf backup.tar.gz backup/, qui se décompresse en backup/dump.sql et non dump.sql — fonctionne avec input: "dump.sql", sans que vous ayez à écrire la disposition interne de l'archive dans le plan.

Un dump PostgreSQL au format répertoire est un répertoire : il bénéficie de la même tolérance. Les fichiers l'emportent quand les deux existent.

Cette résolution ne s'applique pas à exec_in_sandbox

Le champ cmd de exec_in_sandbox est exécuté sans shell. Un /workspace/*/schema.ldif y reste un caractère * littéral, il n'est jamais développé. Voir exec_in_sandbox.

Les secrets

Partout où une valeur peut être un secret — clé S3, mot de passe de base, variable d'environnement d'un conteneur — vous écrivez une référence, jamais la valeur :

secret://ma_cle

Le runner la résout localement, à partir de son propre fichier de configuration (env://, file://, vault://, sops://). Le Control Plane ne voit que la référence.


fetch

Champ Rôle
source s3 ou minio ; http ou https ; local, file ou nfs.
output Nom du fichier dans l'espace de travail.
max_age Âge maximal de la sauvegarde à la source, par exemple 26h.

S3 / MinIO

Champ Rôle
bucket Le bucket.
key La clé exacte de l'objet.
prefix Le préfixe / répertoire dans lequel chercher.
endpoint Le point d'accès (pour MinIO ou un S3 compatible).
region La région.
access_key, secret_key Références secret://.
selection_strategy latest_modified ou latest_name, pour prendre la dernière sauvegarde d'un préfixe au lieu d'une clé fixe.
file_pattern Motif filtrant les objets du préfixe, par exemple *.sql.gz.

HTTP / HTTPS

Champ Rôle
url L'URL à télécharger.
headers En-têtes supplémentaires.
insecure true pour ne pas vérifier le certificat.

Local / NFS

Champ Rôle
path Le chemin sur la machine du runner.

max_age

C'est la seule chose qu'un test de restauration ne peut pas déduire des données elles-mêmes. Une tâche de sauvegarde arrêtée depuis trois mois produit un fichier qui se restaure parfaitement.

max_age fait échouer l'étape quand l'objet à la source est plus vieux que la durée donnée. La date utilisée est celle de la source, pas celle de la copie. Si la source ne sait pas dire quand l'objet a été écrit, l'étape échoue aussi : une garantie invérifiable n'est pas une garantie.

Ce qui entre dans le rapport

Après un fetch réussi, le runner identifie le fichier qui vient d'apparaître, calcule son SHA-256 et l'inscrit dans le rapport signé : nom, source, bucket, préfixe, URL, taille, empreinte, date de modification à la source. Un mot de passe présent dans l'URL est retiré avant.

Si plusieurs fichiers sont apparus d'un coup, rien n'est inscrit : une identité fausse dans une preuve est pire qu'une identité absente.


unpack

Champ Rôle
input L'archive. Vide : le plus gros fichier de l'espace de travail est pris.
format Vide ou auto : déduit de l'extension.
output Répertoire de destination dans l'espace de travail.

Formats acceptés : tar, tar.gz, tgz, tar.bz2, tbz2, tar.xz, txz, gzip, gz, zip.

Détection automatique, par suffixe : .tar.gz et .tgztar.gz ; .tar.bz2 et .tbz2tar.bz2 ; .tar.xz et .txztar.xz ; .tartar ; .gzgzip ; .zipzip. Un nom qui ne finit par aucun de ces suffixes fait échouer l'étape.


start_sandbox

Démarre le conteneur dans lequel les données seront restaurées.

Champ Rôle
image L'image Docker.
name Le nom du conteneur. C'est le nom d'hôte que les sondes utiliseront.
env Variables d'environnement. Une valeur peut être une référence secret://.
ports Ports publiés.
volumes Volumes montés.
command Commande de l'image, si vous la remplacez.
healthcheck Voir ci-dessous.

Le runner place le bac à sable et les sondes sur un réseau propre à l'exécution : c'est ce qui permet à une sonde de joindre le conteneur par son name.

healthcheck

Champ Rôle
cmd La commande, sous forme de liste.
interval Entre deux essais, par exemple "5s".
timeout Délai d'un essai, celui de Docker.
retries Nombre d'essais.

Quand un healthcheck est présent, l'étape attend que le conteneur soit sain. L'attente totale vaut interval × retries ; si l'un des deux manque, elle vaut 60 secondes.

Le conteneur et ses volumes sont détruits à la fin de l'exécution, quel qu'en soit le résultat.


wait

Attend, depuis le runner, qu'une condition soit remplie.

Champ Rôle
type tcp, http ou command.
host, port Pour tcp.
url Pour http. Prêt sur un code 2xx.
command Pour command. Prêt quand la commande sort en 0.
timeout Attente totale. Défaut : 60 s.
interval Entre deux essais. Défaut : 2 s.

timeout et interval s'écrivent en durée Go : "90s", "2m".

Un essai tcp est plafonné à 3 secondes, un essai http à 5 secondes ; c'est timeout qui borne l'ensemble.

La commande de wait s'exécute sur le runner, à travers sh -c, avec l'espace de travail comme répertoire courant. Pour exécuter quelque chose dans le conteneur, c'est exec_in_sandbox.


exec_in_sandbox

L'échappatoire pour la préparation qu'aucune autre étape ne couvre : start_sandbox ne fait que démarrer le conteneur, et certaines images ont besoin d'une commande à l'intérieur avant d'être utilisables — 389-ds, par exemple, ne crée pas le backend du suffixe qu'on lui demande de servir.

Champ Rôle
cmd La commande et ses arguments. Obligatoire.
env Variables d'environnement de la commande. Les valeurs secret:// sont résolues.
ignore_failure true pour qu'un code de retour non nul ne soit pas fatal. Défaut : false.

La commande est exécutée dans le dernier conteneur démarré. Sans start_sandbox avant, l'étape échoue.

cmd : sans shell, jamais

C'est le point sur lequel les plans se cassent le plus souvent.

cmd est exécuté directement dans le conteneur, pas à travers un shell. Il n'y a donc :

  • aucun développement de joker/workspace/*/schema.ldif reste littéral ;
  • aucune substitution de variable$HOME reste $HOME ;
  • aucune redirection ni tube>, |, && sont des arguments comme les autres.

Deux écritures sont acceptées :

{"cmd": ["ldapadd", "-Y", "EXTERNAL", "-H", "ldapi:///", "-f", "/workspace/schema.ldif"]}
{"cmd": "ldapadd -Y EXTERNAL -H ldapi:/// -f /workspace/schema.ldif"}

La forme chaîne est découpée aux espaces, mais un fragment entre guillemets simples ou doubles reste un seul argument — un suffixe LDAP comme "dc=example, dc=org" contient des espaces et des virgules et serait déchiré par une découpe naïve. Rien d'autre du comportement d'un shell n'est implémenté.

Si vous avez vraiment besoin d'un shell, appelez-le explicitement :

{"cmd": ["sh", "-c", "ldapadd -f /workspace/*/schema.ldif"]}

La sortie de la commande est conservée dans le résultat de l'étape, tronquée à 32 Kio.


Les étapes de restauration

Les quatre étapes restore_* partagent la même configuration et le même principe : le runner exécute l'outil de restauration à l'intérieur du bac à sable, sur le fichier de l'espace de travail. Elles exigent toutes qu'un start_sandbox les précède.

Champ Rôle
input Le dump. Résolu comme décrit plus haut.
host Défaut 127.0.0.1. localhost est réécrit en 127.0.0.1.
port Défaut selon le moteur.
user L'utilisateur.
password Le mot de passe, ou une référence secret://.
database La base cible.
format Vide : détecté à partir du fichier.
options Options supplémentaires passées à l'outil.
stop_on_error Voir ci-dessous.

stop_on_error

Non renseigné vaut true.

psql et pg_restore continuent après une erreur SQL et sortent en 0 : un dump à moitié appliqué serait rapporté comme une restauration réussie. C'est exactement la fausse preuve que ce produit existe pour empêcher.

Ne le mettez à false que pour un dump qui référence sciemment des rôles, des extensions ou des tablespaces absents du bac à sable.

restore_postgres

Port par défaut 5432, utilisateur par défaut postgres, base par défaut restoreproof.

Formats détectés : plain, custom, directory, tar.

restore_mysql

Port par défaut 3306, utilisateur par défaut root, base par défaut restoreproof.

Formats détectés : sql, mysqlpump, shell, xtraback. Un dump --all-databases est reconnu et la base cible est alors laissée vide, puisque le dump porte ses propres CREATE DATABASE.

restore_mongo

Port par défaut 27017. Formats détectés : archive (un fichier, avec ou sans --gzip) et directory (un répertoire de .bson).

Champ supplémentaire Rôle
auth_database Où MongoDB vérifie les identifiants. Défaut admin, là où l'image officielle place le compte créé par MONGO_INITDB_ROOT_*.

restore_ldap

Port par défaut 389. Le LDIF est rejoué en ligne avec ldapadd, pas avec slapadd.

Champ supplémentaire Rôle
bind_dn Le DN avec lequel ldapadd s'authentifie, typiquement le rootdn (cn=admin,dc=example,dc=com). C'est le vocabulaire LDAP : user et database n'ont pas d'équivalent ici.
base_dn Le suffixe racine de l'annuaire. Il n'est pas passé à ldapadd, qui prend les DN dans le LDIF ; il est enregistré pour que le rapport puisse dire quel sous-arbre l'exécution a exercé.

Un export slapcat ou db2ldif contient des attributs opérationnels que le serveur cible régénère lui-même : entryUUID, entryCSN, creatorsName, createTimestamp, structuralObjectClass, memberOfldapadd les refuse, parce qu'ils appartiennent au serveur. Le runner écrit donc à côté de l'original une copie nettoyée, nommée <base>.rp-load — sans l'extension .ldif, pour qu'un input: "*.ldif" ne puisse jamais la reprendre pour la sauvegarde. Le fichier d'origine n'est jamais modifié.

Le champ format accepte ici auto (défaut : le mode est déduit de la présence de ces attributs), sanitize (nettoyer, toujours) et plain (rejouer tel quel, pour un LDIF écrit à la main ou exporté par une application).

Ce que cette restauration prouve, et ce qu'elle ne prouve pas

Les entrées nettoyées sont rechargées avec de nouveaux entryUUID, entryCSN et horodatages, produits par le serveur du bac à sable. Ce qui est prouvé, c'est que les données et les mots de passe de l'annuaire reviennent et sont utilisables par le protocole standard — pas que la base interne du serveur est identique octet pour octet à celle qui a été sauvegardée.

Le schéma est vérifié avant d'écrire la première entrée. Le runner compare les types d'attributs et les classes d'objets utilisés par le dump à ceux que l'annuaire du bac à sable déclare dans son sous-schéma. S'il en manque, l'étape échoue en donnant la liste complète, au lieu du Undefined attribute type (17) que ldapadd rend une erreur à la fois. Voir le dépannage.

Si le contrôle ne peut pas s'exécuter — un serveur qui ne répond pas à une requête sur son root DSE — il est simplement ignoré, avec un avertissement dans les journaux : un serveur inhabituel n'est pas la preuve d'une mauvaise sauvegarde.