Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
11 KiB
04 — Nœud de données (PostgreSQL + Redis)
Durée : environ 2 h. C'est l'étape la plus délicate : c'est la seule dont les erreurs ne se rattrapent pas en redéployant.
Prérequis — WAL-G archive les journaux de transaction dès le premier démarrage de PostgreSQL. Créez d'abord le bucket de sauvegarde et ses clés : 07 — Stockage objet, sections 1 et 2. Revenez ensuite ici. Sans le bucket, PostgreSQL démarre quand même mais accumule ses WAL sur disque jusqu'à saturation.
1. Choix d'architecture, et pourquoi
PostgreSQL est hors de Kubernetes. Ce n'est pas un raccourci, c'est un choix :
- Un
StatefulSetPostgreSQL impose des volumes persistants, un ordre de démarrage, et transforme chaque montée de version majeure en opération à risque. - Hors cluster,
pg_dump, WAL-G, la restauration à un instant T et les tests de restauration sont des commandes ordinaires. - Le nœud applicatif redevient entièrement jetable : on peut le détruire et le reconstruire sans jamais approcher les données.
Le trafic vers la base est chiffré. Le réseau privé Hetzner isole les
projets mais ne chiffre pas les paquets. pg_hba.conf n'accepte que des lignes
hostssl : une connexion en clair est refusée, y compris depuis app-01.
2. Installation
scp -i ~/.ssh/xpeditis_prod infra/prod/scripts/01-setup-data-node.sh \
deploy@<db_public_ipv4>:/tmp/
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4> \
'sudo bash /tmp/01-setup-data-node.sh'
Le script :
- formate (si vierge) et monte le volume Hetzner sur
/var/lib/xpeditis/pgdata, avecnofailpour que le serveur démarre même si le volume manque ; - installe Docker Engine depuis le dépôt officiel et le durcit
(
no-new-privileges,icc: false, rotation des journaux) ; - génère le certificat TLS interne de PostgreSQL ;
- installe
age; - installe les timers systemd de sauvegarde.
Il ne démarre pas la base : les secrets ne sont pas encore là.
Vérifier le volume
ssh deploy@<db_public_ipv4> 'df -h /var/lib/xpeditis/pgdata; lsblk'
Le point de montage doit apparaître avec la taille du volume (50 Go), pas celle
du disque système. Si le volume n'a pas été détecté, /var/lib/xpeditis/pgdata
est resté sur le disque système : corrigez avant d'écrire la moindre donnée.
3. Copier la configuration
# Depuis le dépôt, sur votre poste
rsync -az -e "ssh -i ~/.ssh/xpeditis_prod" \
infra/prod/data-node/ \
deploy@<db_public_ipv4>:/tmp/data-node/
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4> '
sudo mkdir -p /opt/xpeditis/data-node
sudo rsync -a /tmp/data-node/ /opt/xpeditis/data-node/
sudo chown -R root:root /opt/xpeditis/data-node
sudo chmod +x /opt/xpeditis/data-node/backup/*.sh
rm -rf /tmp/data-node
'
Relancez ensuite le script d'installation. Il est idempotent : cette seconde
passe ne refait rien de ce qui est déjà en place, mais elle trouve désormais les
fichiers backup/ et installe les timers systemd.
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4> \
'sudo bash /tmp/01-setup-data-node.sh'
4. Secrets
4.1 Générer les mots de passe
Sur votre poste, jamais sur le serveur (l'historique du shell garde tout) :
echo "POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '\n/+=' | head -c 40)"
echo "REDIS_PASSWORD=$(openssl rand -base64 32 | tr -d '\n/+=' | head -c 40)"
# WAL-G attend 32 octets en HEXADÉCIMAL (64 caractères), pas en base64.
echo "WALG_LIBSODIUM_KEY=$(openssl rand -hex 32)"
Générez aussi la paire age dédiée au chiffrement des dumps :
age-keygen -o /tmp/backup-age.key
grep 'public key' /tmp/backup-age.key # → BACKUP_AGE_RECIPIENT
WALG_LIBSODIUM_KEYet la clé privéebackup-age.keysont les deux clés sans lesquelles aucune restauration n'est possible. Sauvegardez-les dans votre gestionnaire de mots de passe et hors ligne, avant d'aller plus loin. Une sauvegarde chiffrée dont on a perdu la clé est un fichier inutile.
4.2 Composer le fichier d'environnement
cp infra/prod/env/data-node.env.example /tmp/.env.data
$EDITOR /tmp/.env.data
Renseignez : POSTGRES_PASSWORD, REDIS_PASSWORD, les identifiants WAL-G
(bucket créé en 07), WALG_LIBSODIUM_KEY,
BACKUP_AGE_RECIPIENT, les coordonnées de la Storage Box, les webhooks.
4.3 Chiffrer pour Git, déposer en clair sur le serveur
# Version chiffrée, versionnée
cd infra/prod
sops -e --input-type dotenv --output-type dotenv /tmp/.env.data \
> data-node/data-node.sops.env
# Version en clair, uniquement sur db-01
scp -i ~/.ssh/xpeditis_prod /tmp/.env.data deploy@<db_public_ipv4>:/tmp/
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4> '
sudo install -m 600 -o root -g root /tmp/.env.data /opt/xpeditis/data-node/.env.data
shred -u /tmp/.env.data
'
# Effacer la copie locale
shred -u /tmp/.env.data
4.4 Clés privées sur db-01
# Clé age de déchiffrement des dumps
scp -i ~/.ssh/xpeditis_prod /tmp/backup-age.key deploy@<db_public_ipv4>:/tmp/
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4> '
sudo mkdir -p /root/.config/xpeditis && sudo chmod 700 /root/.config/xpeditis
sudo install -m 600 -o root -g root /tmp/backup-age.key /root/.config/xpeditis/backup-age.key
shred -u /tmp/backup-age.key
'
shred -u /tmp/backup-age.key
# Clé SSH vers la Storage Box
scp -i ~/.ssh/xpeditis_prod ~/.ssh/xpeditis_storagebox deploy@<db_public_ipv4>:/tmp/sb.key
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4> '
sudo install -m 600 -o root -g root /tmp/sb.key /root/.ssh/storagebox
shred -u /tmp/sb.key
# Enregistre l empreinte de la Storage Box : StrictHostKeyChecking=yes
# échouerait sinon, et le rsync de sauvegarde avec lui.
sudo ssh-keyscan -p 23 uXXXXXX.your-storagebox.de | sudo tee -a /root/.ssh/known_hosts
'
5. Démarrage
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4>
cd /opt/xpeditis/data-node
sudo docker compose -f docker-compose.data.yml --env-file .env.data up -d --build
La construction de l'image PostgreSQL + WAL-G prend deux à trois minutes.
sudo docker compose -f docker-compose.data.yml --env-file .env.data ps
sudo docker compose -f docker-compose.data.yml --env-file .env.data logs postgres | tail -40
Attendu dans les journaux :
database system is ready to accept connections
Si PostgreSQL refuse de démarrer, la cause est presque toujours l'une de ces trois :
| Message | Cause | Correction |
|---|---|---|
could not load server certificate file |
droits du certificat | chown 999:999 et chmod 600 sur /var/lib/xpeditis/certs/server.key |
unrecognized configuration parameter |
faute de frappe dans postgresql.conf |
corriger, puis docker compose restart postgres |
data directory has wrong ownership |
volume monté après le premier démarrage | chown -R 999:999 /var/lib/xpeditis/pgdata |
6. Vérifications
6.1 TLS obligatoire
# Depuis db-01 : connexion locale (socket) — doit fonctionner
sudo docker compose exec -u postgres postgres psql -c 'SELECT version();'
# Le chiffrement est-il actif ?
sudo docker compose exec -u postgres postgres \
psql -c "SELECT name, setting FROM pg_settings WHERE name IN ('ssl','password_encryption');"
Attendu : ssl = on, password_encryption = scram-sha-256.
6.2 Depuis app-01, la connexion doit passer en TLS et uniquement en TLS
ssh deploy@<app_public_ipv4>
sudo apt-get install -y postgresql-client
# Doit RÉUSSIR
PGPASSWORD='<POSTGRES_PASSWORD>' psql "host=10.10.1.20 port=5432 dbname=xpeditis_prod user=xpeditis sslmode=require" -c '\conninfo'
# Doit ÉCHOUER — c'est le résultat attendu
PGPASSWORD='<POSTGRES_PASSWORD>' psql "host=10.10.1.20 port=5432 dbname=xpeditis_prod user=xpeditis sslmode=disable" -c 'SELECT 1;'
Le second doit renvoyer :
FATAL: no pg_hba.conf entry for host "10.10.1.10", ... SSL off
Si la connexion sans TLS réussit, pg_hba.conf n'a pas été pris en compte :
vérifiez que le conteneur est bien lancé avec -c hba_file=/etc/postgresql/pg_hba.conf.
6.3 Redis
# Depuis app-01
redis-cli -h 10.10.1.20 -a '<REDIS_PASSWORD>' --no-auth-warning ping # PONG
redis-cli -h 10.10.1.20 --no-auth-warning ping # NOAUTH
6.4 Rien n'est exposé publiquement
Depuis un autre réseau :
nmap -Pn -p 5432,6379,9187 <db_public_ipv4>
Les trois doivent être filtered.
7. Première sauvegarde
Elle doit être prise avant que la moindre donnée réelle n'existe : cela valide la chaîne complète pendant que l'enjeu est nul.
ssh deploy@<db_public_ipv4> 'sudo systemctl start xpeditis-backup.service'
ssh deploy@<db_public_ipv4> 'sudo journalctl -u xpeditis-backup -n 60 --no-pager'
Attendu :
WAL-G basebackup : termine
pg_dump : NNNNN octets
Envoi vers la Storage Box ...
Sauvegarde terminee.
Vérifiez les deux destinations :
# WAL-G sur Object Storage
ssh deploy@<db_public_ipv4> \
'cd /opt/xpeditis/data-node && sudo docker compose exec -T -u postgres postgres wal-g backup-list --detail'
# Dumps sur la Storage Box
ssh -p 23 -i ~/.ssh/xpeditis_storagebox uXXXXXX@uXXXXXX.your-storagebox.de \
'ls -lh xpeditis-prod/dumps/'
Timers actifs
ssh deploy@<db_public_ipv4> "systemctl list-timers 'xpeditis-*' --no-pager"
Attendu : xpeditis-backup.timer (quotidien 02h30) et
xpeditis-backup-verify.timer (dimanche 04h00).
8. Réglages à connaître
| Paramètre | Valeur | Conséquence |
|---|---|---|
shared_buffers |
2 Go | Sur 8 Go de RAM, dont 1,5 Go plafonnés pour Redis. |
max_connections |
150 | 2 replicas backend + marge d'exploitation. Une alerte se déclenche à 80 %. |
statement_timeout |
5 min | Borne les requêtes folles tout en laissant passer les imports de grilles CSV. |
archive_timeout |
5 min | Plafonne la perte de données à 5 minutes en cas de perte totale du serveur. |
log_min_duration_statement |
500 ms | Toute requête lente est tracée dans Loki. |
log_statement |
ddl |
Chaque changement de schéma (migration) laisse une trace. |
Redis maxmemory-policy |
volatile-lru |
Seules les clés à TTL sont évincées. Voir l'avertissement ci-dessous. |
Redis et l'éviction.
volatile-lrun'évince que les clés porteuses d'un TTL — les cotations tarifaires, qui sont sacrifiables. Mais les entrées de révocation de jetons ont elles aussi un TTL : sous saturation mémoire, l'une d'elles pourrait être évincée, redonnant validité à un jeton révoqué.maxmemoryest fixé à 1 Go pour 0,5 Go estimé, et une alerte se déclenche à 75 % d'occupation. Si elle se déclenche, augmentezmaxmemory— ne changez pas la politique d'éviction.
9. Contrôle
[ ] Volume Hetzner monté sur /var/lib/xpeditis/pgdata (bonne taille)
[ ] PostgreSQL démarré, ssl = on, password_encryption = scram-sha-256
[ ] Connexion TLS depuis app-01 : OK
[ ] Connexion NON TLS depuis app-01 : REFUSÉE
[ ] Redis répond avec mot de passe, refuse sans
[ ] 5432 / 6379 / 9187 filtered depuis Internet
[ ] Première sauvegarde réussie, visible sur Object Storage ET Storage Box
[ ] Timers xpeditis-backup et xpeditis-backup-verify actifs
[ ] WALG_LIBSODIUM_KEY et backup-age.key sauvegardées hors ligne
[ ] .env.data en clair effacé du poste et de /tmp du serveur
→ Suite : 05 — Cluster k3s