Le calcul : instances, images et cloud-init
Pourquoi
Le serveur sur lequel tourne aujourd'hui Signalements a été installé il y a trois ans par une personne qui a quitté l'équipe. Il fonctionne. Personne ne sait exactement ce qu'il contient : un paquet ajouté un soir d'incident, un fichier de configuration modifié à la main, une tâche planifiée dont on a oublié l'objet. Le jour où il tombera, il faudra le reconstruire de mémoire, et l'on découvrira ce qu'il faisait en constatant ce qui ne marche plus.
Le cloud ne règle pas ce problème tout seul. On peut parfaitement louer une machine virtuelle chez Scaleway et l'administrer exactement comme ce vieux serveur : s'y connecter, installer, bricoler. On aura alors le même serveur unique et irremplaçable, simplement facturé à l'heure.
Ce que le cloud rend possible, c'est autre chose : une machine qui se construit seule, à partir d'une description écrite, en quelques minutes, autant de fois qu'on le veut. Si elle tombe, on en crée une autre, identique. Si on veut la modifier, on modifie la description et l'on remplace la machine. Cette leçon montre les trois pièces qui le permettent : l'instance (la machine louée), l'image (son point de départ) et cloud-init (l'outil qui la configure au premier démarrage, à partir de données que vous lui fournissez). À la fin, sig-app-1 et sig-app-2 tournent dans les deux zones choisies à la leçon précédente, et aucune des deux n'a été touchée à la main.
Les concepts
Une instance est une machine virtuelle
Une instance est une machine virtuelle louée à un fournisseur : un système d'exploitation complet, avec ses processeurs virtuels, sa mémoire, ses disques et ses interfaces réseau, qui tourne sur un serveur physique partagé avec d'autres clients. Ce serveur physique exécute un hyperviseur, le logiciel qui découpe ses ressources réelles (cœurs, mémoire, disques, réseau) en machines virtuelles isolées les unes des autres. Le mot « instance » vient de là : chaque machine est une instance d'un type décrit au catalogue.
Chaque fournisseur a son nom pour la même chose :
| Scaleway | AWS | Azure | Google Cloud | OVHcloud |
|---|---|---|---|---|
| Instance | EC2 instance | Virtual Machine | Compute Engine VM | Instance Public Cloud (OpenStack) |
| Image | AMI | Image (Compute Gallery) | Image | Image |
| Cloud-init (données utilisateur) | User data | Custom data, user data | Métadonnée user-data ou startup-script | User data |
| Groupe d'autoscaling (bêta) | Auto Scaling group | Virtual Machine Scale Set | Managed instance group | (via les outils OpenStack) |
Choisir un type d'instance
Un type d'instance fixe quatre choses : le nombre de processeurs virtuels (vCPU), la quantité de mémoire, le type de stockage accepté et la bande passante réseau maximale. Le catalogue de Scaleway les regroupe en gammes, dont la documentation donne la liste complète dans sa fiche technique (Instances datasheet). Au moment de la rédaction :
| Gamme | Familles | vCPU | Pour quoi |
|---|---|---|---|
| Development | DEV1, GP1, PLAY2, STARDUST1 | partagés | Développement, tests, petits services |
| General Purpose | BASIC3, BASIC2, PRO2 (partagés) ; STANDARD3, STANDARD2, POP2 (dédiés) | partagés ou dédiés | Production, de la petite API à la base de données chargée |
| Specialized | COMPUTE3, MEMORY3, POP2-HC, POP2-HM | dédiés | Calcul intensif, mémoire abondante |
| GPU | L4, H100, B300... | dédiés | Apprentissage automatique, inférence |
Les noms suivent souvent la forme FAMILLE-<vCPU>C-<mémoire>G : STANDARD3-X2C-8G a 2 vCPU et 8 Gio de mémoire. Les familles plus anciennes ont des tailles nommées (PRO2-XXS, PRO2-S, DEV1-M). Les générations se succèdent : la documentation présente les STANDARD3 (processeurs AMD EPYC de génération Turin) comme jusqu'à 35 % plus performantes que les POP2. Scaleway documente aussi un cycle de vie des types : « fin de nouvelles fonctionnalités », « fin de commercialisation », puis « fin de vie », où les instances restantes sont arrêtées et migrées vers une offre équivalente. Un type choisi aujourd'hui sera à remplacer dans quelques années ; c'est un argument de plus pour des instances que l'on sait recréer.
vCPU partagés ou dédiés. C'est le choix qui compte le plus après la taille. Sur une instance à vCPU dédiés, chaque vCPU correspond à un fil d'exécution physique réservé : les performances sont constantes. Sur une instance à vCPU partagés, vos vCPU sont ordonnancés sur des cœurs physiques que se partagent plusieurs instances ; quand les voisins sont très actifs, votre instance attend son tour. Ce temps d'attente se voit dans Linux : c'est la colonne st (steal, temps volé) de top ou de vmstat. La documentation de Scaleway résume l'arbitrage : partagés pour le développement, les environnements de préproduction et les petites productions qui tolèrent des variations ; dédiés pour la production critique et les applications sensibles à la latence. La mémoire, elle, est toujours réservée à l'instance.
Stockage local ou bloc. Une instance démarre sur un volume racine. Deux technologies existent chez Scaleway :
- le stockage local est un disque SSD de l'hyperviseur lui-même : rapide, mais lié à la machine physique ; seules certaines gammes anciennes (DEV1, GP1) l'acceptent ;
- le stockage bloc (Block Storage) est un volume attaché par le réseau, comme un disque virtuel que l'on peut détacher et rattacher ailleurs ; les gammes récentes ne démarrent que sur lui (boot-on-block).
La leçon 5 revient en détail sur le stockage. Retenez ici la conséquence pratique : avec un volume bloc, les données du disque survivent à l'arrêt de l'instance et ne dépendent pas d'un hyperviseur particulier.
Le choix de Signalements. L'API est légère : deux processus gunicorn, quelques requêtes par seconde. L'équipe choisit PRO2-XXS : 2 vCPU partagés (AMD EPYC 7543), 8 Gio de mémoire, stockage bloc, 350 Mbit/s de bande passante d'après la fiche technique, et la leçon 3 a vérifié qu'il est disponible en fr-par-1 et fr-par-2. C'est un choix de départ, pas un dogme : si les mesures montrent du temps volé gênant, on passera à des vCPU dédiés (STANDARD3-X2C-8G) en changeant une ligne. Notez aussi que la gamme PRO2 n'a aucun objectif de disponibilité dans le SLA des instances de Scaleway (leçon 8) : c'est acceptable ici parce que la disponibilité de Signalements vient de ses deux instances dans deux zones, pas de la promesse faite sur chacune ; pour un client qui exige un engagement contractuel par machine, choisissez une gamme couverte.
L'image
Une image de machine est le point de départ du volume racine : un système d'exploitation installé, prêt à démarrer. On en distingue deux sortes :
- les images publiques fournies par le fournisseur (chez Scaleway, le catalogue, ou marketplace : Ubuntu, Debian, Rocky Linux, des applications préinstallées appelées InstantApps) ; on les désigne par un libellé stable, comme
ubuntu_noblepour Ubuntu 24.04, que la CLI traduit en l'identifiant de la dernière version de l'image dans la zone visée ; - les images personnalisées, que vous fabriquez vous-même. Chez Scaleway, une image est une copie complète d'une instance, tous volumes compris (
scw instance server backup), ou se construit à partir d'un instantané de volume. On les appelle souvent images dorées (golden images) quand elles servent de base standard à toute une organisation, durcies et à jour.
Toutes les images Linux fournies par Scaleway intègrent cloud-init, préinstallé et activé (la documentation excepte les images Windows).
L'accès : clés SSH et IP publique
Les images Linux de Scaleway s'utilisent avec le compte root, par clé SSH, sans mot de passe. Les clés publiques se déclarent dans l'IAM, au niveau du projet, et sont injectées dans les instances du projet : un script de l'image, scw-fetch-ssh-keys, réécrit le fichier /root/.ssh/authorized_keys à chaque démarrage à partir des métadonnées de l'instance. Le fichier commence d'ailleurs par un avertissement qui le dit. Deux conséquences : une clé ajoutée à la main dans ce fichier disparaît au redémarrage suivant, et une clé retirée du projet disparaît des instances au redémarrage suivant, pas immédiatement.
Pour être joignable depuis Internet, l'instance a besoin d'une adresse IP publique. Par défaut, scw instance server create en crée une (ip=new). Chez Scaleway, elle est flexible : elle appartient à votre projet, indépendamment de l'instance, survit à son arrêt et peut être déplacée d'une instance à l'autre. L'option ip=dynamic donne au contraire une adresse rendue au fournisseur à chaque arrêt ; ip=none, aucune adresse publique. À la leçon 6, les instances de Signalements perdront leur IP publique au profit d'un réseau privé, d'un répartiteur de charge et d'un bastion.
Le cycle de vie, et ce qu'il coûte
Une instance passe par plusieurs états, et chacun a un coût différent. Le tableau suivant résume la documentation Understanding Instance states de Scaleway et les pages sur l'arrêt et la veille :
| État | Ce qui se passe | Ce qui est facturé |
|---|---|---|
| En marche (running) | L'instance tourne sur un hyperviseur | Calcul, stockage, IP |
| En veille (standby) | Le système est arrêté, mais l'instance reste allouée sur son hyperviseur ; une commande poweroff depuis le système y mène aussi | Toujours facturée comme une instance, plus le stockage et les IP |
| Arrêtée (stopped, power off) | L'hyperviseur est libéré ; le volume local éventuel est archivé, et restauré au redémarrage | Stockage (volumes bloc, archive du volume local) et IP flexibles, plus de calcul |
| Supprimée | L'instance et ses volumes locaux disparaissent | Plus rien, sauf les IP flexibles et volumes que vous avez gardés |
Le piège est dans la deuxième ligne. Un sudo poweroff dans l'instance ne l'« éteint » pas au sens de la facture : elle passe en veille et reste facturée. Pour arrêter la facturation du calcul, il faut l'arrêter par l'API (scw instance server stop), ce qui libère l'hyperviseur. Le redémarrage prend alors plus de temps, puisqu'il faut retrouver une place sur un hyperviseur, voire restaurer un volume local archivé, et peut même échouer si le type est en pénurie dans la zone.
Chez les autres fournisseurs, l'idée est la même avec d'autres règles : chez AWS, une instance EC2 arrêtée ne coûte plus que ses volumes EBS ; chez Azure, il faut désallouer une VM (et non seulement l'arrêter depuis le système) pour cesser de payer le calcul.
Le service de métadonnées
Une instance qui démarre a besoin de savoir qui elle est : son nom, son identifiant, ses adresses, les clés SSH autorisées, et la configuration que vous voulez lui appliquer. Elle l'apprend en interrogeant le service de métadonnées, un service HTTP que le fournisseur expose à chaque instance à une adresse lien-local (une adresse du bloc 169.254.0.0/16, joignable seulement depuis l'instance elle-même) :
| Fournisseur | Adresse | Particularité |
|---|---|---|
| Scaleway | http://169.254.42.42 (et http://[fd00:42::42] en IPv6) | Données utilisateur réservées aux ports source inférieurs à 1024 |
| AWS | http://169.254.169.254 | IMDSv2 : jeton de session obtenu par un PUT |
| Azure | http://169.254.169.254 | En-tête Metadata: true obligatoire |
| Google Cloud | http://metadata.google.internal (169.254.169.254) | En-tête Metadata-Flavor: Google obligatoire |
| OVHcloud (OpenStack) | http://169.254.169.254 | Service de métadonnées d'OpenStack |
Chez Scaleway, la documentation décrit trois catégories de données :
- les métadonnées (meta-data) : la description de l'instance, comme si elle s'interrogeait elle-même par l'API (
/conf, au format texte ou JSON) ; - les données du fournisseur (vendor-data) : une configuration que Scaleway applique par défaut ;
- les données utilisateur (user-data) : ce que vous fournissez à la création de l'instance, et que cloud-init exécute.
cloud-init
cloud-init est l'outil standard, sur toutes les distributions Linux et chez presque tous les fournisseurs, qui configure une instance à son premier démarrage. Il détecte sur quel cloud il tourne, lit les métadonnées et les données utilisateur, puis applique la configuration : création d'utilisateurs, installation de paquets, écriture de fichiers, exécution de commandes.
Les données utilisateur acceptent plusieurs formats. Le plus courant, et le seul de cette leçon, est le cloud-config : un document YAML qui commence obligatoirement par la ligne #cloud-config, et dont chaque clé de premier niveau active un module de cloud-init (packages, users, write_files, runcmd...). Un script shell (commençant par #!) est aussi accepté, mais le cloud-config est déclaratif, validable et lisible. Chez Scaleway, seules les données en texte brut sont acceptées : pas de format compressé.
Point essentiel : la plupart des modules ne s'exécutent qu'au premier démarrage d'une instance donnée. cloud-init reconnaît ce premier démarrage à l'identifiant de l'instance : tant qu'il ne change pas, il considère que la configuration a déjà été appliquée. Modifier les données utilisateur d'une instance existante, puis la redémarrer, n'a donc en général aucun effet sur les modules marqués per-instance, ce que la documentation de Scaleway rappelle explicitement. cloud-init est un outil d'amorçage, pas un outil de gestion de configuration continue comme Ansible.
Animaux de compagnie et bétail
Cette propriété (on configure une fois, à la naissance) conduit à une façon de travailler que l'on résume par une image devenue célèbre : les serveurs animaux de compagnie ou bétail (pets vs cattle). Randy Bias, qui l'a popularisée vers 2011-2012 en reprenant une formule de Bill Baker, en donne ces définitions : les animaux de compagnie sont des serveurs (ou des paires de serveurs) traités comme indispensables, uniques, qui ne doivent jamais tomber ; le bétail, ce sont des ensembles de serveurs construits par des outils automatiques et conçus pour la panne, dont aucun n'est irremplaçable.
Un animal de compagnie malade, on le soigne : on s'y connecte, on diagnostique, on corrige. Une tête de bétail malade, on la remplace. Le vieux serveur de Signalements est un animal de compagnie. Les instances de cette leçon seront du bétail.
Chad Fowler a donné en 2013 un nom à la pratique qui en découle : l'infrastructure immuable (immutable infrastructure). Un serveur n'est jamais modifié après sa création ; tout changement (nouvelle version, correctif, configuration) passe par la création d'un nouveau serveur et la suppression de l'ancien. Son argument : si l'on sait qu'un système a été créé par automatisation et jamais modifié depuis, la plupart des problèmes de dérive disparaissent. C'est exactement le modèle des conteneurs, appliqué aux machines.
En pratique
Les commandes suivantes ont été vérifiées contre l'aide de scw 2.62 et la documentation de Scaleway ; elles créent des ressources facturées dans votre projet signalements. La leçon décrit les résultats sans reproduire de sortie. Supprimez les instances à la fin de la séance (dernière section) si vous ne poursuivez pas tout de suite avec la leçon 5.
Déclarer sa clé SSH
Si ce n'est pas déjà fait, déclarez votre clé publique dans le projet. Une clé Ed25519 est le bon choix aujourd'hui :
$ ssh-keygen -t ed25519 -C "camille@signalements" # seulement si vous n'en avez pas
$ scw iam ssh-key create name=camille public-key=@"$HOME/.ssh/id_ed25519.pub"
La syntaxe @chemin demande à la CLI de lire la valeur dans un fichier. La clé est rattachée au projet par défaut de votre profil ; elle sera injectée dans toutes les instances de ce projet.
Écrire la configuration cloud-config
Créez un fichier signalements.yaml. Il décrit tout ce que la machine doit devenir :
#cloud-config
# Instance applicative Signalements : Docker et l'API, rien d'autre.
package_update: true
package_upgrade: true
packages:
- docker.io
users:
- name: exploit
gecos: Compte d'exploitation nominatif partagé par l'astreinte
groups: [sudo]
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
lock_passwd: true
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... camille@signalements
write_files:
- path: /etc/signalements/env
permissions: "0640"
content: |
APP_VERSION=1.2.0
- path: /etc/systemd/system/signalements.service
permissions: "0644"
content: |
[Unit]
Description=API Signalements (conteneur)
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
ExecStartPre=-/usr/bin/docker rm -f signalements
ExecStart=/usr/bin/docker run --rm --name signalements \
--env-file /etc/signalements/env \
-p 127.0.0.1:8000:8000 \
ghcr.io/lyneko-formation/signalements:1.2.0
ExecStop=/usr/bin/docker stop signalements
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
runcmd:
- [systemctl, daemon-reload]
- [systemctl, enable, --now, signalements.service]Quelques choix méritent une explication :
docker.ioplutôt que le dépôt de Docker. Le cours Docker : les fondamentaux compare les deux. Sur une instance qui ne fait que lancer un conteneur, le paquet de la distribution suffit : pas de dépôt tiers ni de clé à importer, des correctifs suivis par Ubuntu, et une configuration cloud-init de trois lignes. Si vous avez besoin des derniers plugins (Compose, Buildx), le moduleaptde cloud-init sait ajouter le dépôt officiel et sa clé ; la configuration est plus longue, et la version installée change au rythme de Docker.package_upgrade: trueapplique les mises à jour de sécurité publiées depuis la fabrication de l'image. Une image publique a souvent quelques semaines ; sans cette ligne, l'instance démarre avec des failles déjà corrigées ailleurs. Le prix : un premier démarrage plus long de quelques minutes.- Un compte nominatif
exploit. Il ne remplace pas l'accèsrootpar les clés du projet (mécanisme de Scaleway, réécrit à chaque démarrage), mais il permet d'administrer avecsudo, donc avec une trace dans les journaux. Seule la clé publique figure dans le fichier : elle n'a rien de secret.lock_passwd: trueinterdit toute connexion par mot de passe. - Une unité systemd plutôt que
docker run --restart alwayslancé une fois. systemd démarre le conteneur au démarrage de la machine, le relance s'il s'arrête, et ses journaux se lisent avecjournalctl -u signalements.ExecStartPre=-(le tiret signifie « ignorer l'échec ») supprime un éventuel conteneur résiduel d'un arrêt brutal. -p 127.0.0.1:8000:8000publie le port uniquement sur l'interface locale. L'instance a une IP publique et le groupe de sécurité par défaut de la zone accepte le trafic entrant : publier sur0.0.0.0exposerait l'API à Internet, sans TLS, avant même que le répartiteur de charge existe. On testera par un tunnel SSH. La leçon 6 publiera le port sur l'interface du réseau privé.- Pas de
DATABASE_URL. Sans elle, Signalements fonctionne en mémoire, ce qui suffit pour cette leçon. L'adresse de la base contient un mot de passe : elle ne doit pas figurer dans les données utilisateur (voir la section Sécurité). Elle sera fournie à l'instance par un autre canal quand la base sera raccordée au réseau privé. runcmdsous forme de listes. Chaque élément est exécuté directement, sans passer par un shell : pas de surprise d'échappement.
Valider avant de créer
Une erreur de syntaxe dans un cloud-config ne se voit qu'après la création de l'instance, et parfois pas du tout : un module mal nommé est ignoré avec un simple avertissement dans les journaux. Validez donc le fichier avant. cloud-init schema le vérifie contre le schéma officiel ; si cloud-init n'est pas installé sur votre poste, un conteneur Ubuntu jetable fait l'affaire :
$ docker run --rm -v "$PWD":/w:ro ubuntu:24.04 sh -c \
'apt-get update -qq && apt-get install -y -qq cloud-init >/dev/null \
&& cloud-init schema -c /w/signalements.yaml --annotate'
-c(ou--config-file) désigne le fichier à valider ;--annotateréaffiche le fichier en signalant chaque erreur à côté de la ligne fautive.
Une configuration valide produit un message de succès ; une clé inconnue ou une valeur du mauvais type est signalée avec sa position.
Créer sig-app-1
$ scw instance server create \
name=sig-app-1 \
type=PRO2-XXS \
image=ubuntu_noble \
zone=fr-par-1 \
cloud-init=@signalements.yaml \
tags.0=app=signalements tags.1=env=prod \
--wait -o json > sig-app-1.json
$ jq -r '.id, .state, .public_ips[0].address' sig-app-1.json
Chaque argument :
name=sig-app-1: le nom, qui devient aussi le nom d'hôte de la machine ;type=PRO2-XXS: le type d'instance choisi plus haut ;image=ubuntu_noble: le libellé de l'image Ubuntu 24.04, résolu dans la zone visée ;zone=fr-par-1: explicite, comme convenu à la leçon 3 ;cloud-init=@signalements.yaml: les données utilisateur, lues dans le fichier ;tags.N=...: des étiquettes libres, qui serviront à retrouver et à facturer les ressources de l'application (leçon 8) ;--wait: attendre que l'instance soit dans un état stable (en marche) avant de rendre la main ;-o json: une sortie exploitable parjq, que l'on garde dans un fichier.
Le volume racine est créé automatiquement en stockage bloc, et une IP flexible est attribuée (ip=new par défaut). Attention : --wait attend que la machine soit démarrée, pas que cloud-init ait fini. Il reste plusieurs minutes de mises à jour et d'installation.
Suivre cloud-init
Récupérez l'adresse et connectez-vous :
$ IP1=$(jq -r '.public_ips[0].address' sig-app-1.json)
$ ssh root@"$IP1" cloud-init status --wait --long
cloud-init status --wait bloque jusqu'à la fin de cloud-init, en affichant des points pendant l'attente, puis donne l'état final ; --long ajoute le détail (source de données détectée, erreurs éventuelles). Le code de sortie est ce qu'un script doit regarder : 0 si tout s'est bien passé, 1 si cloud-init a planté, 2 s'il a terminé avec des erreurs récupérables (un module qui a échoué, par exemple).
Si quelque chose ne va pas, trois journaux et une commande :
$ ssh root@"$IP1"
# less /var/log/cloud-init-output.log # la sortie des commandes (apt, runcmd)
# less /var/log/cloud-init.log # le journal détaillé de cloud-init lui-même
# cloud-init analyze blame # quels modules ont pris le plus de temps
# cloud-init query ds.meta_data.name # une valeur des métadonnées, depuis le cache
Puis vérifiez l'application :
# systemctl status signalements
# journalctl -u signalements -n 20
# curl -s http://127.0.0.1:8000/sante
Depuis votre poste, un tunnel SSH donne accès à l'API sans l'exposer :
$ ssh -N -L 8000:127.0.0.1:8000 exploit@"$IP1" &
$ curl -s http://localhost:8000/sante
$ kill %1
-L 8000:127.0.0.1:8000 fait suivre le port 8000 de votre poste vers le port 8000 de l'interface locale de l'instance, à travers la connexion SSH chiffrée ; -N n'exécute aucune commande distante. Le fait que la connexion exploit@ fonctionne prouve au passage que le module users a bien fait son travail.
Interroger le service de métadonnées
Depuis l'instance, regardez ce qu'elle sait d'elle-même :
# scw-metadata NAME
# curl -s 'http://169.254.42.42/conf?format=json' | jq '{name, id, tags}'
scw-metadata est un petit outil des images Scaleway ; il lit le même service que curl. La réponse contient le nom, l'identifiant, les étiquettes, les adresses, les clés SSH publiques autorisées. Essayez maintenant de lire les données utilisateur, d'abord en tant qu'exploit, sans privilège :
$ curl -s http://169.254.42.42/user_data/cloud-init
La requête est refusée. Le code source de la source de données Scaleway de cloud-init l'explique : le service exige que la requête parte d'un port source inférieur à 1024, que seul root (ou un processus doté de la capacité CAP_NET_BIND_SERVICE) peut ouvrir. En root, curl peut choisir son port local :
# curl -s --local-port 1-1023 http://169.254.42.42/user_data/cloud-init
Cette fois, votre fichier signalements.yaml s'affiche, tel que vous l'avez envoyé. Retenez-le : les données utilisateur restent lisibles pendant toute la vie de l'instance, par tout processus root de la machine, et par quiconque peut consulter l'instance dans la console ou l'API.
Créer sig-app-2
La seconde instance est identique, dans l'autre zone. C'est tout l'intérêt de la description écrite : il n'y a rien à refaire, seulement à relancer.
$ scw instance server create \
name=sig-app-2 type=PRO2-XXS image=ubuntu_noble zone=fr-par-2 \
cloud-init=@signalements.yaml \
tags.0=app=signalements tags.1=env=prod \
--wait -o json > sig-app-2.json
$ ssh root@"$(jq -r '.public_ips[0].address' sig-app-2.json)" cloud-init status --wait
Vérifiez le résultat d'ensemble avec la commande de la leçon 3 :
$ scw instance server list zone=all tags=app=signalements -o json \
| jq -r '.[] | [.name, .zone, .state, .commercial_type] | @tsv'
Remplacer plutôt que réparer
Une nouvelle version de Signalements, 1.3.0, est publiée. L'animal de compagnie se mettrait à jour par SSH. Le bétail se remplace :
- modifier l'étiquette de l'image dans
signalements.yamlet la versionner ; - créer
sig-app-1benfr-par-1avec ce fichier, attendrecloud-init status --wait, vérifier/sante; - faire basculer le trafic (leçon 6 : retirer l'ancienne instance du répartiteur, ajouter la nouvelle) ;
- supprimer l'ancienne instance.
La même procédure sert à appliquer un correctif de sécurité, à changer de type d'instance ou à passer à une nouvelle version d'Ubuntu. Elle est plus lente qu'un apt upgrade sur place ; elle garantit en échange que ce qui tourne est exactement ce qui est décrit, et que l'on sait recréer la machine.
Arrêter et supprimer
Pour arrêter la facturation du calcul sans tout détruire, arrêtez par l'API :
$ scw instance server stop "$(jq -r .id sig-app-1.json)" zone=fr-par-1 --wait
Pour supprimer une instance avec son volume racine et son IP flexible :
$ scw instance server terminate "$(jq -r .id sig-app-1.json)" zone=fr-par-1 \
with-ip=true with-block=true
Sans with-ip=true, l'IP flexible reste dans le projet, et reste facturée. with-block vaut prompt par défaut : la CLI demande confirmation avant de supprimer les volumes bloc. Faites de même pour sig-app-2 en fr-par-2.
Sous le capot
Les étapes de démarrage de cloud-init. cloud-init n'est pas un programme unique, mais une série de services systemd qui s'intercalent dans le démarrage de la machine. La documentation en décrit cinq étapes :
| Étape | Service systemd | Rôle |
|---|---|---|
| Détection | générateur systemd, ds-identify | Reconnaître la plateforme (ici Scaleway) et activer cloud-init |
| Local | cloud-init-local.service | Trouver la source de données et préparer la configuration réseau, avant que le réseau ne monte |
| Réseau | cloud-init.service (renommé cloud-init-network.service à partir de la version 24.3, sur les distributions récentes) | Récupérer et traiter les données utilisateur, écrire les fichiers, créer les utilisateurs ; bloque l'ouverture de SSH |
| Configuration | cloud-config.service | Modules qui n'affectent pas le reste du démarrage (configuration d'apt, runcmd...) |
| Finale | cloud-final.service | Installation des paquets, scripts de l'utilisateur ; l'équivalent de l'ancien rc.local |
Chaque module est rattaché à une étape par le fichier /etc/cloud/cloud.cfg de l'image. Dans la configuration par défaut de cloud-init, write_files et users_groups s'exécutent à l'étape réseau, runcmd à l'étape de configuration, et package_update_upgrade_install à l'étape finale. Un détail surprend : le module runcmd n'exécute rien. Il écrit vos commandes dans un script, que le module scripts_user lance à l'étape finale, après l'installation des paquets. C'est pour cela que notre systemctl enable --now signalements.service trouve Docker installé, alors que le fichier d'unité, lui, a été écrit bien avant que Docker n'existe. Si un fichier doit appartenir à un utilisateur créé par un paquet, l'option defer: true de write_files reporte son écriture à l'étape finale.
La source de données Scaleway. Le code de cloud-init (DataSourceScaleway.py) interroge http://169.254.42.42 puis http://[fd00:42::42], lit les métadonnées sur /conf, et les données utilisateur et fournisseur sur /user_data/cloud-init et /vendor_data/cloud-init. Pour ces deux dernières, il se lie volontairement à un port local inférieur à 1024, et réessaie avec le port suivant si celui-ci est occupé : c'est la protection vue plus haut, qui garantit que seul un processus privilégié lit les données utilisateur. cloud-init en garde ensuite une copie sur le disque, sous /var/lib/cloud/instance/, avec des droits 0600 (lisible par root seulement).
Comment cloud-init sait que ce n'est pas le premier démarrage. Il compare l'identifiant de l'instance, lu dans les métadonnées, à celui qu'il a enregistré sous /var/lib/cloud/. S'ils sont identiques, il saute les modules per-instance. C'est aussi pour cela qu'une image fabriquée à partir d'une instance configurée fonctionne : la nouvelle instance a un autre identifiant, cloud-init la traite comme neuve. Avant de fabriquer une image dorée, on nettoie l'état de cloud-init (cloud-init clean --logs --machine-id) pour que chaque copie reparte de zéro, avec son propre identifiant de machine.
Le temps volé. Sur une instance à vCPU partagés, l'hyperviseur peut retirer un cœur physique à votre vCPU pour le donner à un voisin. Le noyau invité le constate (l'hyperviseur le lui signale) et le compte comme du steal time. Une valeur durablement supérieure à quelques pour cent signifie que votre application attend le processeur : c'est un signal pour passer à des vCPU dédiés, pas pour ajouter des vCPU partagés.
Pièges courants
cloud-init status dit error, mais tout semble marcher. Un module a échoué (une faute de frappe dans un nom de paquet, une commande runcmd qui renvoie un code non nul) et les suivants ont continué. Le code de sortie 2 le signale ; /var/log/cloud-init-output.log donne la commande fautive. Traitez ce cas comme un échec : une instance « presque » configurée est une instance différente des autres.
Modifier les données utilisateur et redémarrer, sans effet. Les modules per-instance ne s'exécutent qu'une fois. La documentation de Scaleway le rappelle, et indique au passage que la CLI ne modifie pas les données utilisateur en place : scw instance server update <id> cloud-init=@fichier remplace tout le contenu. Le bon geste est de recréer l'instance.
Oublier la ligne #cloud-config. Sans elle, le fichier n'est pas reconnu comme un cloud-config, et rien de ce qu'il décrit ne s'applique. cloud-init schema le signale.
Un poweroff qui ne fait pas d'économie. Arrêter le système depuis l'intérieur met l'instance en veille, toujours facturée. Arrêtez par l'API ou la console.
L'IP flexible orpheline. Supprimer une instance sans with-ip=true laisse son IP dans le projet, facturée sans servir. La leçon 8 montre comment repérer ces ressources oubliées.
--wait n'attend pas cloud-init. Un script qui crée l'instance puis s'y connecte immédiatement pour tester l'application échoue au hasard. Enchaînez toujours avec cloud-init status --wait.
Un port Docker publié sur toutes les interfaces. -p 8000:8000 équivaut à -p 0.0.0.0:8000:8000. Docker ouvre lui-même le port dans le pare-feu du noyau, en contournant au passage les règles d'un outil comme ufw sur la machine. Sur une instance qui a une IP publique, l'application est alors exposée à Internet, quels que soient vos réglages locaux ; seul le groupe de sécurité du fournisseur (leçon 6) l'en protège.
Le type en pénurie au moment de redémarrer. Une instance arrêtée par l'API a rendu son hyperviseur. Si le type est en pénurie dans la zone au moment de la relancer, le démarrage peut échouer. Pour une instance qui doit pouvoir redémarrer à tout moment, la veille (facturée) ou une instance de rechange en marche sont des assurances.
Sécurité
Aucun secret dans les données utilisateur. C'est la règle la plus importante de la leçon. Les données utilisateur sont stockées par le fournisseur, affichées dans la console, renvoyées par l'API à toute identité qui peut lire l'instance, copiées sur le disque de la machine, et servies par le service de métadonnées pendant toute la vie de l'instance. Un mot de passe de base de données, une clé d'API ou un jeton de registre qui y figure est un secret partagé avec toutes ces personnes et tous ces processus. Fournissez les secrets par un canal fait pour cela : un gestionnaire de secrets (Scaleway Secret Manager, que Lyneko utilise pour ses applications), que l'instance interroge au démarrage avec une identité aux droits limités.
Le service de métadonnées est une cible. Toute application qui peut être amenée à faire une requête HTTP vers une adresse choisie par un attaquant (une falsification de requête côté serveur, ou SSRF : un champ « URL de l'avatar », un service de prévisualisation de liens, un proxy mal configuré) peut être retournée contre 169.254.x.x. Chez AWS, le service de métadonnées fournit en plus des identifiants temporaires pour le rôle IAM attaché à l'instance : une SSRF suffit alors à voler des droits sur le compte cloud. C'est ce qui a conduit AWS à créer IMDSv2 en 2019 : il faut d'abord obtenir un jeton de session par une requête PUT (avec un en-tête X-aws-ec2-metadata-token-ttl-seconds), puis le présenter dans chaque requête (X-aws-ec2-metadata-token). Une SSRF ordinaire ne sait faire que des GET sans en-têtes choisis ; AWS rejette en outre les PUT qui portent un en-tête X-Forwarded-For (signe d'un proxy), et la réponse au PUT porte par défaut une limite de sauts IP (TTL) de 1, ce qui l'empêche d'atteindre un équipement situé au-delà de l'instance. Configurez les instances AWS pour n'accepter que IMDSv2. Chez Scaleway, la protection des données utilisateur par port privilégié vise le même risque côté processus locaux ; les métadonnées de /conf, elles, restent lisibles par tout processus.
Bloquer l'accès aux métadonnées depuis les conteneurs. Un conteneur partage la pile réseau de sa machine (ou passe par elle) : il peut joindre 169.254.42.42. Si une application n'en a pas besoin, une règle de pare-feu sur l'instance (ou une politique réseau dans Kubernetes) qui interdit cette destination aux conteneurs ferme la porte.
SSH : des clés, des personnes, des traces. Pas de mot de passe (les images Scaleway n'en ont pas pour root), une clé par personne plutôt qu'une clé d'équipe, des comptes nominatifs pour l'administration quotidienne, et la révocation d'une clé dans l'IAM quand quelqu'un quitte l'équipe (en se souvenant qu'elle ne disparaît des instances qu'au redémarrage suivant : pour une révocation immédiate, on remplace les instances). À la leçon 6, SSH ne sera plus joignable depuis Internet, seulement par un bastion.
Les mises à jour de sécurité. package_upgrade: true met l'instance à jour à sa naissance. Ensuite, les images Ubuntu activent par défaut unattended-upgrades, qui installe automatiquement les mises à jour de sécurité ; certaines exigent un redémarrage (noyau), qui n'est pas automatique. Dans un modèle immuable, on préfère reconstruire régulièrement les instances à partir d'une image récente : c'est plus simple à raisonner que des machines qui se modifient seules.
Les étiquettes deviennent des métadonnées. Les tags d'une instance sont visibles par tout processus de la machine, via /conf. N'y mettez rien de confidentiel. Chez Scaleway, un tag de la forme AUTHORIZED_KEY=... sert même à injecter une clé SSH dans une instance précise : qui peut modifier les tags d'une instance peut donc s'y donner un accès.
En production
- Des images dorées plutôt que des installations au démarrage. Notre cloud-config installe Docker et applique les mises à jour à chaque création : c'est lent (plusieurs minutes) et cela dépend des dépôts d'Ubuntu au moment de la création. En production, on fabrique une image avec tout le nécessaire déjà installé (cours Images de machines avec Packer), et cloud-init ne fait plus que la configuration propre à l'instance. Le démarrage passe de minutes à secondes, ce qui compte quand il faut remplacer des instances en urgence.
- Des groupes plutôt que des instances. Créer
sig-app-1etsig-app-2à la main, c'est déjà mieux que le vieux serveur, mais c'est encore deux animaux. Les groupes d'autoscaling maintiennent un nombre d'instances identiques à partir d'un modèle, en remplacent une qui échoue à sa vérification de santé, et en ajoutent quand la charge monte. Scaleway les propose depuis août 2026 en bêta publique dans les régionsfr-par,nl-amsetpl-waw, avec des modèles d'instances (Instance templates) qui figent le type, l'image, le réseau et le cloud-init ; une bêta n'a pas de SLA, réservez-la aux environnements de test pour l'instant. Les équivalents éprouvés sont les Auto Scaling groups d'AWS, les Virtual Machine Scale Sets d'Azure et les managed instance groups de Google Cloud. - Dimensionner par la mesure. Le surdimensionnement « par sécurité » est la première source de gaspillage du cloud. Partez petit, mesurez (CPU, mémoire, temps volé, latence de l'application), et changez de type : c'est l'affaire d'une ligne quand l'instance se recrée.
- Ou ne plus gérer d'instances du tout. Kubernetes (Kapsule chez Scaleway) prend en charge le placement, le redémarrage et le remplacement des conteneurs sur un ensemble de nœuds, qui sont eux-mêmes des instances gérées en groupe. C'est le choix de Lyneko pour ses propres applications, et l'objet des cours Kubernetes : les fondamentaux et Kapsule. Les instances restent le bon outil pour un service isolé, un bastion, ou un logiciel qui ne se conteneurise pas.
- Tout ce qui est fait ici à la main sera du code. Le cours Terraform et OpenTofu : les fondamentaux décrit ces mêmes instances dans des fichiers versionnés, que l'on relit en demande de fusion avant de les appliquer.
Exercices
1. Lire une facture en puissance (niveau 100). Une instance de développement est utilisée de 9 h à 18 h, du lundi au vendredi. Le soir, la développeuse tape sudo poweroff en partant. Que paie l'équipe la nuit et le week-end ? Que proposez-vous ?
Solution
poweroff depuis le système met l'instance en veille : elle reste allouée sur son hyperviseur et facturée comme une instance en marche, plus son stockage et son IP. L'équipe paie donc 24 heures sur 24. Il faut l'arrêter par l'API (scw instance server stop), ce qui ne laisse que le stockage et l'IP flexible facturés, idéalement par une tâche planifiée le soir et un démarrage le matin. Mieux encore pour un environnement de développement : la supprimer le soir et la recréer le matin à partir de son cloud-config, ce qui ne laisse plus rien de facturé et vérifie chaque jour que la recréation fonctionne.
2. Déboguer un cloud-config (niveau 200). Une collègue vous montre ce fichier. L'instance démarre, mais Signalements ne tourne pas. Trouvez les trois problèmes, dans l'ordre où vous les rencontreriez.
packages:
- docker
runcmd:
- docker run -d -p 8000:8000 ghcr.io/lyneko-formation/signalements:1.2.0
write_files:
- path: /etc/signalements/env
content: DATABASE_URL=postgresql://sig:S3cret!@10.0.0.5/signalementsSolution
- La ligne
#cloud-configmanque en tête : le fichier n'est pas reconnu comme un cloud-config et rien de ce qu'il décrit n'est appliqué ;/var/log/cloud-init.logcontient seulement un avertissement sur des données utilisateur non prises en charge.cloud-init schemal'aurait signalé avant la création. - Une fois la ligne ajoutée, l'installation échoue : sous Ubuntu, Docker s'installe avec le paquet
docker.io; le nomdockerne désigne pas Docker. Le module de paquets échoue, la commandedocker runderuncmdaussi (commande introuvable), etcloud-init statussort avec le code 2 ;/var/log/cloud-init-output.logmontre les deux erreurs. - Le mot de passe de la base est en clair dans les données utilisateur : lisible dans la console, par l'API et par le service de métadonnées. Même corrigé, ce fichier ne doit pas être utilisé ; le secret doit être changé, puisqu'il a déjà été exposé.
Deux remarques de plus : -p 8000:8000 expose l'API sur l'IP publique, et un conteneur lancé par docker run -d sans politique de redémarrage ne revient pas après un redémarrage de la machine.
3. Prouver l'immuabilité (niveau 200). Écrivez un petit script qui, pour une nouvelle version X.Y.Z de Signalements, crée une instance sig-app-1-X-Y-Z en fr-par-1 à partir d'un cloud-config où la version est remplacée, attend la fin de cloud-init, vérifie /sante par SSH, et n'affiche « prête » qu'en cas de succès. Ne supprimez pas l'ancienne instance dans le script : pourquoi ?
Solution
#!/usr/bin/env bash
set -euo pipefail
version="$1" # par exemple 1.3.0
nom="sig-app-1-${version//./-}"
sed "s/signalements:[0-9.]*/signalements:${version}/; s/APP_VERSION=.*/APP_VERSION=${version}/" \
signalements.yaml > "cloud-init-${version}.yaml"
scw instance server create name="$nom" type=PRO2-XXS image=ubuntu_noble zone=fr-par-1 \
cloud-init=@"cloud-init-${version}.yaml" tags.0=app=signalements tags.1=env=prod \
--wait -o json > "$nom.json"
ip=$(jq -r '.public_ips[0].address' "$nom.json")
ssh -o StrictHostKeyChecking=accept-new root@"$ip" cloud-init status --wait
ssh root@"$ip" curl -fsS http://127.0.0.1:8000/sante
echo "$nom prête ($ip)"set -e arrête le script au premier échec : cloud-init status --wait qui renvoie 1 ou 2, ou curl -f qui reçoit une erreur HTTP. L'ancienne instance n'est pas supprimée parce que la bascule du trafic (leçon 6) doit avoir lieu avant, et qu'en cas de problème découvert après la bascule, garder l'ancienne instance quelques heures permet un retour arrière immédiat. La suppression est une étape distincte, une fois la nouvelle version validée en production.
4. Choisir un type (niveau 200). Pour chacun de ces usages, proposez une famille d'instances Scaleway et justifiez par le caractère partagé ou dédié des vCPU : (a) un agent de CI qui compile pendant dix minutes par heure ; (b) une base PostgreSQL autogérée de production ; (c) le bastion SSH de la leçon 6.
Solution
(a) Des vCPU partagés (PLAY2, PRO2, BASIC3) conviennent si les durées de compilation peuvent varier ; si le temps de CI devient un goulet, des vCPU dédiés réguliers ou des instances de calcul (COMPUTE3) réduisent la variance, et l'on peut supprimer l'agent entre deux usages. (b) Des vCPU dédiés (STANDARD3, POP2), parce qu'une base est sensible à la latence et que le temps volé se traduit directement en requêtes lentes ; et souvent, plutôt qu'une base autogérée, une base managée (leçon 2). (c) La plus petite instance partagée possible : un bastion ne fait que relayer des connexions SSH.
Récapitulatif
- Une instance est une machine virtuelle louée, découpée par un hyperviseur dans un serveur physique partagé. Son type fixe vCPU, mémoire, stockage et bande passante.
- Les vCPU partagés sont moins chers mais sujets au temps volé ; les vCPU dédiés donnent des performances constantes. Les gammes récentes de Scaleway démarrent sur un volume bloc, attaché par le réseau.
- Une image est le point de départ du volume racine ; les images publiques se désignent par un libellé (
ubuntu_noble), les images personnalisées (images dorées) se fabriquent à partir d'une instance ou d'un instantané. - Chaque état a son coût : la veille reste facturée comme une instance en marche ; seul l'arrêt par l'API libère le calcul ; les IP flexibles et les volumes survivent à la suppression si on ne les supprime pas explicitement.
- Le service de métadonnées (
169.254.42.42chez Scaleway,169.254.169.254ailleurs) dit à l'instance qui elle est et lui transmet les données utilisateur. - cloud-init applique un cloud-config au premier démarrage, en cinq étapes ;
runcmds'exécute après l'installation des paquets ;cloud-init schemavalide,cloud-init status --waitattend et renvoie un code de sortie exploitable. - Aucun secret dans les données utilisateur : elles sont lisibles par l'API, la console et la machine pendant toute sa vie. Le service de métadonnées est une cible des attaques SSRF (IMDSv2 chez AWS).
- On traite les serveurs comme du bétail, pas comme des animaux de compagnie : infrastructure immuable, on remplace plutôt que de réparer.
Pour aller plus loin
- La documentation de cloud-init, en particulier la référence des modules (avec la fréquence d'exécution de chacun) et la page sur les étapes de démarrage.
- L'article de Chad Fowler, Trash Your Servers and Burn Your Code (2013), et celui de Randy Bias sur l'histoire de la formule pets vs cattle (2016).
- L'article du blog sécurité d'AWS sur IMDSv2, Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities, pour comprendre chaque choix de conception du jeton de session.
- La leçon suivante, Le stockage : bloc, fichier, objet, où Signalements gagne un volume de données et un bucket pour ses pièces jointes.
Sources
- Scaleway, Instances : concepts
- Scaleway, Choosing the best Instance type for your workload
- Scaleway, Choosing between shared and dedicated vCPU Instances
- Scaleway, Understanding Instance states
- Scaleway, How to use cloud-init with Scaleway Instances
- Scaleway, Instances datasheet
- cloud-init, Boot stages
- cloud-init, CLI commands
- cloud-init, Scaleway datasource (documentation et code source DataSourceScaleway.py)
- AWS, Use the Instance Metadata Service to access instance metadata (IMDSv2)
- Randy Bias, The History of Pets vs Cattle and How to Use the Analogy Properly (2016)
- Chad Fowler, Trash Your Servers and Burn Your Code: Immutable Infrastructure and Disposable Components (2013)