sauvegarde les bases d'OpenStack et teste leur restauration chaque semaine
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7b0a8583eb
commit
b054888f5a
6 changed files with 136 additions and 5 deletions
|
|
@ -19,6 +19,19 @@ sauvegardes_portail_fichiers:
|
|||
- /var/www/portail/.env
|
||||
- /var/www/portail/storage/app
|
||||
sauvegardes_image_mariadb: "mariadb:11"
|
||||
# OpenStack (bloc A) : bases de Kolla exportées par mariadb-dump. Le test de
|
||||
# restauration utilise la même image que le serveur MariaDB de Kolla.
|
||||
sauvegardes_openstack_conteneur: mariadb
|
||||
sauvegardes_openstack_hote: 10.10.0.1
|
||||
sauvegardes_openstack_mots_de_passe: /etc/kolla/passwords.yml
|
||||
sauvegardes_image_mariadb_openstack: "mariadb:11.4"
|
||||
# Tables dont le nombre de lignes doit être identique entre la copie et la
|
||||
# production (elles ne bougent pas au fil des sessions d'élèves).
|
||||
sauvegardes_openstack_tables_stables:
|
||||
- keystone.project
|
||||
- keystone.user
|
||||
- glance.images
|
||||
- nova_api.flavors
|
||||
sauvegardes_php_version: "8.3"
|
||||
# Test de restauration hebdomadaire (CLAUDE.md : restauration testée chaque semaine).
|
||||
sauvegardes_test_restauration_quand: "Mon *-*-* 04:30:00"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,25 @@ if [ "$(docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ sauvegard
|
|||
> "$WORKDIR/portail.sql"
|
||||
EN_PLUS+=(-C "$WORKDIR" portail.sql)
|
||||
fi
|
||||
|
||||
# OpenStack (bloc A, 2026-10-12) : export logique des bases de Kolla (Keystone,
|
||||
# Nova, Neutron, Glance, Heat, Placement, Grafana), sans redémarrage ni
|
||||
# verrou (--single-transaction). Les comptes MariaDB (base mysql) ne sont pas
|
||||
# exportés : Kolla les recrée au déploiement. Le mot de passe est lu dans
|
||||
# passwords.yml et passé par l'entrée standard, jamais en argument.
|
||||
if [ "$(docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ sauvegardes_openstack_conteneur }} 2>/dev/null)" = "true" ]; then
|
||||
kolla_sql() {
|
||||
printf '[client]\nuser=root\npassword="%s"\nhost=%s\n' \
|
||||
"$(awk '/^database_password:/{print $2}' {{ sauvegardes_openstack_mots_de_passe }})" "{{ sauvegardes_openstack_hote }}" \
|
||||
| docker exec -i {{ sauvegardes_openstack_conteneur }} "$@"
|
||||
}
|
||||
BASES=$(kolla_sql mariadb --defaults-extra-file=/dev/stdin -N -e \
|
||||
"SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('mysql','information_schema','performance_schema','sys')")
|
||||
# shellcheck disable=SC2086 # une base par mot
|
||||
kolla_sql mariadb-dump --defaults-extra-file=/dev/stdin --single-transaction \
|
||||
--routines --triggers --events --databases $BASES > "$WORKDIR/openstack.sql"
|
||||
EN_PLUS+=(-C "$WORKDIR" openstack.sql)
|
||||
fi
|
||||
for chemin in {{ sauvegardes_portail_fichiers | join(' ') }}; do
|
||||
if [ -e "$chemin" ]; then
|
||||
EN_PLUS+=("$chemin")
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ source /root/.dedibackup
|
|||
CLE="{{ sauvegardes_age_cle }}"
|
||||
APP="/var/www/portail"
|
||||
CONTENEUR="syllabus-restauration"
|
||||
CONTENEUR_OS="syllabus-restauration-openstack"
|
||||
DOSSIER=$(mktemp -d /var/tmp/restauration-XXXXXX)
|
||||
nettoyer() {
|
||||
docker rm -f "$CONTENEUR" >/dev/null 2>&1 || true
|
||||
docker rm -f "$CONTENEUR" "$CONTENEUR_OS" >/dev/null 2>&1 || true
|
||||
rm -rf "$DOSSIER"
|
||||
}
|
||||
trap nettoyer EXIT
|
||||
|
|
@ -34,8 +35,9 @@ echo "$DERNIERE ($(du -h "$DOSSIER/archive.age" | cut -f1))"
|
|||
echo "== 2. Déchiffrement et extraction"
|
||||
mkdir "$DOSSIER/extrait"
|
||||
age -d -i "$CLE" "$DOSSIER/archive.age" \
|
||||
| tar -I zstd -xf - -C "$DOSSIER/extrait" portail.sql var/www/portail/.env
|
||||
| tar -I zstd -xf - -C "$DOSSIER/extrait" portail.sql openstack.sql var/www/portail/.env
|
||||
[ -s "$DOSSIER/extrait/portail.sql" ] || { echo "portail.sql absent de l'archive"; exit 1; }
|
||||
[ -s "$DOSSIER/extrait/openstack.sql" ] || { echo "openstack.sql absent de l'archive"; exit 1; }
|
||||
APP_KEY_RESTAUREE=$(grep -oP '^APP_KEY=\K.*' "$DOSSIER/extrait/var/www/portail/.env")
|
||||
if [ "$APP_KEY_RESTAUREE" = "$(grep -oP '^APP_KEY=\K.*' "$APP/.env")" ]; then
|
||||
echo "APP_KEY restaurée identique à la production (valeur non affichée)"
|
||||
|
|
@ -90,6 +92,40 @@ TABLES_MANQUANTES=$(join -v2 "$DOSSIER/copie.txt" "$DOSSIER/production.txt" | wc
|
|||
[ "$TABLES_MANQUANTES" = "0" ] || { echo "$TABLES_MANQUANTES table(s) de production absente(s) de la copie"; exit 1; }
|
||||
echo "lab_sessions vues par le portail dans la copie : $(artisan_copie tinker --execute='echo App\Models\LabSession::count();' 2>/dev/null | tail -1)"
|
||||
|
||||
echo "== 6. Base OpenStack : restauration dans un conteneur jetable"
|
||||
docker run -d --name "$CONTENEUR_OS" --network portail_default \
|
||||
-e MARIADB_ROOT_PASSWORD="$MDP" {{ sauvegardes_image_mariadb_openstack }} >/dev/null
|
||||
for _ in $(seq 1 60); do
|
||||
docker exec "$CONTENEUR_OS" sh -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" mariadb -uroot -e "SELECT 1"' >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
docker exec -i "$CONTENEUR_OS" sh -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" exec mariadb -uroot' < "$DOSSIER/extrait/openstack.sql"
|
||||
# Même requête des deux côtés : la copie (root du conteneur jetable) et la
|
||||
# production (root de Kolla, mot de passe passé par l'entrée standard).
|
||||
requete_copie() {
|
||||
docker exec "$CONTENEUR_OS" sh -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" exec mariadb -uroot -N -e "$0"' "$1"
|
||||
}
|
||||
requete_production() {
|
||||
printf '[client]\nuser=root\npassword="%s"\nhost=%s\n' \
|
||||
"$(awk '/^database_password:/{print $2}' {{ sauvegardes_openstack_mots_de_passe }})" "{{ sauvegardes_openstack_hote }}" \
|
||||
| docker exec -i {{ sauvegardes_openstack_conteneur }} mariadb --defaults-extra-file=/dev/stdin -N -e "$1"
|
||||
}
|
||||
PAR_BASE="SELECT table_schema, COUNT(*) FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') GROUP BY table_schema ORDER BY 1"
|
||||
requete_copie "$PAR_BASE" > "$DOSSIER/os-copie.txt"
|
||||
requete_production "$PAR_BASE" > "$DOSSIER/os-production.txt"
|
||||
printf "%-14s %8s %8s\n" base tables production
|
||||
join -a1 -a2 -e absente -o 0,1.2,2.2 "$DOSSIER/os-copie.txt" "$DOSSIER/os-production.txt" | while read -r b c p; do
|
||||
printf "%-14s %8s %8s\n" "$b" "$c" "$p"
|
||||
done
|
||||
cmp -s "$DOSSIER/os-copie.txt" "$DOSSIER/os-production.txt" || { echo "bases ou tables OpenStack différentes"; exit 1; }
|
||||
for t in {{ sauvegardes_openstack_tables_stables | join(' ') }}; do
|
||||
c=$(requete_copie "SELECT COUNT(*) FROM $t")
|
||||
p=$(requete_production "SELECT COUNT(*) FROM $t")
|
||||
echo "$t : copie $c, production $p"
|
||||
[ "$c" = "$p" ] || { echo "écart sur $t"; exit 1; }
|
||||
done
|
||||
echo "projet paillasse dans la copie : $(requete_copie "SELECT COUNT(*) FROM keystone.project WHERE name='paillasse'")"
|
||||
|
||||
DOSSIER_TEXTFILE="{{ sauvegardes_dossier_textfile | default('/var/lib/node_exporter/textfile') }}"
|
||||
cat > "$DOSSIER_TEXTFILE/restauration.prom.tmp" <<METRIQUE
|
||||
# HELP syllabus_derniere_restauration_reussie_timestamp Horodatage Unix du dernier test de restauration du portail réussi.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ retirer la ligne (et le noter dans le journal) quand c'est fait.
|
|||
| Sujet | Pourquoi c'est une dette | Quand | Origine |
|
||||
|---|---|---|---|
|
||||
| **Image Uptime Kuma non figée** (`louislam/uptime-kuma:1`) | Une version différente peut arriver à n'importe quel redéploiement, sans test. Figer un tag précis (voire le digest) dans une variable du rôle `supervision`, et documenter la mise à jour par Ansible (changer la variable, relancer le rôle, vérifier) — jamais par le bouton de l'interface. Kuma lui-même qualifie ce tag d'« historique » (il injecte `docker-latest-warning.js` dans les images `latest`/`1`, constaté le 2026-10-09) : prévoir la migration vers un tag versionné, en testant la page de statut et les sondes. | Avant le pilote | Demande d'Adam, 2026-10-09 |
|
||||
| **Base de données d'OpenStack non sauvegardée** (MariaDB de Kolla : Keystone, Nova, Neutron, Heat, Placement) | Seule la base du portail l'est. Une perte de ce serveur oblige à recréer projet, compte de service, gabarits, et perd les stacks en cours. Kolla fournit `kolla-ansible mariadb_backup` : l'intégrer à la sauvegarde quotidienne et au test de restauration. | Avant le pilote | Étape 13 |
|
||||
| Projet `paillasse`, compte `paillasse-service` et son rôle créés à la main (étape 6) | Pas rejouables par Ansible (gabarit et quota le sont depuis l'étape 13, rôle `openstack-projet`). | Bloc B | Étape 6 |
|
||||
| Clé age des sauvegardes : copie hors du serveur non confirmée | Sans elle, aucune sauvegarde n'est déchiffrable si le serveur est perdu (voir `docs/runbooks/sauvegardes.md`). Adam doit confirmer l'avoir mise en lieu sûr. | Dès que possible | Étape 3 |
|
||||
| Larastan (PHPStan niveau 6) absent | Exigé par le CLAUDE.md depuis l'étape 8, jamais installé. | Bloc B | Étape 8 |
|
||||
|
|
|
|||
|
|
@ -686,3 +686,33 @@ Dans une visio, l'image et le son ne sont que la partie visible : la sécurité
|
|||
**Comprendre**
|
||||
|
||||
Un accès « sudo sans mot de passe » est pratique pour automatiser, mais il transforme toute clé SSH du compte en clé root. Ici, c'est précisément la clé de Claude Code. Le garder pendant le bloc A est un compromis assumé, pas un oubli : il est écrit dans la dette avec sa date de fin et la commande de retrait.
|
||||
|
||||
## 2026-10-12 — Bloc A : sauvegarde des bases d'OpenStack
|
||||
|
||||
Adam lance la suite du bloc A **sans montée de version d'OpenStack** : la 2025.2 est stable et reste en place.
|
||||
|
||||
**Fait**
|
||||
|
||||
- La sauvegarde quotidienne exporte maintenant toutes les bases de Kolla (Keystone, Nova, Neutron, Glance, Heat, Placement, Grafana) avec `mariadb-dump --single-transaction` : ≈ 30 Mo, sans verrou ni redémarrage. Le mot de passe root de Kolla est lu dans `passwords.yml` et passé à `mariadb` par l'entrée standard (`--defaults-extra-file=/dev/stdin`) : jamais en argument, donc invisible dans `ps`.
|
||||
- Le test de restauration hebdomadaire restaure aussi `openstack.sql` dans un conteneur jetable `mariadb:11.4` (même version que Kolla) et compare : bases, nombre de tables, lignes des tables stables.
|
||||
- Procédure de sinistre OpenStack écrite dans `docs/runbooks/sauvegardes.md` (non jouée : il faudrait un second serveur).
|
||||
- Ligne retirée de `docs/dette.md`.
|
||||
|
||||
**Pourquoi pas `mariabackup`, l'outil de Kolla** : il faut activer `enable_mariabackup` puis relancer `reconfigure` sur MariaDB, qui porte tout OpenStack, pour obtenir une copie physique de 2,5 Go. Un export logique de 30 Mo entre dans l'archive chiffrée existante et se restaure dans n'importe quel MariaDB 11.4. Le prix : la restauration est plus lente qu'une copie physique, ce qui ne compte pas à cette taille.
|
||||
|
||||
**Critère vérifié**
|
||||
|
||||
```
|
||||
glance 16/16 grafana 113/113 heat 16/16 keystone 49/49 neutron 135/135
|
||||
nova 111/111 nova_api 32/32 nova_cell0 111/111 placement 13/13
|
||||
keystone.project : copie 7, production 7
|
||||
keystone.user : copie 8, production 8
|
||||
glance.images : copie 1, production 1
|
||||
nova_api.flavors : copie 1, production 1
|
||||
projet paillasse dans la copie : 1
|
||||
== RESTAURATION RÉUSSIE (quotidienne-20261012.tar.zst.age)
|
||||
```
|
||||
|
||||
**Comprendre**
|
||||
|
||||
Avant, perdre le serveur voulait dire recréer à la main projet, compte de service, gabarit, quotas et image. Les fichiers de configuration (`/etc/kolla`, mots de passe compris) étaient déjà dans l'archive ; il manquait l'état, qui vit dans la base. Les fichiers des images Glance restent hors sauvegarde : ils se reconstruisent par script, et les VM d'élèves sont jetables par nature.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ déclenche `sauvegarde-dedibackup.service`, qui exécute
|
|||
|
||||
Le script :
|
||||
|
||||
1. Archive `/etc`, `/home`, `/root` en `tar.zst` (streaming, sans fichier intermédiaire en clair), plus, depuis l'étape 13, le **portail** : export `mariadb-dump --single-transaction` de sa base (`portail.sql`, à la racine de l'archive), `/var/www/portail/.env` (APP_KEY) et `/var/www/portail/storage/app`. Avant le 2026-10-09, la base du portail n'était **pas** sauvegardée.
|
||||
1. Archive `/etc`, `/home`, `/root` en `tar.zst` (streaming, sans fichier intermédiaire en clair), plus, depuis l'étape 13, le **portail** : export `mariadb-dump --single-transaction` de sa base (`portail.sql`, à la racine de l'archive), `/var/www/portail/.env` (APP_KEY) et `/var/www/portail/storage/app`. Avant le 2026-10-09, la base du portail n'était **pas** sauvegardée. Depuis le 2026-10-12, les **bases d'OpenStack** aussi : export `mariadb-dump --single-transaction` de toutes les bases de Kolla sauf `mysql` (`openstack.sql`, ≈ 30 Mo avant compression ; mot de passe lu dans `/etc/kolla/passwords.yml`, passé par l'entrée standard).
|
||||
2. Chiffre le flux avec **age**, avec la clé publique du serveur (`/root/.age/cle-sauvegardes.txt`).
|
||||
3. Envoie l'archive chiffrée en FTP vers Dedibackup (`dedibackup-dc3.online.net`, joignable uniquement depuis ce serveur).
|
||||
4. Le dimanche (jour 7), duplique aussi l'archive du jour comme hebdomadaire.
|
||||
|
|
@ -78,7 +78,7 @@ lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" \
|
|||
'
|
||||
```
|
||||
|
||||
## Test de restauration du portail (automatique, chaque lundi 04:30)
|
||||
## Test de restauration du portail et d'OpenStack (automatique, chaque lundi 04:30)
|
||||
|
||||
`/usr/local/sbin/syllabus-test-restauration` (rôle `sauvegardes`, minuteur
|
||||
`syllabus-test-restauration.timer`) télécharge la dernière quotidienne, la
|
||||
|
|
@ -97,6 +97,12 @@ ssh paillasse "journalctl -u syllabus-test-restauration --no-pager | tail -40"
|
|||
Des écarts de comptage sont normaux si la production a changé depuis la
|
||||
sauvegarde ; une table absente de la copie fait échouer le test.
|
||||
|
||||
Étape 6 du test (depuis le 2026-10-12) : `openstack.sql` est restauré dans un
|
||||
second conteneur jetable (`mariadb:11.4`, même version que Kolla). Le test
|
||||
échoue si une base ou un nombre de tables diffère de la production, ou si les
|
||||
tables stables (`keystone.project`, `keystone.user`, `glance.images`,
|
||||
`nova_api.flavors`) n'ont pas le même nombre de lignes.
|
||||
|
||||
## Restaurer réellement le portail (sinistre)
|
||||
|
||||
1. Extraire l'archive dans un dossier temporaire (voir plus bas), puis `portail.sql` et `var/www/portail/.env`.
|
||||
|
|
@ -104,6 +110,34 @@ sauvegarde ; une table absente de la copie fait échouer le test.
|
|||
3. Importer la base : `docker exec -i portail_mariadb sh -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" exec mariadb -uroot' < portail.sql` (écrase la base `portail`).
|
||||
4. `php artisan migrate --force`, puis vérifier une connexion prof.
|
||||
|
||||
## Restaurer réellement OpenStack (sinistre)
|
||||
|
||||
Procédure écrite, **jamais jouée en vrai** (il faudrait un second serveur) ;
|
||||
seule la restauration des bases dans un conteneur est testée chaque semaine.
|
||||
|
||||
1. Serveur reconstruit : playbooks `durcissement`, `reseau-openstack`, puis
|
||||
remettre `/etc/kolla/` depuis l'archive (**`passwords.yml` identique**,
|
||||
sinon les mots de passe en base ne correspondent plus) avant `kolla.yml`.
|
||||
2. `kolla-ansible deploy` crée des bases vides et les comptes MariaDB.
|
||||
3. Arrêter les services qui écrivent (`docker stop` des conteneurs `nova_*`,
|
||||
`neutron_*`, `heat_*`, `glance_api`, `keystone*`, `placement_api`), puis
|
||||
importer (le mot de passe ne passe ni en argument ni à l'écran) :
|
||||
```
|
||||
sudo docker cp openstack.sql mariadb:/tmp/openstack.sql
|
||||
printf '[client]\nuser=root\npassword="%s"\nhost=10.10.0.1\n' \
|
||||
"$(sudo awk '/^database_password:/{print $2}' /etc/kolla/passwords.yml)" \
|
||||
| sudo docker exec -i mariadb sh -c 'umask 077; cat > /tmp/r.cnf;
|
||||
mariadb --defaults-extra-file=/tmp/r.cnf < /tmp/openstack.sql;
|
||||
rm -f /tmp/r.cnf /tmp/openstack.sql'
|
||||
```
|
||||
Redémarrer les conteneurs arrêtés.
|
||||
4. Les **fichiers** des images Glance ne sont pas sauvegardés (seulement leur
|
||||
fiche) : reconstruire `paillasse-debian` (`docs/runbooks/images.md`) et
|
||||
supprimer l'ancienne fiche.
|
||||
5. Les VM d'élèves n'existent plus sur le nouvel hôte : supprimer toutes les
|
||||
stacks restantes (`openstack stack list`, puis `stack delete`) ; le stock
|
||||
chaud se reconstitue seul.
|
||||
|
||||
## Restaurer un fichier (procédure testée le 2026-10-07)
|
||||
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue