Un cluster Kapsule à trois nœuds est prêt en un peu plus de trois minutes, et une seule commande crée le cluster et son pool. Cette leçon construit ce cluster, y déploie une application exposée par un Load Balancer, puis provoque deux incidents pour mesurer ce qui tient : le drain d'un nœud pendant que le service reçoit du trafic, et le remplacement d'un nœud, dont vous verrez qu'il ne s'obtient pas avec la commande qu'on croit. Elle se termine par une destruction dans l'ordre des dépendances, vérifiée famille par famille. Toutes les durées données ici ont été chronométrées le 12 septembre 2026.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un cluster et son pool en une commande, et savoir ce que chaque étape coûte en temps.
- Prouver qu'un service survit au drain d'un nœud, chiffres à l'appui.
- Mesurer la fenêtre de capacité réduite pendant le remplacement d'un nœud.
- Détruire dans l'ordre qui ne laisse aucune ressource orpheline.
Prérequis
Section intitulée « Prérequis »- Un compte Scaleway avec un moyen de paiement validé et un projet dédié au lab.
- La CLI
scw2.62.0 ou plus récente, sur un profil isolé (jamais votre profil courant). kubectlinstallé localement, etjqpour lire les sorties JSON.- Les bases de Kubernetes, Pods, Deployments et Services : ce lab les manipule sans les expliquer, la formation Kubernetes du site s'en charge.
- La lecture de Kubernetes managé sur Scaleway, qui pose les décisions irréversibles de ce volet.
Comment créer un cluster Kapsule en une commande ?
Section intitulée « Comment créer un cluster Kapsule en une commande ? »Une seule commande crée le cluster et son premier pool, parce que scw k8s cluster create accepte des arguments pools.0.*. Créer le cluster puis le pool séparément fonctionne aussi, mais laisse un cluster sans nœud pendant l'intervalle, ce qui n'apporte rien.
-
Créer le cluster et son pool, en capturant l'identifiant.
Fenêtre de terminal CLUSTER_ID=$(scw k8s cluster create \name=demo-kapsule \region=fr-par \version=1.37.0 \cni=cilium \pools.0.name=demo-pool \pools.0.node-type=DEV1-M \pools.0.size=3 \pools.0.zone=fr-par-1 \-o json | jq -r '.id')echo "cluster : ${CLUSTER_ID}"La sortie doit afficher un identifiant UUID. La commande rend la main en 2 secondes : l'API accepte la demande et provisionne en arrière-plan.
-
Attendre que le control plane réponde.
Fenêtre de terminal until [ "$(scw k8s cluster get "${CLUSTER_ID}" region=fr-par -o json \| jq -r '.status')" = "ready" ]; do printf '.'; sleep 10; doneecho " control plane prêt"La sortie doit finir par
control plane prêt. Mesuré le 2026-09-12 : 106 secondes. -
Installer le kubeconfig, sans quoi
kubectlne sait pas à qui parler.Fenêtre de terminal scw k8s kubeconfig install "${CLUSTER_ID}" region=fr-parkubectl config current-contextLa sortie doit afficher
demo-kapsule-suivi de l'identifiant du cluster. Le contexte porte le nom du cluster et son identifiant, etinstallle rend courant d'office : il n'y a rien à basculer à la main. Unkubectl config use-context admin@demo-kapsule, calqué sur d'autres fournisseurs, échouerait avecno context exists with the name. La commande fusionne la configuration dans votre~/.kube/configexistant, elle ne l'écrase pas. -
Attendre les trois nœuds, en comparant un champ plutôt qu'en cherchant une chaîne.
Fenêtre de terminal until [ "$(kubectl get nodes --no-headers 2>/dev/null \| awk '$2=="Ready"' | wc -l)" = "3" ]; do printf '.'; sleep 10; donekubectl get nodesLa sortie doit afficher trois nœuds
Readyen versionv1.37.0. Mesuré : 109 secondes de plus, soit un peu plus de trois minutes depuis la première commande.
Pourquoi certains types de nœuds sont-ils refusés ?
Section intitulée « Pourquoi certains types de nœuds sont-ils refusés ? »Kapsule refuse les types d'Instance trop petits, et la liste n'est pas dans la documentation produit : elle est dans l'aide de la commande. scw k8s pool create -h la donne noir sur blanc :
node-type: Nodes with insufficient memory are not eligible (DEV1-S, PLAY2-PICO, STARDUST)
La mémoire est le seul critère d'exclusion, et il vaut la peine de le vérifier plutôt que de l'étendre. On lit souvent que le volume système disqualifierait DEV1-S une seconde fois, puisque la FAQ Kubernetes recommande 20 Go minimum. L'API dit autre chose :
scw instance server-type list zone=fr-par-1 -o json \ | jq -r '.[] | select(.name | startswith("DEV1")) | "\(.name) volume local max : \(.local_volume_max_size / 1000000000) Go"'La sortie doit afficher 20 Go pour DEV1-S et 40 Go pour DEV1-M. DEV1-S est donc exactement au minimum recommandé, pas en dessous : l'argument du volume ne tient pas, seule la mémoire l'exclut.
Attention à l'unité en lisant ce champ. Scaleway compte en Go décimaux, pas en Gio : diviser par 2^30 donne 18 et 37, des valeurs justes mais dans une autre unité, qu'on publie alors à tort comme des Go. C'est une erreur facile, et elle change la conclusion.
Vérifiez la disponibilité réelle avant de choisir, car un type au catalogue peut être en pénurie :
scw instance server-type list zone=fr-par-1 -o json \ | jq -r '.[] | select(.name | startswith("DEV1") or startswith("GP1")) | "\(.name) \(.availability) \(.ram / 1073741824 | floor) Go"'La sortie doit afficher available sur les gammes DEV1 et GP1. Un type en scarce ou shortage peut faire échouer la création : les gammes PLAY2 et PRO2 sortaient en pénurie le 2026-09-12.
DEV1-M est le plus petit type retenu pour ce lab : 3 vCPU, 4 Go, 0,020196 €/h, volume système en Local Storage donc sans Block Storage facturé en plus.
Déployer une application et l'exposer
Section intitulée « Déployer une application et l'exposer »Un Service de type LoadBalancer fait naître un vrai Load Balancer facturé, que rien dans votre manifeste ne nomme. C'est le cloud controller de Scaleway qui le provisionne, et c'est ce qui rend l'ordre de destruction si important.
Déployez une application sur trois répliques, réparties entre les nœuds :
cat > app.yaml <<'YAML'apiVersion: apps/v1kind: Deploymentmetadata: name: demospec: replicas: 3 selector: matchLabels: { app: demo } template: metadata: labels: { app: demo } spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: { app: demo } containers: - name: web image: nginx:1.29.1@sha256:8adbdcb969e2676478ee2c7ad333956f0c8e0e4c5a7463f4611d7a2e7a7ff5dc ports: - containerPort: 80---apiVersion: v1kind: Servicemetadata: name: demo annotations: # Sans cette annotation, le CCM renseigne une adresse IP et non un nom. service.beta.kubernetes.io/scw-loadbalancer-use-hostname: "true"spec: type: LoadBalancer selector: { app: demo } ports: - port: 80 targetPort: 80YAML
kubectl apply -f app.yamlkubectl rollout status deployment/demo --timeout=180sLa sortie doit finir par deployment "demo" successfully rolled out. Mesuré le 2026-09-12 : 7 secondes, l'image étant déjà en cache sur les nœuds.
Récupérez ensuite l'adresse attribuée par le Load Balancer :
until [ -n "$(kubectl get svc demo -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}')" ]; do printf '.'; sleep 5doneADRESSE=$(kubectl get svc demo \ -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}')echo "adresse : ${ADRESSE}"La sortie doit afficher une adresse en .lb.fr-par.scw.cloud. Mesuré : 10 secondes après l'application du manifeste.
C'est l'annotation du manifeste qui produit ce nom. Par défaut, le cloud controller renseigne status.loadBalancer.ingress[0].ip, donc une adresse IP brute ; service.beta.kubernetes.io/scw-loadbalancer-use-hostname: "true" lui fait renseigner un nom à la place. Préférez le nom : une adresse IP de Load Balancer se recycle, un enregistrement DNS qui la cite en dur finit par pointer ailleurs.
Le service survit-il au drain d'un nœud ?
Section intitulée « Le service survit-il au drain d'un nœud ? »Oui, et la preuve se fait en envoyant du trafic pendant l'opération plutôt qu'en le supposant. Lancez la boucle de mesure, puis drainez un nœud pendant qu'elle tourne.
-
Démarrer la mesure dans un terminal, sur 60 requêtes.
Fenêtre de terminal for i in $(seq 1 60); docurl -s -o /dev/null -w '%{http_code}\n' --max-time 5 "http://${ADRESSE}/"sleep 1done | sort | uniq -c -
Drainer un nœud dans un second terminal.
Fenêtre de terminal NOEUD=$(kubectl get nodes --no-headers | awk '$2=="Ready"{print $1; exit}')kubectl drain "${NOEUD}" --ignore-daemonsets --delete-emptydir-data --timeout=120sLa sortie doit afficher
node/... drained. Résultat mesuré le 2026-09-12 : 60 requêtes, 60 codes 200, aucune coupure. -
Remettre le nœud en service.
Fenêtre de terminal kubectl uncordon "${NOEUD}"kubectl get nodesLa sortie ne doit plus afficher
SchedulingDisabled.
drain refuse de partir sans --ignore-daemonsets, et il nomme exactement ce que Scaleway injecte :
ignoring DaemonSet-managed Pods: kube-system/cilium-xxxxx,kube-system/csi-node-xxxxx, kube-system/konnectivity-agent-xxxxxCes trois-là appartiennent au nœud, pas à votre application : cilium porte le réseau, csi-node attache les volumes Block, konnectivity-agent maintient le lien avec un control plane qui vit ailleurs. Les évincer n'aurait aucun sens, puisqu'ils seraient recréés immédiatement sur le même nœud.
Quelle contrainte de répartition choisir ?
Section intitulée « Quelle contrainte de répartition choisir ? »Lisez ce tableau par la colonne du milieu : elle dit dans quelle situation chaque réglage est le bon, et aucun des deux n'est meilleur dans l'absolu. Le choix dépend du nombre de nœuds de marge dont vous disposez, pas d'une bonne pratique générale.
| Réglage | Quand le choisir | Ce que vous acceptez en échange |
|---|---|---|
whenUnsatisfiable: ScheduleAnyway | cluster sans marge, moins de nœuds que de répliques plus un | une répartition temporairement déséquilibrée après un incident |
whenUnsatisfiable: DoNotSchedule | cluster avec au moins un nœud de marge | des pods Pending dès que la contrainte devient insatisfiable |
DoNotSchedule + PodDisruptionBudget | production, charge qui ne tolère pas le déséquilibre | un drain qui bloque plutôt que de dégrader la répartition |
Le troisième cas est celui qu'on vise en production, et il change la nature de l'incident : au lieu de laisser un pod Pending en silence, le PodDisruptionBudget fait échouer le drain, ce qui force une décision humaine au lieu d'une dégradation invisible.
Supprimer un nœud ou le remplacer : deux commandes, deux effets
Section intitulée « Supprimer un nœud ou le remplacer : deux commandes, deux effets »scw k8s node delete ne remplace rien : il retire le nœud et réduit le pool de 1. C'est scw k8s node replace qui remplace. La confusion est facile, parce que l'aide de delete porte encore une ligne d'argument périmée, « node-id : ID of the node to replace », qui contredit sa propre description.
Lisez la première phrase de l'aide, celle qui décrit la commande :
scw k8s node delete -h | head -1La sortie doit afficher Delete a specific Node. Pool size is reduced by 1.
Mesuré le 2026-09-12 sur un cluster 1.37.0 à trois nœuds DEV1-M, en relevant la taille du pool avant et après, ce qui est le seul relevé qui tranche :
| Opération | Pool avant | Pool après | Ancien sorti de l'API | 3 nœuds Ready | Remplacement |
|---|---|---|---|---|---|
scw k8s node delete | 3 | 2 | 10 s | jamais en 600 s | non |
scw k8s node replace | 3 | 3 | 11 s | 125 s | oui |
Les dix premières secondes sont identiques, et c'est ce qui rend l'erreur si facile : les deux commandes drainent le nœud et le sortent de l'API Kubernetes au même rythme. La divergence n'apparaît qu'ensuite, quand l'une provisionne un remplaçant et l'autre vous laisse avec un nœud de moins.
POOL_ID=$(scw k8s pool list cluster-id="${CLUSTER_ID}" region=fr-par -o json | jq -r '.[0].id')NODE_ID=$(scw k8s node list cluster-id="${CLUSTER_ID}" region=fr-par -o json | jq -r '.[0].id')
scw k8s node replace node-id="${NODE_ID}" region=fr-par -o json | jq -r '.status'scw k8s pool get "${POOL_ID}" region=fr-par -o json | jq -r '"pool à \(.size) nœud(s)"'La première sortie doit afficher deleting, et la seconde pool à 3 nœud(s) : le pool ne bouge pas avec replace. Avec delete, la même lecture afficherait pool à 2 nœud(s).
Le remplacement complet a pris 125 secondes, dont une centaine où le cluster tourne avec un nœud de moins. C'est cette fenêtre qui décide du dimensionnement.
Le choix entre les deux commandes est en réalité un choix d'exploitation, et il mérite d'être fait consciemment plutôt que subi. replace est l'outil du remplacement planifié : renouveler une machine, sortir un nœud suspect, appliquer une image système plus récente, sans jamais perdre de capacité durable. delete est l'outil de la réduction volontaire : vous décidez que le pool est trop grand et vous l'assumez. Les employer l'un pour l'autre produit deux incidents opposés. Un delete pris pour un remplacement laisse silencieusement un cluster sous-dimensionné, et personne ne le remarque tant que la charge reste modérée ; un replace appliqué là où l'on voulait réduire fait revenir un nœud qu'on croyait supprimé, et la facture avec lui. Dans les deux cas, la seule vérification qui tranche est la même : relire la taille du pool après l'opération.
Jusqu'où peut-on agrandir un pool ?
Section intitulée « Jusqu'où peut-on agrandir un pool ? »Le plafond est de 150 nœuds sur un control plane mutualisé, et l'API le dit elle-même sans rien provisionner. Provoquez le refus, c'est gratuit et instructif :
POOL_ID=$(scw k8s pool list cluster-id="${CLUSTER_ID}" region=fr-par -o json | jq -r '.[0].id')scw k8s pool update "${POOL_ID}" region=fr-par size=200 -o jsonLa sortie doit afficher un refus contenant :
a kapsule cluster can't have more than 150 nodesRien n'a été créé, rien n'a été facturé, le pool n'a pas bougé. C'est la signature d'une contrainte structurelle du produit, refusée à la validation des arguments, et non d'un quota de compte qui se négocierait avec le support. La distinction compte : une contrainte produit impose de passer à un control plane dédié ou de répartir sur plusieurs clusters.
Nettoyer
Section intitulée « Nettoyer »L'ordre n'est pas arbitraire et ne se devine pas : le Service part en premier, sinon le Load Balancer devient orphelin. Supprimer le cluster d'abord laisse un Load Balancer sans personne pour le réclamer, et sa facturation continue.
-
Supprimer le
Service, pour que le cloud controller reprenne son Load Balancer.Fenêtre de terminal kubectl delete -f app.yaml --ignore-not-found=trueuntil [ "$(scw lb lb list zone=fr-par-1 -o json | jq -r 'length')" = "0" ]; doprintf '.'; sleep 10doneecho " load balancer libéré"La suppression est asynchrone : on attend la disparition effective, on ne la suppose pas.
-
Supprimer le cluster, avec ses ressources associées.
Fenêtre de terminal scw k8s cluster delete "${CLUSTER_ID}" region=fr-par \with-additional-resources=true -o json | jq -r '.status'La sortie doit afficher
deleting. L'aide de la commande dit exactement ce que ce drapeau emporte : tous les volumes, y compris ceux de typeretain, les Private Networks devenus vides, et les Load Balancers dont le nom commence par l'identifiant du cluster. Sans lui, ces ressources survivent au cluster et se facturent en silence. -
Vérifier le retour à zéro, famille par famille.
Fenêtre de terminal for f in "k8s cluster list region=fr-par" "lb lb list zone=fr-par-1" \"instance ip list zone=fr-par-1" "block volume list zone=fr-par-1" \"block snapshot list zone=fr-par-1" "vpc private-network list region=fr-par"; don=$(eval "scw ${f} -o json" | jq -r 'if type=="array" then length else 0 end')printf '%-40s : %s\n' "${f%% *} ${f#* }" "${n}"doneChaque ligne doit afficher 0. Une seule valeur non nulle signifie qu'une ressource facturée a survécu.
Limites, quotas et plafonds
Section intitulée « Limites, quotas et plafonds »Ces chiffres décident du dimensionnement bien avant la première commande. Ils viennent de la documentation produit, de la page des quotas, ou d'un relevé daté sur ce lab.
| Plafond | Valeur | Source |
|---|---|---|
| Nœuds par cluster, control plane mutualisé | 150 | refus de l'API, 2026-09-12 |
| Taille d'etcd, control plane mutualisé | 55 Mo | doc des offres de control plane |
| Clusters Kapsule par Organisation | 20, ou 40 identité validée | page des quotas |
| Volume système recommandé | 20 Go minimum, 100 Go confortable | FAQ Kubernetes |
| Volume local maximum sur DEV1-M | 40 Go décimaux | scw instance server-type list, 2026-09-12 |
| Volume local maximum sur DEV1-S | 20 Go décimaux, soit le minimum recommandé | scw instance server-type list, 2026-09-12 |
| Types refusés par Kapsule | DEV1-S, PLAY2-PICO, STARDUST | aide de scw k8s pool create |
Dépannage
Section intitulée « Dépannage »Ces quatre symptômes couvrent l'essentiel de ce qui bloque un premier cluster. Les deux premiers portent le message exact tel que l'API ou kubectl le renvoient.
| Symptôme | Cause | Solution |
|---|---|---|
a kapsule cluster can't have more than 150 nodes | contrainte produit du control plane mutualisé | passer en control plane dédié, ou répartir sur plusieurs clusters |
Le pool a un nœud de moins après un scw k8s node delete | delete réduit le pool de 1, il ne remplace pas | employer scw k8s node replace, ou remonter la taille du pool |
Un pod reste Pending après un drain | topologySpreadConstraints en DoNotSchedule devient insatisfiable | passer à ScheduleAnyway, ou ajouter un nœud de marge |
L'aide de delete dit « ID of the node to replace » | ligne d'argument périmée, contredite par la description de la commande | lire la première phrase : « Pool size is reduced by 1 » |
Le curl échoue alors que l'adresse existe | les backends n'ont pas fini leurs contrôles de santé | attendre une réponse HTTP, pas l'attribution de l'adresse |
Antipatterns à éviter
Section intitulée « Antipatterns à éviter »Ces quatre erreurs traitent un cluster managé comme un cluster qu'on possède. Kapsule remplace, redémarre et supprime les nœuds quand il le juge utile : toute pratique qui suppose un nœud stable finit par échouer.
| Antipattern | Conséquence | Discipline |
|---|---|---|
| Corriger un nœud en SSH | la correction disparaît au premier remplacement | tout passer par l'API Kubernetes, et désactiver le serveur SSH des nœuds |
| Attendre une adresse plutôt qu'une réponse | le déploiement se déclare fini avant que le service ne réponde | sonder l'application en HTTP jusqu'au premier code 200 |
Supprimer le cluster avant le Service | le Load Balancer devient orphelin et continue d'être facturé | supprimer le Service, attendre, puis le cluster |
| Conclure d'un code de retour que tout est détruit | une suppression asynchrone rend 0 sans avoir abouti | relister chaque famille jusqu'à obtenir zéro |
Ce cluster sous l'angle Well-Architected
Section intitulée « Ce cluster sous l'angle Well-Architected »Les trois piliers posent les questions qu'un auditeur poserait après avoir lu ce lab.
Fiabilité
Section intitulée « Fiabilité »Combien de temps le cluster fonctionne-t-il en capacité réduite ? La mesure de cette leçon répond : environ 115 secondes lors d'un node replace, entre la sortie de l'ancien à la 10e seconde et le retour à trois nœuds à la 125e. Et indéfiniment après un node delete, qui ne remplace rien.
Discipline : dimensionner pour N-1 nœuds, poser un PodDisruptionBudget sur toute charge qui compte, et préférer ScheduleAnyway tant qu'il n'y a pas de nœud de marge.
Excellence opérationnelle
Section intitulée « Excellence opérationnelle »Vos scripts d'attente mesurent-ils l'état que vous croyez ? Ce lab a produit deux chiffres faux à la suite avant de produire le bon, sans jamais lever d'erreur.
Discipline : comparer un champ plutôt que chercher une sous-chaîne, attendre la disparition d'une identité plutôt que le bon compte, et se méfier d'une mesure qui tombe juste du premier coup.
Optimisation des coûts
Section intitulée « Optimisation des coûts »Qui paiera le Load Balancer que personne n'a créé à la main ? Un Service de type LoadBalancer en fait provisionner un vrai, facturé à l'heure, absent de tous vos manifestes.
Discipline : supprimer les Service avant le cluster, employer with-additional-resources=true, et terminer par une vérification de sortie sur les six familles que liste la boucle de nettoyage de cette page.
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
À retenir
Section intitulée « À retenir »- Une seule commande suffit :
scw k8s cluster createaccepte des argumentspools.0.*pour créer le pool dans la foulée. - Un cluster à trois nœuds est prêt en un peu plus de trois minutes : 106 s pour le control plane, 109 s de plus pour les nœuds.
scw k8s kubeconfig installfusionne la configuration dans votre~/.kube/config, il ne l'écrase pas.DEV1-S,PLAY2-PICOetSTARDUSTsont refusés par Kapsule, faute de mémoire suffisante.drainexige--ignore-daemonsetset citecilium,csi-nodeetkonnectivity-agent, qui appartiennent au nœud.scw k8s node replaceremplace en 125 secondes et laisse le pool à sa taille ;node deleteréduit le pool de 1 et ne remplace jamais.- Un pool créé par
cluster createsort avecautoscalingetautohealingàfalse: aucun automatisme sans le demander. - Le plafond de 150 nœuds se vérifie gratuitement, l'API refusant à la validation des arguments.
- Le
Servicese supprime avant le cluster, sinon le Load Balancer reste orphelin et facturé. - Comparer un champ avec
awk '$2=="Ready"', jamais chercherReadyavecgrep, qui attrape aussiNotReady.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Kosmos : Explique ce que le même control plane devient quand les nœuds ne sont plus chez Scaleway.
- Volumes persistants : Ajoute l'état à ce cluster, et montre ce que le remplacement d'un nœud lui fait.
- Monter un cluster de version : Reprend ce cluster et le fait changer de version pendant qu'il reçoit du trafic.
Ressources externes
Section intitulée « Ressources externes »- Démarrage rapide Kubernetes Kapsule : la procédure officielle de création, à confronter aux durées mesurées ici.
- FAQ Kubernetes Scaleway : la source sur le rôle de
kapsule-agent, le volume système recommandé et les effets d'un renommage de cluster. - Dépôt du cloud controller manager : le code qui traduit un
Servicede typeLoadBalanceren Load Balancer facturé, et les annotations qu'il accepte. - Offres de control plane Kubernetes : le tableau officiel des plafonds de nœuds et d'etcd par type de control plane.