Aller au contenu
Répartiteurs de charge et stockage intégrés

Répartiteurs de charge et stockage intégrés

200 Pratiquer ⏱ 1 h 25 kuberneteskapsulescaleway

À la fin, vous saurez

  • Créer un Service LoadBalancer et régler le répartiteur Scaleway par annotations (type, zone, protocole PROXY, vérification de santé)
  • Garder la même adresse IP publique à travers la suppression et la recréation d'un Service
  • Justifier un seul répartiteur devant l'ingress plutôt qu'un par Service
  • Choisir une StorageClass de Block Storage (débit d'opérations, politique de récupération, liaison à la zone) et la définir explicitement
  • Chiffrer un volume avec LUKS, prendre un instantané et agrandir un volume à chaud
  • Décider quand utiliser File Storage plutôt que Block Storage pour un volume partagé

Prérequis

Testé avec kubernetes 1.36 scaleway-cli 2.62.0 , vérifié le 5 octobre 2026

Pourquoi

Un cluster Kubernetes ne sait rien d'un répartiteur de charge ni d'un disque : ce sont des ressources du fournisseur. Sur Kapsule, deux composants que Scaleway installe font la traduction (leçon 1) : le cloud controller manager (CCM) transforme un Service de type LoadBalancer en répartiteur Scaleway, et le pilote CSI transforme un PersistentVolumeClaim en volume Block Storage. Les cours Services et DNS et Volumes et stockage persistant ont posé les objets Kubernetes ; il reste à connaître ce que ces deux composants font de vos objets, et comment les régler.

Sans cela, trois problèmes se rencontrent. Le premier, l'adresse du site change : quelqu'un supprime le Service pour le modifier, l'adresse IP disparaît avec lui, et le DNS des métropoles pointe dans le vide. Le deuxième, la facture grossit : chaque Service LoadBalancer est un répartiteur payant, et vingt Services font vingt répartiteurs. Le troisième, un pod de base de données qui ne redémarre pas après la perte d'un nœud, parce que son volume vit dans une autre zone que celle où l'ordonnanceur l'envoie. Cette leçon les traite, et dit aussi ce que les intégrations ne font pas (par exemple, partager un volume en écriture entre plusieurs pods).

Les concepts

Du Service au répartiteur : le CCM

Quand vous créez un Service type: LoadBalancer, le CCM de Scaleway crée un répartiteur de charge, avec un backend qui désigne les nœuds du cluster (par leurs adresses privées, le répartiteur étant rattaché au réseau privé du cluster, voir la leçon 2), un frontend par port du Service, et une vérification de santé. Il l'observe ensuite en continu : changez le Service, le répartiteur suit ; supprimez-le, le répartiteur est supprimé. L'adresse apparaît dans status.loadBalancer.ingress du Service.

Important

La documentation de Scaleway est catégorique : un répartiteur de cluster se crée et se modifie par le CCM, jamais par la console ou l'API. Un répartiteur créé à la main puis « adopté » par un Service, ou modifié à la main, sera réconcilié, c'est-à-dire écrasé, par le contrôleur. La seule exception est l'annotation scw-loadbalancer-externally-managed, que le dépôt marque comme expérimentale.

Le comportement se règle par des annotations du Service, de la forme service.beta.kubernetes.io/scw-loadbalancer-…. Une annotation absente laisse le CCM appliquer sa configuration par défaut ; une valeur invalide fait échouer la réconciliation du Service. Les principales, relevées dans le dépôt du CCM :

AnnotationRôleDéfaut ou remarque
…-typeOffre du répartiteur (LB-S, LB-GP-M…)variable LB_DEFAULT_TYPE du CCM, sinon défaut de Scaleway
…-zoneZone du répartiteurpremière zone de la région du cluster
…-ip-idsAdresses IP réservées à utiliserun IPv4 au moins ; changer les adresses recrée le répartiteur
…-privateRépartiteur privé (sans IP publique)public par défaut
…-pn-idsRéseaux privés rattachéscelui du cluster (PN_ID)
…-proxy-protocol-v1, …-proxy-protocol-v2Protocole PROXY vers les nœuds (true, * ou liste de ports)désactivé
…-health-check-type, -delay, -timeout, -max-retries, -http-uri…Vérification de santétcp, délai 5s ; type par port possible (80:http;443,8443:https)
…-health-check-send-proxyPROXY aussi pour la vérificationfalse
…-health-check-from-serviceUtiliser le healthCheckNodePort du Service (HTTP /healthz, 200)pour externalTrafficPolicy: Local
…-timeout-client, -server, -connect, -tunnel, -queueDélaisvoir le cours sur le répartiteur
…-max-connections, -max-retries, -redispatch-attempt-count, -on-marked-down-action, -failover-hostProtection des backendsmax-retries : 3
…-enable-access-logs, …-enable-http3, …-connection-rate-limitJournaux d'accès, HTTP/3, limite de débitfalse, false, aucune
…-target-node-labelsNe viser que les nœuds portant ces étiquettes (clé=val,clé=val)tous les nœuds
…-use-hostnameAnnoncer le nom d'hôte du répartiteur plutôt que l'IPpour le trafic interne

Plusieurs annotations anciennes sont obsolètes (send-proxy-v2, remplacée par proxy-protocol-v2 ; force-internal-ip). Le cours Le répartiteur de charge en profondeur explique chaque délai et chaque protection ; ici, c'est leur expression en annotation qui compte.

Garder l'adresse IP stable

Par défaut, l'adresse du répartiteur est éphémère : créée avec le Service, elle est supprimée avec lui, et ne peut pas être récupérée. Pour la garder, on réserve une adresse IP flexible de répartiteur dans une zone, et on la désigne par son identifiant dans l'annotation …-ip-ids. Elle reste alors dans le compte quand le Service disparaît, et peut resservir. Une mise en garde du dépôt : une adresse de répartiteur n'est pas une adresse d'instance, on ne peut pas utiliser l'une pour l'autre.

Le champ standard spec.loadBalancerIP a servi à cela, mais Kubernetes l'a déprécié ; l'annotation ip-ids a priorité dessus. Changer l'adresse d'un répartiteur existant le recrée (une interruption de service est à prévoir), et l'adresse doit se trouver dans la zone du répartiteur.

Le dépôt décrit aussi comment convertir une adresse éphémère en adresse réservée sans recréer le Service (exercice 1).

Un répartiteur pour l'ingress, pas un par Service

Chaque Service LoadBalancer crée son répartiteur, avec son adresse et sa facture : la documentation de Scaleway le dit, chaque Service supplémentaire exige son propre répartiteur externe. Le schéma courant, et celui de Lyneko, est donc :

    flowchart LR
  I["Internet"] --> LB["1 répartiteur Scaleway<br/>(créé par le CCM)"]
  LB --> T["Traefik<br/>(Service LoadBalancer)"]
  T -->|"hôte, chemin"| A["Service Signalements"]
  T --> B["Service Outils"]
  T --> C["Service ..."]
  

Un seul Service LoadBalancer (celui de Traefik) est exposé ; tous les autres sont de type ClusterIP et reçoivent leur trafic de Traefik, qui route par nom d'hôte et par chemin (cours Gateway API en production). Le TLS se termine sur Traefik avec les certificats de cert-manager. On garde un Service LoadBalancer distinct pour ce qui n'est pas du HTTP (un flux TCP propre, un port spécifique, un service qui exige une adresse dédiée), et on documente pourquoi.

Conserver l'adresse du client

Le répartiteur voit l'adresse du client ; Traefik, derrière lui, voit celle du répartiteur. Deux moyens de la transmettre (leçon sur le répartiteur) : l'en-tête X-Forwarded-For en HTTP, ou le protocole PROXY pour du TCP. Sur Kapsule, activer PROXY demande de le faire des deux côtés : l'annotation proxy-protocol-v2 sur le Service, et dans Traefik l'option entryPoints.<nom>.proxyProtocol.trustedIPs listant les adresses autorisées à l'envoyer (la documentation de Traefik traite ce cas sous « ProxyProtocol and Load-Balancers »). Les adresses à autoriser sont celles du répartiteur dans le réseau privé. Le dépôt précise un effet annexe : quand PROXY est actif sur tous les ports, le CCM positionne ipMode: Proxy sur le Service, ce qui empêche kube-proxy de court-circuiter le répartiteur pour le trafic interne.

Le stockage : le pilote CSI

Le pilote csi.scaleway.com est préinstallé sur tout cluster Kapsule (la commande kubectl get csidriver le confirme). Il gère le Block Storage de Scaleway : à chaque PersistentVolumeClaim traité par une de ses classes, il crée un volume dans l'API, l'attache à l'instance du nœud où le pod est ordonnancé, puis le formate (ext4 par défaut, paramètre csi.storage.k8s.io/fstype) et le monte. Le volume est un volume à accès unique (ReadWriteOnce) : il se monte en écriture sur un nœud à la fois.

Les classes préconfigurées, d'après la documentation de Scaleway et le dépôt du pilote, sont :

  • sbs-default, la classe par défaut ;
  • sbs-5k et sbs-15k, qui fixent le niveau de performances à 5 000 ou 15 000 opérations d'entrée-sortie par seconde (IOPS). Le dépôt précise que, sans paramètre iops, les volumes ont 5 000 IOPS, et que le pilote est bâti sur l'offre « faible latence » qui monte jusqu'à 15 000.

D'anciens clusters ou pages montrent des classes scw-bssd et scw-bssd-retain : lisez les classes de votre cluster (kubectl get storageclass -o yaml) plutôt que de supposer.

Warning

Les sources consultées ne s'accordent pas sur le mode de liaison et la politique de récupération des classes préinstallées : les exemples du dépôt du pilote utilisent Immediate, d'anciennes pages de Scaleway montrent WaitForFirstConsumer et Delete. Ne vous fiez ni à l'un ni à l'autre : définissez vos propres classes, avec ces deux champs écrits, pour la production.

Zones et WaitForFirstConsumer

Un volume Block Storage vit dans une zone (cours Volumes et stockage persistant). Le pilote l'exprime par la topologie topology.csi.scaleway.com/zone. Si le volume est créé dès la création du PVC (mode Immediate), il l'est dans une zone choisie sans savoir où ira le pod ; le pod peut ensuite être contraint, par ses ressources ou sa répartition, vers une autre zone, et reste Pending. Avec WaitForFirstConsumer, la création attend l'ordonnancement du premier pod et place le volume dans la zone de son nœud. Scaleway précise que ce mode est requis pour les clusters multi-zones qui ont des volumes persistants. Pour forcer une zone précise, le pilote lit allowedTopologies dans la classe.

La politique de récupération

Delete supprime le volume réel avec le PVC ; Retain le conserve, en état Released, jusqu'à ce qu'un humain le récupère ou le supprime. Pour des données que l'on ne veut pas perdre sur un kubectl delete namespace, ou un élagage d'Argo CD, Retain est le choix prudent, avec son coût : des volumes orphelins à nettoyer, et facturés tant qu'ils existent. Rappel de la leçon 2 : scw k8s cluster delete … with-additional-resources=true supprime aussi les volumes en politique Retain.

Chiffrement, instantanés, agrandissement

  • Chiffrement au repos. Le pilote chiffre un volume avec LUKS (Cryptsetup) quand la classe porte encrypted: "true". La phrase secrète vient d'un Secret Kubernetes, désigné dans la classe par csi.storage.k8s.io/node-stage-secret-name et …-namespace (et, pour l'agrandissement, par node-expand-secret-…), sous la clé encryptionPassphrase. L'agrandissement d'un volume chiffré demande la fonctionnalité CSINodeExpandSecret (par défaut depuis Kubernetes 1.27). Le secret par volume (per volume secret) évite une phrase unique par classe.
  • Instantanés. Le pilote implémente les instantanés CSI : un objet VolumeSnapshot crée un instantané du volume, et un nouveau PVC peut en naître par dataSource. Il faut que les CRD de snapshot et leur contrôleur soient présents (kubectl get volumesnapshotclass l'indique) ; la classe d'instantané des exemples du dépôt s'appelle scw-snapshot. Un instantané n'est pas une sauvegarde au sens de la reprise après sinistre.
  • Agrandissement à chaud, vers le haut seulement, par augmentation du storage du PVC si la classe porte allowVolumeExpansion: true. Le dépôt précise : « en ligne », sans détacher le volume ; jamais de réduction.
  • Autres fonctions : volumes bruts (volumeMode: Block), statistiques d'usage exposées par le kubelet, import de volumes et d'instantanés existants (le volume est désigné par fr-par-1/<identifiant>).

Les limites de Block Storage s'appliquent : la FAQ de Scaleway donne 15 volumes de données au plus par instance (16 avec le volume de démarrage) et une taille maximale de 15 To par volume. Un nœud ne peut donc pas porter plus de quinze PVC, quel que soit le nombre de pods que ses ressources permettraient.

Ce qui n'existe pas : le partage en écriture

Un volume Block Storage est ReadWriteOnce : deux pods sur deux nœuds ne peuvent pas l'écrire en même temps. Pour du partage (téléversements de photos lus par plusieurs réplicas, répertoire de rapports), il y a trois voies :

  • File Storage de Scaleway, avec son pilote CSI dédié : classe sfs-standard, modes ReadWriteMany, ReadOnlyMany et ReadWriteOnce, agrandissement à chaud, volumes de 25 Go à 50 To, performances proportionnelles à la capacité. Conditions notées par la documentation (validée en juillet 2026) : le cluster doit porter l'étiquette scw-filestorage-csi au niveau cluster (sans effet au niveau d'un pool), et le DaemonSet filestorage-csi-node n'apparaît que s'il existe au moins un pool d'instances POP2. L'accès est régional (la topologie est la région), non zonal ;
  • le stockage objet de Scaleway (API S3), qui n'est pas un volume : l'application doit l'utiliser comme tel (c'est le choix de Signalements pour les photos, cours Le stockage : bloc, fichier, objet) ;
  • une base de données ou un service managé (sig-db), plutôt qu'un fichier partagé.

