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 huit 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). |
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 fetch → unpack → start_sandbox →
restore_*, puis les sondes. Il n'y a plus d'étape wait : c'est le contrôle
de santé du bac à sable, dans start_sandbox, qui attend désormais que le
conteneur soit prêt. Un plan enregistré qui porte encore une étape wait
échoue avec un message clair : l'attente fait maintenant partie du démarrage
du conteneur.
La durée d'une exécution¶
Le délai d'expiration du plan (timeout_seconds, une heure par défaut)
borne toute l'exécution, étapes et sondes comprises. Le runner n'accepte pas
plus de 24 heures : un délai plus long est ramené à 24 heures, et le runner
l'écrit dans son journal. Quand il est dépassé, le
runner arrête ce qui tourne, détruit les bacs à sable et les conteneurs de
sondes, et l'exécution échoue avec le message The run exceeded its timeout of
… (timeout_seconds of the plan). Une restauration qui dure normalement plus
longtemps demande un délai plus long dans le plan.
Le nombre d'exécutions par heure¶
Un runner ne démarre pas plus de 30 exécutions par heure glissante, essais
compris. Au-delà, il laisse l'exécution en attente sans la prendre :
RestoreProof la lui propose de nouveau toutes les quelques secondes, et elle
démarre dès qu'une place se libère dans l'heure. Le runner écrit dans son
journal qu'il a laissé une exécution en attente et dans combien de temps une
place se libère. La limite se change avec RESTOREPROOF_MAX_RUNS_PER_HOUR
(jobs.max_per_hour dans la configuration du runner, jobs.maxPerHour dans
le chart Kubernetes).
Trente exécutions par heure laissent de la marge à un usage normal : même l'offre la plus large, 5 000 exécutions par mois, ne fait en moyenne que sept exécutions par heure si un seul runner les porte toutes. La limite borne en revanche ce qu'un plan pourrait apprendre de vos données à force de les interroger : chaque sonde ne rapporte qu'un verdict et des comptes, et le nombre d'exécutions borne le nombre de ces réponses.
Ce qui part vers RestoreProof¶
Le runner exécute des commandes dans les bacs à sable : exec_in_sandbox, les
outils des étapes de restauration, docker run et docker cp, les contrôles
de santé. Leur sortie peut contenir des données de votre sauvegarde, elle ne
quitte donc jamais la machine du runner. Quand une commande échoue,
RestoreProof reçoit le nom de l'étape, le code de sortie et une seule ligne de
200 octets au plus : la dernière qui ressemble à une erreur, ou à défaut la
dernière ligne. La sortie complète est écrite dans les journaux du runner
(docker logs restoreproof-runner), et le message se termine par (full
output in the runner log) pour vous le rappeler.
La sortie des sondes suit la même règle : elle reste dans les journaux du runner, et le rapport ne garde, pour chaque sonde, que son verdict et un message structurel (voir ce que les sondes rapportent).
Toute valeur de secret résolue pendant l'exécution est en plus remplacée par
[REDACTED] dans tout ce qui est envoyé, y compris dans cette ligne. Voir
Fournir les secrets au runner.
Ce que le runner décide seul¶
Le nom du conteneur, l'utilisateur de connexion, le port et la commande de
santé d'un bac à sable ne s'écrivent plus dans le plan. Le runner les
reconnaît à l'image demandée par start_sandbox.image :
| Famille d'image | Utilisateur | Port | Commande de santé | DN d'administration |
|---|---|---|---|---|
postgres* |
postgres |
5432 |
pg_isready -U postgres |
— |
mysql*, mariadb*, percona* |
root |
3306 |
mysqladmin ping -h 127.0.0.1, ou mariadb-admin pour une image qui n'a que ce nom (MariaDB 11) |
— |
mongo* |
(aucune authentification) | 27017 |
mongosh --quiet --eval "db.adminCommand('ping')" |
— |
*openldap* (images Bitnami) |
— | 1389 |
ldapsearch -x -H ldap://localhost:1389 -b "" -s base namingContexts |
cn=admin,<base_dn> |
*389ds*, *dirsrv* |
— | 3389 |
la même recherche, sur le port 3389 | cn=Directory Manager |
*lldap* |
— | 3890 |
/app/lldap healthcheck --config-file /data/lldap_config.toml |
uid=admin,ou=people,<base_dn> |
*pocketbase* |
— | 8090 |
wget --no-verbose --tries=1 --spider http://localhost:8090/api/health |
— |
wordpress* |
— | 80 |
curl -f http://localhost:80/ |
— |
Ces valeurs alimentent trois choses à la fois : le contrôle de santé de
start_sandbox, la connexion de l'étape restore_postgres / restore_mysql
/ restore_mongo / restore_ldap qui suit, et celle de toute sonde qui vise
ce bac à sable. <base_dn> du DN d'administration vient de la variable de
base DN déjà posée dans l'env de start_sandbox — LDAP_ROOT pour les
images Bitnami d'OpenLDAP, DS_SUFFIX_NAME pour 389ds, LLDAP_LDAP_BASE_DN
pour lldap — pas d'un nouveau champ : c'est la même variable que le modèle y
écrit déjà pour que le bac à sable serve le bon suffixe.
RESTOREPROOF_LDAP_BASE_DN sur la sonde ldap reste un
champ séparé, celui de son propre contrôle de comptage, et n'entre pas dans
la composition de ce DN.
L'échappatoire existe à deux endroits, pas partout. Un healthcheck posé
dans start_sandbox, ou une variable d'environnement posée à la main dans la
configuration d'une sonde, l'emportent toujours sur ce que le runner aurait
dérivé — c'est la sortie de secours pour une image maison, ou pour une sonde
qui vise un autre bac à sable que celui que le runner aurait choisi. Elle ne
peut pas viser un service hors de l'exécution : voir
l'isolement réseau. L'étape de
restauration qui suit start_sandbox, elle, exécute toujours son outil à
l'intérieur du conteneur qu'elle vient de démarrer : host, port,
user, bind_dn, base_dn et auth_database n'y ont donc plus de sens et
ne sont plus lus, sans échappatoire — viser autre chose que ce conteneur-là
n'est pas ce que fait cette étape. Le nom du conteneur, lui, n'est plus un
réglage nulle part : il n'y a plus rien à lui opposer.
L'alias réseau¶
Chaque exécution a son propre réseau Docker, et chaque bac à sable y reçoit un
alias réseau plutôt qu'un nom choisi dans le plan : sandbox pour le
premier start_sandbox du plan, sandbox-2 pour le second, sandbox-3 pour
le troisième, et ainsi de suite dans l'ordre où les étapes apparaissent. Le
nom réel du conteneur, lui, est unique à chaque exécution
(rp-<identifiant_court>-<n>) — il ne sert à rien dans un plan, seul l'alias
compte.
C'est cet alias que les sondes et les modèles écrivent : une URL de sonde HTTP
ou PocketBase s'écrit http://sandbox:8090/api/health, et un plan à deux bacs
à sable — WordPress, par exemple — vise explicitement sandbox-2 dans la
configuration de sa seconde sonde. Un client qui connaît l'alias peut
l'utiliser tel quel, sans rien savoir d'aléatoire.
L'isolement réseau¶
Le réseau d'une exécution est fermé. Les bacs à sable et les sondes de
l'exécution s'y joignent entre eux par leur alias, et ne joignent rien d'autre :
ni Internet, ni le réseau de votre entreprise, ni la machine qui héberge le
runner, ni les conteneurs d'une autre exécution. Aucun port n'est publié sur
l'hôte, et le runner lui-même ne rejoint pas ce réseau : il pilote les
conteneurs par Docker, et les restaurations passent par docker exec.
C'est voulu. Le bac à sable fait tourner le contenu de votre sauvegarde, et une sauvegarde peut contenir n'importe quoi : un déclencheur SQL, une extension, un plugin WordPress qui appelle une adresse extérieure au démarrage. Sur ce réseau, cet appel échoue sans rien atteindre.
Une conséquence pratique : l'image d'un bac à sable doit pouvoir démarrer sans rien télécharger. Une image qui installe un paquet ou récupère un fichier au démarrage échoue ici ; il faut construire une image qui contient déjà ce dont elle a besoin.
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.
- Si
inputne contient pas de joker et que le fichier existe à la racine de l'espace de travail, c'est celui-là. - Sinon,
inputest traité comme un motif (*.sql,dump-*.tar.gz). - 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.
- 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.
input est toujours un chemin relatif à l'espace de travail. À
l'enregistrement du plan, un chemin absolu ou un chemin qui en sort par .. est
refusé pour l'input des étapes de restauration et d'unpack, et pour
l'output de fetch et d'unpack. À l'exécution, le runner refuse de même
tout input absolu et tout output absolu de fetch ; pour l'output d'un
unpack, il accepte un chemin absolu seulement s'il désigne un endroit de
l'espace de travail. Un lien symbolique qui reste dans l'espace de travail est
suivi normalement ; un lien qui en sort est refusé, et cette vérification se
fait au moment même où le runner ouvre le fichier, ou parcourt un répertoire
pour un motif comme *.sql. Sans cette règle, un lien vers un fichier du runner
lui-même (sa clé de signature, ses secrets) ferait envoyer ce fichier dans le
bac à sable.
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. Elle commence toujours par secret://. Le Control Plane ne voit que
cette référence ; le runner la résout chez lui.
Le runner retire le secret:// de tête. Ce qui reste s'écrit sous l'une des
trois formes suivantes, jamais autrement :
| Référence | Ce que le runner lit |
|---|---|
secret://env://MA_VAR |
La variable d'environnement MA_VAR du processus du runner. Si son fichier de configuration donne un env_prefix, il est ajouté devant le nom. Une variable vide ou absente fait échouer la résolution, tout comme une variable dont le nom commence par RESTOREPROOF_ : celles-là règlent le runner et ne sont jamais lues. |
secret://file://ma_cle |
Le fichier ma_cle, cherché dans le répertoire file_dir de la configuration du runner quand le chemin est relatif. Le contenu est lu, espaces et retour à la ligne de fin retirés. |
secret://static://ma_cle |
La clé ma_cle de la table static: de la configuration du runner. |
vault:// et sops:// sont reconnus mais répondent « pas encore pris en
charge » : la résolution échoue, l'étape s'arrête.
Pour savoir où poser la valeur derrière chacune de ces références, selon votre plateforme de déploiement, voir Fournir les secrets au runner.
La clé nue est l'ancienne écriture
Le runner accepte encore une clé écrite sans schéma — secret://ma_cle —
et la cherche dans la table static:. C'est l'ancienne forme. Écrivez
secret://static://ma_cle : le schéma dit où chercher, et une source
déclarée dans l'application n'accepte plus que les trois formes explicites.
Ce que la table static: ne fait pas
La valeur écrite dans static: est prise telle quelle. Y mettre
env://MA_VAR ne déclenche pas une seconde résolution : le runner rendrait
la chaîne env://MA_VAR comme mot de passe. Pour lire une variable
d'environnement ou un fichier, écrivez la référence directement dans le
plan — secret://env://MA_VAR, secret://file://ma_cle — et gardez
static: pour des valeurs littérales.
Les trois formes échouent franchement quand elles ne se résolvent pas :
une clé absente de static:, une variable d'environnement vide, un fichier
illisible arrêtent l'étape en le disant.
Le mot de passe du bac à sable : rien à écrire
Le conteneur jetable de l'étape start_sandbox — un PostgreSQL démarré
pour trois minutes puis détruit — n'a pas de clé de mot de passe dans le
plan, ni dans son environnement, ni dans l'étape de restauration, ni dans
la configuration d'une sonde qui le vise. Il n'y a rien à saisir.
Le runner reconnaît la famille de l'image démarrée (PostgreSQL,
MySQL/MariaDB, WordPress, OpenLDAP, 389 Directory Server, lldap), fabrique
les identifiants qu'elle demande, les injecte dans l'environnement du
conteneur et fournit les mêmes valeurs à l'étape de restauration et à
toute sonde qui vise ce conteneur. WordPress, qui démarre deux conteneurs,
est le seul cas où ils doivent se mettre d'accord : le runner s'appuie sur
le WORDPRESS_DB_HOST déjà posé dans le plan pour relier les deux, sans
rien à ajouter.
Rien de tout cela n'est envoyé au Control Plane ni écrit dans le rapport ou les journaux : ces valeurs n'existent que dans le conteneur jetable, le temps de l'exécution.
Une valeur écrite explicitement dans le plan — un start_sandbox.env, le
password d'une étape de restauration, la configuration d'une sonde — l'emporte
toujours sur celle que le runner aurait fabriquée.
Rupture : un runner qui ne connaît pas encore cette fabrication ne trouve aucun mot de passe à lire dans un plan généré aujourd'hui et démarre le conteneur sans, ce qui fait échouer la restauration. Mettez le runner à jour avant de lui confier des plans construits avec les modèles actuels.
fetch¶
| Champ | Rôle |
|---|---|
source |
source_profile (le chemin normal) ; s3 ou minio ; http ou https ; local, file ou nfs. |
source_id |
L'identifiant de la source déclarée, quand source vaut source_profile. |
output |
Nom du fichier dans l'espace de travail. |
max_age |
Âge maximal de la sauvegarde à la source, par exemple 26h. |
source_profile est la forme à privilégier : la source déclarée une fois
dans l'écran Sources porte l'adresse, le chemin et les références de
secrets ; l'étape dit seulement lequel prendre (key, prefix,
file_pattern, selection_strategy, max_age, output). Le runner part de la
source, résout ses secrets chez lui, puis applique par-dessus les champs de
l'étape. Les formes ci-dessous restent exécutées, mais elles recopient dans le
plan ce qu'une source décrit déjà.
Les références portées par une source s'écrivent sans le secret:// de
tête : la source ne contient que des références, le préfixe n'y distingue rien.
env://S3_SECRET_KEY dans une source, secret://env://S3_SECRET_KEY dans une
étape de plan.
Le runner ne télécharge que depuis les hôtes que vous lui avez listés et ne lit
que les dossiers que vous lui avez listés : voir ce que le runner a le droit
d'atteindre.
Un fetch hors de ces listes échoue, et son message nomme la variable et
l'entrée à ajouter.
La taille d'un téléchargement est bornée par RESTOREPROOF_FETCH_MAX_BYTES
(fetch.max_bytes dans la configuration du runner), 512 Gio par défaut. La
variable accepte une unité : 100g, 512m. Au-delà, l'étape échoue et le
fichier partiel est supprimé ; un serveur qui annonce d'emblée une taille
supérieure est refusé avant le premier octet.
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 régional du fournisseur, sans le nom du bucket (https://s3.fr-par.scw.cloud, https://s3.eu-west-1.wasabisys.com, http://minio:9000). Vide pour AWS. |
region |
La région. |
access_key, secret_key |
Références secret://. Sans elles, le runner utilise sa chaîne d'identification par défaut (variables AWS, rôle de l'instance), et ne présente ces identifiants qu'aux hôtes autorisés. |
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, par exemple pour une URL authentifiée. Les valeurs acceptent une référence secret://. Ils ne partent que vers un hôte autorisé, redirections comprises. |
insecure |
true pour ne pas vérifier le certificat. |
headers et insecure n'ont pas de champ dans l'éditeur de plan : ils
s'écrivent en configuration brute de l'étape, dans l'onglet JSON (expert).
Local / NFS
| Champ | Rôle |
|---|---|
path |
Le chemin absolu sur la machine du runner. Il doit se trouver, une fois les liens symboliques résolus, dans l'un des dossiers de RESTOREPROOF_LOCAL_ROOTS. Il ne peut pas non plus désigner les fichiers du runner lui-même, ni les contenir : son répertoire de travail, ses secrets, son fichier de configuration et /proc sont refusés, y compris à travers un lien symbolique. |
selection_strategy |
latest_modified ou latest_name, pour prendre la dernière sauvegarde du répertoire au lieu d'un fichier fixe. Comme pour S3. |
file_pattern |
Motif filtrant les fichiers du répertoire, par exemple *.sql.gz. Comme pour S3. |
Pendant la copie d'un répertoire, un lien symbolique qui pointe vers un fichier
du même répertoire est copié comme ce fichier ; un lien vers un répertoire n'est
pas suivi, son contenu est déjà copié depuis son vrai emplacement. Un lien qui
sort du répertoire fait échouer l'étape : rien en dehors de ce que path
désigne n'est copié.
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 |
Pour une archive (tar*, zip) : le répertoire de destination dans l'espace de travail. Pour gzip, qui ne contient qu'un fichier : le nom du fichier produit. Vide, pour gzip : le nom de l'archive sans son .gz. |
Formats acceptés : tar, tar.gz, tgz, tar.bz2, tbz2, tar.xz,
txz, gzip, gz, zip.
La décompression est bornée par la même limite que les téléchargements,
fetch.max_bytes : une archive, ou un fichier gzip, qui se décompresse
au-delà fait échouer l'étape, et le fichier partiel est supprimé.
Détection automatique, par suffixe : .tar.gz et .tgz → tar.gz ;
.tar.bz2 et .tbz2 → tar.bz2 ; .tar.xz et .txz → tar.xz ; .tar →
tar ; .gz → gzip ; .zip → zip. 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. |
env |
Variables d'environnement. Une valeur peut être une référence secret://. |
volumes |
Des parties de l'espace de travail à monter dans le conteneur, voir ci-dessous. |
command |
Commande de l'image, si vous la remplacez. |
healthcheck |
Voir ce que le runner décide seul. Le vôtre l'emporte s'il est présent. |
Le runner place le bac à sable et les sondes sur le réseau fermé de
l'exécution, et lui donne l'alias sandbox (sandbox-2, sandbox-3… pour
les suivants) : c'est ce qui permet à une sonde de joindre le conteneur, et à
un bac à sable de joindre l'autre. Le plan ne choisit ni le réseau ni les ports
publiés : une étape qui porte encore un champ network ou ports est refusée
à l'enregistrement, et un plan déjà enregistré qui en porte un échoue avec un
message qui le dit. Le nom du conteneur n'est pas non plus un réglage : le
runner en fabrique un unique à chaque exécution.
Un volume s'écrit ${WORKSPACE}/<chemin>:<chemin dans le conteneur>,
éventuellement suivi de :ro (lecture seule) ou :rw, par exemple
${WORKSPACE}/pb_data:/pb_data. ${WORKSPACE} seul monte tout l'espace de
travail. La source vient toujours de l'espace de travail de l'exécution :
un chemin de la machine (/etc, /var/run/docker.sock), un volume Docker
nommé ou un chemin qui en sort par .. sont refusés, à l'enregistrement du
plan comme au démarrage du conteneur. Le runner refuse aussi une source qui
n'existe pas, ou qui passe par un lien symbolique : une archive restaurée
peut contenir un lien pb_data -> /var/run, et Docker le suivrait sur la
machine du runner. Le chemin dans le conteneur doit être absolu.
L'espace de travail entier est toujours monté sur /workspace, en lecture
seule : les étapes de restauration et exec_in_sandbox y lisent les fichiers,
aucune n'y écrit. Seuls les volumes que vous déclarez sans :ro sont
modifiables par le conteneur. Comme un bac à sable fait tourner le contenu de
la sauvegarde, un dossier qu'il a pu modifier n'est plus sûr pour les suivants :
un start_sandbox qui monte un chemin situé à l'intérieur d'un volume
qu'un bac à sable précédent de la même exécution monte en écriture est refusé.
Montez ce volume avec :ro dans le premier bac à sable, ou montez le même
dossier, pas un sous-dossier.
Le conteneur démarre aussi avec des droits réduits. Le runner lui retire toutes
les capacités Linux sauf six, celles dont les images de bases de données et
d'annuaires ont besoin pour démarrer : changer le propriétaire de leurs
fichiers (CHOWN, DAC_OVERRIDE, FOWNER), passer de root à leur propre
utilisateur (SETUID, SETGID), et exécuter le serveur LDAP des images
OpenLDAP de Bitnami et de 389ds, dont le programme est marqué pour écouter sur
un port réservé (NET_BIND_SERVICE). Il l'empêche aussi d'en regagner en cours
de route (no-new-privileges). Les sondes, elles, démarrent sans aucune capacité.
Ses ressources sont bornées : 2 Gio de mémoire, 2 processeurs et 1 024
processus par défaut. Les variables RESTOREPROOF_SANDBOX_MEMORY (4g,
512m), RESTOREPROOF_SANDBOX_CPUS (1.5) et RESTOREPROOF_SANDBOX_PIDS
(2048) les changent ; dans la configuration du runner, ce sont
sandbox.memory_limit (en octets), sandbox.cpu_limit (en milliardièmes de
processeur, 2000000000 pour deux) et sandbox.pids_limit. Sur Kubernetes,
le chart les pose à partir de sandbox.memory, sandbox.cpus et
sandbox.pids. Ces valeurs
laissent de la marge aux images testées : une restauration d'environ 480 Mo
dans PostgreSQL 16, MySQL 8.4, MariaDB 11 ou MongoDB 7 est restée sous 700 Mio
et 60 processus. Une sauvegarde qui lancerait des processus sans fin s'arrête
à la limite, sans toucher à la machine.
L'étape attend que le conteneur soit sain avant de continuer — par le
healthcheck que vous avez fourni, ou par celui que le runner dérive de
l'image. L'attente totale vaut interval × retries du contrôle utilisé ; 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.
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.ldifreste littéral ; - aucune substitution de variable —
$HOMEreste$HOME; - aucune redirection ni tube —
>,|,&&sont des arguments comme les autres.
Deux écritures sont acceptées :
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 :
La sortie de la commande reste sur la machine du runner : elle est écrite dans ses journaux, tronquée à 32 Kio. Si la commande échoue, la page de l'exécution montre son code de sortie et une seule ligne de sa sortie, comme décrit dans ce qui part vers RestoreProof.
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. |
password |
Le mot de passe, ou une référence secret://. |
database |
La base cible. C'est ici, et nulle part ailleurs, que le nom de la base s'écrit : le runner le transmet aux sondes du plan de la même famille. |
format |
Vide : détecté à partir du fichier. |
options |
Options supplémentaires passées à l'outil. |
stop_on_error |
Voir ci-dessous. |
L'hôte, le port et l'utilisateur de connexion ne s'écrivent plus : voir ce que le runner décide seul.
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¶
Base par défaut restoreproof quand database est vide.
Formats détectés : plain, custom, directory, tar. Un dump plain est
rejoué par psql ; les trois autres passent par pg_restore, à qui le runner
donne toujours --no-owner --no-privileges — un bac à sable neuf n'a ni les
propriétaires ni les droits de la production.
Les rôles cités par un dump SQL en clair sont créés avant de le rejouer. Un
pg_dump sans option porte des OWNER TO gitea, des GRANT … TO app_ro, des
SET SESSION AUTHORIZATION : autant de rôles qui existent en production et pas
dans un bac à sable neuf. Le runner lit le dump, relève ces noms et crée les
rôles manquants dans le bac à sable, sans mot de passe et sans droit de
connexion, puis le dit dans le journal de l'exécution. Le dump se rejoue donc
tel quel, avec l'arrêt à la première erreur conservé.
Trois précisions : l'utilisateur de connexion (postgres) n'est jamais
recréé, les rôles internes de PostgreSQL (public, postgres, pg_*) sont
ignorés, et
seuls les dumps en SQL en clair sont concernés — un dump custom ou
directory passe par pg_restore, à qui il faut toujours dire quoi faire des
propriétaires.
restore_mysql¶
Formats détectés : sql et mysqlpump, rejoués par le client mysql (ou
mariadb pour une image qui n'a que ce nom, comme MariaDB 11) ;
shell, le répertoire de mysqlsh util.dumpSchemas, rejoué par mysqlsh. Un
dump --all-databases est reconnu et la base cible est alors laissée vide,
puisque le dump porte ses propres CREATE DATABASE. C'est le seul cas où une
sonde MySQL doit nommer sa base elle-même, dans son option avancée
Base à interroger : le pas de restauration n'en désigne aucune.
Une sauvegarde XtraBackup est reconnue elle aussi, mais elle n'est pas
restaurée : l'étape échoue en le disant, parce qu'un XtraBackup se remonte
avec --prepare puis --copy-back sur les fichiers du serveur, ce qu'aucune
étape ne fait aujourd'hui.
restore_mongo¶
Formats détectés : archive (un fichier, avec ou sans --gzip) et
directory (un répertoire de .bson).
database est obligatoire : mongorestore recrée la base sous le nom
qu'elle porte dans le dump, et il n'y a pas de nom par défaut qui aurait un
sens. Ce nom est aussi celui que les sondes MongoDB du plan interrogent.
L'archive est inspectée avant d'être rejouée. mongorestore sort en 0
quand rien ne correspond à la base demandée : une archive qui ne contient pas
votre base serait rapportée comme restaurée, et seule la sonde verrait le vide.
Le runner fait donc d'abord passer la restauration à blanc. Si aucune
collection ne correspond, l'étape échoue en listant ce que l'archive contient
vraiment, sous la forme base.collection — de quoi voir immédiatement qu'on
a écrit shop là où la sauvegarde dit boutique. Une archive vide est
refusée de la même façon, et une restauration qui se termine sans avoir écrit
un seul document échoue aussi.
restore_ldap¶
L'hôte, le port et le DN d'authentification (le rootdn, typiquement
cn=admin,dc=example,dc=com — LDAP n'a pas d'équivalent à user) viennent de
la famille d'image, comme décrit plus haut :
plus besoin de corriger le port pour une image Bitnami, ni d'écrire le DN
administrateur.
Le LDIF est rejoué en ligne avec ldapadd, pas avec slapadd.
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, memberOf… ldapadd 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.