Une base managée Scaleway est joignable depuis Internet dès sa création, et la restaurer l'y remet. Ce sont les deux faits que cette leçon mesure, puis corrige. Vous allez créer une base PostgreSQL dont l'endpoint public n'existe pas, prendre un snapshot, provoquer la perte, restaurer, et constater que la base revenue porte une adresse publique et une ACL ouverte à tous. Vous en sortirez avec les deux seuls chiffres qu'un exploitant doit connaître, le RPO et le RTO, mesurés plutôt que promis.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Choisir une version de PostgreSQL sur le bon critère, sa fin de vie
- Créer une base sans aucun endpoint public, et le prouver
- Lire le réglage qui fixe votre RPO, actif par défaut et jamais annoncé
- Mesurer un RTO de restauration, et savoir pourquoi ce n'est pas celui du service
- Refermer le réseau qu'une restauration vient de rouvrir
- Purger les artefacts qui survivent à la suppression de la base
Prérequis
Section intitulée « Prérequis »scw2.62.0 ou plus récent, profil dédié au lab- Un VPC compris, au moins dans ses grandes lignes (voir VPC et subnets)
jqetopensslsur le poste- Un projet dédié, avec alerte de budget
Quelle version de PostgreSQL choisir ?
Section intitulée « Quelle version de PostgreSQL choisir ? »Le critère est la date de fin de vie, jamais la place dans la liste. L'API propose des moteurs que vous ne devriez pas prendre, et rien dans son affichage ne les distingue.
scw rdb engine list region=fr-par -o json \ | jq -r '.[] | select(.engine_type=="PostgreSQL") | "\(.name)\tfin de vie \(.end_of_life[0:10])"'Relevé le 2026-09-12 avec scw 2.62.0, la sortie contient 9 versions de PostgreSQL, dont 5 déjà périmées. La commande en filtre une dixième, MySQL-8, que le même appel renvoie :
| Version | Fin de vie | État au 2026-09-12 |
|---|---|---|
| PostgreSQL-17 | 2029-11-08 | recommandée, 1153 jours devant elle |
| PostgreSQL-16 | 2028-11-09 | 789 jours |
| PostgreSQL-15 | 2027-11-11 | 425 jours |
| PostgreSQL-14 | 2026-11-12 | 61 jours, à ne plus démarrer |
| PostgreSQL-13 et antérieures | 2025-11-13 et avant | périmées, toujours proposées |
Se servir dans la liste sans lire end_of_life, c'est démarrer une base sur un moteur non supporté le jour même. C'est le même motif que bssd, que scw rdb node-type list continue de proposer parmi les available_volume_types alors que l'API le refuse à la création : la commande qui inventorie propose ce que la commande qui crée refuse.
Pourquoi une base créée par défaut est-elle exposée à Internet ?
Section intitulée « Pourquoi une base créée par défaut est-elle exposée à Internet ? »Parce que Scaleway lui attribue un endpoint public sans le demander. La documentation officielle l'énonce sans détour : « When you create a Database Instance, a public endpoint is automatically attributed to it ». Ce n'est pas une erreur de manipulation, c'est le comportement par défaut.
Ce que cela produit, relevé sur une base créée sans aucun argument d'endpoint :
| Élément | Valeur par défaut constatée |
|---|---|
| Endpoint | public, via load balancer |
| ACL entrante | 0.0.0.0/0, action allow, description « Allow All » |
| Port | non standard, tiré à la création |
La base répond donc au protocole PostgreSQL depuis n'importe où, et seule la robustesse du mot de passe la protège.
Le port mérite une mise en garde à part. Un premier test visant 5432 reçoit Connection refused, ce dont on conclut volontiers que l'isolation fonctionne. Le refus venait du mauvais port, pas d'une protection. Un tutoriel qui teste 5432 et annonce « c'est fermé » enseigne l'inverse de la réalité : c'est la différence entre un test qui passe et un test qui prouve.
Comment créer une base sans endpoint public ?
Section intitulée « Comment créer une base sans endpoint public ? »On commence par le réseau, parce qu'il est la protection.
VPC_ID=$(scw vpc vpc create name=donnees-vpc region=fr-par -o json | jq -r '.id')PN_ID=$(scw vpc private-network create name=donnees-pn-db \ vpc-id="${VPC_ID}" region=fr-par -o json | jq -r '.id')Vient le mot de passe, et il pose deux pièges qui coûtent chacun une session.
MDP_DB="Aa1!$(openssl rand -base64 18 | tr -d '\n=+/')"Le premier : l'API refuse un openssl rand -base64 nu environ une fois sur deux, avec le message password must be between 8 and 128 characters, contain at least one digit, one uppercase, one lowercase and one special character. Base64 n'emploie que [A-Za-z0-9+/=], et sur 24 caractères la probabilité qu'aucun + ni / n'apparaisse avoisine 47 % : le refus est fréquent, pas systématique, ce qui est pire, puisqu'un script peut passer plusieurs fois avant d'échouer. Le second est plus sournois : la parade naturelle tr -dc 'A-Z' </dev/urandom | head -c 4 tue le script sous set -e -o pipefail, parce que head ferme le tube dès qu'il a son compte, tr reçoit SIGPIPE, le pipeline rend 141 à cause de pipefail, et c'est set -e qui arrête tout. Le symptôme est trompeur, le script s'arrête sans message. Le préfixe fixe garantit les quatre classes sans refermer aucun tube.
Un troisième piège ne concerne que le copier-coller. Dans un shell interactif, !$ déclenche l'expansion d'historique et remplace ces deux caractères par le dernier argument de la commande précédente : le mot de passe obtenu n'est pas celui qu'on croit. En script, l'expansion est désactivée. Pour coller la ligne à la main, isolez le préfixe en apostrophes simples ou posez set +H au préalable.
La base, enfin.
DB_ID=$(scw rdb instance create \ name=donnees-pg \ region=fr-par \ engine=PostgreSQL-17 \ node-type=DB-DEV-S \ is-ha-cluster=false \ user-name=labuser \ password="${MDP_DB}" \ volume-type=sbs_5k \ volume-size=10GB \ init-endpoints.0.private-network.private-network-id="${PN_ID}" \ init-endpoints.0.private-network.enable-ipam=true \ init-endpoints.0.load-balancer=false \ -o json | jq -r '.id')Trois arguments portent toute la leçon, et le premier mérite d'être compris plutôt que recopié.
Ce n'est pas load-balancer=false qui retire l'endpoint public. C'est déjà la valeur par défaut du CLI. Ce qui l'évite, c'est de fournir un init-endpoints qui ne contient qu'un endpoint privé : l'API ne crée l'endpoint public « systématiquement » que lorsque init_endpoints est vide. L'écrire rend l'intention explicite, sans rien changer au résultat.
enable-ipam=true fait réserver l'adresse privée par le service d'adressage plutôt que de la choisir à la main. Et volume-type=sbs_5k choisit du Block, bssd étant refusé par l'API bien qu'encore listé.
Un dernier argument est déprécié et n'est conservé ici que parce que c'est lui qui a été exécuté : is-ha-cluster est remplacé par high-availability-mode (disabled, single_zone, multiple_zone) depuis le 12 août 2026. Sur une nouvelle base, employez le remplaçant.
La base est-elle vraiment isolée ?
Section intitulée « La base est-elle vraiment isolée ? »On compte les endpoints, on ne les suppose pas.
scw rdb instance get "${DB_ID}" region=fr-par -o json \ | jq '[.endpoints[] | {ip, port, prive: (.private_network != null), public: (.load_balancer != null)}]'La sortie doit afficher un seul endpoint, privé, et aucun public :
[{"ip": "172.16.16.2", "port": 5432, "prive": true, "public": false}]Deux choses à relever. Le port est 5432, le port standard : c'est l'endpoint public qui est tiré au hasard, pas le privé. Et la base a mis 264 secondes à passer ready, contre 184 secondes pour son équivalent public mesuré le même jour. L'isolation coûte 80 secondes, une fois, à la création.
Que vaut la sauvegarde par défaut ?
Section intitulée « Que vaut la sauvegarde par défaut ? »Elle est active sans que vous l'ayez demandée, et elle fixe votre RPO à 24 heures.
scw rdb instance get "${DB_ID}" region=fr-par -o json | jq '.backup_schedule'Relevé le 2026-09-12 :
{"frequency": 24, "retention": 7, "disabled": false, "next_run_at": "2026-09-13T16:17:31Z"}Le RPO, Recovery Point Objective, est ce que vous perdez : l'écart entre le dernier point récupérable et l'incident. Avec ce réglage, il vaut jusqu'à 24 heures. Ce n'est pas un défaut du produit, c'est une décision par défaut, et la plupart des tutoriels ne la mentionnent jamais.
Un détail de vocabulaire décide de la suite : sur un volume Block, l'autobackup produit des snapshots, pas des backups. La documentation prévient que « although they bear the same feature name, automated snapshots and automated backups are different in their design ». Conséquence pratique : la restauration ne remet pas les données dans la base existante, elle crée une base neuve.
SNAP_ID=$(scw rdb snapshot create instance-id="${DB_ID}" region=fr-par \ name=donnees-pg-avant-incident -o json | jq -r '.id')
until [ "$(scw rdb snapshot get "${SNAP_ID}" region=fr-par -o json | jq -r '.status')" = "ready" ]; do sleep 10doneCommande acceptée en 1 seconde, snapshot ready en 10 secondes sur un volume de 10 Go.
Le snapshot survit-il à la base qu'il sauvegarde ?
Section intitulée « Le snapshot survit-il à la base qu'il sauvegarde ? »On provoque la perte, plutôt que de l'imaginer.
scw rdb instance delete "${DB_ID}" region=fr-par
scw rdb snapshot get "${SNAP_ID}" region=fr-par -o json \ | jq '{name, status, instance_id, instance_name}'La sortie doit afficher un snapshot ready qui référence encore l'identifiant et le nom d'une base qui n'existe plus. La documentation l'annonce pour les snapshots manuels, ceux de ce lab : « When you delete an Instance, its associated snapshots are not deleted. Snapshots can be manually deleted or will be deleted after the retention period defined ». Elle le dit séparément pour les artefacts d'autobackup : « when you delete a Database Instance, the backups and snapshots created with the autobackup feature are not automatically deleted ».
Bonne nouvelle pour la reprise, mauvaise pour la facture : le snapshot pèse 10 Go et apparaît sur la note en ligne Database Storage SBS 5K Snapshot. Détruire la base ne suffit donc pas à arrêter le compteur.
Combien de temps prend une restauration ?
Section intitulée « Combien de temps prend une restauration ? »NEW_ID=$(scw rdb snapshot restore "${SNAP_ID}" region=fr-par \ instance-name=donnees-pg-restauree node-type=DB-DEV-S -o json | jq -r '.id')
until [ "$(scw rdb instance get "${NEW_ID}" region=fr-par -o json | jq -r '.status')" = "ready" ]; do sleep 20done225 secondes entre la décision de restaurer et une base de nouveau ready. C'est le RTO, Recovery Time Objective, dans sa version la plus favorable.
Ce n'est pas le RTO de votre service. La base restaurée est une base neuve, avec un nouvel endpoint : rien ne rebranche l'application toute seule. Le temps de rétablissement réel ajoute donc la mise à jour de la configuration applicative, son redéploiement, et la vérification. Restaurer des données n'est pas rétablir un service, et confondre les deux fait promettre un RTO qu'on ne tiendra pas.
Restaurer une base privée la rend-elle publique ?
Section intitulée « Restaurer une base privée la rend-elle publique ? »Oui, et c'est le fait le plus important de cette leçon.
scw rdb instance get "${NEW_ID}" region=fr-par -o json \ | jq '[.endpoints[] | {ip, port, prive: (.private_network != null)}]'
scw rdb acl list instance-id="${NEW_ID}" region=fr-par -o jsonLa base d'origine n'avait aucun endpoint public. La base restaurée, relevée le 2026-09-12 :
[{"ip": "203.0.113.84", "port": 9263, "prive": false}][{"ip": "0.0.0.0/0", "protocol": "tcp", "direction": "inbound", "action": "allow", "description": "Allow All", "port": 9263}]Adresse publique, port aléatoire, aucun endpoint privé, et une ACL ouverte à tous. La restauration ne restaure pas la topologie réseau : elle applique les valeurs par défaut du produit.
Une procédure de reprise après incident réexpose donc la base à Internet, en silence. Le moment est mal choisi : on restaure sous pression, on vérifie que les données sont là, on rebranche l'application, et personne ne relit la configuration réseau. Aucune page de documentation ne signale ce comportement.
La conséquence tient en une phrase : un plan de reprise ne s'arrête pas aux données, il referme le réseau.
EP_PUBLIC=$(scw rdb instance get "${NEW_ID}" region=fr-par -o json \ | jq -r '.endpoints[] | select(.load_balancer != null) | .id')scw rdb endpoint delete "${EP_PUBLIC}" instance-id="${NEW_ID}" region=fr-parRelancez ensuite la vérification d'isolation : la sortie doit afficher zéro endpoint public. Sans cette étape, l'exercice de reprise a laissé le système moins sûr qu'avant l'incident.
Nettoyer
Section intitulée « Nettoyer »Trois familles, dans cet ordre, et les snapshots ne partent pas tout seuls.
scw rdb instance delete "${NEW_ID}" region=fr-par
# On attend la disparition RÉELLE : `rdb instance get` sort en erreur pendant# tout état transitoire, y compris `deleting`. Un échec ne prouve donc rien.until [ "$(scw rdb instance list region=fr-par -o json | jq 'length')" = "0" ]; do sleep 10done
scw rdb snapshot list region=fr-par -o json | jq -r '.[].id' | while read -r SNAP; do scw rdb snapshot delete "${SNAP}" region=fr-pardone
scw vpc private-network delete "${PN_ID}" region=fr-parscw vpc vpc delete "${VPC_ID}" region=fr-parLe commentaire du milieu vaut un avertissement. scw rdb instance get sort en erreur pendant un état transitoire, avec le message transient state error for resource 'instance' suivi, en sortie -o json, de current_state: 'deleting' ; en sortie humaine, la même erreur s'écrit Resource instance with ID … is in a transient state 'deleting'.
Le mécanisme mérite d'être nommé, parce qu'il explique pourquoi le symptôme surprend : ce n'est pas la lecture de l'instance qui échoue, c'est l'appel secondaire que le CLI enchaîne derrière elle pour lister les règles d'ACL. Conclure « la base est détruite » depuis l'échec de cette commande donne donc un verdict faux : la base facture encore. On compte les objets qui restent, ce qui est une identité observable.
La vérification de sortie ferme la boucle.
scw rdb instance list region=fr-par -o json | jq 'length'scw rdb snapshot list region=fr-par -o json | jq 'length'scw vpc private-network list region=fr-par -o json | jq 'length'Les trois sorties doivent afficher 0. Coût de la session complète, relevé dans scw billing consumption list : 0,06 EUR.
Quelle protection réseau pour quelle base ?
Section intitulée « Quelle protection réseau pour quelle base ? »Toutes les bases ne méritent pas le même isolement, et le tableau ci-dessous part du besoin plutôt que du réglage. Il se lit de gauche à droite : la colonne du milieu dit ce qu'on pose, la dernière ce qu'on accepte en échange.
| Besoin | Choix | Ce qu'il coûte |
|---|---|---|
| Une base servie uniquement par des applications du même VPC | endpoint privé seul, init-endpoints sans load balancer | 80 s de plus à la création, et plus aucun accès depuis votre poste |
| Un accès d'administration ponctuel depuis l'extérieur | endpoint privé plus un bastion sur Public Gateway | une ressource de plus à exploiter et à payer |
| Un prototype jetable, détruit dans l'heure | endpoint public avec ACL restreinte à votre adresse | une ACL à tenir à jour, et un port non standard à retenir |
| Une base de démonstration laissée en ligne | aucun de ces choix ne convient | le défaut ouvre 0.0.0.0/0 : c'est le cas à ne jamais reproduire |
La première ligne est celle du lab, et c'est le défaut que devrait avoir toute base de production. Les deux suivantes sont des compromis assumés. La dernière est celle qu'on obtient sans rien décider.
Ce que ce lab a prouvé
Section intitulée « Ce que ce lab a prouvé »Trois choses ont marché, une a résisté, et une a démenti la documentation.
| Ce qui a été prouvé | Mesure |
|---|---|
| Une base peut naître sans aucun endpoint public | 0 public, 1 privé sur 172.16.16.2:5432 |
| L'isolation a un coût en temps, pas en argent | 264 s contre 184 s, soit 80 s |
| Un snapshot survit à la base qu'il sauvegarde | snapshot ready référençant une base disparue, 10 Go facturés |
| La restauration rouvre le réseau | base revenue publique, ACL 0.0.0.0/0, port aléatoire |
| Le RTO annoncé n'est pas celui du service | 225 s jusqu'à ready, endpoint à rebrancher ensuite |
Ce qui a résisté : l'aide du CLI, qui annonce l'endpoint public « systematically present » alors que l'API accepte de s'en passer. Croire l'aide aurait fait conclure que l'isolation était impossible.
Ce qui a démenti la documentation : rien n'annonce que la restauration produit une base publique. Le fait ne s'obtient qu'en jouant la reprise.
Dépendances de destruction, validées en lab et non devinées :
| Objet | Bloque | Conséquence |
|---|---|---|
| Base | rien | mais sa suppression est asynchrone |
| Snapshot | rien | survit à la base, à purger séparément |
| Private Network | sa suppression | tant que la base y est rattachée |
| VPC | sa suppression | tant que le Private Network existe |
Coût de la session : 0,06 EUR, relevé dans scw billing consumption list.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Limites, quotas et plafonds
Section intitulée « Limites, quotas et plafonds »Ces valeurs décident de l'architecture avant d'écrire la moindre commande, et aucune ne se devine.
- Un seul Private Network par base. La FAQ officielle : « currently, you can connect one Private Network to each Database Instance ». Un projet qui sépare ses environnements par réseau sépare donc aussi ses bases.
- Sauvegarde manuelle sur Block : 585 Go maximum. La documentation réserve les backups manuels aux bases dont le stockage est « inferior or equal to 585 GB » sur Block Storage.
- Rétention par défaut : 7 jours, fréquence 24 h. Relevé dans
backup_schedulele 2026-09-12. - IPv6 non supporté sur les Database Instances (FAQ).
- L'autobackup ignore les quotas : « if a Database Instance has autobackup set up, even if the quota is exceeded, the backup or snapshot will still be automatically created ». Une base sauvegardée continue donc de produire des artefacts facturés au delà du quota.
Dépannage
Section intitulée « Dépannage »Les deux premières lignes se ressemblent et ne se diagnostiquent pas pareil : un message de politique de mot de passe se lit, un script qui s'arrête sans rien dire se traque.
| Symptôme | Cause probable | Solution |
|---|---|---|
password must be between 8 and 128 characters, contain at least one digit, one uppercase, one lowercase and one special character | mot de passe issu de base64, sans caractère spécial | préfixer les quatre classes, comme dans le bloc ci-dessus |
| Le script s'arrête sans message après la génération du mot de passe | head a fermé un tube, tr a reçu SIGPIPE, le pipeline rend 141 sous pipefail | ne pas refermer de tube prématurément |
bssd volume type is deprecated, this action is no longer supported. Please use sbs volume type | bssd est proposé par node-type list et refusé par l'API | employer sbs_5k ou sbs_15k |
transient state error for resource 'instance' sur un get | la commande échoue pendant provisioning, initializing et deleting | compter les objets restants dans un list, jamais interpréter le code de retour |
Connection refused sur le port 5432 d'une base publique | l'endpoint public est sur un port aléatoire | lire le port dans .endpoints[].port avant de tester |
Antipatterns à éviter
Section intitulée « Antipatterns à éviter »Ces quatre erreurs ont un point commun : elles font confiance à un défaut, à une liste ou à un code de retour plutôt qu'à une observation.
| Antipattern | Conséquence | Discipline |
|---|---|---|
| Créer la base sans arguments d'endpoint | base joignable depuis Internet, ACL « Allow All », protégée par le seul mot de passe | init-endpoints et load-balancer=false dès la création |
| Choisir la version du moteur dans la liste | démarrage sur un moteur déjà en fin de vie | trier sur end_of_life, jamais sur l'ordre d'affichage |
Considérer la restauration comme terminée quand la base est ready | base de reprise publique, et RTO du service sous-estimé | refermer l'endpoint public, puis rebrancher, puis vérifier |
| Supprimer la base et passer à autre chose | un snapshot manuel sans expires-at facture jusqu'à sa suppression, invisible six mois plus tard | purger les snapshots comme une étape à part entière, ou poser expires-at à la création |
PostgreSQL managé sous l'angle Well-Architected
Section intitulée « PostgreSQL managé sous l'angle Well-Architected »Sécurité
Section intitulée « Sécurité »Question clé : qui peut ouvrir une connexion à cette base, à cet instant ? La réponse change sans prévenir, puisqu'une restauration recrée un endpoint public avec une ACL ouverte. La discipline consiste à faire du contrôle d'endpoint une étape du plan de reprise, vérifiée par la même commande que celle du jour de la création, et non une propriété acquise à la création.
Fiabilité
Section intitulée « Fiabilité »Question clé : combien de données acceptez-vous de perdre, et combien de temps d'indisponibilité ? Les deux se lisent, backup_schedule.frequency pour le premier, un essai chronométré pour le second. La discipline est de jouer la restauration au moins une fois : un plan de reprise jamais exécuté n'est pas un plan, et c'est cet essai qui a révélé l'endpoint public. Rappel utile : en mode single_zone, le seul documenté au 2026-09-12, la haute disponibilité place les deux nœuds dans le même datacenter, sur deux baies, et ne remplace donc pas une sauvegarde. L'API expose un mode multiple_zone depuis le 11 août 2026, sans page produit ni entrée de changelog à cette date : à ne pas considérer comme un choix de production tant qu'il n'est pas documenté.
Optimisation des coûts
Section intitulée « Optimisation des coûts »Question clé : qu'est-ce qui continue de facturer après la suppression de la base ? Les snapshots. La politique est posée dès la création, mais le premier snapshot automatique n'arrive que 24 heures plus tard, la date de création servant de base ; la rétention vaut 7 jours par défaut. La discipline est une vérification de sortie qui reliste chaque famille, snapshots compris, plutôt que de conclure depuis la disparition de l'instance.
À retenir
Section intitulée « À retenir »- Un endpoint public est attribué par défaut à toute base créée sans
init-endpoints, avec une ACL0.0.0.0/0décrite « Allow All ». init-endpoints.0.load-balancer=falsesupprime cet endpoint, malgré une aide du CLI qui le dit « systematically present ».- L'endpoint privé écoute sur 5432 ; c'est le public qui est tiré sur un port aléatoire.
- L'isolation coûte 80 secondes à la création : 264 s contre 184 s, mesuré le 2026-09-12.
- Le RPO par défaut est de 24 heures, fixé par
backup_schedule.frequency, actif sans demande. - Le RTO mesuré est de 225 secondes, et ce n'est qu'un plancher : la base restaurée porte un nouvel endpoint.
- Restaurer une base privée la rend publique, ACL ouverte comprise. Le plan de reprise doit refermer le réseau.
- Les snapshots survivent à la base et facturent : leur purge est une étape, pas une conséquence.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Cockpit : observer un incident sans rien provisionner : les quatorze métriques que cette base produit déjà sans agent, dont le retard de réplication et le nombre de connexions actives.
Ressources externes
Section intitulée « Ressources externes »- Managed Database for PostgreSQL and MySQL, FAQ : les quotas qui décident de l'architecture, un Private Network par base et l'absence de filtrage IP sur l'endpoint privé.
- How to manage backups : la différence entre backups et snapshots, et la phrase qui dit que ni les uns ni les autres ne sont supprimés avec la base.
- How to remove a public endpoint : la procédure de fermeture, et la confirmation que l'endpoint public est attribué automatiquement.