En pratique

Les commandes et manifestes ont été vérifiés dans l'aide de la CLI 2.62.0 et dans les dépôts du CCM et du pilote ; les manifestes ont été contrôlés pour leur syntaxe YAML sans être appliqués. Les ressources de cette section sont facturées ; la dernière sous-section les supprime.

Réserver l'adresse de Traefik et exposer le Service

$ scw lb ip create zone=fr-par-1 tags.0=signalements-prod -o json | jq -r '.id, .ip_address'

La commande renvoie l'identifiant et l'adresse de l'IP flexible : notez l'identifiant pour l'annotation, et l'adresse pour le DNS (cours DNS et courriel). Voici le Service de Traefik, tel que Lyneko l'exprime pour Signalements :

apiVersion: v1
kind: Service
metadata:
  name: traefik
  namespace: traefik
  annotations:
    service.beta.kubernetes.io/scw-loadbalancer-zone: "fr-par-1"
    service.beta.kubernetes.io/scw-loadbalancer-type: "LB-S"
    service.beta.kubernetes.io/scw-loadbalancer-ip-ids: "<identifiant de l'IP réservée>"
    service.beta.kubernetes.io/scw-loadbalancer-proxy-protocol-v2: "*"
    service.beta.kubernetes.io/scw-loadbalancer-health-check-type: "tcp"
    service.beta.kubernetes.io/scw-loadbalancer-health-check-delay: "5s"
    service.beta.kubernetes.io/scw-loadbalancer-target-node-labels: "lyneko.com/profil=general"
spec:
  type: LoadBalancer
  externalTrafficPolicy: Cluster
  selector:
    app.kubernetes.io/name: traefik
  ports:
    - {name: web, port: 80, targetPort: web}
    - {name: websecure, port: 443, targetPort: websecure}
  • zone, type : le répartiteur est zonal, on le place explicitement plutôt que de dépendre de la première zone de la région.
  • ip-ids : l'adresse réservée, conservée dans le compte à la suppression du Service.
  • proxy-protocol-v2: "*" : PROXY sur tous les ports ; Traefik doit l'attendre, sinon toutes les connexions échouent.
  • target-node-labels : seuls les nœuds du pool général (étiquette de la leçon 3) servent de backends.
  • externalTrafficPolicy: Cluster : chaque nœud accepte le trafic et le transfère, quitte à un saut de plus. Local ne l'envoie qu'aux nœuds portant un pod Traefik et conserve l'adresse du client, avec health-check-from-service.

Appliquez, puis regardez ce que fait le CCM :

$ kubectl apply -f service-traefik.yaml
$ kubectl -n traefik get svc traefik -w
$ kubectl -n traefik describe svc traefik
$ scw lb lb list zone=fr-par-1

EXTERNAL-IP reste <pending> pendant la création, puis affiche l'adresse réservée. describe liste les événements du CCM (création, erreurs d'annotation). Le CCM ajoute au Service l'annotation scw-loadbalancer-id (<zone>/<identifiant>) : n'y touchez pas.

Côté Traefik, l'entrée doit accepter PROXY depuis le répartiteur (valeurs de chart, donc à adapter à votre installation) :

additionalArguments:
  - "--entryPoints.web.proxyProtocol.trustedIPs=172.16.20.0/22"
  - "--entryPoints.websecure.proxyProtocol.trustedIPs=172.16.20.0/22"

La plage est celle du réseau privé où le répartiteur a ses adresses : vérifiez quelles adresses sources Traefik voit réellement avant de la figer.

Les classes de stockage de Signalements

Les classes de production sont explicites : un volume placé par le consommateur, conservé à la suppression, agrandissable.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: lyneko-sbs-5k-retain
provisioner: csi.scaleway.com
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  iops: "5000"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: lyneko-sbs-5k-chiffre
provisioner: csi.scaleway.com
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  iops: "5000"
  encrypted: "true"
  csi.storage.k8s.io/node-stage-secret-name: "luks-signalements"
  csi.storage.k8s.io/node-stage-secret-namespace: "signalements"
  csi.storage.k8s.io/node-expand-secret-name: "luks-signalements"
  csi.storage.k8s.io/node-expand-secret-namespace: "signalements"
  • reclaimPolicy: Retain : le volume survit au PVC ; un humain décide de son sort.
  • volumeBindingMode: WaitForFirstConsumer : le volume naît dans la zone du pod.
  • iops: "5000" : à passer à "15000" pour une classe rapide (le dépôt indique 15 000 comme maximum de l'offre « faible latence »).
  • encrypted: "true" et les quatre paramètres de secret : la phrase secrète est lue dans le Secret luks-signalements du namespace signalements, sous la clé encryptionPassphrase.

Le Secret ne se versionne jamais en clair : chez Lyneko, External Secrets le crée depuis Secret Manager (cours Secret Manager), à partir d'une entrée distante produisant la clé encryptionPassphrase.

Un volume, un instantané, un agrandissement

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: exports
  namespace: signalements
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: lyneko-sbs-5k-chiffre
  resources:
    requests:
      storage: 20Gi
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: exports-avant-migration
  namespace: signalements
spec:
  volumeSnapshotClassName: scw-snapshot
  source:
    persistentVolumeClaimName: exports

Le PVC reste Pending tant qu'aucun pod ne l'utilise : c'est le signe attendu de WaitForFirstConsumer, pas une panne. Pour l'instantané, vérifiez qu'une classe existe, puis lisez l'état :

$ kubectl get volumesnapshotclass
$ kubectl -n signalements get volumesnapshot exports-avant-migration

READYTOUSE passe à true quand l'instantané est utilisable. Pour agrandir, on édite le PVC :

$ kubectl -n signalements patch pvc exports --type merge -p '{"spec":{"resources":{"requests":{"storage":"40Gi"}}}}'
$ kubectl -n signalements get pvc exports -w

Le pilote agrandit le volume à chaud, puis le système de fichiers. Rien ne permet de revenir à 20 Gi.

Un volume partagé par plusieurs pods

$ scw k8s cluster update <identifiant> region=fr-par tags.0=scw-filestorage-csi
$ kubectl -n kube-system get pods -l app=filestorage-csi-node
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rapports-partages
  namespace: signalements
spec:
  accessModes: [ReadWriteMany]
  storageClassName: sfs-standard
  resources:
    requests:
      storage: 100G

L'étiquette se pose au niveau du cluster ; tags.0= remplace la liste existante : relisez-la avec scw k8s cluster get et reprenez-les toutes. Si aucun pod du pilote n'apparaît, le cluster n'a pas de pool POP2 (condition de la documentation).

Nettoyer

$ kubectl delete -n signalements pvc exports rapports-partages
$ kubectl delete -n signalements volumesnapshot exports-avant-migration
$ kubectl delete -n traefik svc traefik
$ scw lb ip delete <identifiant de l'IP réservée> zone=fr-par-1

Avec Retain, supprimer le PVC laisse le volume (Released dans kubectl get pv) : supprimez-le par la console ou la CLI du Block Storage. L'adresse réservée survit au Service, et se facture si on l'oublie.

Sous le capot

Le CCM, une boucle de réconciliation. À chaque changement d'un Service LoadBalancer ou d'un nœud, il calcule l'état voulu du répartiteur (frontends, backends, serveurs, vérifications, réseaux privés) d'après le Service et ses annotations, le compare à l'état réel dans l'API Scaleway et corrige l'écart. Le trafic suit le chemin habituel d'un Service LoadBalancer : répartiteur, NodePort des nœuds, kube-proxy (ou son remplaçant), pod. L'étiquette node.kubernetes.io/exclude-from-external-load-balancers écarte un nœud. Le CCM étant la source de vérité, toute modification manuelle du répartiteur disparaît à la réconciliation suivante.

Le CSI en trois temps. Le contrôleur du pilote crée le volume par l'API dans la zone retenue ; quand un pod est placé, il attache le volume à l'instance du nœud (d'où la limite de quinze volumes) ; le pilote côté nœud trouve le périphérique, le déchiffre si besoin, crée le système de fichiers et le monte. L'attache est une opération d'API, pas un montage réseau : un volume attaché à un nœud tombé n'est réattachable qu'après son détachement, d'où les délais quand un nœud meurt.

WaitForFirstConsumer côté ordonnanceur. L'ordonnanceur choisit d'abord un nœud pour le pod, en tenant compte du reste (ressources, affinités, répartition), puis annote le PVC avec ce nœud ; le provisionneur crée le volume dans la zone de ce nœud. Le volume est compatible avec le pod par construction. Avec Immediate, le volume précède le nœud et le pod doit s'y adapter.

Ce que LUKS ne protège pas. Le déchiffrement a lieu sur le nœud, avec la phrase lue dans le Secret : un instantané est chiffré, mais un pod qui monte le volume voit les données en clair, et qui lit le Secret obtient la phrase. C'est une protection contre la perte du support, pas contre un administrateur du cluster.

Pièges courants

Un Service supprimé, une adresse perdue. Sans ip-ids, la suppression du Service supprime l'adresse, que le DNS pointe encore. Posez l'annotation avant de publier le nom, ou convertissez l'adresse éphémère.

PROXY d'un seul côté. Annotation posée mais Traefik non configuré : connexions fermées, vérifications de santé en échec. Traefik configuré mais pas l'annotation : le premier octet n'est pas un en-tête PROXY, la connexion est rejetée. Les deux changements se déploient ensemble.

Modifier le répartiteur à la main. La règle ajoutée dans la console disparaît à la réconciliation suivante. Tout passe par les annotations.

Un répartiteur par Service. Cherchez-les : kubectl get svc -A --field-selector spec.type=LoadBalancer.

<pending> qui dure. Lisez kubectl describe svc : annotation invalide, adresse réservée dans une autre zone que celle du répartiteur, quota de répartiteurs atteint, identifiant d'IP faux.

Un pod Pending à cause du volume. volume node affinity conflict : le volume existe dans une zone où le pod ne peut pas aller. Un PVC créé en Immediate fige la zone avant le pod.

Un volume qui ne se rattache pas après la perte d'un nœud. Le nouveau pod reste en ContainerCreating (Multi-Attach error ou attente) tant que l'ancien attachement n'est pas libéré. Patientez ; ne forcez pas la suppression du volume.

kubectl delete namespace avec Delete. Les volumes réels disparaissent avec les PVC, sans corbeille : c'est la raison de Retain en production.

Un PVC agrandi sans classe qui l'autorise. Refus : only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize.

Les quinze volumes d'un nœud. Assez de CPU et de mémoire pour vingt pods à volume, mais quinze attaches seulement : le pod attend. Répartissez sur plus de nœuds.

Sécurité

  • Un Service LoadBalancer donne une adresse publique. Pour un service interne, …-private évite toute exposition. Le répartiteur de Traefik est la seule porte d'entrée : filtrez en amont (Edge Services, ACL, cours sur le répartiteur).
  • Groupes de sécurité des nœuds. Le guide de Scaleway sur l'ingress demande d'ouvrir les ports 80 et 443 dans le groupe de sécurité du cluster, qui refuse l'entrant par défaut. Les sources consultées ne disent pas si c'est nécessaire quand le répartiteur parle aux nœuds par le réseau privé : testez en préproduction et n'ouvrez que le strict nécessaire.
  • PROXY et confiance. N'acceptez l'en-tête que de l'adresse du répartiteur (trustedIPs) : sinon un client annonce l'adresse qu'il veut, comme avec X-Forwarded-For.
  • Droits sur les Services LoadBalancer. Qui en crée un fait créer un répartiteur public : une dépense et une exposition. Limitez-le par le RBAC et une politique d'admission.
  • Chiffrez au repos (encrypted: "true") et gérez la phrase comme un secret de production : rotation, accès restreint, sauvegarde à part. Perdre la phrase, c'est perdre les données.
  • Instantanés et volumes Retain orphelins portent les mêmes données personnelles que le volume : durée de conservation à justifier, revue après suppression d'une application.

En production

  • Un répartiteur public pour Traefik, ClusterIP pour le reste, avec IP réservée déclarée dans le code, type dimensionné, PROXY ou X-Forwarded-For décidé dès le départ.
  • Le répartiteur est zonal : des nœuds dans plusieurs zones ne sauvent pas l'entrée si sa zone tombe. La parade (second répartiteur, aiguillage DNS) a un coût à décider (cours sur le répartiteur).
  • Définissez vos StorageClass et versionnez-les avec le cluster : les classes préinstallées ont varié selon les versions.
  • Sauvegardez les données, pas seulement les volumes. Un instantané protège d'une fausse manipulation, pas de la perte de la zone ni du compte : sig-db a sa sauvegarde managée, les fichiers une copie hors du compte ou de la région.
  • La zone d'un volume décide de celle du pod : une application à volume ne tient la perte d'une zone que si elle réplique ses données ou les confie à un service régional (File Storage, objet, base managée).
  • Alertez sur l'espace des volumes (statistiques du kubelet, leçon 7) : l'agrandissement est possible à chaud, mais irréversible.
  • Chez Lyneko, lyneko-apps a un seul Service LoadBalancer, devant Traefik, avec une IP réservée.

Exercices

1. Garder l'adresse (niveau 200). Le Service de Traefik a été créé sans annotation d'IP et son nom est dans le DNS des métropoles. Comment réserver l'adresse actuelle sans recréer le Service ni interrompre le trafic ?

Solution

Lire l'adresse actuelle (kubectl get svc traefik -o json | jq -r .status.loadBalancer.ingress[0].ip), retrouver l'identifiant de l'IP flexible (scw lb ip list zone=<zone> ip-address=<adresse> -o json | jq -r '.[0].id'), puis poser service.beta.kubernetes.io/scw-loadbalancer-ip-ids par kubectl patch svc … --type merge. L'adresse ne change pas : elle est convertie en adresse réservée, qui ne sera plus supprimée avec le Service (méthode du dépôt du CCM). Décrivez ensuite l'annotation dans le code.

2. Pourquoi ce pod ne démarre pas (niveau 200). Un StatefulSet à deux réplicas, avec un PVC en Immediate par réplica, reste bloqué : un pod est Pending avec « volume node affinity conflict ». Le cluster a des nœuds en fr-par-1 et fr-par-2 et une contrainte de répartition stricte (DoNotSchedule) sur la zone. Expliquez, corrigez, et dites ce que la correction ne résout pas.

Solution

Les deux volumes ont été créés dans la même zone avant le placement ; la contrainte stricte envoie le second pod dans l'autre zone, où son volume n'existe pas. Correction : une classe en WaitForFirstConsumer et des PVC recréés (les volumes existants gardent leur zone). Ce qui reste : un pod est lié à sa zone, la perte de la zone arrête ce réplica, et sans réplication des données entre les deux, elle emporte les données du réplica.

3. RWX ou pas (niveau 200). Trois réplicas de l'API écrivent des PDF dans un répertoire que lisent aussi des jobs. Quelles options, laquelle recommandez-vous, quelles conditions vérifier ?

Solution

Block Storage est ReadWriteOnce : il ne convient pas à des pods répartis sur plusieurs nœuds. Options : File Storage (sfs-standard, ReadWriteMany) ou, mieux pour des fichiers générés puis servis, le stockage objet par l'API S3, déjà le choix de Signalements pour les photos. File Storage se justifie si l'application attend un système de fichiers POSIX. Conditions : étiquette scw-filestorage-csi au niveau du cluster, au moins un pool POP2, taille de 25 Go à 50 To, performances proportionnelles à la capacité.

Récapitulatif

  • Le CCM transforme un Service LoadBalancer en répartiteur Scaleway, réglé par des annotations service.beta.kubernetes.io/scw-loadbalancer-* ; on ne le modifie jamais à la main.
  • L'adresse est éphémère par défaut ; une IP flexible réservée désignée par …-ip-ids survit au Service, et changer d'adresse recrée le répartiteur.
  • Un répartiteur pour l'ingress (Traefik), des Services ClusterIP derrière ; PROXY se configure des deux côtés, avec trustedIPs.
  • Le CSI crée des volumes Block Storage ReadWriteOnce : sbs-default, sbs-5k, sbs-15k ; ne vous fiez pas aux champs des classes préinstallées, définissez les vôtres (WaitForFirstConsumer, Retain, expansion).
  • Un volume est lié à sa zone ; quinze volumes de données au plus par nœud ; agrandissement à chaud, jamais de réduction.
  • LUKS par encrypted: "true" et un Secret de phrase secrète ; un instantané est chiffré aussi, mais n'est pas une sauvegarde hors zone.
  • Pour partager en écriture : File Storage (sfs-standard, étiquette de cluster scw-filestorage-csi, pool POP2) ou le stockage objet.

Pour aller plus loin

  • Les dépôts scaleway-cloud-controller-manager (annotations, exemples) et scaleway-csi (exemples Kubernetes), seule source exhaustive et à jour.
  • La leçon 5, pour les droits que ces composants utilisent et pour ceux qu'on leur accorde.
  • Le cours Gateway API en production pour router depuis un seul répartiteur.
  • Le cours Volumes et stockage persistant pour la mécanique générale des volumes.
  • Le cours Helm, pour installer Traefik et ses valeurs de façon reproductible.
Voir ma constellation →

Sources