Comment l’IA réécrira le pipeline graphique : la trame à moitié générée

Toute équipe graphique connaît ce sentiment : la démo tourne à 60 ips sur la machine de l’ingénieur principal, puis se transforme en un magma haché sur le
matériel réel — juste au moment où la capture marketing commence. Maintenant ajoutez l’IA au pipeline : vous n’expédiez plus seulement des shaders et des
textures ; vous expédiez un modèle, un runtime, des pilotes et des heuristiques « utiles » qui peuvent transformer une mauvaise frame en une seconde entière de regret visuel.

La prochaine décennie du rendu temps réel ne sera pas « l’IA remplace la rastérisation ». C’est plus bordélique et plus intéressant : chaque image devient un
règlement négocié entre le rendu classique et l’inférence neuronale. À moitié rendu, à moitié généré. Et l’exploitation en assumera le résultat — latence,
mémoire, déterminisme, régressions et artefacts étranges qui n’apparaissent qu’après trois heures dans un biome désertique avec le brouillard activé.

Ce que signifie réellement « à moitié généré »

Quand on parle d’« IA graphique », on entend généralement une des trois choses : (1) upscaler un rendu basse résolution en haute résolution, (2) générer
des images intermédiaires entre des frames réelles, ou (3) débruiter une image produite par un moteur bruité (path tracing, effets stochastiques). Ce sont les
usages mainstream, déjà en production, « qui fonctionnent un mardi ».

Mais « la frame à moitié générée » est plus large. C’est un pipeline où le moteur rend délibérément moins que ce qu’exige une image finale, et où l’IA comble
ce qui a été omis — résolution, échantillons, détails de géométrie, détails d’éclairage, voire des parties du G-buffer. Autrement dit, l’IA n’est pas un
post-traitement. C’est un coprocesseur qui compense un calcul ou un temps manquant.

La distinction opérationnelle importante : un post-traitement peut être désactivé quand ça déraille. Un coprocesseur change ce que signifie « sortie correcte ».
Cela affecte les tests, le débogage et ce que vous pouvez raisonnablement revenir en arrière en cas d’incident.

Le modèle mental qui ne vous trahira pas

Considérez la frame hybride comme une transaction multi-étapes avec des budgets stricts :

  • Entrées : profondeur, vecteurs de mouvement, exposition, jitter, frames précédentes, parfois normales/albedo.
  • Rendu classique : rastérisation, compute, peut-être ray tracing partiel avec moins d’échantillons/résolution.
  • Inférence neuronale : reconstruire ou synthétiser les détails manquants à partir des entrées et de l’historique.
  • Composition : HUD/UI, éléments alpha, post-FX qui doivent rester nets et stables.
  • Présentation : cadence des frames, VRR, génération de frames, implications pour la capture/le streaming.

Si vous ne pouvez pas décrire quelle étape possède quels pixels, vous ne pouvez pas le déboguer. « Le modèle l’a fait » n’est pas une cause racine. C’est une
confession.

Où l’IA s’insère dans le pipeline (et où elle ne devrait pas)

1) Upscaling : acheter des pixels avec des maths

L’upscaling est la drogue d’entrée parce qu’il est facile à justifier : rendre à 67 % de la résolution, passer quelques millisecondes en reconstruction, et
livrer une image plus nette qu’un bilinéaire naïf. Le problème opérationnel est que les upscalers sont temporels. Ils utilisent l’historique. Cela signifie :

  • Les vecteurs de mouvement doivent être corrects sinon vous aurez du ghosting et des bords « élastiques ».
  • L’exposition/le tonemapping doivent être stables sinon vous aurez du scintillement et du « breathing ».
  • Les cuts caméra, les overlays UI et les particules deviennent des cas spéciaux.

2) Génération de frames : acheter du temps par prédiction

La génération de frames (FG) insère des frames synthétisées par l’IA entre des frames « réelles ». Ce n’est pas la même chose que doubler les performances.
Vous échangez latence et hallucinations occasionnelles contre un mouvement plus fluide. Cela convient à certains jeux, est catastrophique pour d’autres, et
compliqué pour tout ce qui est compétitif.

La question centrale pour la fiabilité : quel est votre SLO de latence ? Si vous ne pouvez pas y répondre, vous jouez aux dés avec l’entrée utilisateur.
Parfois vous obtiendrez « soyeux ». Parfois vous obtiendrez « pourquoi ma parade a-t-elle raté ? »

3) Dénoising : acheter des échantillons avec des priors

Le débruitage est l’endroit où les méthodes neuronales semblent inévitables. Le path tracing donne un éclairage physiquement plausible mais bruité à faible nombre
d’échantillons. Les débruiteurs neuronaux transforment quelques échantillons en quelque chose de présentable en s’appuyant sur des priors appris. Super — jusqu’à
ce que les priors soient faux pour votre contenu.

Les débruiteurs créent aussi un piège de fiabilité subtil : votre renderer peut être « correct », mais votre débruiteur est sensible à l’encodage des entrées,
à la précision des normales ou à de subtiles différences dans les plages de rugosité. Deux shaders qui se ressemblent dans un pipeline classique peuvent diverger
une fois débruités.

4) Les endroits que l’IA ne devrait pas contrôler (sauf si vous aimez les feux de brousse)

  • UI et texte : gardez-les en résolution native, tard dans le pipeline. Ne laissez pas la reconstruction temporelle brouiller votre typographie.
  • Feedback compétitif sur les hits : si une étape IA peut créer ou supprimer un indice, vous recevrez des rapports de bugs formulés comme des menaces légales.
  • Visualisation critique pour la sécurité : simulateurs de formation, imagerie médicale, tout ce où une hallucination devient une responsabilité.
  • Systèmes de replay déterministes : si votre jeu dépend de replays identiques, les étapes IA doivent être rendues déterministes ou exclues.

Budgets de latence : la seule vérité qui compte

Les anciens débats sur le rendu portaient sur les fps. Les nouveaux débats portent sur la pacing et la latence bout-en-bout. L’IA tend à améliorer
le débit moyen tout en détériorant la latence en queue, parce que l’inférence peut subir des effets de cache, des bizarreries d’ordonnancement du pilote, et des
chemins lents occasionnels (compilation de shader, warmup du modèle, pagination mémoire, transitions d’état d’alimentation).

Un pipeline de production a besoin de budgets qui ressemblent à des SLO :

  • Temps de frame p50 : le cas normal.
  • Temps de frame p95 : ce dont les utilisateurs se souviennent comme « saccade ».
  • Temps de frame p99 : ce que les streameurs coupent et transforment en mèmes.
  • Latence entrée‑vers‑photon : ce que les joueurs compétitifs ressentent dans leurs mains.
  • Marge VRAM : ce qui empêche la pagination intermittente et les pics catastrophiques.

La génération de frames complique les calculs parce que vous avez deux horloges : la cadence de simulation/rendu et la cadence d’affichage. Si votre simu tourne
à 60 et que vous affichez à 120 avec des frames générées, votre mouvement paraît plus fluide mais la latence d’entrée est liée à la cadence de simu plus le buffering.
Ce n’est pas un jugement moral. C’est de la physique plus des files d’attente.

Un pipeline hybride fiable fait deux choses de façon agressive :

  1. Il mesure la latence explicitement (pas seulement les fps).
  2. Il garde de la marge en VRAM, temps GPU et soumission CPU pour que des pics ne deviennent pas des pannes.

Une citation que vous devriez garder scotchée à votre écran, parce qu’elle s’applique ici plus qu’ailleurs : « L’espoir n’est pas une stratégie. » — Gene Kranz.

Blague #1 : Si votre plan est « le modèle n’a probablement pas de pics », félicitations — vous venez d’inventer le budget probabiliste, aussi appelé jouer.

Voies de données et télémétrie : traitez les frames comme des transactions

Le débogage graphique classique est déjà difficile : un million de pièces mobiles, des boîtes noires de pilotes, et des bugs sensibles au timing. L’IA ajoute une
nouvelle catégorie de « faux silencieux » : la frame paraît plausible, mais elle n’est pas fidèle. Pire : c’est dépendant du contenu. Le bug apparaît seulement
sur une certaine map, à une certaine heure virtuelle, avec un certain effet de particules, après que le GPU soit chaud.

Les systèmes de production survivent grâce à l’observabilité. Le rendu hybride a besoin de la même discipline. Vous devriez logger et visualiser :

  • Temps GPU par étape : rendu de base, inférence, post, présentation.
  • Profondeur de file et backpressure : est-ce que les frames s’accumulent quelque part ?
  • Allocations VRAM dans le temps : pas seulement « utilisé », mais « fragmenté » et « évincé ».
  • Métriques d’inférence : version du modèle, mode de précision, forme de batch, état warm/cold.
  • Indicateurs de qualité : % de validité des vecteurs de mouvement, taux de désoccultation, couverture des masques réactifs.

Le gain opérationnel le plus simple : estampiller chaque frame avec un « manifeste de pipeline » qui enregistre les bascules et versions clés qui l’ont influencée.
Si vous ne pouvez pas répondre « quelle version du modèle a produit cet artefact ? » vous n’avez pas un bug — vous avez un roman policier.

Faits et contexte historique qui expliquent les compromis actuels

  • 1) Le temporal anti-aliasing (TAA) a popularisé l’idée que « la frame courante ne suffit pas ». Les upscalers modernes ont hérité de cette vision.
  • 2) Les premiers pipelines GPU étaient à fonctions fixes ; la programmabilité (shaders) a transformé le graphique en logiciel, et le logiciel attire toujours l’automatisation.
  • 3) Les renderers offline utilisaient le débruitage bien avant le temps réel ; les pipelines film ont prouvé qu’on peut échanger des échantillons contre une reconstruction plus intelligente.
  • 4) Le rendu en damier sur consoles était un précurseur de l’upscaling ML : rendre moins de pixels, reconstruire le reste via des motifs et l’historique.
  • 5) Les vecteurs de mouvement existaient pour le flou cinétique et le TAA avant de devenir des entrées critiques pour l’IA ; maintenant un tampon de vélocité mauvais est une panne de qualité.
  • 6) Le ray tracing matériel a rendu « bruité mais correct » faisable ; les débruiteurs neuronaux ont rendu « livrable » faisable dans des budgets temps réel.
  • 7) L’industrie a appris des incidents de streaming de textures : les pics VRAM ne tombent pas en douceur — ils tombent comme un plancher qui s’ouvre sous vos pieds.
  • 8) Les consoles ont forcé une pensée de performance déterministe ; l’IA réintroduit de la variance à moins que vous ne conceviez pour cela.
  • 9) Les encodeurs vidéo utilisent déjà la prédiction motion-compensated ; la génération de frames est conceptuellement adjacente, mais doit tolérer l’interactivité.

Nouveaux modes de défaillance dans le rendu hybride

Taxonomie d’artefacts que vous devriez réellement utiliser

  • Ghosting : l’historique est surconfiant ; vecteurs de mouvement erronés ou désoccultation non gérée.
  • Scintillement : instabilité temporelle ; exposition, jitter, ou boucle de rétroaction de reconstruction.
  • Étirement : l’inférence lisse des détails qui devraient être à haute fréquence (feuillage, fils fins).
  • Bords hallucinatés : l’upscaler invente une structure ; généralement à partir d’entrées sous-spécifiées.
  • Contamination UI : l’étape temporelle considère l’UI comme contenu de scène et la traîne dans le temps.
  • La latence « donne une mauvaise impression » : génération de frames et buffering ; parfois amplifiée par des modes réflexes mal configurés.
  • Pics aléatoires : pagination VRAM, warmup de modèle, compilation de shader, changements d’état d’alimentation, processus en arrière-plan.

Le piège de fiabilité : l’IA masque la dette du rendu

Le rendu hybride peut masquer des problèmes sous-jacents : vecteurs de mouvement instables, profondeur incohérente, masques réactifs manquants, traitement alpha incorrect.
Le modèle couvre… jusqu’à ce que le contenu change et que la dissimulation échoue. Alors vous déboguez deux systèmes à la fois.

Si vous expédiez un rendu hybride, vous devez maintenir un chemin de secours « sans IA » qui soit testé en CI, pas seulement théoriquement possible. C’est la
différence entre un mode dégradé et une panne.

Blague #2 : Le rendu neuronal, c’est comme un collègue qui finit vos phrases — impressionnant jusqu’au moment où il commence à le faire en réunion avec votre patron.

Playbook de diagnostic rapide

Quand les performances ou la qualité déraillent, ne commencez pas par argumenter « IA vs rastérisation ». Commencez par trouver le goulot d’étranglement avec une
approche impitoyable et en étapes. L’objectif est d’identifier quel budget est cassé : temps GPU, soumission CPU, VRAM, ou latence/pacing.

Première étape : confirmer que le symptôme est le pacing, pas les fps moyens

  • Vérifiez les pics de temps de frame p95/p99 et s’ils se corrèlent avec des transitions de scène, des cuts caméra ou des effets.
  • Confirmez si la saccade coïncide avec la pression VRAM ou des événements de compilation de shader.
  • Validez que le chemin d’affichage (VRR, vsync, limiteur) correspond aux hypothèses de test.

Deuxième étape : isoler « rendu de base » vs « inférence IA » vs « présent »

  • Désactivez la génération de frames en premier (si activée). Si la latence et le pacing se normalisent, vous êtes dans le domaine présentation/interpolation.
  • Revenez en résolution native (désactivez l’upscaling). Si les artefacts disparaissent, vos entrées (vecteurs de mouvement, masque réactif) sont suspectes.
  • Passez le débruiteur à un mode plus simple ou de qualité inférieure. Si les pics disparaissent, l’inférence est en cause (ou son comportement mémoire).

Troisième étape : vérifier la marge VRAM et la pagination

  • Si la VRAM est dans les 5–10 % de la limite, supposez que vous allez paginer sur des charges réelles.
  • Cherchez des pics périodiques : ils correspondent souvent au streaming, à un churn d’allocations de type GC, ou à une capture en arrière-plan.
  • Confirmez que les poids du modèle résident et ne sont pas re-téléversés à cause d’une perte de contexte ou de pression mémoire.

Quatrième étape : valider l’intégrité des entrées et de l’historique

  • Vecteurs de mouvement : espace correct, échelle correcte, gestion correcte pour les meshes skinning et les particules.
  • Profondeur : précision stable et mappage near/far cohérent ; évitez les incompatibilités reversed-Z « utiles » entre passes.
  • Réinitialisation de l’historique sur cuts : si vous ne coupez pas l’historique, le modèle essaiera d’assembler deux frames sans rapport.

Cinquième étape : contrôle des régressions

  • Épinglez les versions des pilotes pour les bases QA. Ne déboguez pas deux cibles mouvantes à la fois.
  • Épinglez les versions de modèles et les modes de précision. Si vous ne pouvez pas reproduire, vous ne pouvez pas corriger.
  • Utilisez des feature flags avec kill-switch que l’exploitation peut basculer sans recompiler.

Tâches pratiques : commandes, sorties et décisions

Ce sont des vérifications de niveau ops que vous pouvez exécuter sur une station Linux ou un serveur de build. Elles ne débogueront pas magiquement votre code
de shader, mais elles vous diront si vous vous battez contre le GPU, la pile pilote, la pression mémoire ou votre propre processus.

Task 1: Identify the GPU and driver in the exact environment

cr0x@server:~$ lspci -nn | grep -Ei 'vga|3d|display'
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD104 [GeForce RTX 4070] [10de:2786] (rev a1)

Ce que ça signifie : Vous avez confirmé la classe matérielle. Cela importe parce que le comportement d’inférence diffère selon l’architecture.

Décision : Si des rapports de bugs mentionnent différents device IDs, scindez l’incident par architecture en premier ; ne les moyenez pas.

Task 2: Confirm kernel driver and firmware versions

cr0x@server:~$ uname -r
6.5.0-21-generic

Ce que ça signifie : Les mises à jour du kernel peuvent changer le comportement DMA, l’ordonnancement et les défauts IOMMU — suffisamment pour altérer la saccade.

Décision : Épinglez le kernel pour les bases de test de performance. Mettez à jour volontairement, pas par accident.

Task 3: Confirm NVIDIA driver version (or equivalent stack)

cr0x@server:~$ nvidia-smi
Wed Jan 21 10:14:32 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14              Driver Version: 550.54.14      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------|
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4070        Off |   00000000:01:00.0  On |                  N/A |
| 30%   54C    P2              95W / 200W |     7420MiB / 12282MiB |     78%      Default |
+-----------------------------------------+------------------------+----------------------+

Ce que ça signifie : La version du pilote et l’utilisation VRAM sont visibles. 7.4 GiB utilisés n’est pas alarmant ; 11.8/12.2 l’est.

Décision : Si la VRAM est constamment >90 %, considérez cela comme un risque de pagination et réduisez les budgets (textures, buffers RT, taille du modèle, buffers d’historique).

Task 4: Watch VRAM and utilization over time to catch spikes

cr0x@server:~$ nvidia-smi dmon -s pucm -d 1 -c 5
# gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk
# Idx     W     C     C     %     %     %     %   MHz   MHz
    0    94    55     -    81    63     0     0  9501  2580
    0   102    56     -    88    66     0     0  9501  2610
    0    73    53     -    52    62     0     0  9501  2145
    0   110    57     -    92    70     0     0  9501  2655
    0    68    52     -    45    61     0     0  9501  2100

Ce que ça signifie : Vous pouvez voir des rafales. Si mem% monte puis baisse, vous paginez ou réallouez agressivement.

Décision : Corrélez les pics avec des événements moteur (zones de streaming, cutscenes). Ajoutez du pré-warm ou limitez les allocations.

Task 5: Confirm PCIe link width/speed (hidden throttles happen)

cr0x@server:~$ sudo lspci -s 01:00.0 -vv | grep -E 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 16GT/s, Width x16
LnkSta: Speed 16GT/s, Width x16

Ce que ça signifie : Vous n’êtes pas limité à x4 parce que quelqu’un a utilisé le mauvais slot ou un réglage BIOS.

Décision : Si le lien est dégradé, corrigez le matériel/BIOS avant d’« optimiser » votre renderer en une pâmoison.

Task 6: Check for CPU frequency scaling (frame pacing killer)

cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave

Ce que ça signifie : Le CPU peut être lent à monter en fréquence, causant des saccades de soumission du thread de rendu.

Décision : Pour les tests perf, passez en performance et documentez-le, sinon vos résultats sont des fictions.

Task 7: Set performance governor during controlled benchmarks

cr0x@server:~$ sudo cpupower frequency-set -g performance
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2
Setting cpu: 3

Ce que ça signifie : Le CPU tiendra des fréquences plus élevées de façon plus cohérente.

Décision : Si les saccades disparaissent, vous avez un problème d’ordonnancement/power du CPU, pas un problème « l’IA est lente ».

Task 8: Check memory pressure and swap activity

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            62Gi        41Gi       3.1Gi       1.2Gi        18Gi        19Gi
Swap:          8.0Gi       2.4Gi       5.6Gi

Ce que ça signifie : L’utilisation du swap suggère que le système pagine. Cela peut se manifester par des pics périodiques et des accrochages d’assets.

Décision : Réduisez l’empreinte mémoire, corrigez les fuites, ou augmentez la RAM. Ne faites pas semblant que le tuning GPU résoudra la pagination hôte.

Task 9: Identify top CPU consumers (background capture tools are frequent villains)

cr0x@server:~$ ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head
  PID COMMAND         %CPU %MEM
 4121 chrome          38.2  4.1
 9332 obs             22.7  1.9
 7771 game-bin        18.4  6.8
 1260 Xorg             9.2  0.6
 2104 pulseaudio       3.1  0.1

Ce que ça signifie : Votre « benchmark » concurrence un navigateur et un outil de streaming.

Décision : Reproduisez dans des conditions propres. Si OBS est requis, traitez-le comme une partie de la charge de production.

Task 10: Check disk I/O latency (asset streaming and model loads)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0-21-generic (server) 	01/21/2026 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.41    0.00    3.28    2.91    0.00   81.40

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz aqu-sz  %util
nvme0n1         92.0   18240.0     0.0   0.00    3.12   198.3      44.0    5280.0     2.0   4.35    5.44   120.0   0.36  18.40

Ce que ça signifie : r_await/w_await sont modestes. Si vous voyez des awaits de 50–200 ms, vous aurez des à-coups indépendamment du GPU.

Décision : Si le stockage est lent, corrigez le streaming (prélecture, compression, packaging) avant de toucher aux réglages d’inférence.

Task 11: Validate filesystem space (logs and caches can fill disks mid-run)

cr0x@server:~$ df -h /var
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  220G  214G  6.0G  98% /

Ce que ça signifie : Vous êtes à une session de log enthousiaste d’un mauvais jour.

Décision : Libérez de l’espace ou redirigez caches/logs. Un disque plein peut casser les caches de shader, les caches de modèles et l’écriture des dumps.

Task 12: Inspect GPU error counters (hardware/driver instability)

cr0x@server:~$ sudo journalctl -k -b | grep -Ei 'nvrm|gpu|amdgpu|i915' | tail
Jan 21 09:58:11 server kernel: NVRM: Xid (PCI:0000:01:00): 31, pid=7771, name=game-bin, Ch 0000002c, intr 00000000
Jan 21 09:58:11 server kernel: NVRM: GPU at PCI:0000:01:00: GPU has fallen off the bus.

Ce que ça signifie : Ce n’est pas un problème d’optimisation. C’est un incident de stabilité : reset du pilote, problème d’alimentation, ou défaillance matérielle.

Décision : Arrêtez d’ajuster la qualité. Reproduisez sous tests de contrainte, vérifiez l’alimentation, les thermiques et les problèmes connus du pilote.

Task 13: Check GPU clocks and throttling reasons

cr0x@server:~$ nvidia-smi -q -d CLOCK,PERFORMANCE | sed -n '1,80p'
==============NVSMI LOG==============

Performance State                          : P2
Clocks
    Graphics                               : 2580 MHz
    Memory                                 : 9501 MHz
Clocks Throttle Reasons
    Idle                                   : Not Active
    Applications Clocks Setting            : Not Active
    SW Power Cap                           : Not Active
    HW Slowdown                            : Not Active
    HW Thermal Slowdown                    : Not Active

Ce que ça signifie : Pas de throttling évident. Si un ralentissement thermique est actif pendant les pics, votre « régression IA » peut n’être que de la chaleur.

Décision : Si le throttling apparaît après quelques minutes, testez avec des courbes de ventilateur fixes et un flux d’air de boîtier avant de réécrire le pipeline.

Task 14: Confirm model files are not being reloaded repeatedly (cache thrash)

cr0x@server:~$ lsof -p $(pgrep -n game-bin) | grep -E '\.onnx|\.plan|\.bin' | head
game-bin 7771 cr0x  mem REG  259,2  31248768  1048612 /opt/game/models/upscaler_v7.plan
game-bin 7771 cr0x  mem REG  259,2   8421376  1048620 /opt/game/models/denoiser_fp16.bin

Ce que ça signifie : Les poids du modèle sont memory-mapped. Bien. Si vous voyez des motifs open/close répétés dans les traces, vous payez des coûts de chargement en cours de partie.

Décision : Préchargez et épinglez les modèles au démarrage ou au chargement de niveau ; ne les chargez pas paresseusement lors de la première explosion.

Task 15: Check for shader cache behavior (compilation stutter often blamed on AI)

cr0x@server:~$ ls -lh ~/.cache/nv/GLCache | head
total 64M
-rw------- 1 cr0x cr0x 1.2M Jan 21 09:40 0b9f6a8d0b4a2f3c
-rw------- 1 cr0x cr0x 2.8M Jan 21 09:41 1c2d7e91a1e0f4aa
-rw------- 1 cr0x cr0x 512K Jan 21 09:42 3f4a91c2d18e2b0d

Ce que ça signifie : Le cache existe et est peuplé. S’il est vide à chaque exécution, votre environnement le supprime ou les permissions sont erronées.

Décision : Assurez-vous que les caches de shader persistent en test et en prod. Sinon vous poursuivrez des pics de frames « aléatoires » pour toujours.

Task 16: Measure scheduling jitter on the host (useful for render thread pacing)

cr0x@server:~$ sudo cyclictest -m -Sp90 -i200 -h400 -D5s | tail -n 3
T: 0 ( 2345) P:90 I:200 C: 25000 Min:    5 Act:    7 Avg:    9 Max:  112
T: 1 ( 2346) P:90 I:200 C: 25000 Min:    5 Act:    6 Avg:    8 Max:   98
T: 2 ( 2347) P:90 I:200 C: 25000 Min:    5 Act:    6 Avg:    8 Max:  130

Ce que ça signifie : Un jitter max dans la gamme ~100 µs est généralement acceptable. Si vous voyez un jitter de plusieurs millisecondes, votre OS vous interrompt fortement.

Décision : Pour un profiling reproductible, isolez des CPU, maîtrisez les daemons d’arrière-plan et évitez les voisins bruyants (VMs, modes d’alimentation de laptop).

Trois mini-récits d’entreprise depuis le terrain

Mini-récit n°1 : L’incident causé par une fausse hypothèse

Un studio a expédié un patch qui « ne changeait que l’upscaler ». Les notes de version indiquaient : meilleure netteté, moins d’artefacts. QA avait approuvé la
qualité visuelle dans un ensemble de scènes contrôlées, et la performance semblait stable sur leurs machines de labo.

En quelques heures, les tickets de support ont afflué : des accrochages intermittents, principalement sur des GPU milieu de gamme avec 8–10 GiB de VRAM. Les
saccades n’apparaissaient pas immédiatement. Elles survenaient après 20–30 minutes, souvent après quelques transitions de map. L’équipe a d’abord incriminé la
compilation de shader. L’odeur était celle de la compilation de shader.

La fausse hypothèse : le nouveau modèle serait « à peu près de la même taille » en VRAM parce qu’il avait une résolution entrée/sortie similaire. Mais le
chemin d’inférence du moteur avait activé silencieusement un tampon intermédiaire de plus haute précision pour le nouveau modèle. Ajoutez un tampon d’historique
légèrement plus grand et un masque réactif plus agressif, et la marge VRAM a disparu.

Sur ces GPU, le pilote a commencé à évincer des ressources. Pas toujours les mêmes. Le motif d’éviction dépendait de ce qui était résident : textures, structures
d’accélération RT, shadow maps, outils de capture. La « saccade shader » était en réalité un churn mémoire et des re-téléversements occasionnels.

La correction n’a pas été héroïque : limiter la résolution de l’historique, forcer des intermédiaires FP16, et réserver explicitement un budget VRAM pour le
modèle et les buffers d’historique. Ils ont ajouté un avertissement runtime lorsque la marge tombait sous un seuil et exposé un upscaler « safe mode » qui sacrifiait
de la netteté pour la stabilité. La leçon était aussi ennuyeuse : traitez la VRAM comme un budget avec des garde-fous, pas comme une suggestion best-effort.

Mini-récit n°2 : L’optimisation qui s’est retournée contre eux

Une équipe moteur a décidé de « sauver de la bande passante » en empaquetant vecteurs de mouvement et profondeur dans un format plus serré. Le message de commit
était joyeux : G-buffer plus petit, passes plus rapides, meilleure localité cache. Les benchmarks se sont améliorés de quelques pourcents en moyenne. Tout le
monde a applaudi et est passé à autre chose.

Puis le pipeline hybride a commencé à montrer du ghosting intermittent sur la géométrie fine — clôtures, fils, branches d’arbres — surtout lors de panoramiques
rapides. Certaines scènes seulement. Certains éclairages seulement. Certains GPU seulement. Les rapports de bug étaient vagues, parce que les frames semblaient
« majoritairement correctes » jusqu’à ce que vous les regardiez assez longtemps pour vous détester.

L’optimisation a réduit la précision exactement là où l’upscaler comptait : motion sub-pixel et discontinuités de profondeur précises. Le modèle avait été
entraîné en supposant une certaine distribution d’erreurs de mouvement ; le nouveau packing a changé la distribution. Pas assez pour casser toutes les frames.
Assez pour casser les
cas difficiles.

Le retour de bâton a été organisationnel aussi. L’équipe avait amélioré une métrique (bande passante) tout en détruisant silencieusement une autre (fidélité des entrées).
Parce que les buffers d’entrée semblaient être des « détails internes », personne n’a mis à jour la suite de validation du modèle. Il n’y avait pas de garde-fou pour
la « régression de qualité des vecteurs de mouvement ».

Ils ont reverté le changement de packing pour le chemin IA tout en le gardant pour le chemin non-IA. Puis ils ont créé un contrat : la précision et la plage des vecteurs
de mouvement sont devenues des entrées versionnées, avec des tests automatisés de scènes qui comparaient les métriques de stabilité temporelle avant et après les
changements. Ils ont continué à optimiser — mais seulement avec un budget qualité dans la boucle.

Mini-récit n°3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe plateforme gérait le runtime qui chargeait les modèles, sélectionnait les modes de précision, et négociait avec le backend graphique. Rien de flashy.
Personne n’en a fait un article de blog. Mais ils avaient une pratique qui ressemblait à de la paperasse : chaque artefact modèle était traité comme un déployable
avec version sémantique et changelog incluant les hypothèses d’entrée.

Un vendredi, une mise à jour pilote a frappé leur parc interne. Soudain, un sous-ensemble de machines a commencé à montrer des scintillements rares pendant la
génération de frames — une frame toutes les quelques minutes. Le scintillement était petit mais évident en mouvement. Le genre de bug qui ruine la confiance parce
qu’il est assez rare pour échapper à une reproduction rapide.

Parce que les artefacts modèles et le runtime étaient épinglés par version et loggés par frame, ils ont pu répondre à la question cruciale en une heure : rien
dans le modèle n’avait changé. Le runtime n’avait changé que marginalement. Le pilote avait changé, et seulement sur les machines affectées.

Ils ont basculé le kill-switch pour désactiver la génération de frames pour cette branche pilote tout en laissant l’upscaling et le débruitage intacts. Le jeu est
resté jouable. QA a retrouvé une baseline stable. Pendant ce temps, ils ont travaillé avec le fournisseur sur un repro minimal et l’ont vérifié contre la matrice épinglée.

La pratique salvatrice n’était pas géniale. C’était de l’hygiène opérationnelle ennuyeuse : épinglage des versions, manifestes par frame et contrôles de rollback rapides.
Cela a transformé un incident de week-end potentiel en une dégradation contrôlée avec un périmètre clair.

Erreurs courantes : symptômes → cause racine → correction

1) Symptôme : Traînées fantômes derrière des personnages en mouvement

Cause racine : Vecteurs de mouvement incorrects pour les meshes skinning, les particules ou l’animation de vertex ; masque de désoccultation manquant.

Correction : Valider la vélocité pour chaque chemin de rendu ; générer le mouvement pour les particules séparément ; réinitialiser l’historique sur vecteurs invalides ; ajouter des masques réactifs.

2) Symptôme : Texte UI qui bave ou « résonne » pendant le mouvement de la caméra

Cause racine : UI compositée avant la reconstruction temporelle, ou fuite de l’UI dans les buffers d’historique.

Correction : Composer l’UI après l’upscaling/débruitage ; s’assurer que les targets UI sont exclues de l’historique et des passes de vecteurs de mouvement.

3) Symptôme : Les performances vont bien dans les benchmarks, terribles après 30 minutes

Cause racine : Fragmentation VRAM, croissance du streaming d’assets, poids de modèle évincés sous pression, ou throttling thermique.

Correction : Suivre la VRAM dans le temps ; appliquer des budgets ; pré-warm et épingler les allocations de modèles ; surveiller les raisons de throttling ; corriger les fuites dans les RT transients.

4) Symptôme : La génération de frames paraît fluide mais l’entrée semble lente

Cause racine : Cadence d’affichage découplée de la cadence de simulation ; buffering supplémentaire ; mode latence mal configuré.

Correction : Mesurer entrée‑vers‑photon ; réduire la profondeur de la file de rendu ; régler les modes basse-latence ; offrir des bascules aux joueurs avec des descriptions honnêtes.

5) Symptôme : Scintillement sur le feuillage et la géométrie fine

Cause racine : Instabilité temporelle due à un sous-échantillonnage et masque réactif insuffisant ; perte de précision en profondeur/vélocité ; sharpening agressif.

Correction : Améliorer la précision des entrées ; régler le masque réactif ; réduire le sharpening ; plafonner la contribution de l’historique dans les régions à haute fréquence.

6) Symptôme : Frame noire soudaine ou frame corrompue de temps en temps

Cause racine : Reset pilote GPU, récupération de type TDR, out-of-bounds dans une passe compute, ou chemin d’échec du runtime modèle non géré.

Correction : Capturer les logs kernel ; ajouter un fallback robuste quand l’inférence échoue ; valider les bornes et états de ressources ; escalader comme un problème de stabilité, pas « qualité ».

7) Symptôme : « Ça n’arrive que sur le GPU d’un fournisseur »

Cause racine : Différents modes mathématiques, gestion des dénormaux, ordonnancement, ou valeurs par défaut de précision ; différences de compilateur pilote.

Correction : Construire des baselines spécifiques fournisseur ; contraindre la précision ; tester par fournisseur et par architecture ; ne pas supposer « même API = même comportement ».

8) Symptôme : Des artefacts apparaissent après un cut caméra ou un respawn

Cause racine : Historique non réinitialisé ; le modèle tente de concilier des frames sans rapport.

Correction : Traiter les cuts comme des réinitialisations dures ; fondre la contribution de l’historique ; réinitialiser exposition et séquences de jitter.

Checklists / plan étape par étape

Étape par étape : livrer une frame à moitié générée sans s’embarrasser

  1. Définir des budgets : temps de frame p95 et p99, cible de marge VRAM, cible entrée‑vers‑photon. Notez-les. Rendez-les applicables.
  2. Versionner tout : version du modèle, version du runtime, baseline pilote, feature flags. Loggez-les par frame en builds debug.
  3. Construire une échelle de repli : rendu natif → TAA classique → upscaler ML → ML + génération de frames. Chaque palier doit être expédiable.
  4. Valider les entrées : vecteurs de mouvement (tous types de géométrie), précision de profondeur, stabilité d’exposition, gestion des alpha, détection de désoccultation.
  5. Créer une suite de tests temporels : panoramiques rapides, feuillage, tempêtes de particules, cuts caméra, respawns, overlays UI. Automatiser captures et métriques.
  6. Réserver la VRAM : budgéter explicitement buffers d’historique et poids de modèles ; ne pas « voir ce qui se passe ».
  7. Warm up : précompiler les shaders, pré-initialiser l’inférence, pré-allouer les RT quand possible. Cachez-le derrière des écrans de chargement.
  8. Instrumenter les timings par étape : rendu de base, inférence, post, présentation ; inclure profondeur de file et métriques de pacing.
  9. Contrôler la latence en queue : plafonner le travail pire-cas ; éviter les allocations en-frame ; surveiller la contention CPU en arrière-plan.
  10. Expédier des kill-switches : ops a besoin de bascules pour désactiver la FG ou changer pour un modèle plus petit sans rebuild complet.
  11. Documenter les compromis pour les joueurs : fluidité vs latence, modes qualité vs stabilité. Si vous le cachez, les joueurs le découvriront bruyamment.
  12. Faire des tests d’endurance : 2–4 heures, multiples transitions de map, chemins à fort streaming. La plupart des « problèmes IA » sont en réalité des problèmes temporels de ressources.

Checklist : avant d’accuser le modèle

  • La marge VRAM est-elle >10 % dans les pires scènes ?
  • Les vecteurs de mouvement sont-ils valides pour chaque chemin de rendu (skinned, particules, vertex anim) ?
  • Réinitialisez-vous l’historique sur les cuts et frames invalides ?
  • Composez-vous l’UI après les étapes temporelles ?
  • Les versions pilote et modèle sont-elles épinglées pour la repro ?
  • Pouvez-vous reproduire avec l’IA désactivée ? Si non, votre configuration de mesure est suspecte.

FAQ

1) « Trame à moitié générée » n’est-ce que du marketing pour l’upscaling ?

Non. L’upscaling en fait partie. « À moitié généré » signifie que le pipeline rend intentionnellement des données incomplètes et s’appuie sur l’inférence pour
reconstruire ou synthétiser le reste, parfois incluant le temps (frames générées) et parfois le transport de la lumière (dénoising).

2) La génération de frames augmente-t-elle les performances ou les masque-t-elle ?

Elle augmente le taux d’images affiché, ce qui peut améliorer la fluidité perçue. Elle n’augmente pas le taux de simulation, et peut accroître la latence perçue
selon le buffering et les modes de latence. Mesurez entrée‑vers‑photon, n’entrez pas dans des débats stériles.

3) Quel est le risque opérationnel n°1 quand on ajoute l’IA au rendu ?

La latence en queue et le comportement mémoire. Le temps de frame moyen peut s’améliorer tandis que le p99 se dégrade à cause d’évictions VRAM, warmup, ordonnancement
pilote, ou chemins lents occasionnels.

4) Pourquoi les artefacts apparaissent-ils souvent sur le feuillage et la géométrie fine ?

Ces éléments sont à haute fréquence et souvent sous-échantillonnés. Ils provoquent aussi des fortes désoccultations et des vecteurs de mouvement peu fiables.
La reconstruction temporelle est fragile quand les entrées ne décrivent pas clairement le mouvement.

5) Peut-on rendre les étapes IA déterministes pour les replays ?

Parfois. Vous pouvez contraindre la précision, fixer des seeds, éviter les kernels non déterministes, et épingler runtimes/pilotes. Mais obtenir le déterminisme
entre fournisseurs et versions pilotes est difficile. Si les replays déterministes sont une exigence produit, concevez un mode déterministe dès le départ.

6) Faut-il expédier un gros modèle unique ou plusieurs petits ?

Plusieurs. Vous voulez une échelle : haute qualité, équilibré, safe. Les systèmes de production ont besoin d’une dégradation graduelle. Un gros modèle unique est
un point de défaillance unique avec une belle coiffure.

7) Comment tester la « qualité » sans se reposer sur des captures subjectives ?

Utilisez des métriques temporelles : variance dans le temps, stabilité des bords, heuristiques de ghosting, comptage d’erreurs de désoccultation, et scènes de torture
sélectionnées. Gardez aussi la revue humaine, mais focalisée et reproductible.

8) Que doit exiger l’exploitation des équipes graphiques avant d’activer la génération de frames par défaut ?

Un impact de latence mesuré, un kill-switch, une communication claire aux joueurs, et une matrice de régression sur versions pilotes et matériels courants. Si elles ne
peuvent pas fournir cela, l’activation par défaut est un pari sur la fiabilité.

9) Pourquoi « ça marche sur ma machine » empire-t-il avec l’IA ?

Parce que vous avez ajouté plus d’état caché : caches de modèles, modes de précision, différences d’ordonnancement pilote, variance de marge VRAM, et profils thermiques/power.
Le système devient plus dépendant du chemin d’exécution, ce qui punit des baselines négligentes.

Conclusion : que faire la semaine prochaine

La frame à moitié générée n’est plus un projet scientifique. C’est un pipeline de production avec tous les péchés habituels : budgets ignorés, versions non épinglées,
caches effacés, et « optimisations » qui suppriment les signaux dont le modèle a besoin. La bonne nouvelle est que les correctifs ressemblent à de l’ingénierie normale :
mesure, garde-fous et déploiements contrôlés.

La semaine prochaine, faites ces choses pratiques :

  • Définir les cibles p95/p99 et entrée‑vers‑photon, et en faire des portes de release.
  • Ajouter des manifestes par frame : versions modèle/runtime/pilote et bascules clés, loggés en builds debug.
  • Construire une échelle de repli testée et la connecter à un kill-switch exploitable.
  • Suivre la marge VRAM et le risque de pagination comme métrique de première classe, pas comme un après-coup.
  • Automatiser des scènes de torture temporelle et valider les vecteurs de mouvement comme si votre travail en dépendait — parce que c’est le cas.

Le rendu hybride continuera d’évoluer. Votre travail est de le rendre ennuyeux en production : prévisible, observable et récupérable quand il se comporte mal. La
partie « IA » est impressionnante. La partie « pipeline » est là où vous expédiez — ou passez vos week-ends à regarder des graphiques de temps de frame comme s’ils étaient des cours de bourse.

Disques Proxmox non détectés : checklist rapide HBA, BIOS et câblage

Rien ne promet un « week-end amusant » comme démarrer un nœud Proxmox et découvrir que vos nouveaux disques se sont évaporés. L’installateur ne voit rien. lsblk est un désert. Les pools ZFS disparaissent. Vous jurez que les disques étaient là hier.

Voici une checklist de terrain pour humains en production : ingénieurs stockage, SRE et le pauvre en astreinte qui a hérité d’une extension de disque « simple ». Nous traquerons rapidement le domaine de défaillance : BIOS/UEFI, firmware et mode HBA, PCIe, bizarreries de câblage/backplane/expander, pilotes Linux, et les pièges qui rendent les disques « présents » mais invisibles.

Playbook de diagnostic rapide (faire dans cet ordre)

0) Décidez ce que signifie « non détecté »

  • Pas dans le BIOS/UEFI : matériel, alimentation, câblage, backplane, énumération HBA/PCIe.
  • Dans le BIOS mais pas dans Linux : pilote/module kernel, bizarreries IOMMU, firmware cassé, erreurs PCIe AER.
  • Dans Linux mais pas dans l’interface Proxmox : mauvais écran, partitions existantes, masquage par multipath, ZFS tenant les périphériques, permissions, ou c’est sous /dev/disk/by-id mais pas évident.

1) Commencez par la vérité du kernel

Exécutez ces trois commandes et n’improvisez pas encore :

  1. dmesg -T | tail -n 200 (recherchez PCIe, SAS, SATA, NVMe, réinitialisations de lien)
  2. lsblk -e7 -o NAME,TYPE,SIZE,MODEL,SERIAL,TRAN,HCTL (voyez ce que le kernel a créé)
  3. lspci -nn | egrep -i 'sas|raid|sata|nvme|scsi' (confirmez que le contrôleur existe)

Décision : Si le contrôleur n’apparaît pas dans lspci, cessez d’accuser Proxmox. C’est le BIOS/le placement PCIe/l’allocation des lanes ou la carte est morte.

2) Si le contrôleur existe, vérifiez le pilote et le lien

  • lspci -k -s <slot> → vérifiez « Kernel driver in use ».
  • journalctl -k -b | egrep -i 'mpt3sas|megaraid|ahci|nvme|reset|timeout|aer' → trouvez la cartouche fumante.

Décision : Pas de pilote lié ? Chargez le module ou fixez le firmware/les réglages du BIOS. Réinitialisations/timeouts de lien ? suspectez câblage/backplane/expander/alimentation.

3) Rescannez avant de redémarrer

Rescannez SCSI/NVMe. Si les disques apparaissent après un rescan, vous avez appris quelque chose : hotplug, entraînement de lien, ou timing au démarrage.

4) Si les disques apparaissent mais sont « manquants » dans l’UI Proxmox

Allez en CLI et utilisez des identifiants stables. L’UI ne ment pas ; elle n’est juste pas votre commandant d’incident.

Décision : S’ils existent dans /dev/disk/by-id mais pas dans votre pool, c’est une histoire ZFS/import/partition, pas une détection.

Modèle mental pratique : où les disques peuvent disparaître

La détection des disques est une chaîne. Cassez un maillon et vous regarderez une liste vide.

Couche 1 : Alimentation et connectivité physique

Le disque a besoin d’alimentation, du connecteur correct et d’un backplane qui ne fait pas de danse interprétative. « Monter en rotation » n’est pas identique à « liaison de données établie ». Le SAS en particulier alimentera volontiers un disque tandis que le lien est tombé à cause d’une lane défectueuse.

Couche 2 : Interposer/backplane/expander et leur traduction

Les backplanes SAS peuvent inclure des expanders, multiplexeurs et logique « utile ». Une unique lane marginale peut faire disparaître un disque, ou pire, le faire osciller sous charge. Le SATA derrière des expanders SAS fonctionne—jusqu’à ce qu’il ne fonctionne plus, selon l’expander, le firmware du disque et le câblage.

Couche 3 : Firmware et mode de l’HBA/contrôleur

Les HBA peuvent fonctionner en vrai HBA (mode IT) ou faire semblant d’être un contrôleur RAID (mode IR/RAID). Proxmox + ZFS veut un passage direct. La personnalité RAID peut cacher des disques derrière des volumes virtuels, bloquer SMART et compliquer la récupération d’erreurs.

Couche 4 : Énumération PCIe et budget de lanes

Le contrôleur lui-même est un périphérique PCIe. Si la carte mère ne l’énumère pas, Linux non plus. Les réglages de bifurcation PCIe, le câblage du slot et le partage de lanes avec M.2/U.2 peuvent faire qu’un slot « physique x16 » soit électriquement x4—ou x0, si vous contrariez les dieux des lanes.

Couche 5 : Pilotes kernel Linux + création des nœuds

Même quand le matériel va bien, le kernel peut ne pas attacher le bon pilote, ou udev peut ne pas créer les nœuds comme attendu. Multipath peut cacher volontairement des chemins individuels. Un ancien initramfs peut manquer de modules. Les disques peuvent exister mais sous d’autres noms.

Couche 6 : Présentation du stockage par Proxmox

Proxmox VE est Debian sous une UI. Si Debian ne le voit pas, Proxmox ne le verra pas. Si Debian le voit mais que l’UI ne l’affiche pas où vous cherchez, c’est un problème de workflow, pas matériel.

Paraphrase d’une idée de John Allspaw : la fiabilité vient de la bonne réponse aux pannes, pas de faire comme si elles n’existaient pas.

Blague #1 : « Le mode RAID rendra ZFS heureux » revient à dire « j’ai mis un volant sur le grille-pain ; maintenant c’est une voiture ».

Faits et historique utiles au dépannage

  • Le scan SCSI est ancien… et toujours là. Les piles SAS modernes et même certaines piles SATA reposent encore sur des scans d’hôtes SCSI, d’où le fait que les rescans peuvent « trouver » des disques sans redémarrage.
  • Les HBAs SAS LSI sont devenus la référence. La lignée Broadcom/Avago/LSI compte, car la dénomination des pilotes (mpt2sas/mpt3sas) et les outils firmware s’appuient dessus.
  • Le mode IT est devenu populaire parce que les systèmes de fichiers sont devenus plus intelligents. ZFS et systèmes similaires veulent une visibilité directe des disques. Les contrôleurs RAID étaient conçus pour une époque où le contrôleur gérait l’intégrité.
  • SFF-8087 et SFF-8643 ressemblent à des « simples câbles » mais sont des systèmes de signal. Un mini-SAS partiellement enfiché peut alimenter des disques et tout de même échouer sur les voies de données. Ce n’est pas magique ; ce sont des paires différentielles et des tolérances.
  • Les slots PCIe mentent par marketing. « Slot x16 » signifie souvent « connecteur x16 ». Électriquement, il peut être x8 ou x4 selon le routing CPU/carte mère.
  • UEFI a changé le comportement des option ROM. Certaines cartes de stockage dépendent des option ROM pour les écrans d’énumération au démarrage ; les réglages UEFI peuvent masquer ces écrans sans changer ce que Linux voit.
  • NVMe a apporté son propre chemin de détection. Les périphériques NVMe ne sont pas des « disques SCSI » et n’apparaîtront pas dans les outils HBA SAS ; ils utilisent le sous-système NVMe et l’entraînement du lien PCIe.
  • Le passage SMART n’est pas garanti. Avec les contrôleurs RAID, les données SMART peuvent être bloquées ou nécessiter des outils vendor, ce qui modifie la façon dont vous vérifiez « l’existence » d’un disque.

Tâches pratiques (commandes + signification + décision)

Voici les tâches que j’exécute réellement quand un nœud dit « pas de disques ». Chaque tâche inclut ce que vous regardez et la décision que vous prenez.

Task 1: Confirm the controller is enumerated on PCIe

cr0x@server:~$ lspci -nn | egrep -i 'sas|raid|sata|scsi|nvme'
03:00.0 Serial Attached SCSI controller [0107]: Broadcom / LSI SAS3008 PCI-Express Fusion-MPT SAS-3 [1000:0097] (rev 02)
01:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd NVMe SSD Controller [144d:a808]

Ce que cela signifie : La carte mère voit l’HBA/le contrôleur NVMe. S’il n’est pas ici, Linux ne verra jamais les disques qui en dépendent.

Décision : Dispositif manquant → réenficher la carte, changer de slot, vérifier les réglages PCIe dans le BIOS, désactiver les périphériques en conflit, vérifier l’alimentation des risers.

Task 2: Verify kernel driver binding

cr0x@server:~$ lspci -k -s 03:00.0
03:00.0 Serial Attached SCSI controller: Broadcom / LSI SAS3008 PCI-Express Fusion-MPT SAS-3 (rev 02)
	Subsystem: Broadcom / LSI SAS9300-8i
	Kernel driver in use: mpt3sas
	Kernel modules: mpt3sas

Ce que cela signifie : Le bon pilote est attaché. Si « Kernel driver in use » est vide, vous avez un problème de pilote/firmware/blacklist.

Décision : Pas de pilote lié → vérifiez modprobe, les logs kernel, Secure Boot, la compatibilité du firmware et si vous utilisez un kernel fournisseur bizarre.

Task 3: See what disks Linux created (don’t trust the UI yet)

cr0x@server:~$ lsblk -e7 -o NAME,TYPE,SIZE,MODEL,SERIAL,TRAN,HCTL
NAME    TYPE  SIZE MODEL              SERIAL        TRAN HCTL
sda     disk  3.6T ST4000NM0035-1V4    ZC123ABC      sas  3:0:0:0
sdb     disk  3.6T ST4000NM0035-1V4    ZC123DEF      sas  3:0:1:0
nvme0n1 disk  1.8T Samsung SSD 990 PRO S6Z1NZ0R12345 nvme -

Ce que cela signifie : S’il est dans lsblk, le kernel le voit. TRAN vous indique s’il s’agit de sas, sata, nvme.

Décision : Disques absents → descendez la pile : dmesg, câblage, expander, alimentation. Disques présents mais Proxmox « manquant » → probablement UI/workflow, multipath ou import ZFS.

Task 4: Check kernel logs for link resets/timeouts

cr0x@server:~$ journalctl -k -b | egrep -i 'mpt3sas|megaraid|ahci|nvme|reset|timeout|aer|link down' | tail -n 60
Dec 26 10:12:01 server kernel: mpt3sas_cm0: log_info(0x31120101): originator(PL), code(0x12), sub_code(0x0101)
Dec 26 10:12:01 server kernel: sd 3:0:1:0: rejecting I/O to offline device
Dec 26 10:12:03 server kernel: pcieport 0000:00:1c.0: AER: Corrected error received: 0000:03:00.0
Dec 26 10:12:03 server kernel: nvme nvme0: I/O 42 QID 5 timeout, aborting

Ce que cela signifie : « offline device », « timeout », « link down », spam AER = intégrité du signal, alimentation ou périphérique/contrôleur défaillant.

Décision : Timeouts sur plusieurs disques → câble/backplane/expander/HBA. Timeouts sur un seul disque → ce disque ou son emplacement.

Task 5: List storage controllers the kernel thinks exist

cr0x@server:~$ lsscsi -H
[0]    ata_piix
[2]    mpt3sas
[3]    nvme

Ce que cela signifie : Confirme les adaptateurs hôtes. Si le driver HBA est chargé, il apparaît comme un host.

Décision : HBA absent ici mais présent dans lspci → le pilote ne s’est pas chargé ou a échoué à s’initialiser.

Task 6: Inspect SCSI hosts and rescan for devices

cr0x@server:~$ ls -l /sys/class/scsi_host/
total 0
lrwxrwxrwx 1 root root 0 Dec 26 10:10 host0 -> ../../devices/pci0000:00/0000:00:17.0/ata1/host0/scsi_host/host0
lrwxrwxrwx 1 root root 0 Dec 26 10:10 host2 -> ../../devices/pci0000:00/0000:03:00.0/host2/scsi_host/host2
cr0x@server:~$ for h in /sys/class/scsi_host/host*/scan; do echo "- - -" > "$h"; done

Ce que cela signifie : Force un scan de tous les hôtes SCSI. Si les disques apparaissent après cela, la détection est liée au timing/hotplug/expander.

Décision : Si les rescans « réparent » systématiquement, vérifiez le hotplug BIOS, le démarrage échelonné, le firmware expander et le firmware HBA.

Task 7: Check SATA/AHCI detection (onboard ports)

cr0x@server:~$ dmesg -T | egrep -i 'ahci|ata[0-9]|SATA link' | tail -n 40
[Thu Dec 26 10:10:12 2025] ahci 0000:00:17.0: AHCI 0001.0301 32 slots 6 ports 6 Gbps 0x3f impl SATA mode
[Thu Dec 26 10:10:13 2025] ata1: SATA link down (SStatus 0 SControl 300)
[Thu Dec 26 10:10:13 2025] ata2: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

Ce que cela signifie : « link down » sur un port avec un disque indique câblage/port désactivé dans le BIOS/alimentation.

Décision : Si les ports sont link down partout, vérifiez le mode SATA dans le BIOS (AHCI) et si la carte a désactivé des ports quand M.2 est utilisé.

Task 8: Enumerate NVMe devices and controller health

cr0x@server:~$ nvme list
Node             SN               Model                          Namespace Usage                      Format           FW Rev
---------------- ---------------- -------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1      S6Z1NZ0R12345    Samsung SSD 990 PRO 2TB        1         1.80  TB / 2.00  TB        512   B +  0 B   5B2QJXD7

Ce que cela signifie : NVMe est présent comme son propre sous-système. Si nvme list est vide mais que lspci montre le contrôleur, cela peut être un problème de pilote, ASPM PCIe ou lien.

Décision : Liste vide → vérifiez journalctl -k pour des erreurs NVMe, les réglages BIOS de vitesse PCIe Gen et la bifurcation du slot (pour des adaptateurs multi-NVMe).

Task 9: Confirm stable disk identifiers (what you should use for ZFS)

cr0x@server:~$ ls -l /dev/disk/by-id/ | egrep -i 'wwn|nvme|scsi' | head
lrwxrwxrwx 1 root root  9 Dec 26 10:15 nvme-Samsung_SSD_990_PRO_2TB_S6Z1NZ0R12345 -> ../../nvme0n1
lrwxrwxrwx 1 root root  9 Dec 26 10:15 scsi-35000c500a1b2c3d4 -> ../../sda
lrwxrwxrwx 1 root root  9 Dec 26 10:15 scsi-35000c500a1b2c3e5 -> ../../sdb
lrwxrwxrwx 1 root root  9 Dec 26 10:15 wwn-0x5000c500a1b2c3d4 -> ../../sda

Ce que cela signifie : Ces identifiants survivent aux redémarrages et aux renommages de périphériques (sda devenant sdb après des changements matériels).

Décision : Si vos scripts de pool/import utilisent /dev/sdX, arrêtez. Migrez vers by-id/by-wwn avant que votre prochaine fenêtre de maintenance ne vous dévore.

Task 10: Check SMART visibility (tells you if you’re really seeing the disk)

cr0x@server:~$ smartctl -a /dev/sda | head -n 20
smartctl 7.3 2022-02-28 r5338 [x86_64-linux-6.8.12-4-pve] (local build)
=== START OF INFORMATION SECTION ===
Model Family:     Seagate Exos 7E8
Device Model:     ST4000NM0035-1V4
Serial Number:    ZC123ABC
LU WWN Device Id: 5 000c50 0a1b2c3d4
Firmware Version: SN03
User Capacity:    4,000,787,030,016 bytes [4.00 TB]

Ce que cela signifie : Si SMART fonctionne, vous avez probablement une vraie visibilité en pass-through. Si SMART échoue derrière un contrôleur RAID, vous aurez peut-être besoin d’autres types de périphériques ou d’outils vendor.

Décision : SMART bloqué + vous voulez ZFS → vérifiez le mode HBA IT ou un vrai HBA, pas la personnalité RAID.

Task 11: Detect if multipath is hiding your disks

cr0x@server:~$ multipath -ll
mpatha (3600508b400105e210000900000490000) dm-0 IBM,2810XIV
size=1.0T features='1 queue_if_no_path' hwhandler='0' wp=rw
|-+- policy='service-time 0' prio=50 status=active
| `- 3:0:0:0 sda 8:0  active ready running
`-+- policy='service-time 0' prio=10 status=enabled
  `- 4:0:0:0 sdb 8:16 active ready running

Ce que cela signifie : Multipath peut présenter des disques comme /dev/dm-* et les /dev/sdX bruts ne sont que des chemins.

Décision : Si ce sont des disques locaux, multipath est une mauvaise configuration. Désactivez-le ou blacklistz les HBA locaux, sinon Proxmox/ZFS pourra utiliser des périphériques « bizarres ».

Task 12: Identify if you’re on a RAID controller with virtual disks

cr0x@server:~$ lspci -nn | egrep -i 'megaraid|raid'
04:00.0 RAID bus controller [0104]: Broadcom / LSI MegaRAID SAS-3 3108 [1000:005d] (rev 02)
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,MODEL
NAME  TYPE  SIZE MODEL
sda   disk  7.3T LSI MR9361-8i

Ce que cela signifie : Si le modèle ressemble au contrôleur et que vous ne voyez qu’un seul « disque », vous regardez un disque virtuel, pas les disques physiques.

Décision : Pour ZFS : soit passez à un HBA / flashez le contrôleur en mode IT, soit acceptez que ZFS ne puisse pas gérer correctement les disques individuels.

Task 13: Check if BIOS disabled the slot or remapped lanes

cr0x@server:~$ dmidecode -t baseboard | egrep -i 'Manufacturer|Product|Version'
Manufacturer: Supermicro
Product Name: X11SPH-NCTF
Version: 1.02
cr0x@server:~$ lspci -vv -s 03:00.0 | egrep -i 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 8GT/s, Width x8
LnkSta: Speed 2.5GT/s (downgraded), Width x8

Ce que cela signifie : Lien rétrogradé à 2.5GT/s suggère des problèmes d’intégrité du signal, mauvais slot forcé, ou riser/câble défectueux.

Décision : Liens rétrogradés avec erreurs → essayez de forcer Gen3/Gen4 dans le BIOS, changez de slot, remplacez le riser, vérifiez l’enfichage.

Task 14: Proxmox-specific: confirm the kernel and modules match expectations

cr0x@server:~$ uname -r
6.8.12-4-pve
cr0x@server:~$ modinfo mpt3sas | egrep -i 'filename|version|firmware'
filename:       /lib/modules/6.8.12-4-pve/kernel/drivers/scsi/mpt3sas/mpt3sas.ko
version:        44.100.00.00
firmware:       mpt3sas_fw.bin

Ce que cela signifie : Confirme que vous utilisez le kernel Proxmox et que le module existe. Des kernels/initramfs non alignés peuvent mordre après des mises à jour.

Décision : Si le module est manquant ou le kernel incorrect, corrigez les paquets et régénérez l’initramfs avant de courir après des fantômes matériels.

HBA, BIOS/UEFI et PCIe : la scène du crime habituelle

Mode HBA : IT vs IR/RAID (et pourquoi Proxmox s’en soucie)

Si vous utilisez ZFS (comme beaucoup de boutiques Proxmox), vous voulez que l’HBA présente chaque disque physique directement à Linux. C’est le mode IT en termes LSI/Broadcom. Le mode RAID (IR) est une philosophie différente : le contrôleur abstrait les disques en volumes logiques. Cette abstraction casse plusieurs choses dont vous dépendez en opérations modernes :

  • SMART/état précis par disque (souvent bloqué ou bizarre).
  • Identités de disque prévisibles (les WWN peuvent être cachés ou remplacés).
  • Surfaces d’erreur claires (les timeouts deviennent « le contrôleur dit non »).
  • La capacité de ZFS à gérer la redondance et l’auto-réparation avec une visibilité complète.

De plus : les contrôleurs RAID ont souvent des caches d’écriture, des BBUs et des politiques utiles… jusqu’à ce qu’elles ne le soient plus. ZFS gère déjà sa propre cohérence. Deux capitaines aux commandes donnent le mal de mer.

Paramètres UEFI qui impactent silencieusement la détection

Le BIOS/UEFI peut cacher ou casser votre stockage sans messages d’erreur dramatiques. Les réglages les plus courants à auditer quand les disques disparaissent :

  • Mode SATA : AHCI vs RAID. Sur serveurs, le mode RAID peut router les ports via une couche Intel RST-like que Linux ne gèrera peut-être pas comme vous l’attendez.
  • Configuration des slots PCIe : vitesse Gen forcée vs Auto ; bifurcation x16 → x4x4x4x4 pour adaptateurs multi-NVMe.
  • Option ROM policy : UEFI-only vs Legacy. Cela affecte surtout la visibilité au boot et les écrans de management, mais une mauvaise config peut masquer ce que vous pensez devoir apparaître avant le démarrage.
  • IOMMU/VT-d/AMD-Vi : Pas généralement un casseur de détection disque, mais peut changer le comportement des périphériques en passthrough.
  • Désactivation des stockages onboard : Certaines cartes désactivent des ports SATA lorsque des slots M.2 sont occupés, ou partagent des lanes avec des slots PCIe.

Partage de lanes PCIe : le « pourquoi mon slot a cessé de fonctionner ? » moderne

Les cartes mères sont des policiers du trafic. Mettez un NVMe dans un slot M.2 et votre HBA peut chuter de x8 à x4, ou le slot adjacent peut être désactivé. Ce n’est pas un « mauvais design ». C’est de l’économie et de la physique : les CPU ont un nombre fini de lanes et les vendeurs multiplexent selon leur feuille technique.

Si vous voyez un contrôleur présent mais instable (erreurs AER, lien down/up), les problèmes de lanes ou d’intégrité du signal sont très probables. Les risers, en particulier, aiment être « assez fonctionnels ».

Blague #2 : Un riser PCIe qui « fonctionne si vous ne touchez pas au châssis » est moins un composant et plus un style de vie.

Câblage, backplanes, expanders et « c’est bien enfiché »

Connecteurs Mini-SAS : pourquoi la défaillance partielle est courante

Les câbles SAS transportent plusieurs lanes. Un seul SFF-8643 peut porter quatre lanes SAS ; un backplane peut mapper les lanes à des baies individuelles. Si une lane lâche, vous ne perdez pas toujours tous les disques. Vous perdez « quelques baies », souvent dans un motif qui ressemble à un problème logiciel.

Règle pratique : si des disques manquent selon un motif répétitif (par ex. baies 1–4 OK, 5–8 mortes), suspectez un câble mini-SAS ou un port spécifique. Ne passez pas une heure dans udev pour un problème qui vit dans le cuivre.

Backplanes avec expanders : bien quand ça marche

Les expanders permettent de connecter beaucoup de disques à moins de ports HBA. Ils ajoutent aussi une couche qui peut contenir des bugs firmware, des bizarreries de négociation et une sensibilité aux disques SATA derrière des expanders SAS. Les symptômes incluent :

  • Disques apparaissent après le boot mais disparaissent sous charge.
  • Messages intermittents « device offlined ».
  • Seuls certains modèles de disques se comportent mal.

Quand cela arrive, on ne « tune » pas Linux. On valide le firmware de l’expander, on échange les câbles, on isole en connectant moins de baies et on teste avec un modèle de disque connu bon.

Distribution d’alimentation et spin-up

Surtout dans les châssis denses, l’alimentation peut être le tueur silencieux. Les disques peuvent démarrer mais s’effondrer pendant la négociation du lien ou lorsque plusieurs disques démarrent simultanément. Certains HBA et backplanes supportent le spin-up échelonné. Certains non. Certains le supportent mais sont mal configurés.

Un signe révélateur est plusieurs disques qui tombent en même temps au démarrage ou pendant un scrub, puis réapparaissent plus tard. Ce n’est pas une « chose Proxmox ». C’est de l’alimentation ou du signal.

Vérifications physiques simples qui battent l’ingéniosité

  • Réenfichez les deux extrémités des câbles mini-SAS. Ne « pressez pas doucement ». Déconnectez, inspectez, reconnectez fermement.
  • Échangez les câbles entre un port connu bon et un suspect pour voir si le problème suit le câble.
  • Déplacez un disque dans une autre baie. Si le disque fonctionne ailleurs, la baie/lane du backplane est suspecte.
  • Si possible, connectez temporairement un disque directement à un port HBA (bypassez expander/backplane) pour isoler les couches.

Couche Linux/Proxmox : pilotes, udev, multipath et nœuds de périphérique

Présence du pilote n’est pas santé du pilote

Voir mpt3sas chargé ne garantit pas que le contrôleur s’est initialisé correctement. Un mismatch firmware peut produire une fonctionnalité partielle : le contrôleur s’énumère, mais pas de cibles ; ou les cibles s’énumèrent mais renvoient constamment des erreurs.

Les logs kernel comptent plus que la liste des modules. Si vous voyez des reset répétés, « firmware fault » ou des queues bloquées, traitez cela comme un incident réel : collectez les logs, stabilisez le matériel et envisagez des mises à jour firmware.

Multipath : utile jusqu’à ce que ce ne soit plus le cas

Multipath est conçu pour les SANs et le stockage à double chemin. Sur un nœud Proxmox avec des disques SAS locaux, c’est généralement accidentel et nuisible. Il peut masquer les périphériques attendus, ou créer des nœuds device-mapper que Proxmox/ZFS utilisera de façon incohérente si vous n’êtes pas explicite.

Si vous n’utilisez pas explicitement multipath pour du stockage partagé, vous voulez généralement le désactiver ou le configurer pour ignorer les disques locaux.

Nomination des périphériques : /dev/sdX est un piège

Linux assigne les noms /dev/sdX dans l’ordre de découverte. Ajoutez un contrôleur, réordonnez les câbles, ou changez les réglages de boot BIOS et l’ordre change. C’est ainsi que vous importez les mauvais disques, effacez le mauvais périphérique, ou créez un pool sur les mauvais membres.

Utilisez /dev/disk/by-id ou les WWN. Faites-en une politique. Votre futur vous remerciera en silence.

Quand Proxmox « n’affiche pas les disques » mais Linux oui

Réalités communes :

  • Les disques ont d’anciennes partitions et l’UI Proxmox filtre ce qu’elle considère comme « disponible ».
  • ZFS utilise déjà les disques (ils appartiennent à un pool importé ou à un pool obsolète). ZFS ne partagera pas poliment.
  • Vous regardez au mauvais endroit : disques du nœud vs définitions de stockage vs vue datacenter.
  • Multipath ou device-mapper présente des noms différents de ceux attendus.

Angle ZFS : pourquoi le « mode RAID » n’est pas votre ami

Proxmox intègre un support ZFS de première classe. ZFS suppose qu’il gère la redondance, les checksums et la réparation. Le RAID matériel suppose lui gérer la redondance et la récupération d’erreurs. En empilant les deux, vous créez un système où chaque couche prend des décisions sans information complète.

Ce qui « marche » mais est quand même mauvais

  • Créer un énorme volume RAID0/RAID10 et mettre ZFS dessus : ZFS perd la visibilité par disque et ne peut pas isoler les membres défaillants.
  • Utiliser le cache du contrôleur RAID avec ZFS et des écritures sync : vous pouvez tromper ZFS sur la durabilité si la politique de cache est unsafe.
  • Supposer que le contrôleur remontera proprement les erreurs disque : il peut remapper, relancer ou masquer jusqu’à l’impossibilité.

Ce que vous devriez faire à la place

  • Utilisez un HBA (ou flashez le contrôleur en mode IT) et présentez les disques bruts à ZFS.
  • Utilisez des identifiants stables lors de la création des pools.
  • Préférez des combinaisons firmware stables et éprouvées. Le bleeding edge est génial pour le labo, pas pour votre quorum de cluster.

Erreurs courantes : symptôme → cause → correctif

1) Symptom: HBA not in lspci

Cause : Carte non enfichée, slot mort, partage de lanes a désactivé le slot, riser défaillant, ou BIOS a désactivé ce slot.

Fix : Réenficher, essayer un autre slot, retirer le riser, vérifier « PCIe slot enable » dans le BIOS, vérifier le partage de lanes avec M.2/U.2, mettre à jour le BIOS si ancien.

2) Symptom: HBA in lspci but no disks in lsblk

Cause : Pilote non lié, mismatch firmware, HBA en mode requérant la stack vendor, câble/backplane cassé empêchant la découverte des cibles.

Fix : Vérifier lspci -k, consulter journalctl -k, rescanner les hôtes SCSI, échanger les câbles, valider firmware et mode HBA (IT pour ZFS).

3) Symptom: Some bays missing in a pattern

Cause : Une lane/câble/port SAS est en panne ; le mapping du backplane correspond à l’ensemble manquant.

Fix : Échanger le câble mini-SAS ; déplacer sur un autre port HBA ; réenficher le connecteur ; vérifier pins tordus/dommages.

4) Symptom: Disks appear after rescan but vanish after reboot

Cause : Timing hotplug, bizarreries d’expander, spin-up échelonné mal configuré, alimentation marginale au boot.

Fix : Mettre à jour firmware HBA/backplane/expander, activer le spin-up échelonné si supporté, vérifier l’alimentation et la distribution PSU, inspecter les logs de boot pour resets.

5) Symptom: NVMe not detected, but works in another machine

Cause : Slot désactivé à cause de réglages de bifurcation, PCIe Gen forcé trop haut/bas, partage de lanes avec SATA, ou l’adaptateur nécessite la bifurcation.

Fix : Définir la bifurcation correcte, mettre la vitesse PCIe sur Auto/Gen3/Gen4 appropriée, déplacer dans un slot attaché au CPU, mettre à jour le BIOS.

6) Symptom: Proxmox GUI doesn’t show disks, but lsblk does

Cause : Partitions existantes/métadonnées LVM, ZFS revendique déjà les disques, présentation multipath, ou vous regardez la mauvaise vue UI.

Fix : Utilisez la CLI pour confirmer by-id, vérifiez zpool status/zpool import, vérifiez multipath -ll, effacez les signatures uniquement quand vous êtes sûr.

7) Symptom: SMART fails with “cannot open device” behind controller

Cause : Abstraction du contrôleur RAID ; le passage SMART nécessite un type de périphérique spécial ou n’est pas supporté.

Fix : Utilisez HBA/mode IT pour ZFS ; sinon utilisez les outils vendor et acceptez les limites.

8) Symptom: Disks flap under load, ZFS sees checksum errors

Cause : Intégrité du signal câble/backplane/expander ou alimentation insuffisante ; parfois un disque empoisonne le bus.

Fix : Remplacez les câbles en premier, isolez en retirant des disques, vérifiez dmesg pour des resets, validez la santé du PSU et du backplane.

Checklists / plan étape par étape

Checklist A: “Installer can’t see any disks”

  1. Entrez dans le BIOS/UEFI et confirmez que le contrôleur est activé et visible.
  2. Confirmez que le mode SATA est AHCI (sauf si vous avez explicitement besoin du RAID pour un volume de boot).
  3. Pour un HBA : vérifiez qu’il est en mode IT ou véritable HBA (pas MegaRAID volumes virtuels) si vous voulez ZFS.
  4. Déplacez l’HBA dans un autre slot PCIe (préférez les slots attachés au CPU).
  5. Démarrez un environnement de secours et lancez lspci et dmesg. S’il manque là aussi, c’est matériel.
  6. Remplacez les câbles mini-SAS et réenfichez les connecteurs aux deux extrémités.
  7. Si vous utilisez un backplane expander : faites un test en connexion directe avec un disque.

Checklist B: “Some disks missing behind HBA”

  1. Lancez lsblk et identifiez quelles baies manquent ; cherchez des motifs.
  2. Vérifiez les logs pour des resets de lien et périphériques offlined.
  3. Rescannez les hôtes SCSI ; voyez si les disques manquants apparaissent.
  4. Échangez le câble alimentant l’ensemble de baies manquant.
  5. Déplacez le câble sur un autre port HBA ; voyez si l’ensemble manquant suit.
  6. Déplacez un disque manquant dans une baie connue bonne ; s’il apparaît, la baie/lane est mauvaise.
  7. Mettez à jour le firmware HBA si vous avez une révision problématique connue.

Checklist C: “Disks detected in Linux but not usable in Proxmox”

  1. Confirmez les identifiants stables dans /dev/disk/by-id.
  2. Vérifiez si ZFS propose un pool importable : zpool import.
  3. Vérifiez si les disques ont des signatures : wipefs -n /dev/sdX (le -n est le drapeau sécurité ; conservez-le).
  4. Vérifiez multipath : multipath -ll.
  5. Décidez votre intention : importer des données existantes vs effacer et réutiliser.
  6. Si vous effacez, faites-le délibérément et documentez quels WWN vous avez nettoyés.

Checklist D: “NVMe not showing up”

  1. Confirmez le contrôleur dans lspci.
  2. Vérifiez nvme list et les logs kernel pour des timeouts.
  3. Inspectez le statut du lien PCIe (LnkSta) pour des rétrogradations.
  4. Définissez la bifurcation correcte pour les adaptateurs multi-NVMe.
  5. Déplacez le NVMe dans un autre slot et retestez.

Trois mini-histoires d’entreprise issues du terrain

Mini-story 1: The incident caused by a wrong assumption

L’équipe déployait un nouveau cluster Proxmox pour des workloads CI internes. Le plan de stockage était « simple » : huit disques SAS par nœud, miroirs ZFS, terminé. Les achats ont livré des serveurs avec un « contrôleur SAS RAID » au lieu du HBA demandé. Personne n’a paniqué parce que le contrôleur avait toujours « SAS » dans le nom et le BIOS montrait un gros disque logique.

Ils ont installé Proxmox sur ce volume logique et construit des pools ZFS sur ce que le contrôleur exposait. Ça a fonctionné quelques semaines, ce qui permet aux mauvaises hypothèses de devenir « décisions de conception ». Puis un disque a commencé à lâcher. Le contrôleur a remappé et relancé dans des modes que ZFS ne pouvait pas observer, et le nœud a commencé à se bloquer pendant les scrubs. Les logs étaient pleins de timeouts mais rien ne correspondait proprement à une baie physique.

Pendant la fenêtre de maintenance, quelqu’un a retiré le disque « en échec » selon l’UI du contrôleur. Le mauvais. Le contrôleur avait changé son numérotage interne après des remaps antérieurs, et la feuille de mapping était obsolète. Le volume logique a alors degradé différemment, ZFS s’est fâché, et le cluster a perdu une partie de capacité pendant la charge maximale.

La réparation fut peu glamour : remplacer le contrôleur RAID par un vrai HBA, reconstruire le nœud, et imposer une politique : ZFS obtient des disques bruts, identifiés par WWN, et le mapping baie→WWN est validé avec LEDs et numéros de série avant toute intervention. L’hypothèse « SAS égale HBA » était la cause racine et ça leur a coûté un week-end.

Mini-story 2: The optimization that backfired

Une autre équipe avait des problèmes de performance pendant les resilvers ZFS. Quelqu’un a suggéré « optimiser le câblage » en utilisant un seul backplane expander pour réduire les ports HBA et garder l’assemblage propre. Moins de câbles, moins de points de défaillance, non ?

En pratique, l’expander a introduit un comportement subtil : pendant des I/O intenses, quelques SSD SATA (utilisés comme vdevs spéciaux) perdaient intermittemment la connexion pendant quelques secondes, puis revenaient. Le HBA et le kernel loggaient des resets de lien, et ZFS marquait des périphériques comme défaillants ou dégradés selon le timing. Le symptôme ressemblait à « ZFS est instable » car les coupures étaient transitoires.

L’équipe a essayé de tuner les timeouts et les profondeurs de queue, parce que les ingénieurs aiment tourner des boutons et l’expander semblait « enterprise ». Le tuning a réduit les erreurs visibles mais n’a pas résolu la cause. Lors d’un vrai incident—redémarrage du nœud plus récupération simultanée de VM—les périphériques ont replafonné et le pool a refusé de s’importer proprement sans intervention manuelle.

Ils ont annulé l’« optimisation ». Connecter directement les SSD sensibles, garder l’expander pour les HDD massifs où la latence compte moins, et standardiser les modèles de disques derrière l’expander. La performance et le sommeil se sont améliorés. Parfois moins de câbles, c’est juste moins d’indices quand ça casse.

Mini-story 3: The boring but correct practice that saved the day

Une équipe avait l’habitude, jugée pédante, d’enregistrer chaque disque par WWN et emplacement de baie à l’installation. Ils tenaient une feuille simple : numéro de châssis, numéro de baie, numéro de série du disque, WWN, et l’appartenance prévue au vdev ZFS. Ils étiquetaient aussi les câbles par port HBA et connecteur backplane. Personne n’aimait le faire, mais c’était une politique.

Un an plus tard, un nœud commence à rapporter des erreurs de checksum intermittentes pendant les scrubs. Les logs suggèrent un lien instable, pas un disque qui meurt, mais la topologie du pool comprenait douze disques et un expander backplane. Dans l’ancien monde, cela aurait dégénéré en « retirer des disques jusqu’à ce que les erreurs s’arrêtent ». C’est comme ça qu’on crée de nouveaux incidents.

Au lieu de ça, ils ont corrélé le WWN affecté avec la baie. Les erreurs étaient toujours sur des disques dans les baies 9–12. Ça correspondait à un seul câble mini-SAS alimentant cette section du backplane. Ils ont changé le câble pendant une courte fenêtre de maintenance, relancé un scrub, et les erreurs ont disparu.

Sans drame. Sans devinettes. La pratique ennuyeuse d’inventaire a transformé un incident potentiellement compliqué en une réparation de 20 minutes avec une cause claire. La fiabilité est souvent juste de la tenue de registres avec conviction.

FAQ

1) Proxmox installer shows no disks. Is it always an HBA driver issue?

Non. Si lspci n’affiche pas le contrôleur, c’est BIOS/PCIe/matériel. Si le contrôleur apparaît mais pas les disques, alors cela peut être un pilote/firmware/câblage.

2) I see disks in BIOS but not in Linux. How is that possible?

Le BIOS peut montrer des volumes RAID virtuels ou un résumé du contrôleur sans exposer les targets à Linux. Ou Linux manque du bon module, ou le contrôleur échoue à s’initialiser au boot (vérifiez journalctl -k).

3) Do I need IT mode for Proxmox?

Si vous utilisez ZFS et voulez des opérations raisonnables, oui. Si vous insistez pour du RAID matériel, vous pouvez l’utiliser, mais vous choisissez alors un modèle opérationnel différent avec des outils différents.

4) Why do disks show up as /dev/dm-0 instead of /dev/sda?

Généralement multipath ou empilement device-mapper (LVM, dm-crypt). Pour des disques locaux que vous ne vouliez pas multipath, corrigez la config multipath ou désactivez-le.

5) My disks appear, but Proxmox GUI doesn’t list them as available. Are they broken?

Souvent ils ont des signatures existantes (anciens métadonnées ZFS/LVM/RAID) ou font déjà partie d’un pool importé. Vérifiez avec lsblk, wipefs -n et zpool import avant toute action destructive.

6) Can a bad SAS cable really cause only one disk to disappear?

Oui. Mini-SAS transporte plusieurs lanes ; selon le mapping du backplane, une lane défectueuse peut isoler une seule baie ou un sous-ensemble. Les motifs sont vos amis.

7) NVMe not detected: what’s the single most common BIOS mistake?

Mauvaise configuration de bifurcation pour des adaptateurs multi-NVMe, ou partage de lanes qui désactive le slot quand un autre M.2/U.2 est peuplé.

8) Should I force PCIe Gen speed to fix link issues?

Parfois forcer une Gen inférieure stabilise des liens instables (utile pour diagnostiquer), mais la vraie réparation est souvent l’enfichage, les risers, le câblage ou le choix du slot/carte mère.

9) How do I decide between “replace disk” and “replace cable/backplane”?

Si plusieurs disques montrent des erreurs sur le même port HBA/segment de backplane, suspectez le câble/backplane. Si un seul disque suit le disque à travers les baies, c’est le disque.

10) Is it safe to rescan SCSI hosts on a production node?

Généralement oui, mais faites-le avec conscience de la situation. Les rescans peuvent déclencher des événements de découverte et du bruit dans les logs. Évitez pendant des opérations sensibles de stockage si vous êtes déjà en dégradé.

Conclusion : prochaines étapes pratiques

Si Proxmox ne voit pas les disques, arrêtez de deviner et parcourez la chaîne : énumération PCIe → liaison du pilote → stabilité du lien → découverte des targets → identifiants stables → consommation par Proxmox/ZFS. Les gains rapides sont généralement physiques : enfichage, allocation de lanes et câbles. Les échecs les plus coûteux viennent du mauvais mode de contrôleur et d’un nommage de périphérique négligent.

  1. Exécutez le playbook de diagnostic rapide et classez le domaine de défaillance en 10 minutes.
  2. Collectez des preuves : lspci -k, lsblk et les logs kernel autour du moment de détection.
  3. Standardisez : HBA/mode IT pour ZFS, noms by-id, et une carte baie→WWN.
  4. Corrigez la cause racine, pas le symptôme : remplacez câbles/riser suspects, corrigez la bifurcation BIOS, mettez à jour le firmware avec méthode.
  5. Après récupération, effectuez un scrub/resilver test et relisez les logs. Si vous ne vérifiez pas, vous n’avez pas réparé — vous avez juste arrêté de voir le problème.

MySQL vs PostgreSQL sur un VPS 4GB : quoi régler en premier pour des sites web

Vous avez un VPS 4GB. Quelques sites web. Une base de données. Et maintenant un pager, un ticket ou un email client qui dit : « Le site est lent. » Rien n’est plus humiliant que de regarder une machine à 10 $/mois essayer de faire de l’entreprise parce que quelqu’un a activé un plugin qui « n’exécute qu’une seule requête ».

Ce guide de terrain sert à rendre MySQL ou PostgreSQL suffisamment stable et rapide pour des charges de sites web sur du petit hardware VPS. Pas une fantaisie de benchmark. Pas un dump de config. Les choses que vous réglez en premier, ce que vous mesurez d’abord, et ce que vous arrêtez avant que ça ne vous coûte des week-ends.

Premier choix : MySQL ou PostgreSQL pour des sites sur 4GB

Sur un VPS 4GB, « la meilleure base » est celle que vous pouvez garder prévisible sous pression mémoire et trafic en rafales. Votre ennemi n’est pas le débit théorique. Ce sont les tempêtes de swap, les flux de connexions et les pics de latence stockage qui transforment « assez rapide » en « pourquoi le paiement expire ? »

Choisissez MySQL (InnoDB) quand :

  • Votre stack est déjà natif MySQL (WordPress, Magento, beaucoup d’apps PHP) et vous ne voulez pas être la personne qui réécrit tout « pour le plaisir ».
  • Vous voulez une histoire de cache assez simple : le buffer pool InnoDB est le gros réglage, et il se comporte comme tel.
  • Vous avez besoin d’une réplication facile à exploiter avec des outils courants, et vous acceptez les compromis d’éventuelle consistance selon certains modes.

Choisissez PostgreSQL quand :

  • Vous tenez à la justesse des requêtes et aux fonctionnalités SQL riches (vraies fonctions fenêtre, CTE, meilleures contraintes et types) et que vous allez réellement les utiliser.
  • Vous voulez des plans de requête prévisibles, une bonne observabilité et des valeurs par défaut sensées pour de nombreux patterns d’app modernes.
  • Vous pouvez vous engager à utiliser du pooling de connexions (pgBouncer) parce que le modèle process-per-connection de PostgreSQL punit le « j’ouvre juste plus de connexions » sur de petites machines.

Si c’est majoritairement du trafic CMS avec des plugins que vous ne contrôlez pas, je suis généralement conservateur : restez sur MySQL à moins que l’app ne soit déjà orientée Postgres. Si vous construisez quelque chose de nouveau avec une équipe qui écrit du SQL intentionnellement, PostgreSQL est souvent un meilleur pari long terme. Mais sur 4GB, le gain à court terme est la simplicité opérationnelle, pas la pureté philosophique.

Règle générale : si vous ne pouvez pas décrire vos 5 principales requêtes et leurs index, vous ne « choisissez pas une base de données », vous choisissez quels modes de défaillance vous voulez expérimenter en premier.

Faits intéressants & contexte historique (qui changent réellement les décisions)

  1. La domination précoce de MySQL sur le web vient de l’omniprésence du LAMP et d’une vitesse « suffisante » pour les sites à lecture majoritaire. C’est pourquoi tant d’apps web supposent encore des bizarreries du dialecte MySQL.
  2. InnoDB est devenu le moteur par défaut dans MySQL 5.5 (ère 2010). Si vous pensez encore en termes MyISAM (verrouillage de table, pas de récupération après crash), vous traînez un fossile dans la poche.
  3. Le modèle MVCC de PostgreSQL est une des raisons pour lesquelles il reste cohérent sous concurrence, mais il crée un besoin constant de vacuum. Ignorer le vacuum ne fera pas crier la base ; elle empirera lentement.
  4. PostgreSQL a évolué vers un modèle d’exécution plus adapté au parallélisme (requêtes parallèles, meilleur planner). Sur un petit VPS cela compte moins que sur du gros fer, mais c’est une des raisons pour lesquelles Postgres « paraît moderne » pour les requêtes analytiques.
  5. Le cache de requêtes de MySQL a été supprimé dans MySQL 8.0 parce qu’il ne montait pas bien sous concurrence. Si quelqu’un vous dit d’« activer query_cache_size », vous avez trouvé un voyageur temporel.
  6. Postgres est crédité pour les standards et l’exactitude parce qu’historiquement il a privilégié les fonctionnalités et l’intégrité plutôt que la vitesse brute initiale. Aujourd’hui il est aussi rapide, mais l’ADN culturel se voit dans les valeurs par défaut et les outils.
  7. Les deux moteurs sont conservateurs sur la durabilité par défaut (fsync, WAL/redo). Désactiver des paramètres de durabilité rend les benchmarks héroïques et les postmortems dignes d’une scène de crime.
  8. MariaDB a divergé de MySQL de façon significative. Les conseils de « tuning MySQL » s’appliquent parfois mal aux versions MariaDB et à ses moteurs de stockage. Vérifiez ce que vous exécutez réellement.
  9. RDS et les services managés ont influencé le folklore de tuning : les gens copient des valeurs cloud sur un VPS, puis se demandent pourquoi une machine 4GB se comporte comme si elle était sous l’eau.

Architecture de référence pour un VPS 4GB (et pourquoi ça compte)

Sur un VPS 4GB, vous n’avez pas de « mémoire supplémentaire ». Vous avez un budget. Dépensez-le sur des caches qui réduisent les E/S, et sur une marge qui empêche le swap. Le cache de page de l’OS compte aussi parce que MySQL et PostgreSQL finissent par lire depuis le système de fichiers, et le noyau n’est pas votre ennemi ; c’est votre dernière ligne de défense.

Budget mémoire basé sur la réalité

  • OS + SSH + daemons de base : 300–600MB
  • Serveur web + PHP-FPM : très variable. Quelques centaines de MB à plusieurs GB selon le nombre de processus et le comportement de l’app.
  • Base de données : ce qui reste, mais pas tout. Si vous donnez tout à la BDD, la couche web fera OOM ou swappe lors des pics de trafic.

Pour « sites web sur une seule VPS », la base n’est pas isolée. C’est un des rares cas où « set and forget » n’est pas de la paresse ; c’est de la survie.

Opinion : Si vous hébergez à la fois web et BD sur le même VPS 4GB, prévoyez d’allouer environ 1.5–2.5GB à la couche cache de la base max, à moins que vous n’ayez mesuré l’usage mémoire de PHP sous charge et qu’il soit vraiment faible. Votre objectif est une latence stable, pas un buffer pool héroïque.

Blague #1 : Un VPS 4GB est comme un studio — techniquement vous pouvez y mettre un tapis de course, mais vous détesterez votre vie et vos voisins aussi.

Feuille de diagnostic rapide : trouver le goulot en 10 minutes

Voici l’ordre dans lequel je vérifie les choses quand « le site est lent » et que la base est le suspect principal. Chaque étape indique si vous devez regarder le CPU, la mémoire, les connexions, les locks ou le stockage.

Première étape : la machine manque-t-elle de ressources (CPU, RAM, swap) ?

  • Vérifiez la charge vs le nombre de CPU.
  • Vérifiez l’activité de swap et les défauts de page majeurs.
  • Vérifiez l’historique de l’OOM killer.

Deuxième : est-ce la latence du stockage (IOPS/fsync/WAL/redo) ?

  • Temps d’attente I/O élevé, fsync lent, commits longs, ou checkpoints bloqués.
  • Vérifiez la profondeur de file d’attente et les temps d’attente moyens.

Troisième : est-ce la pression de connexions ?

  • Trop de connexions ou de threads DB.
  • Storms de connexions depuis des workers PHP.
  • Comptes de threads/processus atteignant la RAM.

Quatrième : est-ce des locks ou des transactions longues ?

  • MySQL : locks de métadonnées, locks de lignes InnoDB, transactions de longue durée.
  • Postgres : requêtes bloquées, sessions idle-in-transaction, vacuum bloqué par de vieux snapshots.

Cinquième : est-ce des « mauvaises requêtes + index manquants » ?

  • Slow query logs / pg_stat_statements montrent les pires éléments.
  • Recherchez des scans complets de table et des « filesort »/fichiers temporaires ou des scans séquentiels sur de très grands jeux de lignes.

C’est tout. Ne commencez pas par toucher des réglages au hasard. Ne copiez pas un « my.cnf haute performance » pour un serveur 64GB. Mesurez, puis choisissez un changement que vous pouvez expliquer.

Citation (idée paraphrasée) : L’idée de fiabilité de John Allspaw : la production est l’endroit où les hypothèses vont mourir, donc concevez et opérez pour apprendre, pas pour la certitude.

Tâches pratiques : commandes, sorties et ce que vous faites ensuite

Ce sont des tâches réelles que vous pouvez exécuter sur un VPS Linux. Chacune inclut : la commande, ce que signifie une sortie typique, et la décision que vous prenez. Exécutez-les dans l’ordre quand vous êtes en triage ou en train de poser des bases.

Tâche 1 : Confirmer la pression système de base (CPU, RAM, swap)

cr0x@server:~$ uptime
 14:22:19 up 36 days,  3:18,  1 user,  load average: 5.84, 5.12, 3.90

Ce que cela signifie : Sur un VPS 2 vCPU, des load averages au-dessus de ~2–3 sur des périodes soutenues indiquent souvent des files d’exécution en attente (CPU) ou des attentes I/O non interruptibles.

Décision : Si la charge est élevée, vérifiez immédiatement iowait et mémoire/swap avant de toucher la config de la base.

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       3.3Gi       120Mi        90Mi       420Mi       220Mi
Swap:          1.0Gi       860Mi       164Mi

Ce que cela signifie : Le swap est utilisé et « available » est faible : un signal d’alerte pour des workloads sensibles à la latence.

Décision : Si le swap est utilisé en période de pointe, réduisez l’empreinte mémoire de la BD et/ou le nombre de workers PHP-FPM. Envisagez d’activer une petite quantité de swap comme coussin, pas comme couche de performance.

Tâche 2 : Vérifier le swapping actif et l’iowait

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  1 880000 120000  28000 380000   12   45   210   480  520  900 18  8 40 34  0
 3  0 880000 118000  28000 381000    0   60   160   510  600 1100 20  9 35 36  0
 1  2 880000 115000  28000 379000   20    0   400   200  480  850 15  7 38 40  0
 4  0 880000 119000  28000 382000    0    8    90   520  610 1200 22 10 32 36  0
 2  1 880000 117000  28000 381500    0   35   200   600  590 1050 19  9 34 38  0

Ce que cela signifie : Des valeurs non nulles pour si/so indiquent du swapping. Un wa élevé indique de l’attente I/O.

Décision : Si le swapping est actif, arrêtez de tuner les requêtes et corrigez la mémoire/les compteurs de connexions. Si l’iowait est élevé, concentrez-vous sur la latence du stockage, le comportement des checkpoints/flush et la réduction de l’amplification d’écriture.

Tâche 3 : Identifier rapidement la latence disque et le queueing

cr0x@server:~$ iostat -xz 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.20    0.00    8.10   34.50    0.00   39.20

Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await  aqu-sz
vda              12.0    48.0   900.0  6200.0    0.2     3.1   92.0   21.5    1.8

Ce que cela signifie : %util proche de 100% et await > 10–20ms sur un disque VPS signifie généralement que votre stockage est le goulot.

Décision : Réduisez les écritures (batching, réglage autovacuum, tuning InnoDB flush), migrez la BD vers un stockage meilleur, ou séparez DB et web. Sur certains plans VPS, le seul « tuning » qui compte est de payer pour plus d’IOPS.

Tâche 4 : Confirmer quelle BD et quelle version vous exécutez

cr0x@server:~$ mysql --version
mysql  Ver 8.0.36-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))

Ce que cela signifie : Les versions majeures changent les valeurs par défaut et suppriment des réglages (comme le query cache). La version vous dit quel conseil est applicable.

Décision : N’appliquez pas de recettes de tuning si elles ne correspondent pas à votre version majeure et à votre saveur (MySQL vs MariaDB).

cr0x@server:~$ psql --version
psql (PostgreSQL) 16.1 (Ubuntu 16.1-1.pgdg22.04+1)

Ce que cela signifie : Les versions Postgres plus récentes améliorent le vacuum, le WAL et le planner. Cela change « ce qui fait mal » sur les petites machines.

Décision : Sur un vieux Postgres, vous devrez peut-être le surveiller plus manuellement. Sur un Postgres récent, concentrez-vous davantage sur le pooling de connexions et les seuils autovacuum.

Tâche 5 : Compter les connexions BD (MySQL)

cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Threads_connected';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 185   |
+-------------------+-------+

Ce que cela signifie : 185 connexions sur un VPS 4GB avec PHP est souvent un problème, même avant que les requêtes deviennent lentes.

Décision : Limitez la concurrence applicative, activez les connexions persistantes avec précaution, ou adoptez un pattern qui limite la concurrence DB (mise en file côté app, cache, ou séparation du trafic en lecture). Si vous ne pouvez pas contrôler l’app, réduisez max_connections et acceptez des échecs contrôlés plutôt qu’un effondrement total.

Tâche 6 : Compter les connexions BD (PostgreSQL)

cr0x@server:~$ sudo -u postgres psql -c "SELECT count(*) AS connections FROM pg_stat_activity;"
 connections
-------------
         142
(1 row)

Ce que cela signifie : 142 sessions Postgres = 142 processus backend. Sur un VPS 4GB, c’est un coût mémoire et de commutation de contexte.

Décision : Installez pgBouncer et réduisez max_connections. Sur petites machines, Postgres sans pooling est une plaisanterie de performance que vous vous faites.

Tâche 7 : Trouver les requêtes longues et les bloqueurs (PostgreSQL)

cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, now()-query_start AS age, state, wait_event_type, wait_event, left(query,80) AS q FROM pg_stat_activity WHERE state <> 'idle' ORDER BY age DESC LIMIT 5;"
 pid  |   age    | state  | wait_event_type | wait_event |                                       q
------+----------+--------+-----------------+------------+--------------------------------------------------------------------------------
 9123 | 00:02:18 | active | Lock            | relation   | UPDATE orders SET status='paid' WHERE id=$1
 9051 | 00:01:44 | active | IO              | DataFileRead | SELECT * FROM products WHERE slug=$1
(2 rows)

Ce que cela signifie : Les attentes de lock indiquent de la contention ; les attentes IO indiquent un stockage lent ou des caches manquants.

Décision : Si les attentes de lock dominent, corrigez la portée des transactions et l’indexation. Si les attentes IO dominent, augmentez le caching effectif (avec raison) et réduisez les lectures aléatoires via des index et la mise en forme des requêtes.

Tâche 8 : Trouver les waits de lock (MySQL)

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;"
Id	User	Host	db	Command	Time	State	Info
210	app	10.0.0.12:50344	shop	Query	75	Waiting for table metadata lock	ALTER TABLE orders ADD COLUMN foo INT
238	app	10.0.0.15:38822	shop	Query	12	Sending data	SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 1 DAY

Ce que cela signifie : Les locks de métadonnées peuvent geler les écritures et les lectures derrière des modifications de schéma, selon l’opération et la version.

Décision : Arrêtez de faire des changements de schéma en ligne de façon désinvolte sur un petit VPS unique. Planifiez une maintenance ou utilisez des outils de migration de schéma en ligne conçus pour réduire le verrouillage.

Tâche 9 : Vérifier le hit rate du buffer pool InnoDB et la pression de lecture

cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+---------+
| Variable_name                         | Value   |
+---------------------------------------+---------+
| Innodb_buffer_pool_read_requests      | 9823412 |
| Innodb_buffer_pool_reads              | 412390  |
+---------------------------------------+---------+

Ce que cela signifie : reads sont des lectures physiques ; read_requests sont logiques. Si les lectures physiques sont élevées par rapport aux requêtes, vous manquez de cache.

Décision : Si le working set tient en RAM, augmentez innodb_buffer_pool_size prudemment. Sinon, priorisez les index et réduisez le working set (moins de colonnes, moins de scans).

Tâche 10 : Vérifier le cache Postgres et les débordements de fichiers temporaires

cr0x@server:~$ sudo -u postgres psql -c "SELECT datname, blks_hit, blks_read, temp_files, temp_bytes FROM pg_stat_database ORDER BY temp_bytes DESC LIMIT 5;"
  datname  | blks_hit | blks_read | temp_files |  temp_bytes
-----------+----------+-----------+------------+--------------
 appdb     |  9201123 |   612332  |      1832  | 2147483648
(1 row)

Ce que cela signifie : Beaucoup de temp_bytes suggèrent des tris/hashes déversés sur disque parce que work_mem est trop petit pour ces opérations — ou que les requêtes font trop de travail.

Décision : Ne montez pas work_mem globalement sur un petit VPS. Corrigez d’abord les requêtes et les index ; ensuite augmentez work_mem par rôle ou par session pour des charges spécifiques.

Tâche 11 : Voir les requêtes principales (Postgres, si pg_stat_statements est activé)

cr0x@server:~$ sudo -u postgres psql -c "SELECT calls, mean_exec_time, rows, left(query,80) AS q FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
 calls | mean_exec_time | rows |                                       q
-------+----------------+------+--------------------------------------------------------------------------------
 82021 |          12.45 |    1 | SELECT id FROM sessions WHERE token=$1
  1220 |         210.12 |  300 | SELECT * FROM orders WHERE user_id=$1 ORDER BY created_at DESC LIMIT 50
(2 rows)

Ce que cela signifie : Les requêtes à temps total élevé sont celles qui consument votre budget. Les requêtes à fort nombre d’appels sont vos « mort par mille coupures ».

Décision : Indexez les chemins chauds et réduisez les requêtes bavardes. Si une requête s’exécute 80k fois et prend 12ms, c’est un regret sur un cœur entier.

Tâche 12 : Activer et lire rapidement le slow query log MySQL

cr0x@server:~$ mysql -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=0.5; SET GLOBAL log_queries_not_using_indexes=ON;"
...output omitted...

Ce que cela signifie : Vous activez la collecte d’éléments de preuve. Gardez des seuils raisonnables pour ne pas vous noyer de logs.

Décision : Collectez pendant 15–60 minutes en période de pointe, puis utilisez les données pour corriger les pires éléments. Désactivez log_queries_not_using_indexes si c’est trop bruyant pour votre app.

cr0x@server:~$ sudo tail -n 5 /var/log/mysql/mysql-slow.log
# Query_time: 1.204  Lock_time: 0.000 Rows_sent: 50  Rows_examined: 84512
SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50;

Ce que cela signifie : Rows examined est énorme par rapport à rows sent : index manquant classique ou ordre d’index erroné.

Décision : Ajoutez/ajustez des index composites pour correspondre au filtre + ordre (par ex., (user_id, created_at)), puis vérifiez avec EXPLAIN.

Tâche 13 : Vérifier l’espace filesystem et la pression d’inodes

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        80G   74G  2.1G  98% /

Ce que cela signifie : Les disques presque pleins détruisent la performance et peuvent casser les écritures BD. Postgres peut refuser de checkpointer ; MySQL peut planter ou passer en lecture seule selon les circonstances.

Décision : Libérez de l’espace maintenant. Puis configurez la rotation des logs et une alerte de surveillance à 80–85%.

Tâche 14 : Vérifier la mémoire du noyau et des processus BD

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
 2311 mysqld     1854320 46.2
 1822 php-fpm8.1  412000 10.2
 1825 php-fpm8.1  405000 10.1
  911 postgres    220000  5.4

Ce que cela signifie : RSS montre la mémoire résidente réelle. Quelques gros workers PHP plus un cache BD volumineux peuvent faire basculer la machine en swap.

Décision : Si BD + PHP consomment déjà la majeure partie de la RAM, arrêtez d’augmenter les buffers BD. Réduisez la concurrence et plafonnez les consommateurs mémoire.

Si vous choisissez MySQL : quoi régler en premier sur un VPS 4GB

MySQL sur du petit hardware VPS se comporte généralement bien si vous ne le traitez pas comme un puits sans fond pour les connexions et la mémoire. InnoDB est votre moteur par défaut ; tunez pour InnoDB, pas pour la nostalgie.

1) Réglez innodb_buffer_pool_size comme un adulte

Objectif : Cacher les données/index chauds, réduire les lectures aléatoires, éviter d’étouffer tout le reste.

  • Si la BD est sur la même machine que le web : commencez autour de 1.0–1.5GB.
  • Si la BD est principalement seule : jusqu’à 2.0–2.5GB peut convenir.

Mode d’échec : Surdimensionner le buffer pool ne « consomme pas la mémoire libre ». Il entre en concurrence avec le cache de page OS et la couche web. Ensuite vous swappez. Puis chaque requête devient un benchmark de stockage.

2) Réglez max_connections plus bas que vous ne le pensez

Les threads MySQL consomment de la mémoire. Les apps PHP adorent ouvrir des connexions comme si c’était gratuit. Ce n’est pas gratuit.

  • Commencez autour de 100–200 selon l’app et la latence des requêtes.
  • Si vous voyez 300–800 connexions, vous n’avez pas un « problème de performance de la base ». Vous avez un problème de contrôle de concurrence.

3) Gardez le redo log et le comportement de flush sensés

Sur un petit VPS avec une latence de stockage incertaine, des flushs trop agressifs peuvent causer des pics. Mais transformer la durabilité en suggestion est la façon la plus rapide d’actualiser votre CV.

  • innodb_flush_log_at_trx_commit=1 pour une vraie durabilité (défaut).
  • Si vous devez absolument réduire la pression fsync et pouvez accepter de perdre jusqu’à 1 seconde de transactions en cas de crash : envisagez =2. Documentez-le. Mettez-le dans les runbooks. Ne prétendez pas que c’est gratuit.

4) Désactivez ce dont vous n’avez pas besoin, mais ne vous aveuglez pas

Performance Schema est utile ; il impose aussi un coût. Sur un petit VPS, vous pouvez réduire l’instrumentation au lieu de la supprimer complètement.

  • Si vous êtes constamment CPU-bound avec une faible latence des requêtes, pensez à réduire les consommateurs de Performance Schema.
  • Mais gardez suffisamment de visibilité pour attraper les régressions. Déboguer sans métriques, c’est juste de l’écriture créative.

5) Réglez prudemment les limites de tables temporaires

Les apps web adorent ORDER BY et GROUP BY, souvent avec des jeux de résultats trop larges.

  • tmp_table_size et max_heap_table_size peuvent réduire les tables temporaires sur disque, mais si vous les montez trop haut vous exploserez la mémoire sous concurrence.

Esquisse de configuration de départ MySQL (pas une religion à copier-coller)

Ceci illustre l’esprit pour un VPS mix web+DB 4GB. Ajustez selon les mesures ci-dessus.

cr0x@server:~$ sudo cat /etc/mysql/mysql.conf.d/99-vps-tuning.cnf
[mysqld]
innodb_buffer_pool_size = 1G
innodb_buffer_pool_instances = 1
max_connections = 150
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
slow_query_log = ON
long_query_time = 0.5

Ce que cela signifie : Buffer pool plus petit pour préserver de la marge, connexions plafonnées, I/O direct pour réduire le double-caching (dépend de votre FS et workload), et slow query logging pour obtenir des preuves.

Décision : Appliquez et redémarrez pendant une fenêtre calme, puis re-vérifiez swap/iowait et slow logs. Si la latence s’améliore et que le swap disparaît, vous êtes sur la bonne voie.

Si vous choisissez PostgreSQL : quoi régler en premier sur un VPS 4GB

Postgres est excellent pour les sites web, mais il vous fait payer l’attention à trois choses tôt : le nombre de connexions, le vacuum, et le WAL/checkpoints. Ignorez l’une d’elles et vous aurez des ralentissements « aléatoires » qui ne sont pas du tout aléatoires.

1) Installez le pooling de connexions (pgBouncer) avant d’en « avoir besoin »

Sur 4GB, les backends Postgres ne sont pas jetables. Un pic de trafic qui ouvre des centaines de connexions peut se transformer en pression mémoire et overhead de commutation de contexte.

Faites : exécutez pgBouncer en mode transaction pooling pour les workloads web typiques.

Ne faites pas : monter max_connections à 500 et appeler ça du scaling.

2) Réglez shared_buffers de façon conservatrice

Le folklore dit « 25% de la RAM ». Sur un VPS mix web+BD, je commencerais autour de :

  • 512MB à 1GB pour shared_buffers.

Postgres bénéficie aussi du cache de page du système. Donner tout à shared_buffers peut priver le noyau et les autres processus.

3) Gardez work_mem bas globalement ; augmentez-le chirurgicalement

work_mem est par opération de tri/hash, par requête, par backend. Vous n’avez pas assez de RAM pour l’exubérance ici.

  • Commencez à 4–16MB globalement selon la concurrence.
  • Augmentez pour un rôle ou une session spécifique si vous avez une requête de rapport connue et lourde.

4) Gardez autovacuum en bonne santé

Autovacuum n’est pas un ménage optionnel. C’est la façon dont Postgres évite le bloat des tables et permet les index-only scans.

  • Surveillez les tuples morts et le retard de vacuum.
  • Ajustez les seuils autovacuum par table chaude si nécessaire.

5) Rendez les checkpoints moins en pics

Sur un stockage VPS lent, les pics de checkpoint apparaissent comme des falaises de latence aléatoires. Des checkpoints plus lisses réduisent la douleur.

  • Augmentez checkpoint_timeout (avec raison).
  • Fixez checkpoint_completion_target haut pour étaler les écritures.

Esquisse de configuration de départ Postgres

cr0x@server:~$ sudo cat /etc/postgresql/16/main/conf.d/99-vps-tuning.conf
shared_buffers = 768MB
effective_cache_size = 2304MB
work_mem = 8MB
maintenance_work_mem = 128MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 10min
wal_compression = on
log_min_duration_statement = 500ms

Ce que cela signifie : shared_buffers conservateur, hint réaliste pour le cache effectif, work_mem modeste, checkpoints plus lisses, et journalisation des requêtes lentes.

Décision : Appliquez et reload/restart, puis surveillez la croissance des fichiers temporaires et le timing des checkpoints. Si votre disque est lent, lissage des checkpoints se traduira par moins de falaises de latence.

Connexions : le tueur silencieux sur petites machines

Si vous hébergez des sites, la façon la plus simple de ruiner une base est de laisser l’application décider de la concurrence. PHP-FPM workers + « ouvrir une connexion BD par requête » devient une horde tonitruante. Sur 4GB, vous ne survivez pas en étant plus rapide. Vous survivez en étant plus calme.

À quoi ressemble « trop de connexions »

  • CPU BD élevé mais ne faisant pas de travail utile (commutation de contexte, contention de mutex).
  • L’usage mémoire grimpe avec le trafic jusqu’au swap.
  • La latence augmente même pour les requêtes simples.

Ce que vous faites à la place

  • Limitez la concurrence applicative : moins d’enfants PHP-FPM, ou configurez le gestionnaire de processus pour éviter les explosions.
  • Utilisez du pooling : pgBouncer pour Postgres ; pour MySQL, envisagez du pooling côté application ou assurez-vous que les connexions persistantes sont configurées sagement.
  • Échouez vite : parfois réduire max_connections est la bonne décision parce que cela protège la machine d’un thrash complet.

Blague #2 : Connexions illimitées c’est comme un buffet de crevettes illimité — ça a l’air super jusqu’à ce que vous soyez celui qui ferme le restaurant.

Stockage et réalités du système de fichiers : IOPS, fsync, et pourquoi « SSD rapide » ment

Sur les plateformes VPS, « stockage SSD » peut signifier n’importe quoi, du NVMe respectable à un device bloc réseau partagé ayant une mauvaise journée. Les bases de données se soucient plus de la latence que du débit. Quelques millisecondes de fsync en plus par commit deviennent visibles sur le site.

Comment les écritures vous blessent différemment en MySQL vs PostgreSQL

  • MySQL/InnoDB : redo logging + doublewrite buffer (selon config/version) + flush des pages sales. Des flushs en rafales peuvent amplifier la latence.
  • PostgreSQL : écritures WAL + checkpoints + background writer. Le vacuum crée aussi des I/O, et le bloat augmente les I/O futures.

Bonne pratique pour petit VPS : réduire d’abord l’amplification d’écriture

  • Corrigez les apps bavardes (trop de petites transactions).
  • Batch les écritures quand la cohérence le permet.
  • Évitez de mettre à jour constamment des colonnes « last_seen » sur chaque requête si ce n’est pas nécessaire.
  • Gardez les index légers ; chaque index est une taxe d’écriture.

Pièges du système de fichiers

  • Ne mettez pas les bases sur des filesystems réseau instables à moins que la plateforme garantisse les sémantiques de durabilité.
  • Méfiez-vous des conditions de disque plein : Postgres et MySQL se comportent mal de manière différente, mais aucune de ces manières n’est « agréable ».

Trois mini-histoires d’entreprise issues du terrain

1) L’incident causé par une fausse hypothèse : « Le cache va couvrir »

Une petite équipe gérait une collection de sites marketing et un service de checkout sur un VPS 4GB. Il y avait MySQL, Nginx et PHP-FPM. Le trafic était « majoritairement statique », ce qui était vrai jusqu’à ce qu’une campagne lance et que le service checkout reçoive des rafales de requêtes authentifiées.

L’hypothèse était que le cache de pages et le cache applicatif couvriraient les lectures, donc ils ont poussé innodb_buffer_pool_size près de 3GB pour « rendre la base rapide ». Ça marchait bien en heure calme. Puis la campagne a frappé.

PHP-FPM a spawné pour gérer le trafic. Chaque worker utilisait plus de mémoire que ce que quelqu’un se souvenait. L’OS a commencé à swapper. Le buffer pool énorme de la base laissait moins de place au kernel pour tout le reste. La latence n’a pas augmenté graduellement ; elle s’est écrasée. Le checkout a commencé à expirer, les retries ont augmenté le trafic, et la tempête de retries a transformé un problème de ressource en un DoS auto-hébergé.

La correction n’était pas exotique. Ils ont réduit le buffer pool pour laisser de la marge, plafonné les enfants PHP-FPM, abaissé max_connections de MySQL pour que le système échoue vite au lieu de thrash, et mis une file explicite devant le checkout. Ils ont aussi appris la différence opérationnelle entre « mémoire libre » et « mémoire disponible sous rafale ».

2) L’optimisation qui a mal tourné : « Augmente juste work_mem, c’est bon »

Une appli interne tournait sur PostgreSQL. Les utilisateurs se plaignaient de rapports lents, alors quelqu’un a augmenté significativement work_mem parce qu’un article disait que ça réduirait l’I/O des fichiers temporaires. Ça l’a fait. Pour un utilisateur. Dans une session.

Puis un lundi est arrivé. Plusieurs utilisateurs ont exécuté des rapports en concurrence. Ces rapports faisaient chacun plusieurs tris et hash joins. Postgres a alloué correctement work_mem par opération. L’utilisation mémoire a explosé. Le VPS n’a pas planté immédiatement ; il est devenu de plus en plus lent quand le swap s’est déclenché. La base semblait « vivante » mais chaque requête attendait derrière la tempête d’I/O causée par le swapping.

L’équipe a rétabli work_mem à une valeur conservatrice et a plutôt corrigé la requête de rapport. Ils ont ajouté un index manquant, réduit les colonnes sélectionnées, et introduit une table de synthèse rafraîchie périodiquement. Pour la requête lourde, ils ont utilisé un rôle avec un work_mem plus élevé et l’ont forcée via un parcours de reporting contrôlé. La leçon n’était pas « ne jamais tuner ». C’était « ne touchez pas globalement un paramètre pour un problème local sur une petite machine ».

3) La pratique ennuyeuse mais correcte qui a sauvé la mise : « Capper les connexions et logger les requêtes lentes »

Une autre organisation hébergeait plusieurs petits sites clients sur un VPS 4GB partagé. Rien de fancy. Ils ne cherchaient pas les microsecondes. Ils ont fait trois choses ennuyeuses dès le jour 1 : plafonner les connexions BD, activer le slow query logging avec un seuil sensé, et surveiller l’usage disque avec une alerte bien avant 90%.

Un après-midi, une mise à jour de plugin a introduit une régression de requête. Le site n’est pas tombé immédiatement parce que les caps de connexion ont empêché la charge illimitée de s’empiler dans la BD. À la place, quelques requêtes ont échoué rapidement, ce qui a rendu le problème visible sans fondre la machine entière.

Le slow query log contenait le coupable : une requête qui a commencé à scanner une grande table sans index utile. Ils ont ajouté l’index, ont corrigé la régression, et l’incident a été contenu en une courte fenêtre. Pas de mystère. Pas de « c’est parti tout seul ». Pas d’archéologie du week-end.

C’est à quoi ressemble la fiabilité ennuyeuse : échec contrôlé, collecte de preuves, et assez de marge pour qu’un mauvais déploiement ne devienne pas une catastrophe système.

Erreurs courantes : symptôme → cause racine → correctif

1) Symptom : arrêts soudains de 10–60s sur tout le site

Cause racine : pics de latence stockage pendant checkpoints/flushs ou tempêtes de swap.

Correctif : confirmez avec iostat et vmstat ; réduisez la pression mémoire (caches BD plus petits, moins de workers app), lissez les checkpoints (Postgres), et réduisez l’amplification d’écriture (les deux).

2) Symptom : CPU BD élevé, les requêtes individuellement « pas si lentes »

Cause racine : trop de connexions simultanées ; l’overhead de contention domine.

Correctif : plafonnez les connexions ; ajoutez du pooling (pgBouncer) ; réduisez la concurrence PHP-FPM ; mettez du cache côté app ou reverse proxy ; échouez vite plutôt que de thrash.

3) Symptom : Postgres grossit et la performance se dégrade lentement

Cause racine : retard de vacuum et bloat table/index dû à un autovacuum insuffisant ou des transactions longues.

Correctif : identifiez les sessions idle-in-transaction, réglez autovacuum par table chaude, et arrêtez de garder les transactions ouvertes à travers des requêtes web.

4) Symptom : MySQL « Waiting for table metadata lock » dans le processlist

Cause racine : modification de schéma ou DDL bloquée par des transactions longues ; les requêtes se mettent en file derrière les locks de métadonnées.

Correctif : planifiez le DDL en fenêtres de maintenance ; gardez les transactions courtes ; utilisez des approches de changement de schéma en ligne si nécessaire.

5) Symptom : beaucoup de fichiers temporaires ou « Using temporary; Using filesort » en MySQL

Cause racine : index manquants pour les patterns ORDER BY/GROUP BY ; requêtes triant de grands ensembles de données.

Correctif : ajoutez des index composites correspondant à filter+sort ; réduisez les colonnes sélectionnées ; paginez correctement ; évitez OFFSET pour des pages profondes.

6) Symptom : erreurs fréquentes « too many connections »

Cause racine : fuites de connexion applicatives, pas de pooling, ou pics du nombre de workers web.

Correctif : pooler les connexions ; définissez des timeouts sensés ; limitez la concurrence app ; réglez max_connections à un nombre que vous pouvez financer.

7) Symptom : après le « tuning », la performance a empiré

Cause racine : un réglage global (comme work_mem ou un buffer pool trop grand) a augmenté la mémoire par connexion et déclenché le swap sous concurrence.

Correctif : revenez en arrière ; appliquez le tuning par utilisateur/par requête ; mesurez explicitement la mémoire et la concurrence.

Listes de vérification / plan étape par étape

Étape 0 : Décidez ce que « bon » signifie

  • Choisissez un objectif de type SLO : ex., homepage p95 < 500ms, checkout p95 < 800ms.
  • Choisissez une fenêtre de mesure et capturez la baseline (CPU, RAM, swap, iowait, connexions BD, requêtes lentes).

Étape 1 : Stabilisez l’hôte

  • Assurez-vous que le disque a au moins 15–20% d’espace libre.
  • Assurez-vous que vous ne swappez pas pendant les pics normaux.
  • Définissez des limites de service conservatrices (systemd limits si nécessaire) pour éviter des processus incontrôlés.

Étape 2 : Capper la concurrence délibérément

  • Réglez PHP-FPM max children à un nombre que vous pouvez financer en RAM.
  • Réglez DB max_connections pour protéger la machine.
  • Sur Postgres : déployez pgBouncer et réduisez les connexions backend.

Étape 3 : Réglez les premiers knobs mémoire

  • MySQL : réglez innodb_buffer_pool_size pour que le working set rentre sans priver l’OS.
  • Postgres : réglez shared_buffers de façon conservatrice ; gardez work_mem bas globalement.

Étape 4 : Activez la collecte d’éléments de preuve

  • MySQL : slow query log à 0.5–1s pendant la pointe, puis analyser et corriger.
  • Postgres : log_min_duration_statement et idéalement pg_stat_statements.

Étape 5 : Corrigez les 3 principaux patterns de requêtes

  • Ajoutez les index manquants qui réduisent les scans de lignes.
  • Éliminez les N+1 dans l’app.
  • Arrêtez de faire des requêtes coûteuses par requête ; pré-calculer ou mettre en cache.

Étape 6 : Retester et mettre des garde-fous

  • Relancez vos tâches de triage en période de pointe.
  • Ajoutez des alertes sur l’activité swap, l’utilisation disque, les compteurs de connexions, et le taux de requêtes lentes.
  • Documentez vos réglages « sûrs » et la raison pour que votre futur vous ne les annule pas.

FAQ

1) Sur un VPS 4GB, dois-je prioriser le cache BD ou le cache de page OS ?

Priorisez la stabilité. Pour une machine unique web+BD, ne privez pas l’OS. Un cache BD modéré plus de la marge l’emporte sur un cache géant qui déclenche le swap lors des pics.

2) PostgreSQL est-il « plus lent » que MySQL pour les sites web ?

Pas en règle générale. Pour beaucoup de workloads web, l’un ou l’autre est assez rapide quand les index sont corrects. Le vrai différenciateur sur 4GB est la gestion des connexions et les patterns d’écriture, pas la vitesse brute du moteur.

3) Quel est le premier réglage MySQL que je devrais changer ?

innodb_buffer_pool_size, dimensionné selon votre réalité. Ensuite plafonnez max_connections. Puis activez le slow query logging et corrigez ce qu’il montre.

4) Quel est le premier réglage PostgreSQL que je devrais changer ?

La stratégie de pooling des connexions (pgBouncer) et max_connections. Ensuite shared_buffers conservateur et la journalisation/pg_stat_statements pour identifier les principales requêtes.

5) Puis-je juste augmenter le swap pour résoudre les problèmes mémoire ?

Vous pouvez augmenter le swap pour éviter des crashs OOM brutaux, mais le swap n’est pas de la RAM performante. Si votre base ou les workers PHP atteignent régulièrement le swap, la latence deviendra imprévisible.

6) Dois-je désactiver fsync pour gagner en vitesse ?

Non pour des sites en production où l’intégrité des données compte. Si vous désactivez la durabilité et que l’hôte plante, vous pouvez perdre des données. Les benchmarks adorent ; les clients non.

7) Comment savoir si je suis lié par l’I/O ?

Un iowait élevé dans vmstat, un await et %util élevés dans iostat, et des sessions DB en attente d’événements IO (Postgres) sont des signaux forts.

8) Quand devrais-je séparer web et BD sur des serveurs distincts ?

Quand vos réglages deviennent des compromis entre la mémoire web et la mémoire BD, ou quand la latence stockage rend les écritures BD imprévisibles. La séparation vous apporte isolation et planification de capacité plus claire.

9) Les valeurs par défaut sont-elles suffisantes aujourd’hui ?

Les valeurs par défaut sont meilleures qu’avant, mais elles ne sont pas adaptées à votre situation « tout sur une seule machine 4GB ». Les caps de connexions et le budget mémoire restent de votre ressort.

10) Quel est le « gain de performance » le plus sûr que je peux faire sans expertise DB profonde ?

Activez le slow query logging (ou pg_stat_statements), identifiez les 3 gros consommateurs de temps, et ajoutez les index appropriés. Plafonnez aussi les connexions pour que le serveur reste stable sous charge.

Prochaines étapes qui ne vous mettront pas dans l’embarras plus tard

Sur un VPS 4GB, vous n’optimisez pas une base de données. Vous gérez la contention entre web, base et stockage tout en essayant de garder la latence ennuyeuse.

  1. Exécutez la feuille de diagnostic rapide pendant la pointe et notez ce qui se passe réellement : swap, iowait, connexions, locks, requêtes principales.
  2. Plafonnez la concurrence en premier : workers PHP-FPM et connexions BD. Ajoutez pgBouncer si vous êtes sur Postgres.
  3. Réglez le premier knob mémoire (InnoDB buffer pool ou shared_buffers Postgres) à une valeur conservative qui laisse de la marge.
  4. Activez les preuves (slow query logs / pg_stat_statements) et corrigez les principaux coupables avec des index et des changements de requêtes.
  5. Re-vérifiez le disque et le comportement d’écriture ; lissez les checkpoints, réduisez les déversements temp, et arrêtez les écritures bruyantes inutiles.
  6. Décidez si la vraie correction est architecturale : déplacer la BD sur un VPS séparé, upgrader le tier de stockage, ou utiliser une DB managée. Parfois le paramètre de tuning le plus efficace est votre facture.

Si vous ne faites qu’une chose aujourd’hui : plafonnez les connexions et arrêtez le swap. Tout le reste n’est que garniture.

MySQL vs ClickHouse : empêcher l’analytique de tuer l’OLTP (Plan de séparation propre)

Quelque part dans votre entreprise, un analyste bien intentionné vient de rafraîchir un tableau de bord. Désormais le paiement est lent, l’API fait des timeouts et le canal on-call ressemble à une séance de thérapie de groupe.

Ce n’est pas un problème de « mauvaise requête ». C’est un problème d’architecture : l’OLTP et l’analytique sont des animaux différents, et les mettre dans la même cage se termine comme prévu. La solution est une séparation propre — MySQL gère les transactions, ClickHouse gère l’analytique, et vous empêchez la curiosité de faire un DDoS sur votre chemin de revenu.

Le vrai problème : l’OLTP et l’analytique se battent au niveau du stockage

L’OLTP concerne la latence, la correction et la concurrence prévisible. Vous optimisez pour des milliers de petites lectures/écritures par seconde, des index serrés et des ensembles de travail chauds qui tiennent en mémoire. Le coût d’une seule requête lente est payé immédiatement — dans l’expérience client, les timeouts et les retry qui amplifient la charge.

L’analytique concerne le débit, les lectures larges et l’agrégation. Vous optimisez pour lire beaucoup de données, bien les compresser et utiliser l’exécution vectorisée pour convertir le CPU en réponses. Les requêtes analytiques sont souvent « embarrassingly parallel » et tolèrent quelques secondes de plus — jusqu’à ce qu’on les pointe sur votre base transactionnelle et qu’elles deviennent un déni de service avec un tableau croisé dynamique en prime.

Le point clé : l’OLTP et l’analytique se disputent les mêmes ressources finies — cycles CPU, I/O disque, cache de pages, buffer pools, verrous/loquets et maintenance en arrière-plan (flush, checkpoints, merges). Même si vous ajoutez une réplica en lecture, vous partagez souvent la même douleur fondamentale : latence de réplication, saturation d’I/O et performance incohérente causée par des scans imprévisibles.

Où le couteau s’enfonce : contention des ressources dans MySQL

  • Pollution du buffer pool : une grosse requête de reporting lit une tranche historique froide, fait sortir des pages chaudes et soudain votre charge primaire devient liée au disque.
  • Pression background d’InnoDB : scans longs + tables temporaires + tris peuvent augmenter les pages sales et la pression de redo. Les “flush storms” ne sont pas polis.
  • Verrous et verrous de métadonnées : certains schémas de reporting déclenchent des interactions désagréables (pensez « ALTER TABLE en heures de bureau » rencontrant « SELECT … » qui tient un MDL).
  • Latence de réplication : les lectures lourdes sur une réplica volent l’I/O et le CPU au thread SQL qui applique les changements.

Où ClickHouse s’insère

ClickHouse est conçu pour l’analytique : stockage en colonnes, compression, exécution vectorisée et parallélisme agressif. Il attend que vous lisiez beaucoup de lignes, mais seulement quelques colonnes, et il vous récompense pour le partitionnement et les clés triées bien pensées.

La discipline est simple : traitez MySQL comme le système d’enregistrement des transactions. Traitez ClickHouse comme le système de vérité pour l’analytique — vérité signifiant « dérivé de l’enregistrement, reproductible et interrogeable à l’échelle ».

Idée paraphrasée de Werner Vogels : « Tout échoue ; concevez pour l’échec. » Ça s’applique aussi aux données : concevez pour des modes de défaillance comme les tempêtes de requêtes, la latence et les backfills.

MySQL vs ClickHouse : les vraies différences qui comptent en production

Disposition du stockage : ligne vs colonne

MySQL/InnoDB est orienté ligne. Idéal pour récupérer une ligne par clé primaire, mettre à jour quelques colonnes, maintenir des index secondaires et appliquer des contraintes. Mais scanner un milliard de lignes pour calculer des agrégats signifie faire traverser des lignes entières au moteur, toucher des pages dont vous n’aviez pas besoin et brûler le cache.

ClickHouse est orienté colonne. Il lit uniquement les colonnes demandées, les compresse bien (souvent de façon spectaculaire) et les traite en vecteurs. Vous payez d’avance avec des contraintes de modélisation différentes — dénormalisation, clés d’ordre réfléchies et un processus de merge à respecter.

Modèle de concurrence : transactions vs parallélisme analytique

MySQL gère bien de nombreuses transactions courtes concurrentes — jusqu’aux limites de votre schéma, de vos index et de votre matériel. ClickHouse gère aussi de nombreuses lectures concurrentes, mais la magie vient de la parallélisation efficace des grosses lectures et agrégations. Si vous pointez un outil BI sur ClickHouse sans limites de concurrence, il essaiera de mettre votre CPU en feu. Il faut et vous devez le gouverner.

Consistance et exactitude

MySQL est ACID (avec les réserves usuelles, mais oui, c’est votre ancre transactionnelle). ClickHouse est typiquement éventuellement consistant pour les données ingérées et orienté append. Vous pouvez modéliser des mises à jour/suppressions, mais vous le faites selon les règles de ClickHouse (ReplacingMergeTree, CollapsingMergeTree, colonnes de version ou suppressions asynchrones). C’est acceptable : l’analytique veut habituellement la vérité actuelle et une série temporelle des changements, pas une sémantique transactionnelle par ligne.

Indexation et patterns de requêtes

Les index MySQL sont des B-trees adaptés aux recherches ponctuelles et aux scans de plage. ClickHouse utilise l’ordre de la clé primaire et des index épars, plus des index de saut de données (comme les filtres Bloom) quand c’est utile. La meilleure requête ClickHouse est celle qui peut sauter de gros morceaux de données parce que votre partitionnement et votre ordre correspondent aux modèles d’accès.

Posture opérationnelle

Les opérations MySQL tournent autour de la santé de la réplication, des sauvegardes, des migrations de schéma et de la stabilité des requêtes. Les opérations ClickHouse tournent autour des merges, de l’utilisation du disque, du nombre de parts, des TTL et de la gouvernance des requêtes. En d’autres termes : vous échangez un ensemble de dragons contre un autre. L’affaire en vaut la peine parce que vous empêchez l’analytique de saboter votre flux de paiement.

Blague n°1 : Un rafraîchissement de tableau de bord est le seul type de « engagement utilisateur » qui peut augmenter à la fois le taux d’erreur et le churn.

Faits et contexte historique (utile, pas du trivia)

  1. InnoDB est devenu le moteur par défaut dans MySQL 5.5 (ère 2010), consolidant le comportement OLTP en row-store pour la plupart des déploiements.
  2. ClickHouse a démarré chez Yandex pour alimenter des charges analytiques à grande échelle ; il est né dans un monde où scanner rapidement de grosses données était le métier.
  3. Les stores en colonnes ont pris de l’ampleur parce que le CPU est devenu plus rapide que les disques, et la compression + l’exécution vectorisée permettent de dépenser du CPU pour éviter l’I/O.
  4. La « pollution » du buffer pool InnoDB est un mode de défaillance classique lorsque des scans longs balayent les pages chaudes ; le moteur n’est pas « cassé », il fait ce que vous lui avez demandé.
  5. L’analytique basée sur la réplication existe depuis des décennies : les gens expédient les changements OLTP vers des entrepôts de données depuis avant que « data lake » ne devienne un mot-clé de CV.
  6. Le query cache de MySQL a été supprimé dans MySQL 8.0 car il créait de la contention et ne montait pas bien ; le caching n’est pas gratuit, et les locks globaux sont coûteux.
  7. La famille MergeTree de ClickHouse stocke les données en parts et les fusionne en arrière-plan — excellent pour les écritures et la compression, mais cela crée des signaux opérationnels (nombre de parts, backlog de merges) à surveiller.
  8. Le modèle en étoile et la modélisation dimensionnelle préexistent aux outils modernes ; ClickHouse pousse souvent les équipes vers des formes dénormalisées et favorables aux requêtes car les joins à grande échelle ont de vrais coûts.

Le plan de séparation propre : des modèles qui n’embrasent pas la prod

Principe 1 : MySQL sert les utilisateurs, pas la curiosité

Faites-en une politique : MySQL de production n’est pas une base de reporting. Pas « habituellement ». Pas « sauf pour une requête rapide ». Jamais. Si quelqu’un a besoin d’un one-off, exécutez-le sur ClickHouse ou dans un environnement snapshot contrôlé.

Vous aurez des objections. C’est normal. L’astuce consiste à remplacer le « non » par « voici la manière sûre ». Fournissez la voie sûre : accès ClickHouse, jeux de données sélectionnés et un workflow qui n’implique pas de supplier l’on-call pour autoriser un JOIN sur une année de commandes.

Principe 2 : Choisissez une stratégie de transfert de données qui correspond à votre tolérance aux pannes

Il y a trois façons courantes d’alimenter ClickHouse depuis MySQL. Chacune a des angles vifs.

Option A : ETL par lots (dump et chargement)

Vous extrayez des snapshots horaires/quotidiens (mysqldump, exports CSV, jobs Spark), chargez dans ClickHouse et acceptez une certaine staleness. C’est le plus simple opérationnellement mais pénible si vous avez besoin de métriques quasi-temps réel, et les backfills peuvent être lourds.

Option B : Ingestion pilotée par réplication (CDC)

Capturez les changements depuis le binlog MySQL et streammez-les vers ClickHouse. Cela vous donne de l’analytique quasi temps réel tout en protégeant MySQL de la charge des requêtes. Mais cela introduit la santé du pipeline comme préoccupation de production : lag, dérive de schéma et retraitement deviennent vos nouveaux hobbies.

Option C : Double écriture (l’application écrit dans les deux)

Ne le faites pas. Ou, si vous devez absolument le faire, faites-le seulement avec une idempotence robuste, une livraison asynchrone et un job de réconciliation qui suppose que la double écriture vous mentira parfois.

Le plan de séparation propre signifie généralement CDC plus modèles de données sélectionnés dans ClickHouse. L’ETL par lots est acceptable quand la staleness est tolérable. La double-écriture est un piège à moins d’aimer expliquer des divergences de données lors des postmortems d’incident.

Principe 3 : Modelez ClickHouse pour vos questions, pas pour votre schéma

La plupart des schémas OLTP sont normalisés. L’analytique veut moins de joins, des clés stables et des tables orientées événement. Votre travail consiste à construire une représentation analytique facile à interroger et difficile à mal utiliser.

  • Privilégiez les tables d’événements : orders_events, sessions, payments, shipments, support_tickets. Append d’événements. Dérivez des faits.
  • Partitionnez par temps : généralement par jour ou par mois. Cela vous donne un pruning prévisible et des TTL gérables.
  • Order by selon les dimensions de requête : placez les clés de filtrage/group-by les plus fréquentes tôt dans ORDER BY (après la clé temporelle si vous filtrez toujours par temps).
  • Pré-aggrégez quand c’est stable : les vues matérialisées peuvent produire des rollups pour que les tableaux de bord n’épluchent pas toujours les données brutes.

Principe 4 : La gouvernance bat les héros

ClickHouse peut répondre assez vite pour que les gens posent de pires questions plus souvent. Vous avez besoin de garde-fous :

  • Séparez les utilisateurs et quotas : les utilisateurs BI ont des timeouts et une mémoire max. L’ETL a un profil différent.
  • Fixez max threads et la concurrence : évitez un « thundering herd » de requêtes parallèles.
  • Utilisez des jeux de données « gold » dédiés : vues ou tables stables sur lesquelles les tableaux de bord s’appuient, versionnées si nécessaire.
  • Définissez des SLO : le SLO de latence MySQL est sacré. Le SLO de fraîcheur ClickHouse est négociable mais mesurable.

Tâches pratiques (commandes, sorties, décisions)

Voici les mouvements que vous faites réellement à 02:13. Chaque tâche inclut une commande, une sortie d’exemple, ce que cela signifie et la décision à en tirer.

Task 1: Confirm MySQL is suffering from analytic scans (top digests)

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN ? AND ? GROUP BY customer_id
COUNT_STAR: 9421
total_s: 18873.214
*************************** 2. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE created_at > ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 110233
total_s: 8211.532

Ce que cela signifie : Votre pire temps provient d’un agrégat classique de reporting sur une plage de dates. Ce n’est pas « une requête lente », c’est une douleur répétée.

Décision : Bloquez ou redirigez le pattern de requête analytique. Ne transformez pas MySQL en moteur OLAP. Commencez par déplacer ce tableau de bord vers ClickHouse ou une table de rollup.

Task 2: Check current MySQL thread activity (is it a dogpile?)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head
Id	User	Host	db	Command	Time	State	Info
31	app	10.0.2.14:51234	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
44	app	10.0.2.14:51239	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
57	app	10.0.2.14:51241	prod	Query	1	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id

Ce que cela signifie : De nombreuses requêtes identiques s’exécutent en parallèle. C’est un tableau de bord ou une flotte de workers qui font le même travail coûteux.

Décision : Bridez au niveau applicatif/BI et introduisez du caching ou du pré-agrégat dans ClickHouse. Envisagez aussi des limites de connexion MySQL et des contrôles de ressources par utilisateur.

Task 3: Validate InnoDB buffer pool pressure (hot pages getting evicted)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
Variable_name	Value
Innodb_buffer_pool_read_requests	987654321
Innodb_buffer_pool_reads	12345678

Ce que cela signifie : Un nombre élevé de lectures physiques (Innodb_buffer_pool_reads) par rapport aux lectures logiques suggère que votre working set ne tient pas en mémoire — souvent à cause de gros scans.

Décision : Arrêtez les scans (déplacez l’analytique), puis seulement envisagez d’augmenter le buffer pool ou d’ajuster la charge. Le matériel ne peut pas battre un mauvais mix de charge indéfiniment.

Task 4: Catch disk I/O saturation on the MySQL host

cr0x@server:~$ iostat -xz 1 3
Linux 6.2.0 (mysql01) 	12/30/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.12    0.00    6.44   31.55    0.00   43.89

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
nvme0n1         820.0  64200.0     0.0    0.0   12.4    78.3     410.0  18800.0    9.8   18.2   98.7

Ce que cela signifie : %util proche de 100% et un iowait élevé signifient que le disque est le goulot d’étranglement. Les scans analytiques adorent ce résultat.

Décision : Immédiat : réduisez la concurrence des requêtes, tuez les plus gros coupables, déplacez l’analytique vers ClickHouse. À long terme : séparez stockage et charges ; ne comptez pas sur un « NVMe plus rapide » comme stratégie.

Task 5: Identify MySQL replication lag (your “read replica” isn’t helping)

cr0x@server:~$ mysql -h mysql-replica01 -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_SQL_Running|Slave_IO_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 487

Ce que cela signifie : La réplica a ~8 minutes de retard. Les tableaux de bord qui la lisent mentent. Pire : en cas de bascule, vous pourriez perdre des transactions récentes.

Décision : N’utilisez pas la réplica comme puits analytique. Utilisez CDC vers ClickHouse, ou au minimum une réplica dédiée avec accès de requête contrôlé et ressources garanties.

Task 6: Show the actual expensive query plan (stop guessing)

cr0x@server:~$ mysql -e "EXPLAIN SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: range
possible_keys: idx_created_at
key: idx_created_at
rows: 98234123
Extra: Using where; Using temporary; Using filesort

Ce que cela signifie : Même avec un index, vous scannez ~98M de lignes et utilisez temporary/filesort. Ce n’est pas une requête OLTP ; c’est un travail OLAP.

Décision : Déplacez-la. Si vous devez conserver certains agrégats dans MySQL, utilisez des tables de synthèse mises à jour de manière incrémentale, pas des GROUP BY ad hoc sur des faits bruts.

Task 7: Confirm ClickHouse health basics (are merges or disk the issue?)

cr0x@server:~$ clickhouse-client -q "SELECT hostName(), uptime()"
ch01
345678

Ce que cela signifie : Vous pouvez vous connecter et le serveur est en ligne depuis suffisamment longtemps pour être utile.

Décision : Poursuivez avec des vérifications plus approfondies : parts/merges, charge de requêtes et disque.

Task 8: Check ClickHouse active queries and their resource usage

cr0x@server:~$ clickhouse-client -q "SELECT user, query_id, elapsed, read_rows, formatReadableSize(memory_usage) AS mem, left(query, 80) AS q FROM system.processes ORDER BY memory_usage DESC LIMIT 5 FORMAT TabSeparated"
bi_user	0f2a...	12.4	184001234	6.31 GiB	SELECT customer_id, sum(total) FROM orders_events WHERE event_date >= toDate('2025-12-01')
etl	9b10...	3.1	0	512.00 MiB	INSERT INTO orders_events FORMAT JSONEachRow

Ce que cela signifie : BI consomme de la mémoire. C’est acceptable si c’est budgété. C’est un problème si cela prive les merges ou déclenche un OOM.

Décision : Fixez per-user max_memory_usage, max_threads et éventuellement max_concurrent_queries. Gardez l’ETL fiable.

Task 9: Check ClickHouse merges backlog (parts growing like weeds)

cr0x@server:~$ clickhouse-client -q "SELECT database, table, sum(parts) AS parts, formatReadableSize(sum(bytes_on_disk)) AS disk FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(parts) DESC LIMIT 10 FORMAT TabSeparated"
analytics	orders_events	1842	1.27 TiB
analytics	sessions	936	640.12 GiB

Ce que cela signifie : Des milliers de parts peuvent indiquer une fragmentation d’insert ou des merges en retard. Les performances des requêtes se dégraderont, et le démarrage/les métadonnées deviennent plus lourds.

Décision : Ajustez le batching des inserts, paramétrez les merges prudemment et envisagez la stratégie de partitionnement. Si le nombre de parts continue de grimper, traitez cela comme un incident sur le long terme.

Task 10: Validate partition pruning (if it scans everything, you modeled it wrong)

cr0x@server:~$ clickhouse-client -q "EXPLAIN indexes=1 SELECT customer_id, sum(total) FROM analytics.orders_events WHERE event_date BETWEEN toDate('2025-12-01') AND toDate('2025-12-30') GROUP BY customer_id"
Expression ((Projection + Before ORDER BY))
  Aggregating
    Filter (WHERE)
      ReadFromMergeTree (analytics.orders_events)
        Indexes:
          MinMax
            Keys: event_date
            Condition: (event_date in [2025-12-01, 2025-12-30])
            Parts: 30/365
            Granules: 8123/104220

Ce que cela signifie : Il lit 30/365 parts grâce au filtre de date. Voilà à quoi ressemble « fonctionne comme prévu ».

Décision : Si le nombre de parts lues est proche du total, changez le partitionnement et/ou exigez des filtres temporels dans les tableaux de bord.

Task 11: Monitor ClickHouse disk usage and predict capacity trouble

cr0x@server:~$ df -h /var/lib/clickhouse
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme1n1    3.5T  3.1T  330G  91% /var/lib/clickhouse

Ce que cela signifie : 91% utilisé. Vous n’êtes qu’à un backfill d’une journée noire, et les merges ont besoin d’espace libre.

Décision : Arrêtez les backfills non essentiels, augmentez le stockage, appliquez des TTL et optimisez le modèle. ClickHouse sous pression disque devient imprévisible et risqué.

Task 12: Verify CDC pipeline lag at the consumer (is analytics stale?)

cr0x@server:~$ clickhouse-client -q "SELECT max(ingested_at) AS last_ingest, now() AS now, dateDiff('second', max(ingested_at), now()) AS lag_s FROM analytics.orders_events"
2025-12-30 19:03:12	2025-12-30 19:03:29	17

Ce que cela signifie : ~17 secondes de lag. C’est sain pour de l’analytique « quasi temps réel ».

Décision : Si le lag augmente, mettez en pause les requêtes lourdes, vérifiez le débit du pipeline et décidez de dégrader les tableaux de bord ou de risquer l’OLTP.

Task 13: Check MySQL binary log format for CDC correctness

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'binlog_format';"
Variable_name	Value
binlog_format	ROW

Ce que cela signifie : Le format ROW est typiquement ce que veulent les outils CDC pour la correction. STATEMENT peut être ambigu pour les requêtes non déterministes.

Décision : Si vous n’êtes pas en ROW, planifiez une fenêtre de changement. La correction du CDC n’est pas quelque chose sur lequel on « espère ».

Task 14: Confirm MySQL has sane slow query logging (so you can prove causality)

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
Variable_name	Value
slow_query_log	ON
slow_query_log_file	/var/log/mysql/mysql-slow.log
Variable_name	Value
long_query_time	0.500000

Ce que cela signifie : Vous capturez les requêtes plus lentes que 500ms. C’est agressif, mais utile pendant une période bruyante.

Décision : Pendant les incidents, baissez temporairement long_query_time et échantillonnez. Ensuite, remettez un seuil stable et utilisez des résumés digest.

Task 15: Verify ClickHouse user limits (prevent a BI “parallelism party”)

cr0x@server:~$ clickhouse-client -q "SHOW CREATE USER bi_user"
CREATE USER bi_user IDENTIFIED WITH sha256_password SETTINGS max_memory_usage = 4000000000, max_threads = 8, max_execution_time = 60, max_concurrent_queries = 5

Ce que cela signifie : BI est cloisonné : 4GB mémoire, 8 threads, 60s d’exécution, 5 requêtes concurrentes. C’est la différence entre un tableau de bord et un test de stress.

Décision : Si vous ne pouvez pas poser de limites pour des raisons « business », vous ne faites pas de l’analytique, vous jouez à la roulette.

Mode opératoire de diagnostic rapide

Ceci est l’ordre qui trouve rapidement le goulot d’étranglement, sans transformer l’incident en débat philosophique.

Premièrement : MySQL est-il surchargé par des lectures, écritures, verrous ou I/O ?

  1. Top query digests (digests performance_schema ou résumés du slow log) : identifiez les familles de requêtes qui consomment du temps.
  2. États des threads (SHOW PROCESSLIST) : « Sending data » suggère scan/agrégation ; « Locked » suggère contention ; « Waiting for table metadata lock » suggère collision DDL.
  3. I/O disque (iostat) : si iowait est élevé et %util du disque est élevé, arrêtez les scans avant de tuner quoi que ce soit d’autre.

Deuxièmement : la « solution » (réplica) n’empire-t-elle pas les choses ?

  1. Latence de réplication (SHOW SLAVE STATUS) : si le lag est de plusieurs minutes, les utilisateurs analytiques prennent des décisions sur des données obsolètes et vous en tiennent responsable.
  2. Contention des ressources sur la réplica : les requêtes lourdes peuvent priver le thread SQL et augmenter encore le lag.

Troisièmement : si ClickHouse existe, est-il sain et gouverné ?

  1. system.processes : identifiez les requêtes BI hors de contrôle et les gros consommateurs de mémoire.
  2. Parts et merges (system.parts) : trop de parts signifie problème de forme d’ingestion ou backlog de merges.
  3. Headroom disque (df) : les merges et TTL ont besoin d’espace ; 90% plein est une dette opérationnelle avec intérêt.

Quatrièmement : la fraîcheur des données est-elle la vraie plainte ?

  1. Lag CDC (max ingested_at) : quantifiez l’obsolescence.
  2. Communiquez une solution de repli : si la fraîcheur se dégrade, dégradez les tableaux de bord — pas le checkout.

Trois mini-récits d’entreprise venus des tranchées

Incident causé par une mauvaise hypothèse : « Les réplica de lecture servent au reporting »

Une entreprise d’abonnement de taille moyenne avait un cluster MySQL primaire et deux réplica de lecture. Leur outil BI pointait une réplica parce que « les lectures n’affectent pas les écritures ». Cette phrase a causé plus d’incidents que le café n’a empêché.

Pendant la clôture du mois, la finance a lancé une série de rapports de cohortes et de revenus. Le disque de la réplica a atteint la saturation : scans lourds plus tables temporaires. Le lag de réplication est passé de secondes à dizaines de minutes. Personne n’a remarqué au début parce que le trafic applicatif allait bien ; le primaire n’était pas directement impacté.

Puis quelqu’un a fait la seconde hypothèse : « Si le primaire échoue, on peut basculer vers une réplica. » Juste au moment où le lag était le pire, le primaire a eu un incident d’hôte non lié et est devenu sain. L’automatisation a tenté de promouvoir la « meilleure » réplica — sauf que la « meilleure » avait 20 minutes de retard.

Ils n’ont pas perdu toute la base. Ils ont perdu suffisamment de transactions récentes pour créer un cauchemar support client : paiements qui « ont réussi » côté externe mais qui n’existaient pas en interne, et sessions qui ne correspondaient pas à la facturation. La récupération a été un mélange minutieux d’exploration de binlog et de réconciliation contre le fournisseur de paiement.

La correction n’a pas été héroïque. Ils ont séparé les responsabilités : une réplica dédiée au failover avec blocage strict des requêtes, et l’analytique a été déplacée vers ClickHouse via CDC. Le reporting est devenu rapide, et le failover est devenu fiable parce que la réplica n’était plus un punching-ball.

Optimisation qui s’est retournée contre eux : « Ajoutons juste un index »

Une équipe e-commerce avait une requête de reporting lente sur orders : filtre par plage de temps plus group-by. Quelqu’un a ajouté un index sur created_at et un index composite sur (created_at, customer_id). La requête est devenue plus rapide en isolation, ils ont livré et célébré.

Deux semaines plus tard, la latence d’écriture a commencé à grimper. Les inserts dans orders ont ralenti, et le taux de flush background a augmenté. Les nouveaux index ont augmenté l’amplification d’écriture — chaque insert entretenait plus de structures B-tree. Aux pics de trafic, ils payaient une taxe d’index sur chaque transaction pour rendre quelques rapports moins chers.

Puis l’outil BI a eu un nouveau tableau de bord qui exécutait la même requête chaque minute. La requête étant plus rapide, la concurrence a augmenté (les humains aiment rafraîchir quand c’est rapide). Le système a échangé une requête lente contre beaucoup de requêtes moyennes-rapides et s’est retrouvé I/O bound de toute façon.

La vraie solution a été de retirer la surcharge d’index, garder l’OLTP léger et construire une table de rollup ClickHouse mise à jour continuellement. Les tableaux de bord interrogent ClickHouse. Les transactions sont restées fluides. L’équipe a appris la leçon : indexer n’est pas « de la vitesse gratuite », c’est une facture en temps d’écriture que vous payez pour toujours.

Pratique ennuyeuse mais correcte qui a sauvé la journée : quotas et backfills étagés

Une société SaaS B2B utilisait ClickHouse pour l’analytique avec des profils d’utilisateurs stricts. Les utilisateurs BI avaient max_execution_time, max_memory_usage et des limites de concurrence. L’ETL avait des limites différentes et tournait dans une file contrôlée. Personne n’aimait ces contraintes. Tout le monde en a profité.

Un après-midi, un analyste a tenté d’exécuter une requête large sur deux années d’événements bruts sans filtre temporel. ClickHouse a commencé à scanner, a atteint la limite de temps d’exécution et a tué la requête. L’analyste s’est plaint. L’on-call n’a pas été page. C’est un bon compromis.

Plus tard dans le mois, l’équipe data a eu besoin d’un backfill à cause d’un changement de schéma dans le CDC en amont. Ils l’ont planifié : un jour à la fois, vérifiant le nombre de parts, le headroom disque et le lag après chaque tranche. Lent, soigneux, mesurable. Le backfill s’est terminé sans menacer les tableaux de bord de production.

La pratique ennuyeuse n’était pas un algorithme sophistiqué. C’était de la gouvernance et de la discipline opérationnelle : limites, files et backfills incrémentaux. Cela les a sauvés parce que le système s’est comporté de façon prévisible quand les humains ont été imprévisibles.

Blague n°2 : La seule chose plus permanente qu’un tableau de bord temporaire est le canal d’incident qu’il crée.

Erreurs courantes : symptôme → cause racine → correction

  • Symptôme : latence p95 MySQL augmente pendant les « heures de reporting »
    Cause racine : scans longs et requêtes GROUP BY en compétition avec l’OLTP pour le buffer pool et l’I/O
    Correction : Déplacez le reporting vers ClickHouse ; appliquez la politique ; ajoutez des rollups sélectionnés ; bloquez les utilisateurs BI sur MySQL.
  • Symptôme : le lag de la réplica augmente quand les analystes lancent des rapports
    Cause racine : I/O et CPU de la réplica saturés ; le thread SQL n’applique pas le binlog assez vite
    Correction : Retirez l’accès analytique des réplica de failover ; utilisez CDC vers ClickHouse ; limitez la concurrence des requêtes.
  • Symptôme : les requêtes ClickHouse ralentissent avec le temps sans changement de taille de données
    Cause racine : explosion de parts ; merges en retard à cause d’inserts fragmentés ou de pression disque
    Correction : Batch des inserts ; tunez prudemment les paramètres de merge ; surveillez les parts ; assurez-vous d’un headroom disque ; envisagez le repartitionnement.
  • Symptôme : les tableaux de bord sont « rapides parfois » et font des timeouts aléatoirement sur ClickHouse
    Cause racine : concurrence BI non bornée ; pression mémoire ; requêtes voisins bruyants
    Correction : Fixez des limites par utilisateur (mémoire, threads, temps d’exécution, requêtes concurrentes) ; créez des tables pré-agrégées ; ajoutez du routage des requêtes.
  • Symptôme : les données analytiques ont des doublons ou un « mauvais état courant »
    Cause racine : CDC appliqué en append-only sans dédup/versioning ; updates/deletes mal modélisés
    Correction : Utilisez des colonnes de version et ReplacingMergeTree quand approprié ; stockez des événements et dérivez l’état courant via des vues matérialisées.
  • Symptôme : le disque ClickHouse ne cesse de grimper jusqu’à l’urgence
    Cause racine : pas de TTL ; conservation brute éternelle ; backfills lourds ; pas de garde-fous de capacité
    Correction : Appliquez des TTL pour les données froides ; downsamplez ; compressez ; archivez ; imposez des quotas et procédures de backfill.
  • Symptôme : « Nous sommes passés à ClickHouse mais MySQL est toujours lent »
    Cause racine : le pipeline CDC lit encore MySQL lourdement (extracts full-table, snapshots fréquents), ou l’app exécute toujours des rapports sur MySQL
    Correction : Utilisez CDC basé sur binlog ; révisez les sources de requêtes MySQL ; firewall/retirez les comptes de reporting ; validez avec des données digest.
  • Symptôme : fraîcheur ClickHouse en retard pendant les pics
    Cause racine : goulot d’ingestion (débit du pipeline), merges ou pression disque ; parfois trop petits inserts
    Correction : Batch des inserts ; scalez l’ingestion ; surveillez le lag ; réduisez temporairement la concurrence BI ; priorisez les ressources ETL.

Checklists / plan pas à pas

Étapes pas à pas : plan d’implémentation de la séparation propre

  1. Déclarez la frontière : MySQL de production est uniquement OLTP. Écrivez-le. Faites-le respecter avec des comptes et une politique réseau.
  2. Inventoriez les requêtes analytiques : utilisez les tables digest MySQL et les résumés du slow log pour lister les 20 familles de requêtes principales.
  3. Choisissez la méthode d’ingestion : CDC pour quasi temps réel ; batch pour quotidien/horaires ; évitez la double-écriture.
  4. Définissez les tables analytiques dans ClickHouse : commencez par des tables d’événements, partitionnement temporel et clés ORDER BY alignées sur les filtres.
  5. Construisez des jeux de données « gold » : vues matérialisées ou tables de rollup pour les tableaux de bord ; conservez les données brutes pour les analyses profondes.
  6. Mettez en place la gouvernance dès le premier jour : profils utilisateurs, quotas, max_execution_time, max_memory_usage, max_concurrent_queries.
  7. Mesurez la fraîcheur : suivez le lag d’ingestion et publiez le SLO aux parties prenantes. Les gens tolèrent l’obsolescence quand elle est explicite.
  8. Basculer les tableaux de bord : migrez d’abord les tableaux de bord à impact élevé (ceux qui pagent l’on-call indirectement).
  9. Bouchez l’ancien chemin : retirez les credentials BI de MySQL ; firewall si nécessaire ; évitez les régressions.
  10. Backfill en sécurité : incrémental, mesurable, avec vérifications d’espace disque ; pas de fantaisies « lancez-le juste cette nuit ».
  11. Load testez l’analytique : simulez la concurrence des tableaux de bord. ClickHouse acceptera volontiers votre optimisme puis vous punira.
  12. Opérationnalisez : alertes sur nombre de parts ClickHouse, utilisation disque, échecs de requêtes, lag d’ingestion ; et sur latence/I/O MySQL.

Checklist de release : déplacer un tableau de bord de MySQL vers ClickHouse

  • La requête du tableau de bord inclut-elle un filtre temporel compatible avec le partitionnement ?
  • Existe-t-il une table de rollup/vue matérialisée pour éviter de scanner les événements bruts à répétition ?
  • L’utilisateur ClickHouse est-il limité (mémoire, threads, temps d’exécution, concurrence) ?
  • La métrique de lag CDC est-elle visible par les utilisateurs du tableau de bord ?
  • La vieille requête MySQL est-elle bloquée ou au moins retirée de l’app/outil BI ?
  • Avez-vous validé les résultats pour une fenêtre temporelle connue (contrôles ponctuels des totaux et comptages) ?

Checklist opérationnelle : hygiène hebdomadaire qui prévient les catastrophes lentes

  • Revoir les parts actives ClickHouse par table ; investiguer les croissances rapides.
  • Revoir le headroom disque ClickHouse ; garder suffisamment d’espace libre pour merges et backfills.
  • Revoir les top requêtes BI par read_rows et utilisation mémoire ; optimiser ou pré-agréger.
  • Revoir les top digests MySQL pour s’assurer que l’analytique ne s’est pas infiltrée.
  • Tester les chemins de restauration : sauvegardes MySQL, métadonnées ClickHouse et attentes de récupération des données.

FAQ

1) Ne puis-je pas simplement scaler verticalement MySQL et régler le problème ?

Vous le pouvez, et vous obtiendrez un soulagement temporaire. Le mode de défaillance revient quand le prochain tableau de bord ou la prochaine requête de cohortes apparaît. Le problème est l’inadéquation de la charge, pas seulement la puissance brute.

2) Si j’ai déjà des réplica MySQL — dois-je pointer le BI dessus ?

Seulement si vous êtes à l’aise avec le lag et si vous n’utilisez pas ces réplica pour le failover. Même dans ce cas, limitez la concurrence et considérez cela comme un pont temporaire, pas l’état final.

3) ClickHouse est-il « temps réel » assez pour des tableaux de bord opérationnels ?

Souvent oui, avec CDC. Mesurez explicitement le lag d’ingestion et concevez les tableaux de bord pour tolérer de petits délais. Si vous avez besoin de vérité transactionnelle sous-seconde, c’est le terrain de MySQL.

4) Comment gérer les updates et deletes de MySQL dans ClickHouse ?

Privilégiez la modélisation événementielle (append de changements). Si vous avez besoin d’un « état courant », utilisez des lignes versionnées avec des engines comme ReplacingMergeTree et concevez des requêtes/vues matérialisées en conséquence.

5) ClickHouse remplacera-t-il mon entrepôt de données ?

Parfois. Pour de nombreuses entreprises, il devient le magasin analytique principal. Mais si vous avez besoin de transformations lourdes, de gouvernance ou de modélisation cross-systèmes, vous pouvez garder une couche entrepôt. Ne forcez pas une conversion religieuse.

6) Quel est le gain le plus rapide si nous sommes en feu aujourd’hui ?

Arrêtez immédiatement d’exécuter l’analytique sur MySQL : tuez les pires requêtes, retirez l’accès BI et basculez le tableau de bord vers ClickHouse ou un rollup mis en cache. Ensuite réparez proprement.

7) Quelle est la plus grande surprise opérationnelle ClickHouse pour les équipes MySQL ?

Les merges et les parts. Les gens habitués au row-store s’attendent à « j’ai inséré, c’est fait ». ClickHouse continue de travailler en arrière-plan, et vous devez surveiller ce travail.

8) Comment empêcher les analystes d’écrire des requêtes ClickHouse coûteuses ?

Utilisez des profils utilisateur avec quotas et timeouts, fournissez des tables « gold » sélectionnées, et enseignez que l’absence de filtre temporel n’est pas « exploration », c’est de l’arson.

9) Les vues matérialisées résolvent-elles tout ?

Non. Elles sont excellentes pour les rollups stables et les agrégats communs. Mais elles peuvent ajouter de la complexité et un coût de stockage. Utilisez-les là où elles réduisent de façon mesurable le travail répété.

10) Et si mes requêtes analytiques demandent des joins complexes sur de nombreuses tables ?

Dénormalisez pour les chemins fréquents, pré-calculer les dimensions et limitez les joins. ClickHouse sait joindre, mais les meilleurs systèmes analytiques de production évitent de le faire systématiquement à l’exécution.

Conclusion : prochaines étapes pratiques

Si vous ne faites qu’une action cette semaine, faites celle-ci : supprimez la charge analytique de MySQL. Pas en suppliant les utilisateurs d’« être prudents », mais en fournissant un meilleur endroit pour poser des questions.

  1. Verrouillez MySQL : comptes séparés, blocage des réseaux BI et faites respecter que MySQL de production sert d’abord les utilisateurs.
  2. Mettez en place la gouvernance ClickHouse : limites, quotas et jeux de données sélectionnés avant d’inviter toute l’entreprise.
  3. Déplacez les 5 pires requêtes : répliquez les données nécessaires via CDC ou batch, puis construisez des rollups pour que les tableaux de bord restent peu coûteux.
  4. Operationalisez la fraîcheur : publiez le lag d’ingestion et traitez-le comme une exigence produit. Il vaut mieux être honnêtement 60 secondes en retard que incorrect sans le savoir.
  5. Exercez-vous aux backfills : étagés, mesurables, réversibles. Votre futur vous remerciera la retenue actuelle.

La séparation propre n’est pas glamour. C’est simplement la différence entre une base de données qui sert des clients et une base de données qui héberge un combat de cage analytique quotidien. Choisissez la vie plus calme.

SLI/CrossFire : pourquoi le multi-GPU était un rêve — et pourquoi il a disparu

Si vous avez déjà essayé de « simplement ajouter une autre GPU » en vous attendant à voir la courbe monter sans accroc, vous avez déjà rencontré le méchant de cette histoire : le monde réel. Le multi-GPU grand public — NVIDIA SLI et AMD CrossFire — ressemblait à une pure vertu d’ingénierie : parallélisme, plus de silicium, plus d’images, terminé.

Puis vous l’avez livré. Les temps de trame se sont transformés en grille irrégulière. La pile de pilotes est devenue une négociation entre moteur de jeu, ordonnanceur GPU, PCIe et la synchronisation d’affichage que vous croyiez comprendre. Votre seconde carte chère devenait souvent un radiateur d’appoint avec un CV.

La promesse : monter en puissance en ajoutant des GPU

Le multi-GPU, tel qu’il était vendu aux joueurs, était un conte opérationnel : votre jeu est limité par le GPU, donc un GPU de plus signifie presque le double de performance. C’est le pitch. C’est aussi la première hypothèse erronée. Les systèmes ne montent pas en charge parce qu’une diapositive marketing dit « 2× » ; un système évolue quand la partie la plus lente de la chaîne cesse d’être le goulot d’étranglement.

Une image de jeu moderne est une chaîne de montage chaotique : simulation CPU, soumission d’appels de dessin, rendu GPU, post-traitement, composition, présentation et un contrat de timing avec votre affichage. SLI/CrossFire a essayé de masquer la complexité du multi-GPU derrière des pilotes, des profils et un pont. C’est précisément ce masquage qui l’a condamné.

Le rêve du multi-GPU est mort parce qu’il s’est heurté à la physique (latence et synchronisation), à l’économie logicielle (les développeurs ne testent pas les configs rares) et aux changements de plateforme (DX12/Vulkan ont déplacé la responsabilité du pilote vers le moteur). Et parce que la « moyenne FPS » s’est révélée être un mensonge par omission : ce que vos yeux perçoivent, c’est la régularité des temps de trame, pas la moyenne.

Comment SLI/CrossFire fonctionnait réellement

Multi-GPU géré par le pilote : des profils partout

À l’époque classique, SLI/CrossFire reposait sur des heuristiques de pilote et des profils par jeu. Le pilote décidait comment répartir le rendu entre les GPU sans que le jeu le sache explicitement. Ça semble pratique. C’est aussi un cauchemar opérationnel : vous avez maintenant un système distribué où un nœud (le jeu) ne sait pas qu’il est distribué.

Les profils étaient cruciaux parce que la plupart des jeux n’étaient pas écrits pour être parallélisés en toute sécurité entre GPU. Le pilote avait besoin d’« indices » spécifiques au jeu pour éviter des risques comme lire des données qui n’avaient pas encore été produites, ou appliquer un post-traitement qui suppose l’historique complet d’une image.

Les modes principaux : AFR, SFR et « s’il vous plaît, ne faites pas ça »

Alternate Frame Rendering (AFR) était le cheval de bataille. Le GPU0 rend la trame N, le GPU1 rend la trame N+1, etc. Sur le papier : fantastique. En pratique : AFR est une machine à latence et à pacing. Si la trame N prend 8 ms et la trame N+1 prend 22 ms, votre « FPS moyen » peut sembler correct alors que vos yeux subissent un diaporama saccadé.

Split Frame Rendering (SFR) divise une trame en régions. Cela exige un équilibrage de charge soigné : une moitié de l’écran peut contenir une explosion, des shaders de cheveux, des volumétriques et vos regrets ; l’autre moitié est un mur. Devinez quel GPU finit en premier et reste inactif.

Il y avait aussi des modes hybrides et des astuces spécifiques aux fabricants. Plus vous avez besoin de hacks, moins la solution est générale. À un moment donné, vous n’êtes plus en train de « prendre en charge le multi-GPU » ; vous écrivez une réponse incidente par titre au niveau du pilote.

Ponts, PCIe et pourquoi l’interconnexion n’a jamais été le héros

Les ponts SLI (et les ponts CrossFire dans les premières époques) fournissaient un chemin à plus large bande passante et plus faible latence pour certaines opérations de synchronisation et de partage de tampons que le PCIe seul. Mais le pont ne fusionnait pas magiquement la VRAM. Chaque GPU gardait sa mémoire propre. En AFR, chaque GPU avait typiquement besoin de sa propre copie des mêmes textures et géométries. Donc vos « deux cartes 8 Go » ne devenaient pas « 16 Go ». Elles restaient « 8 Go, en double ».

Lorsque les développeurs ont commencé à s’appuyer davantage sur des techniques temporelles — TAA, réflexions en espace-écran avec buffers d’historique, sur-échantillonneurs temporels — AFR est devenu de plus en plus incompatible. Vous ne pouvez pas facilement rendre la trame N+1 sur le GPU1 si elle a besoin de l’historique de la trame N qui vit sur le GPU0, à moins d’ajouter de la synchronisation et des transferts de données qui effacent le gain de performance.

Une idée paraphrasée, largement attribuée à l’esprit de la fiabilité des systèmes (et souvent prononcée par des ingénieurs dans l’orbite SRE de Google) : L’espoir n’est pas une stratégie.
Elle colle parfaitement au multi-GPU. SLI/CrossFire vous demandait d’espérer que le pipeline de rendu de votre jeu s’aligne sur les hypothèses du pilote.

Pourquoi ça a échoué : la mort par mille cas limites

1) Le pacing des images a tué le « ressenti »

AFR peut fournir un FPS moyen élevé tout en produisant des temps de trame inégaux (micro-saccades). Les humains remarquent la variance. Votre overlay peut afficher « 120 FPS », tandis que votre cerveau enregistre « irrégulier ». C’était l’échec central de l’expérience utilisateur : SLI/CrossFire pouvait gagner des benchmarks et perdre l’impression de fluidité.

Le pacing des images n’est pas juste « un petit jitter ». Il interagit avec le VSync, le VRR (G-SYNC/FreeSync), la profondeur de la file de rendu et l’ordonnancement CPU. Si le pilote met trop agressivement en file des images, vous obtenez de la latence d’entrée. S’il en met trop peu, vous avez des bulles et du stutter.

Blague #1 : Le multi-GPU, c’est comme avoir deux stagiaires écrivant alternativement des pages du même rapport — rapide, jusqu’au moment où vous constatez qu’ils ne s’entendent pas sur l’intrigue.

2) Mirroring de la VRAM : vous payiez pour de la mémoire inutilisable

Le multi-GPU grand public répliquait presque toujours les ressources dans la mémoire de chaque GPU. Cela permettait de scaler sans traiter la mémoire comme un pool cohérent partagé, mais cela signifiait aussi que les textures haute résolution, la géométrie volumineuse et les structures d’accélération modernes pour le ray tracing étaient limitées par la VRAM d’une seule carte.

À mesure que les jeux devenaient plus gourmands en VRAM, le plan « ajoutez une seconde GPU » empirait : votre goulot d’étranglement passait du calcul à la capacité mémoire, et le multi-GPU n’aidait pas. Pire encore, une seconde GPU augmentait la consommation électrique, la chaleur et les besoins d’aération du boîtier tout en conservant la même limite de VRAM qu’une seule carte.

3) Le CPU est devenu le coordinateur, et lui non plus n’a pas monté en charge

Le multi-GPU n’est pas seulement « deux GPU ». C’est du travail supplémentaire pour le pilote, plus de gestion de tampons de commandes, plus de synchronisation et souvent plus d’appels de dessin. Beaucoup de moteurs étaient déjà limités par le CPU sur le thread de rendu. Ajouter un second GPU peut déplacer le goulot d’étranglement en amont et faire du CPU le facteur limitant.

En termes de production : vous avez ajouté de la capacité à un service aval sans augmenter le débit en amont. Félicitations, vous avez inventé une nouvelle file d’attente.

4) Le modèle de profils pilotes n’a pas survécu à la chaîne d’approvisionnement logicielle

SLI/CrossFire géré par le pilote exigeait que les vendeurs suivent les nouvelles sorties de jeux, les correctifs, les mises à jour de moteurs et les nouvelles techniques de rendu. Les studios livraient des mises à jour hebdomadaires. Les fournisseurs de GPU sortaient des pilotes à un rythme plus lent et devaient tester des milliers de combinaisons.

Un profil multi-GPU qui fonctionne en version 1.0 peut casser en 1.0.3 parce qu’un post-traitement a changé d’ordre, ou parce qu’un nouveau filtre temporel lit maintenant un tampon d’une trame précédente. Le pilote « optimisant » aveuglément peut devenir la chose qui corrompt l’image.

5) Le VRR (taux de rafraîchissement variable) et le multi-GPU se sont mis des bâtons dans les roues

Le taux de rafraîchissement variable est une des meilleures améliorations de qualité de vie pour le jeu PC. Il complique aussi le pacing multi-GPU : l’écran s’adapte au rythme de livraison des images, donc si AFR crée des rafales et des creux, le VRR ne peut pas « lisser » cela ; il va montrer fidèlement l’irrégularité.

Beaucoup d’utilisateurs ont acheté des écrans VRR et ont découvert que leur configuration multi-GPU auparavant « correcte » semblait maintenant pire. Ce n’est pas la faute de l’écran. C’est que vous voyez enfin la vérité.

6) Le multi-GPU explicite est arrivé, et l’industrie n’a pas voulu payer la facture

DX12 et Vulkan ont rendu possible le multi-adapter explicite : le moteur peut contrôler plusieurs GPU directement. C’est techniquement plus propre que la magie du pilote. C’est aussi un travail d’ingénierie coûteux qui bénéficie à une infime fraction des clients.

Les studios ont priorisé des fonctionnalités qui s’adressent à tout le monde : meilleurs sur-échantillonneurs temporels, meilleur anti-aliasing, meilleures chaînes de contenu, parité console. Le multi-GPU était une charge de support avec un faible ROI. Il est mort comme beaucoup de fonctionnalités d’entreprise : silencieusement, parce que personne n’a financé la rotation d’astreinte.

7) Puissance, thermique et contraintes du boîtier : la couche physique a résisté

Deux GPU haut de gamme exigent une réelle marge sur l’alimentation, une bonne circulation d’air et souvent une carte mère capable de fournir suffisamment de lanes PCIe sans throttle. La configuration « boîtier grand public + deux GPU flagship » est un projet d’ingénierie thermique. Et la plupart des gens voulaient un ordinateur, pas un hobby qui brûle la poussière.

8) Sécurité et stabilité : la pile pilote est devenue un plus grand périmètre de défaillance

Plus la logique d’ordonnancement du pilote et de synchronisation inter-GPU est complexe, plus il y a de modes de défaillance : écrans noirs, TDR (timeout detection and recovery), corruptions étranges, plantages spécifiques à un jeu. En termes d’exploitation, vous avez augmenté la complexité système et réduit le temps moyen pour innocenter une hypothèse.

Blague #2 : SLI promettait « deux fois plus de GPU », mais parfois livrait « deux fois plus de dépannage », ce qui n’est la caractéristique que personne ne mesure en benchmark.

Contexte historique : les faits que l’on oublie

  • Fait 1 : Le nom original « SLI » venait de Scan-Line Interleave de 3dfx à la fin des années 1990 ; NVIDIA a réutilisé l’acronyme plus tard avec une approche technique différente.
  • Fait 2 : Le multi-GPU grand public s’est souvent appuyé lourdement sur l’AFR parce que c’était le moyen le plus simple de monter en charge sans réécrire les moteurs.
  • Fait 3 : L’évolutivité multi-GPU était notoirement incohérente : certains titres voyaient des gains presque linéaires, d’autres zéro, et certains devenaient plus lents à cause du surcoût CPU/pilote.
  • Fait 4 : Les « micro-saccades » sont devenues une plainte grand public au début des années 2010 quand les testeurs ont commencé à mesurer les temps de trame plutôt que le FPS moyen.
  • Fait 5 : AMD a investi dans des améliorations de pacing dans ses pilotes après de nombreuses critiques ; cela a aidé, mais n’a pas changé les contraintes fondamentales de l’AFR.
  • Fait 6 : De nombreux moteurs ont de plus en plus utilisé des buffers d’historique temporels (TAA, upscalers temporels, vecteurs de mouvement), intrinsèquement peu compatibles avec l’AFR.
  • Fait 7 : La bande passante PCIe a augmenté au fil des générations, mais la latence et le surcoût de synchronisation sont restés des problèmes centraux pour les dépendances trame-à-trame.
  • Fait 8 : Le multi-GPU explicite DX12/Vulkan donne le contrôle à l’application ; la plupart des studios ont choisi de ne pas l’implémenter car la matrice de tests explosait.
  • Fait 9 : NVIDIA a progressivement restreint/modifié le support SLI dans les générations récentes, en se concentrant sur les segments haut de gamme et des cas d’usage spécifiques plutôt que sur un support large des jeux.

Ce qui l’a remplacé (à sa manière) : multi-GPU explicite et alternatives modernes

Multi-GPU explicite : meilleure architecture, pire économie

Le multi-GPU explicite (multi-adapter DX12, device groups Vulkan) est la façon dont vous le concevriez si vous étiez sobre : le moteur sait quelles charges peuvent tourner sur quel GPU, quelles données doivent être partagées et quand synchroniser. Cela enlève beaucoup de suppositions côté pilote.

Cela exige aussi que le moteur soit structuré pour le parallélisme entre dispositifs : duplication des ressources, barrières inter-dispositifs, gestion soignée des effets temporels et stratégies différentes pour différentes combinaisons de GPU. Ce n’est pas « prendre en charge SLI ». C’est construire un second moteur de rendu.

Quelques titres ont expérimenté. La plupart des studios ont fait le calcul et ont acheté autre chose : upscalers temporels, meilleur threading CPU et optimisations de contenu qui aident tous les utilisateurs.

Le « multi-GPU » moderne qui fonctionne réellement : la spécialisation

Le multi-GPU est vivant là où la charge de travail est naturellement parallèle et ne nécessite pas de cohérence stricte trame-à-trame :

  • Rendu hors-ligne / path tracing : vous pouvez répartir échantillons ou tuiles entre GPU et fusionner les résultats.
  • Calcul / entraînement ML : parallélisme de données avec des frameworks explicites, bien que toujours plein de douleurs de synchronisation.
  • Chaînes d’encodage vidéo : des GPU séparés peuvent gérer des flux ou des étapes distinctes.

Pour le jeu en temps réel, la stratégie gagnante est devenue : un GPU puissant, un meilleur ordonnancement, un meilleur upscaling et de meilleures techniques de génération d’images. Pas parce que c’est « cool », mais parce que c’est opérationnellement sensé.

Mode d’emploi pour un diagnostic rapide

Quand quelqu’un dit « mon second GPU ne sert à rien » ou « SLI a empiré les choses », ne commencez pas par des basculements mystiques du pilote. Traitez-le comme un incident. Établissez ce qui est goulot d’étranglement, puis isolez.

Première étape : confirmez que le système voit les deux GPU et que le lien est sain

  • Les deux dispositifs sont-ils présents sur le PCIe ?
  • Fonctionnent-ils à la génération/largeur PCIe attendue ?
  • Le bon pont est-il installé (si nécessaire) ?
  • Les connecteurs d’alimentation sont-ils corrects et stables ?

Deuxième étape : confirmez que le chemin logiciel est vraiment multi-GPU

  • Le jeu est-il connu pour supporter SLI/CrossFire pour votre génération GPU ?
  • Le profil pilote est-il présent/activé ?
  • Le chemin API (DX11 vs DX12 vs Vulkan) est-il compatible avec le mode multi-GPU du fournisseur ?

Troisième étape : mesurez les temps de trame et identifiez la ressource limitante

  • Utilisation GPU par carte (pas seulement « total »).
  • Saturation du thread de rendu CPU.
  • Utilisation de la VRAM et comportement de pagination.
  • Pacing des images (percentile 99 du temps de trame), pas seulement le FPS moyen.

Quatrième étape : retirez les variables jusqu’à ce que le comportement soit explicable

  • Désactivez VRR/VSync temporairement pour observer le pacing brut.
  • Testez un titre/benchmark connu pour son bon scaling documenté.
  • Testez chaque GPU individuellement pour éliminer une carte marginale.

Tâches pratiques : commandes, sorties et décisions

Ces commandes supposent une station Linux utilisée pour les tests/CI, la reproduction en labo, ou simplement parce que vous aimez la douleur reproductible. Le point n’est pas que Linux soit l’apogée du multi-GPU en jeu ; c’est que Linux vous offre de l’observabilité sans chasse au trésor GUI.

Task 1: List GPUs and confirm the PCIe topology

cr0x@server:~$ lspci -nn | egrep -i 'vga|3d|display'
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP102 [GeForce GTX 1080 Ti] [10de:1b06]
02:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP102 [GeForce GTX 1080 Ti] [10de:1b06]

Ce que cela signifie : Deux GPU sont énumérés sur le bus PCIe. Si vous n’en voyez qu’un, arrêtez : vous avez un problème matériel/firmware.

Décision : Si un GPU manque, resseyez-le, vérifiez les alimentations, les réglages du BIOS (Above 4G decoding, config des slots PCIe), puis retestez.

Task 2: Verify PCIe link width and generation for each GPU

cr0x@server:~$ sudo lspci -s 01:00.0 -vv | egrep -i 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 8GT/s, Width x16
LnkSta: Speed 8GT/s, Width x16

Ce que cela signifie : Le GPU négocie PCIe Gen3 x16 comme attendu. Si vous voyez x8 ou Gen1, vous avez trouvé un goulot ou une régression.

Décision : Si le lien est rétrogradé, vérifiez le câblage des slots, le partage de lanes de la carte mère (M.2 prenant des lanes), le BIOS, les risers et l’intégrité du signal.

Task 3: Confirm NVIDIA driver sees both GPUs and reports utilization

cr0x@server:~$ nvidia-smi -L
GPU 0: GeForce GTX 1080 Ti (UUID: GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
GPU 1: GeForce GTX 1080 Ti (UUID: GPU-ffffffff-1111-2222-3333-444444444444)

Ce que cela signifie : La couche pilote voit les deux dispositifs. Si l’un manque ici mais est présent dans lspci, vous avez probablement un problème d’association pilote/firmware.

Décision : Si manquant, vérifiez dmesg pour des erreurs GPU, confirmez les modules du noyau et que les deux GPU sont supportés par le pilote installé.

Task 4: Watch per-GPU utilization and memory during load

cr0x@server:~$ nvidia-smi dmon -s pucvmet
# gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk   pviol  rxpci  txpci
    0   210    78     -    92    55     0     0  5500  1582      0    120    110
    1    95    64     -    18    52     0     0  5500  1582      0     40     35

Ce que cela signifie : GPU0 fait le gros du travail ; GPU1 est en grande partie inactif mais occupe une VRAM similaire (miroir des assets). C’est le comportement classique du « second GPU non utilisé ».

Décision : Si GPU1 reste au repos, vérifiez que le chemin applicatif prend en charge le multi-GPU ; sinon, cessez d’essayer de réparer une non-fonctionnalité.

Task 5: Confirm Xorg/Wayland session details (to avoid compositor surprises)

cr0x@server:~$ echo $XDG_SESSION_TYPE
wayland

Ce que cela signifie : Vous êtes sur Wayland. Certains outils et certains chemins multi-GPU hérités se comportent différemment sous Wayland vs Xorg.

Décision : Si vous déboguez des problèmes de rendu/presentation, reproduisez sous Xorg comme témoin pour isoler les effets du compositeur.

Task 6: Check kernel logs for PCIe errors and GPU resets

cr0x@server:~$ sudo dmesg -T | egrep -i 'pcie|aer|nvrm|gpu|xid' | tail -n 12
[Mon Jan 13 10:19:22 2026] NVRM: Xid (PCI:0000:02:00): 79, GPU has fallen off the bus.
[Mon Jan 13 10:19:22 2026] pcieport 0000:00:03.1: AER: Corrected error received: 0000:02:00.0

Ce que cela signifie : « Fallen off the bus » indique souvent une instabilité d’alimentation/thermique, un riser défaillant, un slot capricieux ou des problèmes d’intégrité du signal — le multi-GPU rend cela plus probable.

Décision : Traitez cela comme de la fiabilité matérielle : réduisez la limite de puissance, améliorez le refroidissement, resseyez, changez de slot, retirez les risers, mettez à jour le BIOS et retestez la stabilité avant d’accuser les pilotes.

Task 7: Check CPU bottleneck indicators (load, run queue, throttling)

cr0x@server:~$ uptime
 10:22:11 up 3 days,  6:41,  1 user,  load average: 14.82, 13.97, 12.10

Ce que cela signifie : Une charge moyenne élevée peut indiquer une saturation CPU ou des threads exécutables qui s’accumulent. Les jeux peuvent être limités par le CPU sur un seul thread de rendu même si le total CPU n’est pas à « 100% ».

Décision : Si la charge est élevée et que l’utilisation GPU est faible, arrêtez de chercher des réglages SLI. Réduisez les paramètres gourmands CPU (distance de vue, densité de foule), ou acceptez que vous êtes limité par le CPU.

Task 8: Inspect per-core usage to catch a pegged render thread

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (server)  01/13/2026  _x86_64_  (16 CPU)

10:22:18 AM  CPU   %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10:22:19 AM  all   42.0  0.0  8.0   0.2    0.0  0.5    0.0    0.0    0.0   49.3
10:22:19 AM    3   98.5  0.0  1.0   0.0    0.0  0.0    0.0    0.0    0.0    0.5

Ce que cela signifie : Un cœur (CPU3) est saturé. C’est votre goulot de rendu/jeu. Deux GPU n’aideront pas si la trame ne peut être fournie.

Décision : Réduisez les paramètres liés au CPU, ou passez à une plateforme/CPU avec un meilleur comportement mono‑thread. Le multi-GPU ne résoudra pas un tuyau amont étroit.

Task 9: Verify memory pressure (paging can masquerade as “GPU stutter”)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            32Gi        30Gi       500Mi       1.2Gi       1.5Gi       1.0Gi
Swap:           16Gi        10Gi       6.0Gi

Ce que cela signifie : Vous swappez beaucoup. Cela détruira les temps de trame quel que soit le nombre de GPU empilés.

Décision : Réglez la pression mémoire d’abord : fermez les applis en arrière-plan, réduisez les paramètres de texture, ajoutez de la RAM et retestez. Traitez l’utilisation du swap comme une alerte critique pour le pacing.

Task 10: Confirm CPU frequency and throttling status

cr0x@server:~$ lscpu | egrep -i 'model name|cpu mhz'
Model name:                           AMD Ryzen 9 5950X 16-Core Processor
CPU MHz:                               3599.998

Ce que cela signifie : La fréquence actuelle est affichée, mais pas si elle est throttlée sous charge soutenue.

Décision : Si les clocks chutent sous charge de jeu, rectifiez le refroidissement ou les limites d’alimentation. Le multi-GPU augmente la chaleur du boîtier, ce qui peut réduire silencieusement le boost CPU.

Task 11: Check power capping / throttling flags on NVIDIA

cr0x@server:~$ nvidia-smi -q -d PERFORMANCE | egrep -i 'Power Limit|Clocks Throttle Reasons' -A3
    Power Limit                        : 250.00 W
    Clocks Throttle Reasons
        Idle                           : Not Active
        Applications Clocks Setting     : Not Active
        SW Power Cap                   : Active

Ce que cela signifie : Le GPU atteint une limite de puissance logicielle. En configuration multi-GPU, les PSU et les VRM thermiques peuvent forcer des limites conservatrices.

Décision : Si le plafonnement de puissance est actif et que la performance est incohérente, considérez réduire l’objectif FPS, améliorer le flux d’air ou faire tourner un seul GPU à des clocks soutenus plus élevés.

Task 12: Check PCIe lane sharing hints from NVLink/bridge status (when available)

cr0x@server:~$ nvidia-smi topo -m
        GPU0    GPU1    CPU Affinity
GPU0     X      PHB     0-7
GPU1    PHB      X      8-15

Ce que cela signifie : PHB indique que la communication passe par le pont hôte PCIe ; ce n’est pas un chemin pair-à-pair serré. Cela peut pénaliser toute charge ayant besoin de trafic inter-GPU.

Décision : Si la topologie est mauvaise, cessez d’attendre que la collaboration inter-GPU soit bon marché. Préférez le rendu sur un seul GPU ou une partition explicite des tâches évitant le partage.

Task 13: Confirm Vulkan device visibility (for explicit multi-GPU experiments)

cr0x@server:~$ vulkaninfo --summary | egrep -i 'GPU id|deviceName' -A1
GPU id : 0 (GeForce GTX 1080 Ti)
deviceName     = GeForce GTX 1080 Ti
GPU id : 1 (GeForce GTX 1080 Ti)
deviceName     = GeForce GTX 1080 Ti

Ce que cela signifie : Vulkan voit les deux dispositifs. C’est un prérequis pour les applis multi-GPU explicites, pas une garantie qu’un jeu le supporte.

Décision : Si un seul apparaît, corrigez l’installation du pilote/runtime. Si les deux apparaissent, passez aux vérifications au niveau applicatif des capacités.

Task 14: Validate storage latency (yes, it can look like “GPU stutter”)

cr0x@server:~$ iostat -xz 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          38.12    0.00    6.21    8.93    0.00   46.74

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s w_await aqu-sz  %util
nvme0n1         210.0   38400.0     0.0   0.00   3.10   182.86    75.0   10240.0   9.80   2.10  78.00

Ce que cela signifie : Un iowait élevé et des temps d’attente relevés peuvent causer des hitchs de streaming. Le multi-GPU ne corrigera pas des stalls dus à la compilation de shaders ou à la latence de streaming d’assets.

Décision : Si le stockage est saturé, réduisez les IO en arrière-plan, déplacez le jeu vers un stockage plus rapide et traitez le comportement du cache de shaders. Corrigez le vrai goulot d’étranglement.

Erreurs courantes (symptômes → cause → solution)

1) « Le second GPU affiche 0–10% d’utilisation »

Symptômes : Un GPU chauffe, l’autre reste au repos ; FPS identique à un seul GPU.

Cause : Le chemin jeu/API ne prend pas en charge le multi-GPU géré par pilote, ou le profil pilote est manquant/désactivé.

Solution : Validez le support du titre pour votre génération GPU et le mode API. Si le jeu est en DX12/Vulkan et n’implémente pas le multi-GPU explicite, acceptez le single GPU.

2) « FPS moyen plus élevé, mais ça semble pire »

Symptômes : Le benchmark indique un gain ; le jeu semble saccadé ; le VRR rend cela plus visible.

Cause : Variance des temps de trame en AFR (micro-saccades), mise en file ou charge incohérente par trame.

Solution : Mesurez les temps de trame et limitez le FPS pour stabiliser le pacing, ou désactivez le multi-GPU. Priorisez les 1% low / percentile 99 du temps de trame plutôt que les moyennes.

3) « Les textures popent, puis les hitchs deviennent brutaux en 4K »

Symptômes : Pics soudains, surtout en pivotant rapidement ou en entrant dans de nouvelles zones.

Cause : La limite de VRAM est par GPU ; le mirroring fait que vous n’avez pas gagné en capacité. Vous paginez des assets et vous vous bloquez.

Solution : Baissez la résolution des textures, réduisez les réglages RT, ou passez à un seul GPU avec plus de VRAM.

4) « Écrans noirs aléatoires / GPU disparu »

Symptômes : Reset du pilote, un GPU disparaît du bus, instabilités intermittentes.

Cause : Instabilité de l’alimentation, stress thermique, intégrité marginale du signal PCIe, ou un overclock « stable » sur une carte qui ne l’est plus en duo.

Solution : Revenez aux clocks stock, réduisez la limite de puissance, améliorez le refroidissement, vérifiez le câblage, évitez les risers, mettez à jour le BIOS et testez chaque GPU seul.

5) « Ça marchait sur une version de pilote, puis ça casse avec la suivante »

Symptômes : Le scaling disparaît ou des artefacts apparaissent après une mise à jour pilote.

Cause : Changements de profil, modifications d’ordonnancement ou régression dans les chemins multi-GPU (désormais peu prioritaires).

Solution : Verrouillez les versions de pilotes pour votre cas d’usage, documentez les combinaisons connues bonnes et ne considérez pas « dernier pilote » comme nécessairement meilleur pour le multi-GPU.

6) « Deux GPU, mais l’utilisation CPU semble faible — pourtant CPU-bound »

Symptômes : Utilisation GPU faible, FPS plafonné, CPU total sous 50%.

Cause : Un ou deux threads chauds (thread de rendu, thread de jeu). Le total CPU masque la saturation par cœur.

Solution : Observez l’utilisation par cœur. Réduisez les paramètres lourds CPU ; visez des temps de trame stables ; envisagez une montée de plateforme plutôt que d’ajouter des GPU.

7) « PCIe en x8/x4 inattendu, scaling médiocre »

Symptômes : Scaling pire que prévu ; stutter élevé pendant le streaming ; topo montre des chemins PHB.

Cause : Partage de lanes avec M.2/autres dispositifs, mauvais choix de slot ou limitations du chipset uplink.

Solution : Utilisez les slots corrects, réduisez les consommateurs de lanes ou choisissez une plateforme avec plus de lanes CPU si vous insistez sur les configurations multi-dispositifs.

Trois mini-récits d’entreprise depuis le terrain

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Un petit studio disposait d’un « labo performance » avec quelques rigs de test haut de gamme. Quelqu’un avait monté une machine monstrueuse : deux GPU haut de gamme, beaucoup de RGB, et un tableau de chiffres de benchmark qui réjouissait la direction. Le studio l’utilisait pour valider les budgets de performance d’un nouveau niveau riche en contenu.

L’hypothèse erronée était subtile : ils ont supposé que le scaling était représentatif. Leur machine d’approbation fonctionnait en AFR avec un profil pilote qui, par hasard, fonctionnait bien pour cette build spécifique. Elle donnait d’excellents FPS moyens en labo. Elle ne produisait pas de bons temps de trame sur la plupart des machines clients, et elle ne représentait certainement pas la base single-GPU que la majorité possédait.

La semaine de sortie est arrivée. Les réseaux sociaux se sont remplis de plaintes « saccades dans le nouveau niveau ». En interne, le rig labo semblait « correct ». Les ingénieurs ont commencé à chasser des bugs fantômes dans l’animation et la physique parce que les graphiques GPU ne semblaient pas saturés.

Le vrai coupable était le streaming d’assets et un nouvel effet temporel. Sur le rig labo, l’AFR masquait du temps GPU en recouvrant les opérations, tout en dégradant le pacing d’une manière que le studio ne mesurait pas. Sur les config single-GPU des consommateurs, le même effet poussait la VRAM au-delà du seuil et déclenchait de la pagination et du thrash du cache de shaders. Le studio avait optimisé pour la mauvaise réalité.

La solution n’a pas été un tweak multi-GPU magique. Ils ont reconstruit leur gate perf : single-GPU, basés sur les temps de trame, avec des seuils de pression mémoire. Le rig double GPU est resté en labo, mais il a cessé d’être la source de vérité. L’incident s’est terminé quand ils ont arrêté de faire confiance à un benchmark qui ne correspondait pas à la population d’utilisateurs.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une équipe de visualisation entreprise (imaginez : grandes scènes CAO, walkthroughs temps réel) a essayé d’« obtenir des performances gratuites » en activant l’AFR dans un environnement contrôlé. Leurs scènes reposaient fortement sur l’accumulation temporelle : anti-aliasing, débruitage et beaucoup de logique « utiliser la trame précédente ». Quelqu’un a argué que puisque les GPU étaient identiques, les résultats devraient être cohérents.

Ils ont obtenu un débit moyen plus élevé avec une caméra statique. Belle démo. Puis ils ont envoyé une beta à quelques parties prenantes internes. Dès que vous bougiez la caméra, la stabilité de l’image se dégradait : ghosting, scintillement et filtres temporels incohérents. Pire, la latence interactive semblait plus importante parce que la profondeur de file augmentait sous AFR.

Le retour de bâton était architectural : le pipeline temporel du renderer supposait un historique de trame cohérent. AFR a scindé cet historique entre dispositifs. L’équipe a ajouté des points de sync et des transferts cross-GPU pour « corriger » cela, ce qui a détruit le gain de performance et introduit de nouveaux stalls. Désormais, ils avaient de la complexité et aucun gain.

Ils ont fini par retirer l’AFR et investir dans une série d’améliorations ennuyeuses : culling côté CPU, simplification des shaders et règles de LOD de contenu. Le système final était plus rapide sur un seul GPU que la build AFR sur deux. L’optimisation a échoué parce qu’elle optimisait le mauvais niveau : elle a voulu paralléliser quelque chose de fondamentalement sériel en dépendance temporelle.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Un groupe de validation hardware dans une société de taille moyenne maintenait une flotte de nœuds de test GPU. Ils ne jouaient pas dessus ; ils exécutaient des régressions de rendu et de calcul et reproduisaient de temps en temps des bugs clients. Les nœuds incluaient des machines multi-GPU parce que des clients les utilisaient pour du calcul, pas parce que c’était amusant.

Leur arme secrète n’était pas un ordonnanceur intelligent. C’était un changelog. Chaque nœud avait une version de pilote figée, une baseline firmware figée et une matrice simple « connue‑bonne ». Les mises à jour étaient staged : un nœud canari d’abord, puis un petit lot, puis le reste. Pas d’exceptions. Personne n’adorait ça. Ça paraissait lent.

Une semaine, un nouveau pilote a introduit des erreurs PCIe corrigeables intermittentes sur une révision de carte mère spécifique quand les deux GPU étaient sous charge mixte. Sur le poste d’un développeur, cela ressemblait à des crashes d’applis aléatoires. Dans la flotte, le nœud canari a commencé à émettre des logs AER en quelques heures.

Parce que le groupe avait une discipline ennuyeuse, ils ont corrélé la timeline, rollbacké le canari et bloqué le déploiement. Pas d’instabilité à l’échelle de la flotte, pas de reimagings massifs, pas de panique. Ils ont déposé un ticket fournisseur avec des logs reproductibles et une recette de reproduction précise.

Le « sauvetage » n’était pas un débogage héroïque. C’était la pratique opérationnelle des rollouts stagés et du verrouillage de versions. Les systèmes multi-GPU amplifient les problèmes marginaux ; la seule réponse sensée est de traiter les changements comme des changements de production, pas comme des expériences du week-end.

Listes de contrôle / plan étape par étape

Étape par étape : décider si le multi-GPU vaut la peine

  1. Définissez l’objectif. Est-ce un FPS moyen plus élevé, de meilleurs 1% low, ou une charge compute/render spécifique ?
  2. Identifiez le type de charge. Jeu temps réel avec effets temporels ? Supposez « non ». Rendu hors-ligne/compute ? Peut-être « oui ».
  3. Vérifiez la réalité du support. Si l’appli n’implémente pas le multi-GPU explicite et que le vendeur ne supporte plus les profils pilotes, arrêtez ici.
  4. Mesurez la base. Un seul GPU, pilote stable, temps de trame, utilisation VRAM, CPU par cœur.
  5. Ajoutez le second GPU. Vérifiez la largeur de lien PCIe, l’alimentation, la thermique et la topologie.
  6. Re-mesurez. Cherchez des améliorations sur le percentile 99 du temps de trame et sur le débit, pas seulement sur la moyenne FPS.
  7. Décidez. Si les gains sont faibles ou que le pacing est pire, retirez-le. La taxe de complexité est réelle.

Étape par étape : stabiliser une machine multi-GPU (si vous devez la faire tourner)

  1. Tournez d’abord avec les clocks stock. Des overclocks « stables » sur un GPU peuvent échouer en conditions thermiques dual-GPU.
  2. Validez le budget d’alimentation. Assurez-vous de la marge PSU ; évitez les câbles d’alimentation PCIe en chaîne pour forts draws.
  3. Verrouillez les versions. Bloquez pilote/firmware ; staged updates comme en production.
  4. Instrumentez. Loggez dmesg, événements AER, raisons de throttling GPU, températures et utilisations.
  5. Fixez les attentes. Pour le jeu, vous optimisez la stabilité et le pacing, pas des captures de benchmark.

FAQ

1) Est-ce que SLI/CrossFire a jamais vraiment fonctionné ?

Oui — parfois. Dans des titres bien profilés DX11 avec des pipelines amicaux à l’AFR et peu de dépendances temporelles, le scaling pouvait être important. Le problème est que « parfois » n’est pas une stratégie produit.

2) Pourquoi la VRAM ne s’additionnait-elle pas entre GPU pour les jeux ?

Parce que chaque GPU avait besoin d’un accès local aux textures et géométries à pleine vitesse, et le multi-GPU grand public répliquait typiquement les ressources par carte. Sans modèle de mémoire unifié, vous ne pouvez pas traiter deux pools de VRAM comme un seul sans payer de lourds coûts de synchronisation et de transfert.

3) Qu’est-ce que le micro-saccades, opérationnellement parlant ?

C’est la variance de latence. Vous livrez des trames à des intervalles incohérents — rafales et creux — donc le mouvement paraît irrégulier. C’est pourquoi le « FPS moyen » est une métrique dangereusement incomplète.

4) Pourquoi DX12/Vulkan ont rendu le multi-GPU plus rare au lieu de plus courant ?

Ils l’ont rendu explicite. C’est honnête architecturalement mais cela déplace le travail vers l’équipe moteur : gestion des ressources, synchronisation, tests sur combinaisons de GPU et couverture QA. La plupart des studios n’ont pas voulu financer cela pour une petite base d’utilisateurs.

5) Deux GPU différents peuvent-ils travailler ensemble pour le jeu aujourd’hui ?

Pas comme avant où le pilote le faisait pour vous. Le multi-adapter explicite peut, en théorie, utiliser des GPU hétérogènes, mais le support dans la réalité est rare et généralement spécialisé. Pour les jeux typiques : supposez non.

6) Et NVLink — ça règle le problème ?

NVLink aide dans certains scénarios pair-à-pair de bande passante et est précieux en calcul. Il ne résout pas automatiquement le pacing, les dépendances temporelles ni le problème économique logiciel. Les interconnexions ne corrigent pas l’architecture.

7) Si je possède déjà deux GPU, que devrais-je faire ?

Pour le jeu : utilisez un seul GPU et vendez l’autre, ou réutilisez-le pour du calcul/encodage. Pour le calcul : utilisez des frameworks qui supportent explicitement le multi-GPU et mesurez le scaling avec des tailles de batch réalistes et le surcoût de synchronisation.

8) Quelles métriques dois-je croire en testant le multi-GPU ?

Percentiles des temps de trame (comme le 99e), ressenti de latence d’entrée (difficile à mesurer, facile à remarquer), utilisation par GPU, marge VRAM et logs de stabilité. Le FPS moyen est une métrique de vanité dans ce contexte.

9) Le multi-GPU est-il complètement mort ?

Pas totalement — juste en tant que voie d’accélération par défaut pour le jeu temps réel grand public. Le multi-GPU prospère là où la charge peut être partitionnée proprement : rendu hors-ligne, calcul scientifique, ML et certaines pipelines de visualisation professionnelle.

Étapes suivantes que vous pouvez réellement entreprendre

Si vous pensez au multi-GPU pour le jeu en 2026, voici un conseil franc : n’en faites pas. Achetez le meilleur GPU unique que vous pouvez justifier, puis optimisez les temps de trame, la marge VRAM et une pile pilote stable. Vous obtiendrez un système comportementalement prévisible, ce que vous voulez quand c’est vous qui devez le déboguer.

Si vous devez absolument faire tourner du multi-GPU — parce que votre charge est du compute, du rendu hors-ligne ou de la visualisation spécialisée — traitez-le comme une infrastructure de production : verrouillez les versions, effectuez des rollouts stagés, instrumentez tout et supposez que le second GPU augmente votre surface de défaillance plus que votre performance.

Étapes pratiques suivantes :

  • Basculez votre état d’esprit de test de « FPS moyen » vers les percentiles des temps de trame et des runs reproductibles.
  • Validez la largeur de lien PCIe, la topologie et la stabilité d’alimentation avant de toucher aux pilotes.
  • Décidez à l’avance si votre application utilise le multi-GPU explicite ; si non, arrêtez d’investir du temps.
  • Conservez un pilote connu‑bon et traitez les mises à jour comme des rollouts contrôlés.

Les bizarreries réseau de Docker Desktop : accès LAN, ports et correctifs DNS qui fonctionnent

Vous lancez docker run -p 8080:80, naviguez vers localhost:8080 et ça marche. Vous communiquez l’URL à un collègue sur le même Wi‑Fi, et… rien.
Ou votre conteneur peut faire un curl vers Internet mais ne parvient pas au NAS sur votre LAN. Ou le DNS se met à déconner à chaque connexion VPN.

Le réseau de Docker Desktop n’est pas « cassé ». Il ne correspond simplement pas au modèle réseau Linux que vous pensez utiliser.
C’est une VM, un NAT, une couche d’adaptations spécifiques à la plateforme, et quelques noms spéciaux qui existent principalement pour nous sauver la mise.

Modèle mental : pourquoi Docker Desktop est différent

Sur Linux, Docker branche généralement les conteneurs sur un réseau bridge de l’hôte, utilise iptables/nftables pour NATer le trafic sortant,
et ajoute des règles DNAT pour les ports publiés. Votre hôte est l’hôte. Le noyau qui exécute les conteneurs est le même que celui qui exécute votre shell.

Docker Desktop sur macOS et Windows est différent par conception. Il exécute une petite VM Linux (ou un environnement Linux via WSL2),
et les conteneurs vivent derrière une frontière de virtualisation. Cette frontière explique pourquoi le « network host » se comporte étrangement,
pourquoi l’accès LAN n’est pas symétrique, et pourquoi la publication de ports peut sembler destinée uniquement à localhost.

Pensez en couches :

  • Votre système d’exploitation physique (macOS/Windows) : possède votre interface Wi‑Fi/Ethernet, votre client VPN et votre pare‑feu.
  • La VM Docker / WSL2 : a sa propre NIC virtuelle, sa propre table de routage, ses propres iptables et son comportement DNS.
  • Réseaux de conteneurs : ponts à l’intérieur de cet environnement Linux ; vos conteneurs touchent rarement directement le LAN physique.
  • Couche de redirection des ports : Docker Desktop transfère les ports de l’OS hôte vers la VM puis vers le conteneur.

Donc quand quelqu’un dit « le conteneur ne peut pas atteindre le LAN », votre première réponse devrait être : « Quelle couche ne parvient pas à atteindre quelle couche ? »

Faits intéressants et bref historique (ce qui explique les douleurs actuelles)

  1. Le modèle réseau original de Docker supposait Linux. Docker a popularisé le schéma « bridge + NAT + iptables » parce que Linux le rendait simple et portable.
  2. macOS ne peut pas exécuter des conteneurs Linux nativement. Docker Desktop sur macOS a toujours reposé sur une VM Linux parce que les conteneurs nécessitent des fonctionnalités du noyau Linux (namespaces, cgroups).
  3. Windows a connu deux ères. D’abord Docker Desktop basé sur Hyper‑V ; ensuite WSL2 est devenu la voie par défaut pour de meilleurs comportements de système de fichiers et de ressources, avec des particularités réseau différentes.
  4. host.docker.internal existe parce que « l’hôte » est ambigu. Dans un conteneur, « localhost » est le conteneur ; Docker Desktop avait besoin d’un nom stable pour « l’OS hôte ».
  5. Les ports publiés ne sont pas que des règles iptables sur Desktop. Sur Linux, oui ; sur Desktop, ils sont souvent implémentés par un proxy/forwardeur en espace utilisateur à travers la frontière VM.
  6. Les clients VPN aiment réécrire votre DNS et vos routes. Ils installent souvent un nouveau serveur DNS, bloquent le split DNS ou ajoutent une interface virtuelle avec une priorité supérieure au Wi‑Fi.
  7. Les solutions de sécurité d’entreprise injectent fréquemment un proxy local. Cela peut casser le DNS des conteneurs, MITM le TLS ou détourner silencieusement le trafic vers une infrastructure d’inspection.
  8. ICMP vous ment dans les réseaux virtuels. « Impossible de pinguer » ne signifie pas forcément « impossible de se connecter », surtout quand des pare‑feu bloquent ICMP mais autorisent TCP.

Blague #1 : Le réseau Docker Desktop est comme un organigramme—il y a toujours une couche de plus que vous pensez, et ce n’est jamais la couche responsable.

Playbook de diagnostic rapide (vérifier premier/deuxième/troisième)

La façon la plus rapide de gagner est d’arrêter de deviner. Diagnostiquez dans cet ordre, car cela isole les couches avec un minimum d’effort.

1) S’agit‑il d’un problème de publication de port ou d’un problème de routage/DNS ?

  • Si localhost:PORT fonctionne sur votre machine mais que des clients LAN ne peuvent pas y accéder, vous avez probablement un problème de pare‑feu hôte / adresse de binding / filtrage de routes par le VPN.
  • Si les conteneurs ne peuvent pas résoudre les noms ou atteindre aucun hôte externe, commencez par le DNS et le routage sortant depuis l’intérieur du conteneur/VM.

2) Identifiez où le paquet meurt (OS hôte → VM → conteneur)

  • Depuis l’OS hôte : pouvez‑vous joindre la cible LAN ?
  • Depuis l’intérieur d’un conteneur : pouvez‑vous joindre la même cible LAN par IP ?
  • Depuis l’intérieur d’un conteneur : pouvez‑vous résoudre le nom ?

3) Vérifiez l’adresse réelle d’écoute/binding et le forwarder

  • Le service écoute‑t‑il sur 0.0.0.0 à l’intérieur du conteneur, ou seulement sur 127.0.0.1 ?
  • Docker publie‑t‑il le port sur toutes les interfaces ou seulement sur localhost ?
  • Le pare‑feu hôte bloque‑t‑il les entrées depuis le LAN ?

4) Vérifiez tôt le comportement VPN et override DNS

  • Si le problème apparaît/disparaît avec le VPN, arrêtez de le traiter comme un bug Docker. C’est une politique, des routes, du DNS ou de l’inspection.

5) Ce n’est qu’ensuite que vous touchez aux réglages Docker Desktop

  • Changer les serveurs DNS ou les plages réseau peut aider, mais faites‑le avec des preuves. Sinon vous allez juste créer un nouveau mystère.

Tâches pratiques : commandes, sorties et décisions (12+)

Voici les vérifications que j’exécute vraiment. Chacune inclut : commande, sortie d’exemple, ce que cela signifie et la décision suivante.
Les commandes sont montrées avec un prompt générique ; adaptez les noms d’interface et les IP à votre environnement.

Task 1: Confirm which Docker context you’re using

cr0x@server:~$ docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT
default *           Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
desktop-linux       Docker Desktop                            unix:///Users/me/.docker/run/docker.sock

Sens : Si vous pensez parler à Desktop mais que vous êtes connecté à un daemon distant (ou inversement), toutes les hypothèses réseau seront fausses.
Décision : Si le contexte étoilé n’est pas celui attendu, changez‑le : docker context use desktop-linux.

Task 2: Inspect a container’s IP and network attachment

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES          PORTS
web            0.0.0.0:8080->80/tcp
db             5432/tcp
cr0x@server:~$ docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}} {{.Gateway}}{{end}}' web
/web 172.17.0.2 172.17.0.1

Sens : Le conteneur vit sur un bridge interne (ici 172.17.0.0/16). Ce n’est pas votre LAN.
Décision : Si vous essayez d’atteindre 172.17.0.2 depuis un autre ordinateur sur le Wi‑Fi, stoppez. Publiez un port ou utilisez un autre schéma réseau.

Task 3: Check what address your service is actually listening on

cr0x@server:~$ docker exec -it web sh -lc "ss -lntp | head -n 5"
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1,fd=6))

Sens : Écouter sur 0.0.0.0 est bon ; cela accepte le trafic depuis le réseau des conteneurs.
Si vous voyez 127.0.0.1:80, la publication de port « fonctionnera » de façon confuse ou échouera complètement.
Décision : Si le service est lié à localhost, corrigez la config de l’application : lier sur 0.0.0.0.

Task 4: Verify published port bindings on the Docker side

cr0x@server:~$ docker port web
80/tcp -> 0.0.0.0:8080

Sens : Docker pense avoir publié sur toutes les interfaces.
Décision : Si cela affiche 127.0.0.1:8080, les clients LAN ne pourront pas y accéder. Relancez avec -p 0.0.0.0:8080:80 (ou corrigez votre fichier compose).

Task 5: Confirm the host OS is listening on the expected port

cr0x@server:~$ ss -lntp | grep ':8080'
LISTEN 0      4096      0.0.0.0:8080     0.0.0.0:*    users:(("com.docker.backend",pid=2314,fd=123))

Sens : Sur Desktop, vous voyez souvent le processus backend de Docker écoutant, pas le PID du conteneur. C’est normal.
Décision : Si rien n’écoute, votre publication n’a pas appliqué, ou un autre processus a pris le port.

Task 6: Test from the host OS to confirm the forward path works

cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ | head
HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Sat, 03 Jan 2026 09:12:52 GMT
Content-Type: text/html

Sens : Le transfert de port host→container fonctionne localement.
Décision : Si les clients LAN ne peuvent pas se connecter, concentrez‑vous sur le pare‑feu/VPN/binding‑sur‑localhost, pas sur l’application du conteneur.

Task 7: Test from a LAN peer (simulate with another namespace/host if you can)

cr0x@server:~$ nc -vz 192.168.1.50 8080
Connection to 192.168.1.50 8080 port [tcp/http-alt] succeeded!

Sens : Le port est joignable depuis le LAN.
Décision : Si cela échoue par « timed out » vous avez probablement des problèmes de pare‑feu/routage. Si « refused », quelque chose écoute mais n’accepte pas sur cette interface ou le forwarder n’est pas lié correctement.

Task 8: Check container DNS configuration

cr0x@server:~$ docker exec -it web sh -lc "cat /etc/resolv.conf"
nameserver 192.168.65.5
search localdomain
options ndots:0

Sens : Docker Desktop injecte souvent une IP de stub resolver (exemple : 192.168.65.5) à l’intérieur du réseau de la VM.
Décision : Si ce serveur de noms est inaccessible ou dysfonctionnel (commun avec les VPN), surchargez le DNS au niveau du daemon/compose.

Task 9: Test DNS resolution inside the container (don’t guess)

cr0x@server:~$ docker exec -it web sh -lc "getent hosts example.com | head -n 2"
2606:2800:220:1:248:1893:25c8:1946 example.com
93.184.216.34 example.com

Sens : Le DNS fonctionne suffisamment pour résoudre AAAA et A.
Décision : Si cela bloque ou ne retourne rien, vous avez un problème de chemin DNS. Étape suivante : essayez de résoudre en utilisant un serveur spécifique (si vous avez les outils) ou surchargez les resolvers.

Task 10: Test direct IP connectivity to a LAN resource from inside the container

cr0x@server:~$ docker exec -it web sh -lc "nc -vz 192.168.1.10 445"
192.168.1.10 (192.168.1.10:445) open

Sens : Le routage container → VM → OS hôte → LAN fonctionne pour cette destination.
Décision : Si l’IP fonctionne mais le nom échoue, c’est du DNS. Si ni l’un ni l’autre ne fonctionnent, c’est du routage/VPN/politique.

Task 11: Check the container’s default route (basic but decisive)

cr0x@server:~$ docker exec -it web sh -lc "ip route"
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

Sens : Le conteneur route vers la gateway du bridge. La gateway décide ensuite comment atteindre votre LAN/internet.
Décision : Si la route par défaut manque ou est incorrecte, vous avez construit une configuration réseau personnalisée ; revenez en arrière et testez avec un réseau bridge standard.

Task 12: Check whether you’re colliding with a corporate/VPN subnet

cr0x@server:~$ ip route | head -n 12
default via 192.168.1.1 dev wlan0
10.0.0.0/8 via 10.8.0.1 dev tun0
172.16.0.0/12 via 10.8.0.1 dev tun0
192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.50

Sens : Si vos réseaux Docker utilisent 172.16.0.0/12 et que votre VPN route aussi 172.16.0.0/12, vous avez créé un routage ambigu.
Desktop est particulièrement sensible au chevauchement parce qu’il effectue déjà du NAT.
Décision : Changez les plages internes de Docker pour éviter le chevauchement avec les routes d’entreprise.

Task 13: Inspect Docker networks and their subnets

cr0x@server:~$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f1e2d3c4b5a6   host      host      local
123456789abc   none      null      local
cr0x@server:~$ docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}'
172.17.0.0/16

Sens : Vous savez maintenant quelles sous‑réseaux Docker consomme.
Décision : Si cela chevauche les routes VPN ou votre LAN, déplacez‑les.

Task 14: Validate that the container can reach the host OS via Docker Desktop’s special name

cr0x@server:~$ docker exec -it web sh -lc "getent hosts host.docker.internal"
192.168.65.2    host.docker.internal

Sens : Le mappage spécial existe et pointe vers l’endpoint côté hôte que Docker fournit.
Décision : Si ce nom ne se résout pas, vous êtes sur une configuration ancienne, un mode réseau personnalisé ou quelque chose a modifié le DNS dans le conteneur. N’utilisez une IP explicite qu’en dernier recours.

Schémas d’accès LAN : ce qui fonctionne, ce qui trompe

Il y a trois demandes courantes :

  • LAN → votre service conteneurisé (un collègue veut atteindre votre serveur de dev).
  • Conteneur → ressource LAN (le conteneur a besoin d’atteindre un NAS, une imprimante, une API interne, Kerberos, peu importe).
  • Conteneur → OS hôte (le conteneur appelle un service qui tourne sur votre portable).

Schéma A : LAN → conteneur via ports publiés (le seul comportement sensé par défaut)

Publiez les ports sur l’OS hôte, pas en essayant de donner les IP des conteneurs.
Avec Docker Desktop, vous ne pouvez pas traiter les IP des conteneurs comme routables sur le LAN physique. Elles vivent derrière un NAT, dans une VM, derrière un autre NAT si votre OS fait aussi quelque chose d’astucieux.

Ce qu’il faut faire :

  • Lier sur toutes les interfaces : -p 0.0.0.0:8080:80 ou en Compose "8080:80" et s’assurer que la publication ne se limite pas par défaut à localhost.
  • Ouvrir le pare‑feu de l’hôte pour ce port (et limiter la portée ; n’exposez pas votre base de données de dev au Wi‑Fi d’un café).
  • Si votre VPN interdit les connexions entrantes depuis le LAN pendant qu’il est connecté, acceptez la réalité : testez sans VPN ou utilisez un vrai environnement de dev ailleurs.

Schéma B : conteneur → ressources LAN (le routage marche jusqu’à ce qu’il cesse)

Les conteneurs atteignent généralement votre LAN hors de la boîte, parce que Docker Desktop NATe le trafic sortant via l’OS hôte.
Puis vous connectez un VPN, et l’OS hôte change DNS et routes. Soudain votre conteneur ne peut plus résoudre ou atteindre des sous‑réseaux maintenant « possédés » par le VPN.

Quand ça échoue, ça échoue de façons répétables :

  • Chevauchement de sous‑réseau : Docker choisit une plage privée que votre VPN route. Les paquets disparaissent dans le tunnel.
  • Mismatch de split DNS : l’hôte résout les noms internes via le DNS d’entreprise, mais les conteneurs sont coincés sur un stub resolver qui ne relaie pas correctement les domaines séparés.
  • Politique de pare‑feu : l’agent d’entreprise refuse le trafic depuis des interfaces virtuelles « inconnues ».

Schéma C : conteneur → services de l’OS hôte (utilisez les noms spéciaux)

Utilisez host.docker.internal. C’est pour ça que ça existe.
Ce n’est pas élégant, mais c’est stable face aux changements DHCP et moins fragile que de coder en dur une 192.168.x.y.

Si vous êtes sur Linux (pas Desktop) vous ne l’aurez peut‑être pas ; sur Desktop vous l’avez généralement.

Ports : publication, adresses de binding, et pourquoi les collègues ne peuvent pas atteindre votre serveur de dev

Les ports publiés sont la monnaie d’échange pour « rendre mon conteneur accessible ». Tout le reste est dette.

Localhost n’est pas une vertu morale, c’est une adresse de binding

Deux choses différentes sont constamment confondues :

  • Où l’app écoute à l’intérieur du conteneur (127.0.0.1 vs 0.0.0.0).
  • Où Docker lie le port publié sur l’hôte (127.0.0.1:PORT vs 0.0.0.0:PORT).

Si l’un ou l’autre est « localhost‑seulement », les clients LAN perdent. Et vous perdrez du temps à blâmer l’autre couche.

Astuce Compose : ne vous liez pas accidentellement à localhost

Compose supporte le binding explicite de l’IP hôte. C’est génial quand vous le voulez et terrible quand ce n’est pas le cas.

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"

Sens : Ce service est volontairement accessible uniquement depuis l’OS hôte.
Décision : Si vous voulez un accès LAN, changez‑le en "8080:80" ou "0.0.0.0:8080:80", puis gérez correctement la portée du pare‑feu.

Quand les ports publiés restent inatteignables depuis le LAN

Si Docker affiche 0.0.0.0:8080 mais que les clients LAN ne peuvent pas se connecter :

  • Pare‑feu hôte : pare‑feu d’application macOS, Windows Defender Firewall, outils d’endpoint tiers.
  • Sélection d’interface : le port peut être lié, mais l’OS peut bloquer l’entrée sur le Wi‑Fi tout en l’autorisant sur Ethernet (ou l’inverse).
  • Politique VPN : certains clients appliquent « bloquer le LAN local » pour réduire le risque de mouvement latéral.
  • Quirks NAT hairpin : certains réseaux ne vous laissent pas atteindre votre propre IP publique depuis l’intérieur ; ce n’est pas Docker, c’est votre routeur qui fait de son mieux.

Blague #2 : Rien n’améliore l’esprit d’équipe comme dire à quelqu’un « ça marche sur ma machine » en le prenant comme une déclaration d’architecture réseau.

Correctifs DNS : passer de « c’est instable » à « c’est déterministe »

Le DNS est l’endroit où les bizarreries de Docker Desktop deviennent de la légende. Le problème n’est généralement pas « Docker ne peut pas gérer le DNS ».
Le problème est : vous avez maintenant au moins deux résolveurs (OS hôte et VM), parfois trois (le VPN), et ils ne s’accordent pas sur les règles de split‑horizon.

Mode de défaillance 1 : le DNS du conteneur résout les noms publics mais pas les noms internes

Split DNS d’entreprise classique : git.corp ne se résout qu’avec des serveurs DNS internes, accessibles seulement via le VPN.
Votre OS hôte fait la bonne chose. Votre conteneur utilise un stub resolver qui ne relaie pas correctement les domaines internes.

Options de correction, du meilleur au pire :

  1. Configurer le DNS de Docker Desktop pour utiliser vos résolveurs internes quand vous êtes sur VPN, et des résolveurs publics hors VPN. Parfois c’est un basculement manuel parce que l’« auto » peut être peu fiable.
  2. DNS par projet dans Compose :
    • Définir dns: sur les IP des résolveurs qui peuvent répondre aux noms internes et externes (souvent ceux fournis par le VPN).
  3. Hardcoder /etc/hosts dans les conteneurs. C’est un bricolage tactique, pas une stratégie.

Task 15: Override DNS in Compose and verify inside container

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: alpine:3.20
    command: ["sleep","infinity"]
    dns:
      - 10.8.0.53
      - 1.1.1.1
cr0x@server:~$ docker compose up -d
[+] Running 1/1
 ✔ Container web-1  Started
cr0x@server:~$ docker exec -it web-1 sh -lc "cat /etc/resolv.conf"
nameserver 10.8.0.53
nameserver 1.1.1.1

Sens : Le conteneur utilise désormais les serveurs DNS que vous avez spécifiés.
Décision : Si les domaines internes se résolvent maintenant, vous avez prouvé que c’est un problème de chemin DNS / split DNS, pas un problème d’application.

Mode de défaillance 2 : le DNS fonctionne, mais seulement parfois (timeouts, builds lents, installations de paquets instables)

Les échecs DNS intermittents proviennent souvent de :

  • Des serveurs DNS VPN qui perdent des paquets UDP sous charge ou nécessitent TCP pour les grandes réponses.
  • Des agents de sécurité d’entreprise interceptant le DNS et provoquant parfois des timeouts.
  • Des problèmes MTU/MSS sur les liens tunnellisés (DNS sur UDP fragmenté puis qui meurt silencieusement).

Task 16: Detect DNS timeouts vs NXDOMAIN inside container

cr0x@server:~$ docker exec -it web-1 sh -lc "time getent hosts pypi.org >/dev/null; echo $?"
real    0m0.042s
user    0m0.000s
sys     0m0.003s
0

Sens : Succès rapide.
Décision : Si cela prend des secondes ou échoue de façon intermittente, préférez changer de résolveurs (ou forcer TCP via un résolveur différent) plutôt que de faire tourner indéfiniment vos scripts de build.

Mode de défaillance 3 : le service interne fonctionne par IP mais pas par nom (et seulement sur VPN)

C’est encore du split DNS, avec une couche en plus : parfois le VPN pousse un suffixe DNS et des domaines de recherche à l’OS hôte,
mais le resolver de Docker Desktop n’hérite pas proprement de ces paramètres.

Task 17: Confirm search domains inside container

cr0x@server:~$ docker exec -it web-1 sh -lc "cat /etc/resolv.conf"
nameserver 10.8.0.53
search corp.example
options ndots:0

Sens : Le domaine de recherche est présent.
Décision : S’il manque, les noms courts peuvent échouer tandis que les FQDN fonctionnent. Utilisez des FQDN ou configurez les domaines de recherche au niveau du conteneur.

VPN, split‑tunnels et l’« aide » des agents d’entreprise

Les VPN provoquent deux grandes classes de problèmes : changements de routage et changements DNS. Docker Desktop amplifie les deux parce qu’il est effectivement un réseau imbriqué.

Routage : quand le VPN vous vole l’espace RFC1918

Beaucoup de réseaux d’entreprise routent de larges plages privées comme 10.0.0.0/8 ou 172.16.0.0/12 via le tunnel.
Docker utilise souvent 172.17.0.0/16 pour le bridge et d’autres plages 172.x pour les réseaux utilisateur.

Sur un hôte Linux pur, vous pouvez généralement gérer cela avec des subnets bridge personnalisés et iptables. Sur Desktop, vous pouvez toujours le faire, mais il faut en faire une configuration de première classe.

Task 18: Create a user-defined network on a “safe” subnet

cr0x@server:~$ docker network create --subnet 192.168.240.0/24 devnet
9f8c7b6a5d4e3c2b1a0f
cr0x@server:~$ docker run -d --name web2 --network devnet -p 8081:80 nginx:alpine
b1c2d3e4f5a6
cr0x@server:~$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web2
192.168.240.2

Sens : Vous avez déplacé le réseau du conteneur hors des plages communes aux routes d’entreprise.
Décision : Si la reachabilité liée au VPN s’améliore, institutionnalisez une politique de sous‑réseau pour les réseaux de dev.

Endpoint security : le middlebox invisible

Certains outils d’endpoint traitent les NIC de virtualisation comme « non fiables ». Ils peuvent bloquer l’entrée ou la sortie, ou forcer le trafic via un proxy.
Les symptômes incluent : les ports publiés ne fonctionnent que lorsque l’agent de sécurité est mis en pause, le DNS devient lent, ou les services internes échouent TLS à cause de l’inspection.

Vous ne pouvez pas « SRE » votre chemin hors d’une politique. Ce que vous pouvez faire, c’est obtenir rapidement des preuves, puis escalader avec des éléments concrets.

Task 19: Prove it’s local firewall/policy with a quick inbound test

cr0x@server:~$ python3 -m http.server 18080 --bind 0.0.0.0
Serving HTTP on 0.0.0.0 port 18080 (http://0.0.0.0:18080/) ...

Sens : Ce n’est pas Docker. C’est un processus hôte simple.
Décision : Si un pair LAN ne peut pas atteindre ceci non plus, arrêtez de déboguer Docker et corrigez les réglages pare‑feu/VPN « bloquer le réseau local ».

Windows + WSL2 spécificités (où les paquets vont se reposer)

Sur Windows moderne, Docker Desktop exécute souvent son moteur dans WSL2. WSL2 a son propre réseau virtuel (NAT derrière Windows).
Cela signifie que vous pouvez avoir : NAT du conteneur derrière Linux, derrière le NAT de WSL2, derrière les règles de pare‑feu Windows. C’est du NAT jusqu’au fond.

Symptômes typiques Windows

  • Port publié joignable depuis le localhost Windows mais pas depuis le LAN. Habituellement règles d’entrée du Windows Defender Firewall, ou binding en loopback seulement.
  • Les conteneurs ne peuvent pas atteindre un sous‑réseau LAN que Windows peut atteindre. Habituellement les routes VPN ne sont pas propagées comme vous le pensez dans WSL2, ou la politique bloque les interfaces WSL.
  • Le DNS diffère entre Windows et WSL2. WSL2 écrit son propre /etc/resolv.conf ; parfois il pointe vers un résolveur côté Windows qui ne voit pas le DNS du VPN.

Task 20: Check WSL2’s resolv.conf and route table (from inside WSL)

cr0x@server:~$ cat /etc/resolv.conf
nameserver 172.29.96.1
cr0x@server:~$ ip route | head
default via 172.29.96.1 dev eth0
172.29.96.0/20 dev eth0 proto kernel scope link src 172.29.96.100

Sens : WSL2 utilise une gateway/resolver virtuelle côté Windows.
Décision : Si le DNS casse uniquement sur VPN, envisagez de configurer le comportement DNS de WSL2 (resolv.conf statique) et d’aligner le DNS de Docker avec les résolveurs fournis par le VPN.

macOS spécificités (pf, vmnet, et l’illusion de localhost)

Sur macOS, Docker Desktop exécute une VM Linux et redirige les ports vers macOS.
Vos conteneurs ne sont pas des citoyens de première classe sur votre LAN physique. Ce sont des invités derrière un concierge très poli.

Ce qui embrouille les utilisateurs macOS

  • « Ça marche sur localhost mais pas depuis mon téléphone. » Habituellement le pare‑feu macOS ou un port publié uniquement sur la loopback.
  • Le DNS change quand le Wi‑Fi change de réseau. Le résolveur hôte change rapidement ; la VM met parfois du temps ou met en cache des bizarreries.
  • Le VPN d’entreprise bloque l’accès au sous‑réseau local. Votre téléphone ne peut pas atteindre votre portable lorsque le VPN est connecté, indépendamment de Docker.

Task 21: Confirm the host OS has the right IP and interface for LAN testing

cr0x@server:~$ ip addr show | sed -n '1,25p'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 192.168.1.50/24 brd 192.168.1.255 scope global dynamic wlan0

Sens : Votre IP LAN est 192.168.1.50.
Décision : C’est l’adresse qu’un pair LAN doit utiliser pour atteindre votre port publié. Si les pairs utilisent une IP ancienne, ils testent la mauvaise machine.

Erreurs courantes : symptôme → cause racine → correction

1) Symptom: localhost:8080 works, coworker can’t reach 192.168.x.y:8080

  • Cause racine : Port publié seulement sur 127.0.0.1, ou pare‑feu hôte bloquant l’entrée.
  • Correction : Publiez sur toutes les interfaces (-p 0.0.0.0:8080:80), puis autorisez l’entrée pour ce port sur le pare‑feu de l’hôte pour le profil réseau approprié.

2) Symptom: container can reach internet but not 192.168.1.10 (LAN NAS)

  • Cause racine : Politique VPN « block local LAN » ou routes poussant les sous‑réseaux LAN dans le tunnel.
  • Correction : Testez sans VPN ; si cela règle le problème, demandez des exceptions split‑tunnel ou exécutez la charge de travail dans un vrai environnement (VM distante, staging). Ne combattez pas la politique avec des bricolages.

3) Symptom: container can reach LAN IPs but internal hostnames fail

  • Cause racine : Split DNS non propagé dans Docker Desktop ; les conteneurs utilisent un stub resolver qui ne voit pas les zones internes.
  • Correction : Configurez le DNS au niveau du projet (dns: dans Compose) pour inclure les serveurs DNS d’entreprise accessibles via VPN ; vérifiez avec getent hosts.

4) Symptom: DNS flaps during builds (apt/npm/pip failing randomly)

  • Cause racine : DNS UDP peu fiable via VPN, problèmes MTU, interception par l’endpoint.
  • Correction : Préférez des résolveurs stables ; utilisez deux résolveurs (interne + public) lorsque la politique le permet ; réduisez le risque de fragmentation en traitant le MTU côté VPN si vous le contrôlez.

5) Symptom: service is published, but you get “connection refused” from LAN

  • Cause racine : L’app écoute seulement sur le localhost du conteneur, ou mauvais port conteneur publié.
  • Correction : Vérifiez ss -lntp à l’intérieur du conteneur ; corrigez l’adresse de binding ; vérifiez docker port et le mapping de ports du conteneur.

6) Symptom: can’t connect to host.docker.internal from container

  • Cause racine : Override DNS a supprimé le nom spécial, ou vous utilisez un mode réseau où Desktop ne l’injecte pas.
  • Correction : Évitez d’écraser le DNS aveuglément ; si vous devez le faire, assurez‑vous que le nom spécial se résout toujours (ou ajoutez une entrée hôte explicite via extra_hosts en dernier recours).

7) Symptom: everything breaks only on one Wi‑Fi network

  • Cause racine : Ce réseau isole les clients (AP isolation) ou bloque les connexions entrantes entre appareils.
  • Correction : Utilisez un réseau approprié (ou filaire), ou exécutez le service derrière un tunnel inverse ; ne supposez pas que « même Wi‑Fi » signifie « mutuellement joignable ».

Trois mini‑histoires d’entreprise (réalistes, anonymisées, douloureuses)

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

Une équipe produit a construit un environnement de démonstration sur des portables pour un atelier client sur site. Le plan était simple : exécuter quelques services dans Docker Desktop, publier des ports,
et permettre aux participants de se connecter via le Wi‑Fi de l’hôtel. Tout le monde avait fait « -p 8080:8080 » mille fois. L’hypothèse erronée était que Docker Desktop se comporte comme un hôte Linux sur un LAN plat.

Le matin de l’atelier, la moitié des participants ne pouvait pas se connecter. Les services étaient pourtant démarrés. Le curl local fonctionnait. Les présentateurs se connectaient parfois entre eux.
Les gens ont commencé à redémarrer comme en 1998. Le problème réseau n’était pas Docker ; c’était le Wi‑Fi de l’hôtel qui faisait de l’isolation client—les appareils atteignaient Internet mais pas entre eux.

La deuxième mauvaise hypothèse est arrivée immédiatement : « Utilisons les IP des conteneurs et évitons le mapping de ports. »
Ils ont essayé de distribuer des adresses 172.17.x.x visibles à l’intérieur de la VM Docker, qui bien sûr n’étaient pas joignables depuis d’autres portables.
Cela a mené à dix minutes de certitudes confuses et à un diagramme sur tableau blanc fortement regretté.

La correction fut ennuyeuse : créer un hotspot local sur un téléphone permettant le trafic pair‑à‑pair,
et publier explicitement les ports nécessaires sur 0.0.0.0 avec une règle de pare‑feu rapide.
Les services fonctionnaient. L’hypothèse sur le « même réseau » était la vraie panne.

Mini‑histoire 2 : L’optimisation qui a échoué

Une équipe plateforme voulait des builds CI plus rapides sur les machines dev. Ils ont remarqué de nombreuses requêtes DNS pendant les builds et ont décidé « d’optimiser » en forçant les conteneurs Docker à utiliser un résolveur DNS public.
Ça marchait bien en test café : résolutions plus rapides, moins de timeouts, jolis graphiques.

Puis le premier ingénieur a essayé de builder en étant sur VPN. Les registres de paquets internes n’étaient accessibles qu’via le DNS d’entreprise et des routes internes.
Soudain, les builds échouaient avec « host not found » alors que l’OS hôte résolvait correctement. Le contournement est devenu « déconnecte le VPN »,
ce qui est une excellente façon de créer le prochain incident.

La situation s’est empirée parce que certains noms internes se résolvaient publiquement en IPs factices (pour des raisons de sécurité), donc « le DNS avait réussi » mais les connexions allaient dans un trou noir.
Le débogage fut brutal : on voyait des enregistrements A, l’application timeoutait, et tout le monde blâmait TLS, les proxys et Docker dans un ordre aléatoire.

La correction finale fut d’arrêter d’optimiser le DNS globalement. Ils sont passés à des paramètres DNS par projet :
résolveurs internes en priorité quand le VPN est actif, résolveurs publics seulement hors VPN.
Ils ont aussi documenté comment tester la résolution à l’intérieur des conteneurs, parce que « ça se résout sur mon hôte » n’est pas une donnée fiable dans un réseau imbriqué.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Un service sensible à la sécurité utilisait Docker Desktop pour des tests d’intégration locaux. Il devait appeler une API interne et aussi accepter des webhooks entrants d’un outil de test sur une autre machine du même bureau.
L’équipe avait une habitude que je respecte : avant de changer quoi que ce soit, ils capturaient une preuve « connue‑bonne » du réseau—routes, config DNS, bindings de ports—quand tout fonctionnait.

Un lundi, tout a cassé après une mise à jour OS. Les conteneurs ne pouvaient plus résoudre les noms internes. Les webhooks d’une machine LAN n’arrivaient plus.
Au lieu de deviner, ils ont comparé l’état actuel au baseline : les ports publiés étaient maintenant liés uniquement à localhost, et le stub DNS à l’intérieur des conteneurs pointait vers une nouvelle IP côté VM qui ne relaie pas le split DNS.

Ils ont corrigé le binding des ports dans Compose, puis figé le DNS des conteneurs vers les résolveurs internes quand le VPN est actif.
Parce qu’ils avaient le baseline, ils ont pu montrer à l’équipe sécurité endpoint exactement ce qui avait changé et pourquoi.
L’incident n’est pas devenu une semaine de blâme généralisé.

Cette pratique—capturer un état de référence, comparer lors d’une panne—est aussi excitante que regarder la peinture sécher.
Elle marche pourtant.

Listes de contrôle / plan étape par étape (soporifique volontairement)

Checklist 1: Exposer un service Docker Desktop à votre LAN de façon fiable

  1. Assurez‑vous que l’app écoute sur 0.0.0.0 à l’intérieur du conteneur (ss -lntp).
  2. Publiez le port sur toutes les interfaces : -p 0.0.0.0:8080:80 (ou Compose "8080:80").
  3. Confirmez que Docker voit le mapping : docker port CONTAINER.
  4. Confirmez que l’OS hôte écoute sur ce port : ss -lntp | grep :8080.
  5. Testez localement : curl http://127.0.0.1:8080.
  6. Testez depuis un pair LAN : nc -vz HOST_LAN_IP 8080.
  7. Si le test LAN échoue, lancez un écouteur non‑Docker (python3 -m http.server) pour isoler le pare‑feu/VPN des problèmes Docker.

Checklist 2: Faire en sorte que les conteneurs atteignent les ressources LAN internes (NAS, APIs internes)

  1. Depuis l’OS hôte, vérifiez que la cible est joignable par IP.
  2. Depuis l’intérieur du conteneur, testez la connectivité IP (nc -vz ou curl).
  3. Si l’IP échoue seulement sur VPN, vérifiez le chevauchement de routes (ip route) et les politiques VPN (« block local LAN »).
  4. Si l’IP fonctionne mais le nom échoue, vérifiez /etc/resolv.conf et résolvez avec getent hosts.
  5. Surchargez le DNS par projet via Compose dns: si nécessaire.
  6. Évitez le chevauchement de sous‑réseaux : déplacez les réseaux Docker vers une plage que votre VPN ne route pas.

Checklist 3: Stabiliser le DNS pour les builds de dev (pip/npm/apt qui flanchent)

  1. Mesurez le temps de résolution dans le conteneur avec time getent hosts.
  2. Inspectez les résolveurs actuels dans /etc/resolv.conf.
  3. Si vous êtes sur VPN, préférez les résolveurs internes fournis par le VPN (et ajoutez une fallback publique seulement si autorisé).
  4. Ne hardcodez pas un DNS public globalement pour tous les projets ; vous casserez les workflows split DNS.
  5. Retestez dans le conteneur après les changements ; ne faites pas confiance aux résultats de l’OS hôte.

FAQ

1) Pourquoi ne puis‑je pas simplement utiliser l’IP du conteneur depuis une autre machine sur mon LAN ?

Parce que sur Docker Desktop cette IP est sur un bridge interne dans une VM Linux (ou un environnement WSL2). Votre LAN ne la route pas. Publiez des ports à la place.

2) Pourquoi -p 8080:80 marche localement mais pas depuis mon téléphone ?

Habituellement soit le port est lié uniquement à localhost (explicitement ou via Compose), soit votre pare‑feu/VPN hôte bloque les connexions entrantes depuis le LAN.

3) Quelle est la différence entre 127.0.0.1 et 0.0.0.0 dans ce contexte ?

127.0.0.1 signifie « n’accepter les connexions que depuis cette même pile réseau ». 0.0.0.0 signifie « écouter sur toutes les interfaces ».
Vous avez besoin de 0.0.0.0 si vous attendez des connexions depuis d’autres appareils.

4) Est‑ce que --network host est la solution pour le réseau Docker Desktop ?

Non. Sur Docker Desktop, « host network » n’est pas identique au networking Linux et ne vous donnera souvent pas ce que vous attendez. Préférez bridge + ports publiés.

5) Pourquoi le DNS fonctionne sur mon hôte mais pas dans les conteneurs ?

Le conteneur peut utiliser un chemin de résolveur différent (un stub dans la VM), et il peut ne pas hériter de la configuration split DNS de votre VPN.
Vérifiez avec cat /etc/resolv.conf et getent hosts dans le conteneur, puis surchargez le DNS par projet si nécessaire.

6) Dois‑je définir le DNS de Docker Desktop sur un résolveur public pour « tout réparer » ?

Seulement si vous n’avez jamais besoin du DNS interne. Les résolveurs publics peuvent casser les domaines d’entreprise, les registres internes et les configurations split‑horizon.
Utilisez un DNS spécifique au projet ou un comportement conditionnel lié à l’état du VPN.

7) Mon conteneur ne peut pas atteindre un appareil LAN seulement quand le VPN est connecté. Est‑ce la faute de Docker ?

Presque jamais. Les clients VPN peuvent router des sous‑réseaux privés via le tunnel ou bloquer l’accès LAN local.
Prouvez‑le en testant la même connexion depuis l’OS hôte et en déconnectant le VPN comme témoin.

8) Quelle est la manière la plus fiable pour un conteneur d’appeler un service sur mon portable ?

Utilisez host.docker.internal et gardez‑le cohérent entre les environnements. Évitez les IP hôtes codées en dur qui changent avec les réseaux Wi‑Fi.

9) Comment savoir si le problème vient du pare‑feu ou du mapping de ports Docker ?

Lancez un écouteur non‑Docker sur l’hôte (comme python3 -m http.server). Si le LAN ne peut pas atteindre cela, Docker n’est pas le problème.

10) Quel principe simple pour garder la tête froide avec le réseau Desktop ?

Traitez Docker Desktop comme « des conteneurs derrière une VM derrière votre OS ». Publiez des ports, évitez le chevauchement de sous‑réseaux, et validez le DNS depuis l’intérieur du conteneur.

Conclusion : prochaines étapes que vous pouvez faire aujourd’hui

Le réseau Docker Desktop cesse d’être étrange quand vous arrêtez de vous attendre à ce qu’il soit le networking d’un hôte Linux. C’est une frontière VM avec une couche de forwarding.
Une fois que vous l’acceptez, la plupart des problèmes se résument à trois catégories : adresses de binding, politique pare‑feu/VPN, et dérive DNS/resolvers.

Prochaines étapes pratiques :

  1. Choisissez un service de test, publiez‑le sur 0.0.0.0, et vérifiez la reachabilité LAN de bout en bout avec ss, curl et nc.
  2. Capturez un état de référence quand tout fonctionne : docker port, /etc/resolv.conf du conteneur, et la table de routage de l’hôte.
  3. Si vous utilisez un VPN, évitez que les réseaux Docker chevauchent les routes d’entreprise. Standardisez une plage « sûre » pour les réseaux de dev.
  4. Faites du DNS une configuration par projet quand des noms internes sont importants. Les « correctifs » globaux sont la meilleure façon de créer des ruptures inter‑équipes.

Une idée paraphrasée de Werner Vogels (CTO d’Amazon) : « Tout échoue ; concevez vos systèmes — et vos opérations — pour absorber cet échec. »
Le réseau Docker Desktop n’est pas spécial. C’est juste l’échec avec des couches en plus.

Ubuntu 24.04 : « Failed to get D-Bus connection » — réparer les sessions et services cassés (cas n°48)

Vous lancez systemctl et il répond : « Failed to get D-Bus connection ». Soudain votre « simple redémarrage » ressemble à une scène de crime : les services ne communiquent plus, les connexions semblent hantées, et toutes les automatisations qui attendent une session propre commencent à échouer.

Cette erreur n’est que rarement « juste D-Bus ». Il s’agit généralement d’un contrat brisé entre systemd, votre login/session et les sockets de bus sous /run. La réparation est ennuyeuse — mais seulement après que vous ayez arrêté de deviner et commencé à prouver.

Ce que l’erreur signifie réellement (et ce qu’elle ne signifie pas)

Quand un outil indique « Failed to get D-Bus connection », il se plaint de ne pas pouvoir atteindre un socket de bus de messages qu’il s’attend à trouver. Sur Ubuntu 24.04, l’appelant habituel est systemctl, loginctl, des composants GNOME, des invites policykit, des aides snapd, ou tout processus qui attend soit :

  • Le bus système à /run/dbus/system_bus_socket (utilisé pour les services système), ou
  • Le bus de session utilisateur (par utilisateur) typiquement à /run/user/UID/bus, géré par systemd --user et dbus-daemon ou dbus-broker selon la configuration.

La formulation est trompeuse parce que la cause racine n’est souvent pas « D-Bus en panne ». Le bus peut très bien fonctionner ; votre environnement peut être incorrect, votre répertoire runtime peut ne pas exister, vous pouvez être dans un conteneur/namespace, ou vous pouvez utiliser sudo d’une manière qui supprime les variables du bus.

Deux règles pour rester sain d’esprit :

  1. Décidez si vous avez besoin du bus système ou du bus utilisateur. Si vous gérez des services avec systemctl (scope système), vous vous préoccupez du PID 1, de dbus et du socket système. Si vous lancez des actions de bureau/session, vous vous préoccupez de systemd --user, de XDG_RUNTIME_DIR et du socket par utilisateur.
  2. Testez toujours le socket, pas vos impressions. La plupart des pannes « connexion D-Bus » sont en réalité des chemins /run manquants, des sessions utilisateur mortes, ou un gestionnaire de connexion cassé.

Une idée paraphrasée de Gene Kim (auteur DevOps/fiabilité) : Les améliorations viennent en réduisant le travail en cours et en rendant les problèmes visibles tôt. Cela s’applique ici : rendez l’échec visible en vérifiant d’abord les chemins de bus et l’état des sessions, au lieu de redémarrer des démons au hasard.

Mode d’urgence : diagnostic rapide

Quand cela se produit en production à 02:00, vous ne voulez pas de théorie. Vous voulez une boucle de triage qui converge.

Étape 1 : Identifiez quel bus échoue

  • Si l’erreur apparaît en lançant systemctl status foo en root, il s’agit probablement du bus système ou de la connectivité vers PID 1.
  • Si l’erreur apparaît dans une application de bureau, les paramètres GNOME ou systemctl --user, c’est le bus de session utilisateur (/run/user/UID/bus).
  • Si cela n’arrive que via SSH ou dans de l’automatisation, suspectez les variables d’environnement et les shells non-login.

Étape 2 : Vérifiez les sockets et répertoires runtime (signal le plus rapide)

  • /run/dbus/system_bus_socket existe et est un socket ?
  • /run/user/UID existe et est possédé par l’utilisateur ?
  • /run/user/UID/bus existe et est un socket ?

Étape 3 : Validez le gestionnaire de session et l’état de systemd

  • systemctl is-system-running vous dit si PID 1 est sain.
  • systemctl status dbus vous indique si le service dbus existe/démarré.
  • loginctl list-sessions vous dit si logind voit votre session (critique pour la création de /run/user/UID).

Étape 4 : Réparez la bonne couche, pas la plus bruyante

  • Manque /run/user/UID ? Réparez le cycle de vie logind/session.
  • Le socket existe mais accès refusé ? Corrigez les permissions, les politiques SELinux/AppArmor ou le contexte utilisateur.
  • Fonctionne en local mais pas avec sudo ? Corrigez la préservation d’environnement, ne « redémarrez pas dbus » par dépit.

Faits et contexte intéressants (vous déboguerez plus vite)

  • D-Bus a été conçu au début des années 2000 pour remplacer des mécanismes IPC ad hoc sur les bureaux Linux ; il est devenu, par la suite, un pilier pour les services système aussi.
  • systemd n’a pas créé D-Bus, mais systemd a rendu les dépendances D-Bus plus explicites avec l’ordre des unités, l’activation par socket et les services utilisateur.
  • Les répertoires runtime utilisateur sous /run/user/UID sont généralement créés par systemd-logind lors du démarrage d’une session — et supprimés à la fin de la dernière session.
  • Ubuntu a livré à la fois dbus-daemon et des alternatives (comme dbus-broker dans certains environnements) ; ce qui compte est le contrat du socket, pas la marque de l’implémentation.
  • XDG_RUNTIME_DIR fait partie de la spec XDG Base Directory ; il doit être spécifique à l’utilisateur, sécurisé et éphémère — exactement le contraire d’un répertoire aléatoire sous /tmp.
  • systemctl communique avec systemd via D-Bus ; si systemctl ne peut pas atteindre un bus, il ne peut rien demander à systemd, même si systemd est techniquement vivant.
  • Les sessions SSH ne sont pas toujours « sessions logind » selon la configuration PAM ; lorsqu’elles ne le sont pas, vous pouvez perdre la création automatique du répertoire runtime et l’accès au bus utilisateur.
  • Les conteneurs n’ont souvent pas de bus système complet parce que PID 1 n’est pas systemd, ou parce que /run est isolé. Cette erreur y est normale sauf si vous la raccordez volontairement.
  • PolicyKit (polkit) dépend de D-Bus pour les requêtes d’autorisation ; un accès bus cassé peut se traduire par « les invites d’authentification n’apparaissent jamais » ou « permission denied » sans UI.

Blague #1 : D-Bus, c’est comme le courrier interne du bureau — quand il tombe en panne, tout le monde découvre soudain combien de choses en dépendent sans qu’on le sache.

Guide de terrain : isoler quel « bus » est inaccessible

Il existe quelques formes d’échec courantes :

  • Root sur un serveur : systemctl échoue. Habituellement le socket du bus système est manquant, l’unité dbus est failed, ou PID 1 est dans un état dégradé/à moitié mort.
  • Session utilisateur de bureau : les paramètres GNOME échouent, gsettings casse, systemctl --user échoue. Habituellement XDG_RUNTIME_DIR n’est pas défini, /run/user/UID est manquant, ou systemd --user ne fonctionne pas.
  • Automatisation via sudo : fonctionne en tant qu’utilisateur, échoue en root, ou l’inverse. Habituellement les variables d’environnement et le contexte de session sont incorrects.
  • Dans des conteneurs/CI : systemctl et busctl échouent par conception parce qu’il n’y a pas de systemd PID 1.

Voici l’idée clé : le bus est un fichier socket Unix. Si le socket n’est pas là, vous n’allez pas « réessayer plus fort ». S’il est là mais que votre processus ne peut pas y accéder, vous avez des problèmes de permissions, de namespaces ou d’identité. S’il est là et accessible mais que les réponses échouent, alors vous avez un problème de démon.

Tâches pratiques : commandes, sorties attendues et décisions

Voici les tâches que j’exécute réellement. Chacune inclut ce que la sortie signifie et quelle décision prendre ensuite. Lancez-les dans l’ordre jusqu’à ce que le mode de défaillance devienne évident. Vous ne collectez pas des logs pour le plaisir ; vous réduisez l’espace de recherche.

Task 1: Confirm the exact failing command and context

cr0x@server:~$ whoami
cr0x
cr0x@server:~$ systemctl status ssh
Failed to get D-Bus connection: No such file or directory

Signification : Le client ne peut pas atteindre son socket de bus. « No such file or directory » suggère un chemin de socket manquant, pas un problème de permission.

Décision : Déterminez s’il s’agit d’un échec du bus système (scope système) ou du bus utilisateur (scope utilisateur). Ensuite : vérifiez si vous êtes root et quel systemctl vous avez lancé.

Task 2: Check whether PID 1 is systemd (containers and chroots)

cr0x@server:~$ ps -p 1 -o pid,comm,args
  PID COMMAND         COMMAND
    1 systemd         /sbin/init

Signification : PID 1 est systemd ; systemctl devrait fonctionner si le chemin du bus système est présent.

Décision : Si PID 1 n’est pas systemd (commun dans les conteneurs), la « correction » consiste à éviter systemctl ou à exécuter un init adéquat. Si c’est systemd, continuez.

Task 3: Verify the system bus socket exists

cr0x@server:~$ ls -l /run/dbus/system_bus_socket
srwxrwxrwx 1 root root 0 Dec 30 10:12 /run/dbus/system_bus_socket

Signification : Le fichier socket du bus système existe et est bien un socket (le préfixe s dans les permissions). Le fait qu’il soit accessible en écriture pour tous est normal pour l’endpoint ; le contrôle d’accès reste géré par la politique D-Bus.

Décision : Si manquant : concentrez-vous sur le service dbus et les problèmes d’amorçage précoces. S’il est présent : testez si dbus répond.

Task 4: Check dbus service health (system bus)

cr0x@server:~$ systemctl status dbus --no-pager
● dbus.service - D-Bus System Message Bus
     Loaded: loaded (/usr/lib/systemd/system/dbus.service; static)
     Active: active (running) since Mon 2025-12-30 10:12:01 UTC; 2min ago
TriggeredBy: ● dbus.socket
       Docs: man:dbus-daemon(1)
   Main PID: 842 (dbus-daemon)
      Tasks: 1 (limit: 18939)
     Memory: 3.8M
        CPU: 52ms

Signification : Le bus système est en cours d’exécution ; le problème peut être la capacité de systemctl à se connecter à systemd (pas dbus), ou un problème de namespace/permission.

Décision : Si dbus est inactive/failed, redémarrez-le et lisez les logs. S’il est actif, vérifiez systemd lui-même et le socket privé de systemd.

Task 5: Confirm systemd is responsive

cr0x@server:~$ systemctl is-system-running
running

Signification : PID 1 rapporte un état sain. Si vous voyez encore « Failed to get D-Bus connection », vous exécutez peut-être systemctl dans un environnement qui ne voit pas /run ou qui manque le namespace de montage correct.

Décision : Si la sortie est degraded ou maintenance, allez directement dans le journal pour les pannes systémiques. Si c’est running mais que les clients échouent, suspectez un namespace, un chroot ou des problèmes de système de fichiers sous /run.

Task 6: Inspect /run mount and free space (yes, really)

cr0x@server:~$ findmnt /run
TARGET SOURCE FSTYPE OPTIONS
/run   tmpfs  tmpfs  rw,nosuid,nodev,relatime,size=394680k,mode=755,inode64
cr0x@server:~$ df -h /run
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           386M  2.1M  384M   1% /run

Signification : /run est un tmpfs ; il doit être inscriptible et disposer d’espace/inodes. Si /run est en lecture seule ou plein, les sockets ne pourront pas être créés et vous obtiendrez des erreurs de bus manquant.

Décision : Si plein/ro : corrigez cela d’abord (souvent un processus hors contrôle ou une mauvaise taille de tmpfs). Si sain : continuez aux vérifications de session utilisateur si l’erreur est en scope utilisateur.

Task 7: Determine if you’re dealing with the user bus

cr0x@server:~$ echo "$XDG_RUNTIME_DIR"
/run/user/1000
cr0x@server:~$ echo "$DBUS_SESSION_BUS_ADDRESS"
unix:path=/run/user/1000/bus

Signification : Les variables d’environnement pointent vers le bus par utilisateur. Si l’une est vide, votre session est incomplète (courant avec sudo, cron ou PAM cassé).

Décision : Si non définies : vous devez établir un contexte de session approprié ou configurer explicitement un bus utilisateur (privilégiez la première option). Si définies : vérifiez que le socket existe.

Task 8: Validate the user bus socket exists and has sane ownership

cr0x@server:~$ id -u
1000
cr0x@server:~$ ls -ld /run/user/1000
drwx------ 12 cr0x cr0x 320 Dec 30 10:12 /run/user/1000
cr0x@server:~$ ls -l /run/user/1000/bus
srw-rw-rw- 1 cr0x cr0x 0 Dec 30 10:12 /run/user/1000/bus

Signification : Le répertoire runtime existe, est privé (0700) et le socket du bus existe. Bien. Si /run/user/1000 est manquant, votre session n’a pas été enregistrée correctement auprès de logind.

Décision : Si manquant : passez à la résolution via loginctl et PAM/logind. S’il est présent mais mal possédé : corrigez la propriété et enquêtez pourquoi elle a dérivé (souvent un script malveillant exécuté en root).

Task 9: Prove the user systemd instance is alive

cr0x@server:~$ systemctl --user status --no-pager
● cr0x@server
    State: running
    Units: 221 loaded (incl. snap units)
     Jobs: 0 queued
   Failed: 0 units
    Since: Mon 2025-12-30 10:12:05 UTC; 2min ago
  

Signification : Votre gestionnaire utilisateur fonctionne et est joignable. Si vous obtenez « Failed to connect to bus », le chemin du bus utilisateur ou l’environnement est cassé.

Décision : Si cela échoue mais que le socket existe, votre environnement peut mentir (mauvais XDG_RUNTIME_DIR) ou vous êtes dans un namespace différent (commun avec sudo et certains outils distants).

Task 10: Use loginctl to verify logind sees your session

cr0x@server:~$ loginctl list-sessions
SESSION  UID USER SEAT  TTY
     21 1000 cr0x seat0 tty2

1 sessions listed.
cr0x@server:~$ loginctl show-user cr0x -p RuntimePath -p State -p Linger
RuntimePath=/run/user/1000
State=active
Linger=no

Signification : logind a une session active pour l’utilisateur et connaît le chemin runtime. S’il n’y a pas de sessions, votre répertoire runtime utilisateur peut ne pas être créé.

Décision : Si la session est absente via SSH : vérifiez la configuration PAM et si votre chemin de connexion utilise systemd/logind. Si vous avez besoin de services utilisateur en arrière-plan, envisagez le lingering (prudemment).

Task 11: Diagnose “sudo broke my bus” (classic)

cr0x@server:~$ sudo -i
root@server:~# echo "$DBUS_SESSION_BUS_ADDRESS"

root@server:~# systemctl --user status
Failed to connect to bus: No medium found

Signification : Le shell root n’a pas de contexte de bus utilisateur ; systemctl --user sous root n’est pas votre session utilisateur. Cette erreur est attendue.

Décision : Ne « corrigez » pas cela en exportant des variables aléatoires dans root. Utilisez systemctl (scope système) en root, et systemctl --user en tant qu’utilisateur dans la session. Si vous devez gérer une unité utilisateur depuis root, utilisez machinectl shell ou runuser avec l’environnement approprié, ou ciblez le gestionnaire utilisateur via loginctl enable-linger et exécutez systemctl --user sous cet utilisateur.

Task 12: Check journal for the first failure, not the last complaint

cr0x@server:~$ journalctl -b -u systemd-logind --no-pager | tail -n 20
Dec 30 10:11:58 server systemd-logind[701]: New session 21 of user cr0x.
Dec 30 10:11:58 server systemd-logind[701]: Watching system buttons on /dev/input/event3 (Power Button)
Dec 30 10:12:01 server systemd-logind[701]: Removed session 19.

Signification : logind crée des sessions. Si vous voyez à la place des échecs répétés pour créer des répertoires runtime, c’est votre indice principal.

Décision : Si logind affiche des erreurs concernant le répertoire runtime ou les cgroups, corrigez ces couches. Redémarrer dbus ne réparera pas « impossibilité de créer /run/user/UID ».

Task 13: Confirm dbus packages and user-session support are installed

cr0x@server:~$ dpkg -l | egrep 'dbus|dbus-user-session|libpam-systemd' | awk '{print $1,$2,$3}'
ii dbus 1.14.10-4ubuntu4.1
ii dbus-user-session 1.14.10-4ubuntu4.1
ii libpam-systemd 255.4-1ubuntu8

Signification : Les composants requis existent. L’absence de dbus-user-session peut entraîner un comportement de bus de session manquant sur certaines installations minimales.

Décision : Si manquant : installez les paquets manquants et reconnectez-vous. S’ils sont présents : poursuivez vers PAM/logind et les problèmes d’environnement.

Task 14: Check PAM session hooks for systemd/logind (SSH-focused)

cr0x@server:~$ grep -R "pam_systemd.so" -n /etc/pam.d/sshd /etc/pam.d/login
/etc/pam.d/sshd:15:session    required     pam_systemd.so
/etc/pam.d/login:14:session    required     pam_systemd.so

Signification : PAM est configuré pour enregistrer les sessions auprès de systemd/logind pour SSH et les connexions console. Si absent, vous pouvez vous retrouver sans répertoire runtime et sans bus utilisateur.

Décision : Si absent pour le chemin de connexion utilisé : ajoutez-le (prudemment, avec contrôle de changement) et testez avec une nouvelle session. Si présent : concentrez-vous sur pourquoi logind ne crée toujours pas les répertoires runtime (souvent lié au lingering, aux cgroups ou à un état systemd cassé).

Task 15: Check if the user runtime dir is being removed unexpectedly

cr0x@server:~$ sudo ls -l /run/user
total 0
drwx------ 12 cr0x cr0x 320 Dec 30 10:12 1000
drwx------ 10 gdm  gdm  280 Dec 30 10:11 120

Signification : Les répertoires runtime existent pour les utilisateurs actifs. Si le vôtre disparaît à la déconnexion SSH, vous n’avez probablement pas de lingering et vous n’avez pas de session active.

Décision : Pour des services utilisateur en arrière-plan : envisagez loginctl enable-linger username. Pour le travail interactif : assurez-vous d’avoir une vraie session et évitez d’exécuter des commandes dépendantes de la session depuis des contextes non-session.

Task 16: Enable lingering (only if you truly need user services without a login)

cr0x@server:~$ sudo loginctl enable-linger cr0x
cr0x@server:~$ loginctl show-user cr0x -p Linger
Linger=yes

Signification : Le gestionnaire utilisateur peut survivre au-delà des connexions, maintenant les services utilisateur et le répertoire runtime disponibles.

Décision : Utilisez cela pour des services headless exécutés en scope utilisateur (parfois agents CI, podman par utilisateur, etc.). Ne l’activez pas partout « au cas où ». C’est comme ça qu’on obtient des gestionnaires utilisateur zombies consommant de la RAM sur des hôtes partagés.

Task 17: If systemctl fails as root, test D-Bus directly

cr0x@server:~$ busctl --system list | head
NAME                      PID PROCESS         USER CONNECTION UNIT SESSION DESCRIPTION
:1.0                      842 dbus-daemon     root :1.0       -    -       -
org.freedesktop.DBus      842 dbus-daemon     root :1.0       -    -       -
org.freedesktop.login1    701 systemd-logind  root :1.2       -    -       -

Signification : Le bus système répond. Si systemctl renvoie encore une erreur, vous pourriez avoir un endpoint D-Bus systemd cassé ou un décalage d’environnement/namespace.

Décision : Si busctl échoue aussi : le bus système est vraiment cassé. Si busctl fonctionne : concentrez-vous sur la connectivité systemd et l’environnement client.

Task 18: Check the systemd private socket (systemd’s IPC endpoint)

cr0x@server:~$ ls -l /run/systemd/private
srw------- 1 root root 0 Dec 30 10:11 /run/systemd/private

Signification : Le socket privé de systemd existe ; systemctl l’utilise sur certains chemins. S’il manque, quelque chose est profondément incorrect avec PID 1 ou /run.

Décision : S’il manque : traitez-le comme un problème systemd/système de fichiers runtime ; envisagez un redémarrage contrôlé après avoir extrait les logs. S’il est présent : revenez à la portée (système vs utilisateur) et aux problèmes de namespace.

Task 19: Spot chroot/namespace issues (common in recovery shells)

cr0x@server:~$ readlink /proc/$$/ns/mnt
mnt:[4026532585]
cr0x@server:~$ sudo readlink /proc/1/ns/mnt
mnt:[4026531840]

Signification : Votre shell est dans un namespace de montage différent de PID 1. Vous pourriez ne pas voir le vrai /run où résident les sockets.

Décision : Si les namespaces diffèrent, exécutez les diagnostics depuis le namespace hôte (ou entrez-y) au lieu de « réparer » des chemins fantômes dans votre vue isolée.

Task 20: Last resort, controlled restarts (in the right order)

cr0x@server:~$ sudo systemctl restart systemd-logind
cr0x@server:~$ sudo systemctl restart dbus
cr0x@server:~$ sudo systemctl daemon-reexec

Signification : Ces redémarrages peuvent récupérer un logind/dbus/systemd bloqué. daemon-reexec est lourd ; il ré-exécute PID 1 sans rebooter.

Décision : N’exécutez cela qu’après avoir confirmé que vous n’êtes pas dans un conteneur et après avoir capturé suffisamment de logs pour expliquer l’incident. Si les sessions utilisateur sont cassées à cause de logind, redémarrer logind peut couper des sessions ; planifiez-le en connaissance de cause.

Erreurs courantes : symptôme → cause → correction

1) « systemctl fonctionne en local en root, échoue via SSH »

Symptôme : Via SSH, systemctl renvoie « Failed to get D-Bus connection », mais sur la console ça marche.

Cause racine : Vous êtes dans un environnement restreint (commande forcée, chroot, toolbox), ou votre session SSH ne voit pas le /run de l’hôte (différence de namespace de montage).

Correction : Confirmez PID 1 et le namespace de montage ; assurez-vous que votre chemin SSH n’est pas chrooté et a accès à /run. Utilisez la Task 2 et la Task 19.

2) « systemctl –user échoue après sudo -i »

Symptôme : Vous devenez root et tentez de gérer des services utilisateur ; ça échoue avec des erreurs de bus.

Cause racine : Root n’a pas le contexte du bus utilisateur. De plus, le gestionnaire utilisateur de root n’est pas votre gestionnaire utilisateur.

Correction : Exécutez systemctl --user en tant qu’utilisateur dans la session. Si nécessaire depuis root, utilisez runuser -l username -c 'systemctl --user …' et assurez-vous qu’une session appropriée existe (ou activez le lingering).

3) « GNOME Settings ne s’ouvre pas ; les invites polkit n’apparaissent jamais »

Symptôme : Les actions GUI échouent silencieusement ou se plaignent de D-Bus.

Cause racine : Le bus de session utilisateur est cassé : XDG_RUNTIME_DIR manquant, DBUS_SESSION_BUS_ADDRESS obsolète, ou /run/user/UID/bus manquant.

Correction : Vérifiez Task 7/8. Déconnectez-vous et reconnectez-vous pour recréer une session propre. Si ça persiste, vérifiez logind et l’intégration PAM.

4) « Script cron échoue avec erreurs de connexion D-Bus »

Symptôme : Un script utilisant gsettings, notify-send ou systemctl --user échoue dans cron.

Cause racine : Cron s’exécute sans session utilisateur et sans XDG_RUNTIME_DIR.

Correction : N’exécutez pas de commandes dépendant du bureau/session dans cron à moins de créer un contexte de session. Utilisez des services système à la place, ou activez le lingering et exécutez un service utilisateur indépendant de l’état GUI.

5) « /run/user/UID existe mais appartient à root »

Symptôme : Le répertoire existe, mais les permissions sont erronées ; des erreurs de bus utilisateur suivent.

Cause racine : Quelqu’un a lancé un « nettoyage » en root et recréé les répertoires incorrectement, ou un script défaillant a écrit dans /run/user.

Correction : Déconnectez l’utilisateur (terminez les sessions), supprimez le répertoire runtime incorrect et laissez logind le recréer. Si vous devez corriger en direct, corrigez la propriété et redémarrez prudemment le gestionnaire utilisateur.

6) « socket du bus système manquant après le boot »

Symptôme : /run/dbus/system_bus_socket est absent ; systemctl échoue globalement.

Cause racine : dbus.socket ou dbus.service n’a pas démarré, ou /run n’a pas été monté correctement.

Correction : Validez le montage /run (Task 6), puis systemctl status dbus dbus.socket, et vérifiez les logs du boot précoce.

7) « Ça marche sur l’hôte mais échoue dans un conteneur »

Symptôme : systemctl et busctl échouent dans une image conteneur ou un runner CI.

Cause racine : Pas de systemd PID 1, pas de bus système, ou /run isolé.

Correction : N’utilisez pas systemctl dans ce conteneur. Lancez le service en avant-plan, ou exécutez un conteneur basé sur systemd intentionnellement avec les privilèges et mounts appropriés.

Trois mini-récits d’entreprise venant du terrain

Mini-récit n°1 : L’incident causé par une mauvaise hypothèse

Dans une entreprise de taille moyenne, un ingénieur on-call a reçu une alerte « l’hôte de déploiement ne redémarre pas les services ». Il s’est connecté en SSH, a lancé sudo systemctl restart app, et a reçu « Failed to get D-Bus connection ». L’hypothèse fut immédiate et confiante : « dbus est en panne ; redémarrons-le. »

Il a redémarré dbus. Puis logind. Puis tenté un daemon-reexec. L’hôte est devenu plus difficile d’accès, et quelques sessions interactives ont été coupées. L’application ne redémarrait toujours pas. L’incident a pris de l’ampleur.

Le vrai problème était banal : l’ingénieur n’était pas sur l’hôte. Il se trouvait dans un chroot de maintenance utilisé par les outils de secours de l’équipe. Cet environnement avait un namespace de montage différent et un /run différent. Évidemment /run/dbus/system_bus_socket n’existait pas là ; le socket du bus vivait dans le namespace de l’hôte.

Une fois sorti du chroot et la même commande lancée dans l’environnement réel de l’hôte, systemctl a fonctionné immédiatement. La « panne D-Bus » était une mirage créée par le contexte. La correction fut d’ajouter une bannière claire pour les environnements de secours et d’apprendre à l’équipe à exécuter la Task 2 et la Task 19 avant de toucher aux démons.

Mini-récit n°2 : L’optimisation qui s’est retournée

Une autre équipe voulait des temps de connexion plus rapides et moins de processus en arrière-plan sur des postes développeurs. Quelqu’un a décidé de « simplifier » en retirant des paquets de l’image de base, y compris des composants de session qu’ils considéraient comme du « fluff » desktop.

L’image a été déployée, et elle était rapide. Pendant environ une semaine. Puis sont arrivés les tickets : intégration IDE en panne, invites de mot de passe qui n’apparaissent pas, basculements de paramètres inefficaces, et une étrange — services utilisateur ne fonctionnant que après reconnexion via bureau à distance.

Ils avaient retiré des éléments qui assuraient indirectement un bus de session utilisateur stable. Le bus système existait toujours, mais l’infrastructure par utilisateur était incohérente selon les méthodes de connexion. Certaines connexions créaient correctement /run/user/UID ; d’autres non, parce que les hooks PAM étaient incomplets et les paquets de session utilisateur absents.

L’optimisation n’était pas « mauvaise » parce qu’elle économisait du CPU. Elle était mauvaise parce qu’elle supprimait l’échafaudage rendant le bus utilisateur prévisible. Le rollback a réinstallé les paquets nécessaires et standardisé les chemins de connexion. Le temps de connexion a légèrement augmenté, et le taux d’incidents a fortement diminué. Parfois « rapide » n’est que « fragile avec un meilleur marketing ».

Mini-récit n°3 : La pratique ennuyeuse mais correcte qui a sauvé la journée

Dans un environnement régulé, une équipe gérait des serveurs Ubuntu qui avaient parfois besoin d’un travail d’urgence sur console. Ils avaient une politique qui semblait old-school : chaque intervention commence par capturer l’état, y compris des extraits de journalctl -b et un instantané des chemins de sockets dans /run, avant tout redémarrage.

Cela semblait bureaucratique jusqu’à ce qu’un hôte production commence à lancer des erreurs de connexion D-Bus après une mise à jour du noyau. L’on-call a suivi la politique. Il a capturé findmnt /run, vérifié l’espace libre, confirmé que /run/systemd/private existait, et noté que /run/dbus/system_bus_socket était manquant. Il a aussi récupéré les logs de boot montrant des avertissements de montage tmpfs précoces.

Parce qu’ils avaient des preuves, ils n’ont pas thrashé. Ils ont trouvé que /run était monté en lecture seule à cause d’une défaillance subtile d’initramfs/mount. Avec la correction et un redémarrage contrôlé, le socket du bus est apparu, systemctl a récupéré et la panne s’est terminée proprement.

La pratique ennuyeuse n’a pas seulement réparé la machine ; elle a préservé le récit. En environnements corporate, le récit est la moitié de la récupération : il faut expliquer ce qui s’est passé sans accuser des rayons cosmiques.

Listes de vérification / plan étape par étape

Checklist A: Vous voyez « Failed to get D-Bus connection » en lançant systemctl (scope système)

  1. Confirmez que vous êtes sur l’hôte et que PID 1 est systemd (Task 2).
  2. Vérifiez le montage et la capacité de /run (Task 6).
  3. Vérifiez que /run/dbus/system_bus_socket existe (Task 3).
  4. Contrôlez systemctl status dbus dbus.socket (Task 4).
  5. Vérifiez le socket privé de systemd /run/systemd/private (Task 18).
  6. Testez la réactivité du bus avec busctl --system list (Task 17).
  7. Récupérez les logs : journalctl -b et les unités concernées (Task 12).
  8. Si vous devez redémarrer, faites-le délibérément : logind → dbus → daemon-reexec (Task 20).

Checklist B: Vous voyez l’erreur en lançant systemctl –user ou des outils de bureau (scope utilisateur)

  1. Vérifiez XDG_RUNTIME_DIR et DBUS_SESSION_BUS_ADDRESS (Task 7).
  2. Vérifiez que /run/user/UID et /run/user/UID/bus existent et appartiennent à l’utilisateur (Task 8).
  3. Contrôlez systemctl --user status (Task 9).
  4. Utilisez loginctl list-sessions et loginctl show-user (Task 10).
  5. Si c’est SSH/cron, décidez : avez-vous besoin d’une vraie session ou d’un service système à la place ?
  6. Si vous avez besoin de services utilisateur en arrière-plan, activez le lingering pour cet utilisateur (Task 16), puis retestez.
  7. Si le répertoire runtime disparaît continuellement, corrigez le cycle de vie des sessions et PAM (Task 14/15).

Checklist C: Vous êtes en automatisation/CI et ça échoue

  1. Confirmez si vous êtes dans un conteneur et que PID 1 n’est pas systemd (Task 2).
  2. Cessez d’essayer d’utiliser systemctl dans cet environnement. Lancez le service directement, ou re-concevez le job.
  3. Si vous avez vraiment besoin de systemd, exécutez un environnement compatible systemd intentionnellement, pas par accident.

Blague #2 : Redémarrer dbus sans vérifier les sockets, c’est comme redémarrer une imprimante parce qu’il n’y a plus de papier — cathartique, inefficace, et étrangement populaire.

FAQ

1) Pourquoi systemctl utilise-t-il D-Bus du tout ?

systemctl est un client. Il parle aux APIs du gestionnaire systemd, exposées généralement via D-Bus et le socket privé de systemd. Pas de bus, pas de conversation.

2) Je vois dbus-daemon en fonctionnement. Pourquoi j’obtiens toujours l’erreur ?

Parce que le processus daemon existant n’est pas la même chose que l’accessibilité du socket dans votre namespace/contexte. Vérifiez les chemins de socket sous /run et confirmez que vous êtes dans le namespace de montage de l’hôte (Task 3, 6, 19).

3) Que changent « No such file or directory » vs « Permission denied » ?

No such file signifie généralement que le chemin du socket n’existe pas dans votre vue (montage /run manquant, répertoire runtime absent, problème de namespace). Permission denied signifie que le socket existe mais que le contrôle d’accès vous bloque (mauvais utilisateur, politique ou confinement).

4) Pourquoi ça casse seulement via SSH ?

Soit votre session SSH n’est pas enregistrée auprès de logind (mauvaise configuration PAM), soit vous exécutez dans un wrapper/chroot restreint. Vérifiez pam_systemd.so et si /run/user/UID est créé pour cette session (Task 10, 14).

5) Est-ce sûr d’activer le lingering ?

C’est sûr si vous savez pourquoi vous en avez besoin : exécuter des services utilisateur sans connexions actives. C’est dangereux comme palliatif généralisé car vous conserverez des gestionnaires utilisateur vivants, ce qui peut masquer des bugs de déconnexion et gaspiller des ressources. Activez-le utilisateur par utilisateur, délibérément (Task 16).

6) Puis-je simplement exporter DBUS_SESSION_BUS_ADDRESS et continuer ?

Vous pouvez, mais vous ne devriez pas. Exporter des adresses obsolètes crée des « ça marche sur ma console » fantômes qui casseront plus tard. Préférez établir une vraie session et laissez logind/systemd définir XDG_RUNTIME_DIR et l’adresse du bus.

7) Quelle est la façon la plus rapide de distinguer bus système vs bus utilisateur ?

Si vous utilisez systemctl sans --user, c’est le scope système. Si le socket pertinent est /run/dbus/system_bus_socket, c’est le bus système. Si c’est /run/user/UID/bus, c’est le bus de session utilisateur.

8) Je suis sur une installation serveur minimale — ai-je besoin de dbus-user-session ?

Si vous exécutez des services en scope utilisateur ou attendez qu’une session utilisateur ait un bus, oui, c’est souvent nécessaire. Si vous ne gérez que des services système, vous pouvez parfois vous en passer. Réponse guidée par le symptôme : si le bus utilisateur manque, vérifiez la présence des paquets (Task 13).

9) Pourquoi systemctl --user échoue en root alors que l’utilisateur est connecté ?

Parce que l’environnement de root n’est pas celui de l’utilisateur, et root n’est pas « attaché » au bus de session de cet utilisateur. Exécutez la commande en tant qu’utilisateur dans la session, ou utilisez un outil approprié pour cibler ce gestionnaire utilisateur.

10) Quand dois-je rebooter au lieu de déboguer ?

Si PID 1 est malsain, /run est corrompu/en lecture seule, ou les sockets systemd manquent et vous ne pouvez pas les récupérer proprement, un redémarrage contrôlé est souvent la solution la plus fiable. Capturez les logs avant.

Conclusion : prochaines étapes à déployer aujourd’hui

« Failed to get D-Bus connection » n’est pas une invitation à redémarrer des services au hasard. C’est une demande de vérification d’un contrat : /run est monté et inscriptible, le bon socket existe, votre session est réelle et votre environnement pointe vers le bus correct.

Faites ceci ensuite :

  1. Exécutez le playbook rapide : sockets, répertoires runtime, sessions logind. Ne sautez pas aux redémarrages.
  2. Décidez si votre workflow dépend du bus utilisateur. Si oui, standardisez les chemins de connexion (PAM + logind) et évitez cron pour le travail de session.
  3. Si c’est un problème de parc, ajoutez une vérification de santé légère : validez que /run/dbus/system_bus_socket et /run/systemd/private existent, et alertez sur les répertoires runtime manquants pour les sessions actives.
  4. Rédigez la règle de contexte : les chroots/conteneurs sont autorisés à échouer sur systemctl. Vos playbooks doivent l’indiquer clairement.

RAID n’est pas une sauvegarde : la phrase que l’on apprend trop tard

L’appel arrive généralement quand le tableau de bord est vert et que les données ont disparu. L’ensemble est « healthy ». La base de données est « running ».
Et pourtant le directeur financier regarde un rapport vide, l’équipe produit regarde un bucket vide, et vous regardez la phrase que vous auriez voulu tatouer sur le bon de commande : RAID n’est pas une sauvegarde.

RAID est excellent pour une chose : maintenir un système en ligne face à certains types de défaillance de disque. Il n’est pas conçu pour vous protéger des
suppressions, corruptions, ransomwares, incendies, doigts maladroits, firmware défaillant, ou de l’impulsion humaine étrange et intemporelle d’exécuter rm -rf
dans la mauvaise fenêtre.

Ce que le RAID fait réellement (et ce qu’il n’a jamais promis)

Le RAID est un schéma de redondance pour la disponibilité du stockage. C’est tout. C’est une manière de continuer à servir des lectures et écritures quand un disque
(ou parfois deux) cesse de coopérer. Le RAID concerne la continuité du service, pas la continuité de la vérité.

En termes de production : le RAID vous achète du temps. Il réduit la probabilité qu’une seule défaillance de disque devienne une panne. Il peut améliorer
les performances selon le niveau et la charge de travail. Il peut simplifier la gestion de capacité. Mais il ne crée pas une copie séparée, indépendante et versionnée
de vos données. Et indépendance est le mot qui vous garde votre poste.

Disponibilité vs durabilité vs capacité de restauration

Les gens confondent ces notions sous l’étiquette « sécurité des données ». Ce ne sont pas les mêmes choses :

  • Disponibilité : le système peut-il continuer à fonctionner maintenant ? Le RAID aide ici.
  • Durabilité : les bits resteront-ils corrects dans le temps ? Le RAID aide parfois, parfois il masque la réalité.
  • Capacité de restauration : pouvez-vous restaurer un état connu et sain après un incident ? C’est le rôle des sauvegardes, snapshots, réplication et processus.

Le RAID peut continuer à servir des données corrompues. Le RAID peut répliquer fidèlement votre suppression accidentelle. Le RAID peut propager avec zèle vos blocs chiffrés par un ransomware.
Le RAID est un employé loyal. Loyal ne signifie pas intelligent.

Ce que « sauvegarde » signifie dans un système défendable

Une sauvegarde est une copie séparée des données qui est :

  • Indépendante du domaine de défaillance primaire (disques différents, hôte différent, idéalement compte/crédentials différents).
  • Versionnée pour pouvoir revenir avant que la catastrophe n’ait eu lieu.
  • Restaurable dans un délai acceptable (RTO) et jusqu’à un point dans le temps acceptable (RPO).
  • Testée, car « nous avons des sauvegardes » n’est pas un fait tant que vous n’avez pas restauré depuis elles.

Les snapshots et la réplication sont d’excellents outils. Ils ne sont pas automatiquement des sauvegardes. Ils deviennent des sauvegardes quand ils sont indépendants, protégés
contre les mêmes erreurs d’administration, et que vous pouvez les restaurer sous pression.

Blague n°1 : RAID est la ceinture de sécurité. La sauvegarde est l’ambulance. Si vous comptez sur la ceinture pour faire une chirurgie, vous allez passer une longue journée.

Pourquoi le RAID échoue comme sauvegarde : les modes de défaillance importants

La raison pour laquelle « RAID n’est pas une sauvegarde » est répétée, c’est que les modes de défaillance sont contre-intuitifs. La défaillance d’un disque n’est qu’un type de perte de données.
Les systèmes modernes perdent des données à cause des logiciels, des humains et des attaquants plus souvent que par l’explosion d’un seul disque.

1) Suppression et écrasement sont instantanément répliqués

Supprimez un répertoire. Le RAID réplique la suppression. Écrasez une table. Le RAID stripera cette nouvelle vérité sur l’ensemble. Il n’y a pas d’« annulation » parce que le travail du RAID
est de garder les copies cohérentes, pas de garder les copies historiques.

2) Corruption silencieuse, bit rot, et le piège du « ça a l’air OK »

Disques, contrôleurs, câbles et firmwares peuvent renvoyer de mauvaises données sans lever d’erreur. Les systèmes de fichiers avec sommes de contrôle (comme ZFS, btrfs) peuvent
détecter la corruption, et avec la redondance ils peuvent souvent s’auto-réparer. Le RAID traditionnel sous un système de fichiers qui ne contrôle pas les blocs
peut renvoyer joyeusement des blocs corrompus et l’appeler un succès.

Même avec des sommes de contrôle de bout en bout, vous pouvez toujours corrompre les données à un niveau supérieur : écritures applicatives erronées, compactage bogué, migrations à moitié appliquées.
Le RAID conservera la corruption parfaitement.

3) Le ransomware se moque de votre parité

Le ransomware chiffre ce qu’il peut atteindre. S’il peut accéder à votre système de fichiers monté, il peut chiffrer vos données sur RAID1, RAID10, RAID6,
miroirs ZFS, peu importe. La redondance n’empêche pas le chiffrement. Elle fait juste en sorte que le chiffrement soit hautement disponible.

4) Les pannes de contrôleur et de firmware emportent l’ensemble

Le RAID matériel ajoute un domaine de défaillance : le contrôleur, son module cache, son firmware, sa batterie/supercap, et son format de métadonnées.
Si le contrôleur meurt, vous pourriez avoir besoin d’un modèle de contrôleur identique et d’un niveau de firmware identique pour réassembler proprement l’ensemble.

Le RAID logiciel a aussi des domaines de défaillance (noyau, métadonnées md, outils utilisateurs), mais ils tendent à être plus transparents et portables.
Transparent ne veut pas dire sûr. Ça veut juste dire que vous pouvez voir le couteau avant d’y marcher dessus.

5) Les rebuilds sont stressants et s’aggravent avec la taille des disques

La reconstruction est le moment où les maths rencontrent la physique. Pendant un rebuild, chaque disque restant est lu intensivement, souvent proche de la bande passante maximale, pendant des heures ou des jours.
C’est une tempête parfaite pour révéler des erreurs latentes sur les disques restants. Si vous perdez un autre disque dans un RAID5 pendant la reconstruction, vous perdez l’ensemble.
RAID6 vous donne plus de marge, mais la reconstruction augmente toujours le risque et dégrade les performances.

6) Erreur humaine : le mode de défaillance le plus courant et le moins respecté

Un ingénieur fatigué remplace le mauvais disque, enlève la mauvaise baie, ou exécute la bonne commande sur le mauvais hôte. Le RAID ne protège pas contre les humains. Il les amplifie.
Un mauvais clic se réplique à la vitesse du réseau.

7) Catastrophes de site et rayon d’impact

Le RAID est local. Le feu est local aussi. La même chose pour le vol, les événements électriques, et « oups on a supprimé tout le compte cloud ». Une vraie stratégie de sauvegarde suppose
que vous perdrez un domaine de défaillance entier : un hôte, une baie, une région, ou un compte.

Faits intéressants et un peu d’histoire (la partie utile)

Quelques faits concrets rendent ce sujet marquant parce qu’ils montrent comment le RAID a fini par être traité comme une formule magique.
Voici neuf faits, tous pertinents, aucun romantique.

  1. Le RAID a été nommé et popularisé dans un article de l’UC Berkeley en 1987 qui présentait les « redundant arrays of inexpensive disks » comme une alternative aux gros disques coûteux.
  2. Le marketing RAID initial a beaucoup misé sur la « tolérance aux pannes », et beaucoup de gens ont traduit cela silencieusement par « protection des données », ce qui n’est pas le même contrat.
  3. Les niveaux RAID n’ont jamais été une norme officielle unique. Les fournisseurs implémentaient « RAID5 » avec des comportements et politiques de cache différents, puis discutaient de la sémantique pendant votre fenêtre d’incident.
  4. Les contrôleurs RAID matériels utilisaient historiquement des formats de métadonnées propriétaires sur disque, ce qui explique pourquoi une panne de contrôleur peut tourner à l’archéologie.
  5. L’arrivée des disques multi-téraoctets a rendu les rebuilds RAID5 dramatiquement plus risqués parce que le temps de reconstruction a augmenté et la probabilité de rencontrer un secteur illisible pendant le rebuild a monté.
  6. Les taux URE (unrecoverable read error) étaient largement discutés dans les années 2000 comme raison pratique de préférer la double parité pour les grands ensembles, surtout sous forte charge de reconstruction.
  7. ZFS (publié pour la première fois au milieu des années 2000) a poussé les sommes de contrôle de bout en bout dans les opérations courantes et a rendu « bit rot » acceptable en salle de réunion parce qu’on pouvait enfin le détecter.
  8. Les snapshots sont devenus courants dans le stockage d’entreprise dans les années 1990 mais étaient souvent stockés sur le même ensemble — rollback rapide, pas reprise après sinistre.
  9. Le ransomware a déplacé la conversation sur les sauvegardes de « bande vs disque » vers « immutabilité vs identifiants », parce que les attaquants ont appris à supprimer les sauvegardes en premier.

Trois mini-récits d’entreprise venant des tranchées

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une entreprise SaaS de taille moyenne exécutait son cluster PostgreSQL principal sur une paire de serveurs haut de gamme avec RAID10 matériel. Le discours du fournisseur sonnait
rassurant : disques redondants, cache d’écriture sur batterie, hot spares. L’équipe a entendu « pas de perte de données » et rangé mentalement les sauvegardes dans la case « agréable à avoir ».

Un après-midi, un développeur a lancé un script de nettoyage contre la production. Il était censé cibler un schéma de staging ; il a ciblé celui en production.
En quelques secondes, des millions de lignes ont été supprimées. La base de données continuait de servir le trafic, et les graphiques de monitoring semblaient corrects — les requêtes étaient même plus rapides,
parce qu’il y avait moins de données.

Ils ont essayé de récupérer en utilisant la fonctionnalité de « snapshot » du contrôleur RAID, qui n’était pas un snapshot au sens système de fichiers. C’était un profil
de configuration pour le comportement du cache. Le fournisseur de stockage, à leur crédit, n’a pas ri. Il a simplement posé la question qui met fin aux carrières :
« Quels sont vos derniers backups connus bons ? »

Il n’y en avait pas. Il y avait un dump logique nocturne configuré des mois auparavant, mais il écrivait sur le même volume RAID, et le script de nettoyage a supprimé aussi le répertoire du dump.
L’entreprise a reconstruit à partir des journaux applicatifs et des flux d’événements tiers. Ils ont récupéré la plupart des choses, mais pas tout, et ont passé des semaines à corriger des dommages référentiels subtils.

La mauvaise hypothèse n’était pas « le RAID est sûr ». C’était « disponibilité implique capacité de restauration ». Ils avaient une haute disponibilité et peu de vérité.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une plateforme média était obsédée par les performances. Ils ont déplacé les métadonnées de leur stockage d’objets d’une configuration conservatrice vers un large RAID5 pour grappiller
plus de capacité utile et un meilleur débit d’écriture sur le papier. Ils ont aussi activé un cache contrôleur agressif pour améliorer les débits d’ingestion.

En fonctionnement normal, c’était super. Les profondeurs de file d’attente étaient basses. La latence a baissé. La direction avait sa diapositive « efficacité de stockage » pour le rapport trimestriel.
Tout le monde a mieux dormi pendant environ un mois.

Puis un seul disque a commencé à renvoyer des erreurs de lecture intermittentes. L’ensemble l’a marqué comme « predictive failure » mais l’a gardé en ligne. Une reconstruction a été lancée
vers un hot spare pendant les heures de pointe parce que le système était « redondant ». Ce rebuild a saturé les disques restants. La latence a grimpé, les timeouts ont augmenté,
et les retries applicatifs ont créé une boucle de rétroaction.

En plein rebuild, un autre disque a rencontré un secteur illisible. RAID5 ne peut pas gérer cela pendant une reconstruction. Le contrôleur a déclaré le disque virtuel défaillant.
Le résultat n’a pas été seulement une indisponibilité. C’était une corruption partielle des métadonnées qui a rendu la récupération plus lente et plus sale qu’un crash propre.

L’optimisation n’était pas malveillante ; elle était sans bornes. Ils ont optimisé pour la capacité et les performances de benchmark, puis payé le prix avec le risque de rebuild et un plus grand rayon d’impact.
Ils ont remplacé la configuration par une double parité, déplacé les fenêtres de rebuild en heures creuses, et — plus important — construit une pipeline de sauvegarde hors ensemble pour que la prochaine panne soit ennuyeuse.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une société de services financiers exploitait un service de fichiers utilisé par des équipes internes. Le stockage était un ensemble miroir ZFS : simple, conservateur, pas excitant.
La partie excitante était leur hygiène de sauvegarde : snapshots nocturnes, réplication hors site vers un domaine administratif différent, et tests de restauration mensuels.
Tout le monde se plaignait des tests de restauration parce qu’ils « faisaient perdre du temps ». Le manager SRE les a rendus non optionnels quand même.

L’ordinateur portable d’un sous-traitant a été compromis. L’attaquant a obtenu l’accès VPN puis des identifiants privilégiés pouvant écrire sur le partage de fichiers.
Durant la nuit, un ransomware a commencé à chiffrer les répertoires utilisateurs. Comme le partage était en ligne et inscriptible, le chiffrement s’est propagé rapidement.

ZFS a fait exactement ce qu’on lui a demandé : il a stocké les nouveaux blocs chiffrés avec intégrité. Le miroir RAID a assuré que le chiffrement était durable.
Le lendemain matin, les utilisateurs ont trouvé leurs fichiers renommés et illisibles. Le miroir était « healthy ». L’entreprise ne l’était pas.

La société a mis le partage réseau hors ligne, fait pivoter les identifiants, et vérifié la cible de sauvegarde immuable. Les sauvegardes étaient stockées dans un environnement séparé
avec des permissions de suppression restreintes et des verrous de rétention. L’attaquant n’a pas pu y toucher.

La restauration n’était pas magique ; elle était pratiquée. Ils ont restauré d’abord les répertoires les plus critiques selon une liste de priorités pré-établie, puis le reste
le jour suivant. Le post-mortem était ennuyeux de la meilleure manière. La morale était aussi ennuyeuse : le processus ennuyeux bat la redondance sophistiquée.

Feuille de route pour un diagnostic rapide : trouver le goulet d’étranglement et le rayon d’impact

Quand quelque chose ne va pas avec le stockage, les équipes perdent du temps à se disputer si c’est « les disques », « le réseau » ou « la base de données ».
La bonne approche est d’établir : (1) ce qui a changé, (2) ce qui est lent, (3) ce qui est dangereux, et (4) ce en quoi vous pouvez encore avoir confiance.

Premièrement : arrêtez d’empirer la situation

  • Si vous suspectez une corruption ou un ransomware, geler les écritures là où c’est possible : remonter en lecture seule, arrêter les services, révoquer les identifiants.
  • Si un ensemble est dégradé et en reconstruction, envisagez de réduire la charge pour éviter une seconde défaillance pendant le rebuild.
  • Commencez un journal d’incident : commandes exécutées, horodatages, changements effectués. La mémoire n’est pas une preuve.

Deuxièmement : identifiez si c’est de la performance, de l’intégrité ou de la disponibilité

  • Performance : latence élevée, timeouts, profondeur de file d’attente, iowait. Les données peuvent être encore correctes.
  • Intégrité : erreurs de checksum, corruption au niveau applicatif, changements inattendus de fichiers. Les performances peuvent sembler normales.
  • Disponibilité : périphériques manquants, ensembles dégradés/échoués, systèmes de fichiers non montés. Le système crie.

Troisièmement : localisez rapidement le domaine de faute

  1. Hôte : logs noyau, erreurs disque, état du contrôleur.
  2. Pile de stockage : RAID/mdadm/ZFS, santé du système de fichiers, statut des scrubs.
  3. Chemin IO : multipath, HBA, SAS expander, NICs, switches si stockage réseau.
  4. Application : plans de requête, contentions de locks, boucles de retry.
  5. Posture de sauvegarde/restauration : avez-vous un point de restauration connu et atteignable ?

Quatrièmement : choisissez l’objectif

Dans une panne, vous devez choisir un objectif principal :

  • Maintenir en fonctionnement (disponibilité) : stabiliser, accepter un mode dégradé.
  • Protéger les données (intégrité) : geler les écritures, prendre des copies médico-légales, restaurer depuis un point connu bon.
  • Récupérer le service (recoverability) : basculer, reconstruire ailleurs, restaurer les sauvegardes.

Ces objectifs sont en conflit. Prétendre le contraire, c’est finir avec un système qui fonctionne mais qui sert de mauvaises données.

Tâches pratiques avec commandes : quoi exécuter, ce que ça signifie, ce que vous décidez

Ci-dessous des tâches concrètes à exécuter sur des systèmes Linux pour comprendre votre posture de redondance et votre réelle capacité de restauration.
Chaque tâche inclut : commande, sortie exemple, ce que ça signifie, et la décision que vous prenez à partir de cela.

Tâche 1 : Vérifier les périphériques bloc actuels et l’appartenance RAID

cr0x@server:~$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL,SERIAL
NAME    SIZE TYPE FSTYPE MOUNTPOINT MODEL            SERIAL
sda   3.6T disk       	        HGST_HUS726T4TAL  K8H1ABCD
├─sda1 512M part vfat   /boot/efi
└─sda2 3.6T part
sdb   3.6T disk       	        HGST_HUS726T4TAL  K8H1EFGH
└─sdb1 3.6T part
md0   3.6T raid1 ext4   /data

Ce que ça signifie : Vous avez un device RAID logiciel md0 monté sur /data, construit à partir de partitions.

Décision : Si vous pensiez avoir des « sauvegardes », ce n’est pas le cas. Il s’agit seulement de redondance. Confirmez que l’emplacement des sauvegardes est séparé.

Tâche 2 : Inspecter la santé mdadm et le statut de reconstruction

cr0x@server:~$ cat /proc/mdstat
Personalities : [raid1]
md0 : active raid1 sdb1[1] sda2[0]
      3906886464 blocks super 1.2 [2/2] [UU]

unused devices: <none>

Ce que ça signifie : [UU] indique que les deux membres sont opérationnels. Pendant un rebuild vous verriez [U_] et une ligne de progression.

Décision : Si dégradé, réduisez la charge et planifiez le remplacement du disque. Aussi : prenez un snapshot de sauvegarde maintenant si vous n’en avez pas hors hôte.

Tâche 3 : Obtenir des informations détaillées mdadm, y compris les compteurs d’événements

cr0x@server:~$ sudo mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Mon Oct  2 11:22:09 2023
        Raid Level : raid1
        Array Size : 3906886464 (3726.02 GiB 4000.79 GB)
     Used Dev Size : 3906886464 (3726.02 GiB 4000.79 GB)
      Raid Devices : 2
     Total Devices : 2
       State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
 Spare Devices : 0
           Name : server:0
           UUID : 1a2b3c4d:5e6f:7890:abcd:ef0123456789
         Events : 12891

    Number   Major   Minor   RaidDevice State
       0       8        2        0      active sync   /dev/sda2
       1       8       17        1      active sync   /dev/sdb1

Ce que ça signifie : « clean » est bon, mais cela ne dit rien sur le fait que les fichiers soient corrects, cohérents ou restaurables.

Décision : Utilisez ceci pour confirmer la topologie et identifier quel disque physique correspond à quel membre avant de toucher le matériel.

Tâche 4 : Vérifier les logs noyau pour erreurs IO et resets

cr0x@server:~$ sudo journalctl -k -S "1 hour ago" | egrep -i "ata|sas|scsi|i/o error|reset|timeout" | tail -n 8
Jan 22 10:11:41 server kernel: ata3.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x6 frozen
Jan 22 10:11:41 server kernel: ata3.00: failed command: READ DMA EXT
Jan 22 10:11:41 server kernel: blk_update_request: I/O error, dev sdb, sector 9175040 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
Jan 22 10:11:42 server kernel: ata3: hard resetting link
Jan 22 10:11:47 server kernel: ata3: link is slow to respond, please be patient
Jan 22 10:11:52 server kernel: ata3: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

Ce que ça signifie : Les resets de lien et erreurs IO sont des signes avant-coureurs. Ça peut venir du disque, du câble, du backplane ou du contrôleur.

Décision : Traitez comme « intégrité à risque ». Lancez une sauvegarde fraîche si possible ; planifiez une maintenance et une isolation matérielle.

Tâche 5 : Interroger SMART pour la santé et les compteurs clés

cr0x@server:~$ sudo smartctl -a /dev/sdb | egrep -i "SMART overall|Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|Power_On_Hours"
SMART overall-health self-assessment test result: PASSED
  5 Reallocated_Sector_Ct   0x0033   100   100   010    Pre-fail  Always       -       12
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       2
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       2
  9 Power_On_Hours          0x0032   089   089   000    Old_age   Always       -       41231

Ce que ça signifie : « PASSED » n’est pas rassurant. Les secteurs en attente / offline-uncorrectable comptent davantage. Ce disque se détériore.

Décision : Remplacez de façon proactive. Si en RAID5/6, le risque de rebuild augmente ; planifiez la reconstruction avec charge réduite et sauvegardes vérifiées.

Tâche 6 : Pour le RAID matériel, vérifier l’état du contrôleur/du disque virtuel (exemple storcli)

cr0x@server:~$ sudo storcli /c0/vall show
Controller = 0
Status = Success
Description = Show Virtual Drives

DG/VD TYPE  State Access Consist Cache Cac sCC     Size Name
0/0   RAID5 dgrd  RW     No      RWBD  -   OFF  10.913 TB data_vd0

Ce que ça signifie : Le disque virtuel est dgrd (dégradé). « Consist No » suggère qu’une vérification de cohérence est nécessaire.

Décision : Mettez en pause les écritures non essentielles, identifiez les disques défaillants/prédictifs, et assurez-vous d’avoir une sauvegarde restaurable avant la reconstruction.

Tâche 7 : Confirmer la politique de cache d’écriture et l’état batterie/supercap

cr0x@server:~$ sudo storcli /c0 show battery
Controller = 0
Status = Success
Description = Battery Status

BatteryType = iBBU
Status = Failed
Replacement required = Yes

Ce que ça signifie : Si la protection du cache est défaillante, les contrôleurs désactivent souvent le write-back cache ou risquent de perdre des écritures reconnues en cas de coupure de courant.

Décision : Attendez-vous à des changements de performance et à un risque d’intégrité si la politique est mal configurée. Remplacez la batterie/supercap et revoyez le mode de cache.

Tâche 8 : Mesurer si vous manquez de CPU ou d’IO (iostat)

cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 	01/22/2026 	_x86_64_	(16 CPU)

avg-cpu:  %user %nice %system %iowait  %steal   %idle
          12.34  0.00    5.12   31.45    0.00   51.09

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
md0              85.0   5420.0     0.0    0.0   18.20    63.76     40.0   3120.0   44.10   2.90   98.7

Ce que ça signifie : Un %iowait élevé et un %util proche de 100% indiquent un goulet IO. La latence d’écriture est élevée.

Décision : Limitez les jobs intensifs, vérifiez un rebuild/scrub, et envisagez de déplacer les charges chaudes hors de l’ensemble pendant que vous stabilisez.

Tâche 9 : Trouver les processus qui saturent l’IO (iotop)

cr0x@server:~$ sudo iotop -oPa -n 5
Total DISK READ: 55.43 M/s | Total DISK WRITE: 12.10 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN  IO>  COMMAND
18422 be/4   postgres  40.22 M/s   8.10 M/s  0.00 % 92.00 % postgres: checkpointer
27109 be/4   root      12.11 M/s   0.00 B/s  0.00 % 15.00 % rsync -aH --delete /data/ /mnt/backup/

Ce que ça signifie : Votre job de sauvegarde et la maintenance de la base se concurrencent. Ce n’est pas une leçon morale ; c’est de la physique.

Décision : Reprogrammez les sauvegardes/fenêtres de maintenance ou mettez en place une limitation de débit pour que les sauvegardes n’entraînent pas de panne (ou l’inverse).

Tâche 10 : Vérifier rapidement les erreurs du système de fichiers (exemple ext4)

cr0x@server:~$ sudo dmesg | egrep -i "EXT4-fs error|I/O error|Buffer I/O error" | tail -n 6
[915230.112233] EXT4-fs error (device md0): ext4_find_entry:1531: inode #524301: comm nginx: reading directory lblock 0
[915230.112240] Buffer I/O error on device md0, logical block 12345678

Ce que ça signifie : Le système de fichiers voit des erreurs de lecture. Le RAID peut masquer certaines défaillances, mais pas toutes.

Décision : Arrêtez les services si possible, capturez les logs, planifiez un fsck contrôlé (ou une restauration) plutôt que de laisser la corruption se propager.

Tâche 11 : Vérifier la santé du pool ZFS et les compteurs d’erreurs

cr0x@server:~$ sudo zpool status -v
  pool: tank
 state: DEGRADED
status: One or more devices has experienced an unrecoverable error.
action: Replace the faulted device, or use 'zpool clear' to mark the device repaired.
  scan: scrub repaired 0B in 00:42:18 with 0 errors on Sun Jan 18 02:15:01 2026
config:

        NAME        STATE     READ WRITE CKSUM
        tank        DEGRADED     0     0     0
          mirror-0  DEGRADED     0     0     0
            sdc     FAULTED      0     0     8  too many errors
            sdd     ONLINE       0     0     0

errors: Permanent errors have been detected in the following files:

        tank/data/app.db

Ce que ça signifie : ZFS a détecté des erreurs de checksum et peut indiquer quel fichier est affecté. C’est la différence entre « on pense » et « on sait ».

Décision : Traitez les fichiers nommés comme suspects. Restaurez les données affectées depuis une sauvegarde ou une réplication applicative ; remplacez le disque défaillant.

Tâche 12 : Vérifier les snapshots ZFS et si vous les confondez avec des sauvegardes

cr0x@server:~$ sudo zfs list -t snapshot -o name,creation -s creation | tail -n 5
tank/data@hourly-2026-01-22-0600  Thu Jan 22 06:00 2026
tank/data@hourly-2026-01-22-0700  Thu Jan 22 07:00 2026
tank/data@hourly-2026-01-22-0800  Thu Jan 22 08:00 2026
tank/data@hourly-2026-01-22-0900  Thu Jan 22 09:00 2026
tank/data@hourly-2026-01-22-1000  Thu Jan 22 10:00 2026

Ce que ça signifie : Sympa. Mais si ces snapshots vivent sur le même pool, ils ne survivront pas à la perte du pool, à la compromission du compte, ou à une défaillance de site.

Décision : Répliquez les snapshots vers une cible indépendante avec des identifiants différents et des protections contre la suppression.

Tâche 13 : Confirmer que des sauvegardes existent et sont récentes (exemple restic)

cr0x@server:~$ restic -r /mnt/backup/restic-repo snapshots --last
repository 9b2f1c12 opened (version 2, compression level auto)
ID        Time                 Host        Tags        Paths
a1b2c3d4  2026-01-22 09:00:14  server                  /data

Ce que ça signifie : Vous avez un snapshot de sauvegarde datant d’aujourd’hui. C’est un début.

Décision : Validez la capacité de restauration, pas seulement l’existence. Si les snapshots cessent de s’actualiser, traitez cela comme un incident.

Tâche 14 : Faire une restauration test d’un fichier pour prouver la capacité de restauration

cr0x@server:~$ mkdir -p /tmp/restore-test && restic -r /mnt/backup/restic-repo restore latest --target /tmp/restore-test --include /data/important/report.csv
repository 9b2f1c12 opened (version 2, compression level auto)
restoring <Snapshot a1b2c3d4 of [/data] at 2026-01-22 09:00:14 by cr0x@server> to /tmp/restore-test
Summary: Restored 1 files/dirs (42.133 KiB) in 0:00

Ce que ça signifie : C’est le moment où « nous avons des sauvegardes » devient un fait. Vous avez restauré un vrai fichier avec succès.

Décision : Faites-en un exercice programmé avec sélection aléatoire de fichiers et résultats documentés.

Tâche 15 : Vérifier si votre cible de « sauvegarde » est réellement indépendante

cr0x@server:~$ mount | egrep "/data|/mnt/backup"
 /dev/md0 on /data type ext4 (rw,relatime)
 server:/export/backup on /mnt/backup type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2)

Ce que ça signifie : Les sauvegardes vont vers NFS. L’indépendance dépend d’où se trouve ce NFS et qui peut le supprimer.

Décision : Si NFS est sur le même serveur, la même baie, ou accessible avec les mêmes identifiants admin, ce n’est pas suffisamment indépendant. Corrigez cela.

Tâche 16 : Vérifier la rétention et l’immutabilité au niveau système de fichiers (chattr)

cr0x@server:~$ sudo lsattr -d /mnt/backup
-------------e---- /mnt/backup

Ce que ça signifie : Pas de drapeaux d’immutabilité ici. Ça peut être acceptable, mais alors l’immutabilité doit provenir du système de sauvegarde ou de la cible de stockage.

Décision : Si le ransomware est dans votre modèle de menace (et il l’est), implémentez des verrous de rétention / immutabilité hors de la portée facile de l’admin principal.

Tâche 17 : Vérifier si vous n’êtes qu’à une faute de frappe de la suppression des sauvegardes (permissions)

cr0x@server:~$ namei -l /mnt/backup/restic-repo | tail -n 4
drwxr-xr-x root root /mnt
drwxr-xr-x root root /mnt/backup
drwxrwxrwx root root /mnt/backup/restic-repo

Ce que ça signifie : Dépôt de sauvegarde accessible en écriture par tous. Ce n’est pas une sauvegarde ; c’est un projet artistique communautaire.

Décision : Verrouillez les permissions, séparez les identifiants de sauvegarde, et envisagez des cibles append-only ou immuables.

Tâche 18 : Détecter un rebuild ou un scrub qui tue silencieusement les performances

cr0x@server:~$ sudo zpool iostat -v 1 3
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        2.10T  1.40T    820    210  92.1M  18.2M
  mirror-0                  2.10T  1.40T    820    210  92.1M  18.2M
    sdc                         -      -    420    105  46.0M   9.1M
    sdd                         -      -    400    105  46.1M   9.1M
--------------------------  -----  -----  -----  -----  -----  -----

Ce que ça signifie : Des lectures soutenues élevées peuvent indiquer un scrub/resilver ou un changement de charge. Il faut corréler avec le statut du pool et les jobs cron.

Décision : Si cela coïncide avec une gêne utilisateur, reprogrammez les scrubs, ajustez la priorité de resilver, ou ajoutez de la capacité/capacité tampon.

Blague n°2 : Un rebuild RAID est l’équivalent stockage du « juste un petit changement en production ». Ce n’est jamais rapide, et ça change définitivement les choses.

Erreurs communes : symptômes → cause racine → correction

Cette section est volontairement spécifique. Un conseil générique ne survit pas à un incident ; il finit seulement cité dans le postmortem.

1) « L’ensemble est healthy, mais les fichiers sont corrompus »

  • Symptômes : Erreurs applicatives en lisant des fichiers spécifiques ; mismatches de checksum au niveau app ; utilisateurs voient des médias corrompus ; RAID montre optimal.
  • Cause racine : Corruption silencieuse sur disque/contrôleur/câble, ou l’application a écrit de mauvaises données. La parité/le miroir RAID l’a préservée.
  • Correction : Utiliser un système de fichiers avec checksums (ZFS) ou checksums applicatifs ; lancer des scrubs ; restaurer les objets corrompus depuis des sauvegardes indépendantes ; remplacer le matériel défectueux.

2) « Nous ne pouvons pas reconstruire : un second disque a échoué pendant le rebuild »

  • Symptômes : Le disque virtuel RAID5 échoue en plein rebuild ; des URE apparaissent ; plusieurs disques montrent des erreurs média.
  • Cause racine : Parité simple plus disques volumineux plus charge de lecture de rebuild lourde ; marge insuffisante pour les erreurs de secteur latentes.
  • Correction : Préférer RAID6/RAIDZ2 ou miroirs pour les grands ensembles ; conserver des hot spares ; exécuter des lectures de patrol/scrubs ; remplacer les disques de façon proactive ; assurer des sauvegardes restaurables avant rebuild.

3) « Les sauvegardes existent mais les restaurations sont trop lentes pour respecter le RTO »

  • Symptômes : Le job de sauvegarde rapporte un succès ; la restauration prend des jours ; l’entreprise a besoin d’heures.
  • Cause racine : Le RTO n’a jamais été conçu ; bande passante vers la cible de sauvegarde trop faible ; trop de données, pas de priorisation ; pas de plan de restauration par paliers.
  • Correction : Définir RTO/RPO par jeu de données ; implémenter une récupération locale rapide (snapshots) plus des sauvegardes hors site ; prépositionner les jeux critiques ; pratiquer des restaurations partielles.

4) « Les snapshots nous ont sauvés… jusqu’à la perte du pool »

  • Symptômes : Planning de snapshots confiant ; puis perte catastrophique du pool ; les snapshots ont disparu avec lui.
  • Cause racine : Snapshots stockés dans le même domaine de défaillance que les données primaires.
  • Correction : Répliquer les snapshots vers un système/compte différent ; ajouter de l’immutabilité ; considérer « même hôte » comme « même rayon d’impact ».

5) « Le ransomware a chiffré la production et les sauvegardes »

  • Symptômes : Le dépôt de sauvegarde est supprimé/chiffré ; rétention purgée ; des identifiants légitimes ont été utilisés.
  • Cause racine : Le système de sauvegarde est inscriptible/supprimable par les mêmes identifiants compromis en production ; pas d’immutabilité / pas d’air gap.
  • Correction : Séparer les identifiants et activer MFA ; rôles sauvegarde en écriture seule ; verrou d’objet immuable ou cibles append-only ; copie hors ligne pour le pire scénario ; surveiller les événements de suppression.

6) « Les performances se sont effondrées après le remplacement d’un disque »

  • Symptômes : Pics de latence après remplacement ; systèmes timeout ; rien d’autre n’a changé.
  • Cause racine : Rebuild/resilver saturant l’IO ; throttling du contrôleur ; mode dégradé sur des ensembles parité.
  • Correction : Planifier des fenêtres de rebuild ; limiter la vitesse de rebuild ; déplacer les charges ; ajouter des plateaux/SSDs ; garder de la marge ; ne pas reconstruire en période de pointe sauf si vous aimez le chaos.

7) « Le contrôleur est mort et nous ne pouvons pas importer l’ensemble »

  • Symptômes : Les disques apparaissent mais les métadonnées d’ensemble ne sont pas reconnues ; l’outil du fournisseur ne voit pas le disque virtuel.
  • Cause racine : Métadonnées RAID matériel liées à la famille/firmware du contrôleur ; défaillance du module cache ; confusion de config étrangère.
  • Correction : Standardiser les contrôleurs et garder des pièces de rechange ; exporter les configs du contrôleur ; préférer le stockage défini par logiciel pour la portabilité ; surtout, avoir des sauvegardes qui ne requièrent pas l’existence du contrôleur.

Listes de contrôle / plan pas à pas : construire des sauvegardes qui survivent à la réalité

Voici le plan qui fonctionne quand vous êtes fatigué, en sous-effectif, et qu’on attend toujours de vous de ne pas vous tromper.
Il est volontairement opinionné parce que la production l’est.

Étape 1 : Classifier les données par conséquence métier

  • Tier 0 : authentification/identité, facturation, données clients, base de données cœur.
  • Tier 1 : outils internes, analytics, logs nécessaires pour la sécurité/forensique.
  • Tier 2 : caches, artefacts de build, jeux de données reproductibles.

Si tout est « critique », rien ne l’est. Définissez RPO et RTO par tier. Écrivez-le là où la finance peut le voir.

Étape 2 : Choisir la règle de base puis la dépasser

La règle classique est 3-2-1 : trois copies des données, sur deux media/types différents, avec une copie hors site. C’est un point de départ, pas une médaille.
Pour les ransomwares, « hors site » doit aussi signifier « non supprimable par les mêmes identifiants ».

Étape 3 : Séparer volontairement les domaines de défaillance

  • Matériel différent : pas « un répertoire différent ».
  • Frontière administrative différente : comptes/roles séparés ; la production ne doit pas pouvoir supprimer les sauvegardes.
  • Géographie différente : au moins une copie hors du site/baie/région que vous pouvez perdre.

Étape 4 : Utiliser les snapshots pour la vitesse, les sauvegardes pour la survie

Les snapshots locaux servent pour les récupérations rapides : suppressions accidentelles, mauvais déploiements, rollback rapide. Gardez-les fréquents et à courte rétention.
Les sauvegardes servent quand la machine, l’ensemble, ou le compte a disparu.

Étape 5 : Chiffrer et authentifier la pipeline de sauvegarde

  • Chiffrez au repos et en transit (et gérez les clés comme si elles comptaient, parce que c’est le cas).
  • Utilisez des identifiants dédiés pour les sauvegardes avec des permissions minimales.
  • Privilégiez des chemins en écriture seule depuis la production vers la sauvegarde quand c’est possible.

Étape 6 : Faire de la rétention une politique, pas une vibe

  • Court : horaire/quotidien pour rollback rapide.
  • Moyen : hebdomadaire/mensuel pour besoins métiers/légaux.
  • Long : trimestriel/annuel si requis, stocké à moindre coût et de façon immuable.

Étape 7 : Tester les restaurations comme si vous le pensiez vraiment

La sauvegarde la plus chère est celle que vous ne restaurez jamais jusqu’au jour où vous en avez besoin. Les tests de restauration doivent être planifiés, journalisés et assumés.
Faites tourner la responsabilité pour que le savoir ne vive pas dans la tête d’une seule personne.

Étape 8 : Surveiller les bonnes choses

  • Fraîcheur des sauvegardes : dernier snapshot réussi par jeu de données.
  • Intégrité des sauvegardes : vérification périodique ou restauration test.
  • Événements de suppression : alertes sur suppressions inhabituelles de sauvegardes.
  • Santé du stockage : SMART, état RAID, erreurs ZFS, résultats de scrub.

Étape 9 : Faire un exercice tabletop pour les scénarios horribles

Pratiquez :

  • Suppression accidentelle (restaurer un répertoire).
  • Ransomware (supposer que l’attaquant a des droits admin production).
  • Panne de contrôleur (supposer que l’ensemble primaire est irrécupérable).
  • Perte de site (supposer que toute la baie/région a disparu).

Étape 10 : Décidez à quoi sert le RAID (et arrêtez de lui demander d’être une sauvegarde)

Utilisez RAID/miroirs/codage d’effacement pour atteindre les objectifs de disponibilité et de performance. Utilisez des sauvegardes pour atteindre les objectifs de capacité de restauration.
Si votre choix de RAID est motivé par « nous n’avons pas besoin de sauvegardes », vous faites de l’architecture en mode souhait.

Une citation à garder au-dessus de votre moniteur

Idée paraphrasée : l’espoir n’est pas une stratégie. — Général Jim Mattis (souvent cité dans les cercles d’ingénierie et d’opérations)

Si vous construisez du stockage sur de l’espoir, vous ne construisez pas du stockage. Vous construisez un futur rapport d’incident avec un long délai de réalisation.

FAQ

1) Si j’ai du RAID1, ai-je encore besoin de sauvegardes ?

Oui. Le RAID1 protège contre la défaillance d’un disque. Il ne protège pas contre la suppression, la corruption, le ransomware, les bugs de contrôleur, ou la perte de site.
Le RAID1 fait fonctionner le système pendant que la mauvaise chose se produit.

2) Les snapshots sont-ils une sauvegarde ?

Pas automatiquement. Les snapshots sont des références point-in-time, généralement stockées sur le même système. Ils deviennent « de type sauvegarde » seulement lorsqu’ils sont répliqués
vers une cible indépendante avec une rétention qu’on ne peut pas supprimer à la légère.

3) Le RAID6 est-il « suffisamment sûr » pour se passer de sauvegardes ?

Non. RAID6 réduit la probabilité de perte d’ensemble due à des disques pendant la reconstruction. Il ne fait rien contre les défaillances logiques (suppression, écrasement),
les maliciels, ou les événements catastrophiques. Les sauvegardes existent parce que la défaillance disque n’est pas la seule menace.

4) Qu’en est-il du stockage cloud avec redondance — est-ce que ça compte comme sauvegarde ?

La redondance du fournisseur cloud concerne typiquement la durabilité des objets stockés, pas votre capacité à récupérer de vos propres erreurs.
Si vous supprimez ou écrasez, le cloud le fera de façon fiable. Vous avez toujours besoin de versioning, de verrous de rétention et de copies indépendantes.

5) Quel est le plan de sauvegarde minimal viable pour une petite entreprise ?

Commencez par : sauvegardes quotidiennes vers une cible indépendante, au moins 30 jours de rétention, et une copie hors site. Ajoutez une rétention hebdomadaire/mensuelle si nécessaire.
Ensuite, planifiez des tests de restauration. Si vous ne faites qu’une seule chose avancée, faites les tests de restauration.

6) À quelle fréquence devons-nous tester les restaurations ?

Pour les systèmes critiques, un test mensuel est une base raisonnable, avec des restaurations partielles plus fréquentes (hebdomadaires c’est bien).
Après des changements majeurs — nouveau stockage, nouvelles clés de chiffrement, nouvel outil de sauvegarde — testez immédiatement.

7) Quelle est la différence entre réplication et sauvegarde ?

La réplication copie les données ailleurs, souvent en quasi-temps réel. C’est excellent pour la haute disponibilité et un RPO faible, mais ça peut répliquer instantanément
les mauvaises modifications. Les sauvegardes sont versionnées et conservées pour pouvoir revenir avant la défaillance. Beaucoup d’environnements utilisent les deux.

8) Comment protéger les sauvegardes contre les ransomwares ?

Séparez les identifiants et restreignez la suppression. Utilisez l’immutabilité / verrous de rétention sur la cible de sauvegarde. Gardez au moins une copie hors ligne ou dans un domaine admin séparé.
Surveillez les suppressions suspectes et désactivez l’accès au dépôt de sauvegarde depuis des hôtes à usage général.

9) ZFS élimine-t-il le besoin de sauvegardes ?

ZFS améliore l’intégrité avec des checksums et l’auto-guérison (avec redondance), et les snapshots sont excellents pour rollback rapide.
Mais ZFS ne vous empêche pas de supprimer des données, de les chiffrer, ou de perdre tout le pool. Vous avez toujours besoin de sauvegardes indépendantes.

10) Quel RPO/RTO devons-nous choisir ?

Choisissez en fonction de la douleur métier, pas de ce que l’équipe stockage souhaiterait. Pour les données Tier 0, un RPO de minutes/heures et un RTO de quelques heures peuvent être nécessaires.
Pour des tiers inférieurs, des jours peuvent être acceptables. L’important est que les chiffres soient conçus et testés, pas simplement déclarés.

Prochaines étapes à faire cette semaine

Le RAID est un outil pour rester en ligne face à certaines défaillances matérielles. Ce n’est pas une machine à remonter le temps. Ce n’est pas un témoin de tribunal. Il ne se soucie
pas de la correction des données ; il se soucie que les bits soient cohérents à travers les disques.

Si vous exploitez des systèmes de production, faites ces étapes cette semaine :

  1. Inventoriez votre stockage : niveau RAID, type de contrôleur, âges des disques, comportement de reconstruction.
  2. Notez RPO/RTO pour vos trois jeux de données principaux. Si vous ne pouvez pas, vous n’avez pas de plan de sauvegarde — vous avez un plan d’espoir.
  3. Vérifiez l’indépendance : confirmez que les sauvegardes vivent en dehors du domaine de défaillance primaire et hors de la portée des identifiants faciles à supprimer.
  4. Effectuez un test de restauration : un fichier, un répertoire, et (si vous êtes courageux) une restauration de base de données vers un environnement de test.
  5. Configurez des alertes pour la fraîcheur des sauvegardes et les anomalies de suppression, pas seulement pour la santé des disques.

Ensuite, et seulement ensuite, profitez de votre RAID. C’est utile quand vous le traitez honnêtement : comme de la redondance, pas comme une salvation.

Scrub ZFS lent : distinguer la lenteur normale d’un vrai problème

Votre scrub est « en cours » depuis suffisamment longtemps pour que les gens se demandent si le stockage est hanté. Les applications semblent lentes, les tableaux de bord affichent une mer d’I/O, et l’ETA est soit absent soit mensonger. Il faut savoir : est-ce un scrub normal et ennuyeux qui fait son travail, ou est-ce le symptôme de quelque chose qui va vous mordre plus tard ?

Ceci est la méthode adaptée à la production pour répondre à cette question. Nous allons séparer la lenteur attendue (celle que vous planifiez et tolérez) de la lenteur pathologique (celle que vous corrigez avant qu’elle ne devienne un incendie de tickets de support).

Ce que fait réellement un scrub (et pourquoi « lent » est parfois correct)

Un scrub ZFS n’est pas un benchmark ni une opération de copie. C’est une patrouille d’intégrité des données. ZFS parcourt les blocs alloués du pool, les lit, vérifie les checksums et — si la redondance le permet — répare les corruptions silencieuses en réécrivant les bonnes données sur les mauvaises. C’est de la maintenance proactive, du type « trouver le problème avant que l’utilisateur ne le fasse ».

Cela implique deux choses qui surprennent :

  • Les scrubs sont fondamentalement orientés lecture (avec des écritures occasionnelles lors des réparations). Votre pool peut être « lent » parce que les lectures sont lentes, parce qu’il y a de la contention avec des workloads réels, ou parce que ZFS choisit volontairement d’être poli.
  • Les scrubs opèrent au niveau bloc, pas fichier. La fragmentation, le choix de recordsize et le surcoût métadonnées peuvent peser davantage que le débit brut disque en MB/s.

Les scrubs se comportent aussi différemment selon la topologie des vdevs. Les mirrors ont tendance à scrubber plus vite et de façon plus prévisible que les RAIDZ, car les mirrors peuvent servir les lectures depuis l’un ou l’autre côté et ont des calculs de parité plus simples. Les scrubs RAIDZ sont parfaitement valides quand le pool est sain, mais ils peuvent devenir une longue promenade si vous avez des vdevs larges, des disques marginaux ou une I/O aléatoire lourde générée par les applications.

Voici la règle de production que j’utilise : le temps de scrub est une propriété observable de votre système, pas une faute morale. Mais un débit de scrub qui s’effondre, ou un ETA qui augmente, est un signal. Pas toujours un incendie, mais toujours digne d’un examen.

Blague courte #1 : Un scrub sans ETA, c’est comme une panne de stockage sans postmortem — techniquement possible, socialement inacceptable.

Faits intéressants et un peu d’histoire

  • ZFS a popularisé les checksums de bout en bout dans le stockage serveur grand public. Les checksums sont stockés séparément des données, ce qui permet à ZFS de détecter les « disques menteurs » qui renvoient des blocs corrompus sans erreurs d’I/O.
  • Le scrub est la réponse de ZFS au « bit rot » — la corruption silencieuse et incrémentale que les RAID traditionnels détectent rarement sauf si une lecture déclenche une reconstruction de parité.
  • Le terme « scrub » vient de systèmes de stockage plus anciens qui scannaient périodiquement les médias pour des erreurs. ZFS l’a rendu routinier et visible par l’utilisateur.
  • RAIDZ a été conçu pour éviter le write hole observé dans les implémentations RAID5/6 classiques, en gardant des métadonnées transactionnelles cohérentes et une sémantique copy-on-write.
  • ZFS est né chez Sun Microsystems puis s’est largement diffusé via OpenZFS. Le comportement moderne de ZFS dépend de la version d’OpenZFS, pas seulement de la marque « ZFS ».
  • Avant, les scrubs étaient plus douloureux sur des systèmes sans bon ordonnancement I/O ou où le throttling de scrub était primitif. Les piles Linux et FreeBSD modernes offrent plus de leviers, mais aussi plus de façons de se tirer une balle dans le pied.
  • Les métadonnées comptent. Les pools avec des millions de petits fichiers peuvent scrubber plus lentement qu’un pool avec moins de gros fichiers, même si l’« espace utilisé » paraît similaire.
  • Les disques SMR rendent les scrubs plus imprévisibles dans la pratique. Quand le disque effectue sa collecte de déchets shingled en arrière-plan, une « lecture » peut devenir une « lecture plus réécriture interne dramatique ».
  • Les baies entreprise effectuent des patrol reads depuis des décennies, souvent de façon invisible. ZFS vous donne simplement la vérité au grand jour — et il se trouve que la vérité peut être lente.

Lenteur de scrub normale vs véritable problème : modèle mental

« Scrub lent » est ambigu. Il faut préciser de quel type de lenteur il s’agit. Je le divise en quatre catégories :

1) Lenteur « grand pool, physique normal »

Si vous avez des centaines de To et des disques tournants, un scrub qui prend des jours peut être normal. Il est limité par la bande passante de lecture séquentielle, la topologie des vdevs, et le fait que les scrubs n’ont pas toujours des accès parfaitement séquentiels (les blocs alloués ne sont pas forcément contigus).

Signes que c’est normal :

  • Le taux de scrub est stable sur des heures.
  • La latence disque n’explose pas.
  • L’impact sur les applications est prévisible et borné.
  • Pas d’erreurs de checksum, pas d’erreurs de lecture.

2) Lenteur « volontairement bridée »

ZFS se limite souvent lors des scrubs pour que les workloads de production ne s’effondrent pas. Cela signifie que votre scrub peut sembler décevant de lenteur tandis que le système reste utilisable. C’est un comportement d’ingénierie souhaitable. Vous pouvez le régler, mais faites-le délibérément.

Signes que c’est du throttling :

  • Le CPU est globalement tranquille.
  • Les IOPS ne sont pas saturés, mais la progression du scrub est lente.
  • La latence des workloads reste dans les SLO.

3) Lenteur « contention par les workloads »

Si le pool sert une base de données occupée, une ferme de VM ou un workload objet, les lectures du scrub concurrencent les lectures/écritures applicatives. Le débit du scrub devient une fonction des heures d’activité. Ce n’est pas une défaillance de ZFS ; c’est un échec de planification.

Signes que c’est de la contention :

  • La vitesse du scrub varie avec les motifs de trafic.
  • Les pics de latence se corrèlent aux heures de pointe applicatives.
  • Désactiver le scrub rend les utilisateurs à nouveau heureux.

4) Lenteur « quelque chose ne va pas »

C’est la catégorie qui vous intéresse vraiment. La lenteur du scrub devient un symptôme : un disque réessaie des lectures, un contrôleur signale des erreurs, un lien s’est négocié à 1,5 Gbps, un membre de vdev est malade et traîne tout le monde, ou vous avez construit une topologie de pool adaptée à la capacité mais mauvaise pour le scrub.

Signes d’un vrai problème :

  • Erreurs de lecture, erreurs de checksum, ou octets « réparés » qui augmentent d’un scrub à l’autre.
  • Un disque affiche une latence ou un débit bien plus faible que ses pairs.
  • Le taux de scrub s’effondre au fil du temps (commence normal, puis ralenti).
  • Les logs noyau montrent des resets, timeouts ou problèmes de lien.
  • Les attributs SMART montrent des secteurs réalloués/pending ou des erreurs UDMA CRC.

Le point clé : « lent » n’est pas un diagnostic. Vous chassez un goulot d’étranglement, puis vous demandez si ce goulot est attendu, configuré ou en train de tomber en panne.

Playbook de diagnostic rapide (premier/deuxième/troisième)

Quand vous êtes en astreinte, vous n’avez pas le temps pour un long séminaire philosophique. Il vous faut un entonnoir rapide qui réduise le problème à l’une des catégories : attendu, contestation, throttling ou cassé.

Premier : le scrub est-il sain ?

  • Vérifiez le statut du pool pour des erreurs et le débit réel du scrub.
  • Cherchez tout membre de vdev dégradé, faulted ou avec « trop d’erreurs ».
  • Décision : si des erreurs existent, traitez cela comme un incident de fiabilité d’abord et une question de performance ensuite.

Deuxième : un périphérique traîne-t-il tout le vdev ?

  • Vérifiez la latence par disque et les temps de service I/O pendant le scrub.
  • Vérifiez SMART rapidement pour secteurs pending, erreurs média et CRC de lien.
  • Décision : si un disque est lent ou réessaie, remplacez-le ou isolez-le ; les scrubs sont le canari.

Troisième : contention ou throttling ?

  • Corrélez la vitesse du scrub avec les métriques workloads (IOPS, latence, profondeur de queue).
  • Vérifiez les tunables ZFS et si le scrub est volontairement limité.
  • Décision : si vous êtes limité, ajustez prudemment ; si c’est de la contention, replanifiez ou séparez les workloads.

Ce n’est qu’après ces trois étapes que vous abordez les questions d’architecture comme la largeur des vdevs, le recordsize, les vdevs spéciaux ou l’ajout de caches. Si le scrub est lent parce qu’un câble SATA est défaillant, aucune « optimisation » ne réparera la physique.

Tâches pratiques : commandes, signification des sorties et décision

Les tâches suivantes sont conçues pour être exécutées pendant qu’un scrub est actif (ou juste après). Chacune inclut une commande réaliste, une sortie exemple, ce que cela signifie, et la décision suivante. L’invite et les sorties sont illustratives, mais les commandes sont standard en environnements réels.

Task 1: Confirm scrub status, rate, and errors

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub in progress since Mon Dec 23 01:00:02 2025
        12.3T scanned at 612M/s, 8.1T issued at 403M/s, 43.2T total
        0B repaired, 18.75% done, 2 days 09:14:33 to go
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            sda                     ONLINE       0     0     0
            sdb                     ONLINE       0     0     0
            sdc                     ONLINE       0     0     0
            sdd                     ONLINE       0     0     0
            sde                     ONLINE       0     0     0
            sdf                     ONLINE       0     0     0

errors: No known data errors

Ce que cela signifie : ZFS affiche à la fois « scanned » et « issued ». Issued est plus proche du taux réel d’achèvement des I/O physiques. Si issued est bien inférieur à scanned, vous pouvez observer du readahead, des effets de cache ou une attente sur des périphériques lents.

Décision : Si les compteurs READ/WRITE/CKSUM sont non nuls, cessez de considérer cela comme « juste lent ». Inspectez le(s) périphérique(s) défaillant(s) avant de tuner.

Task 2: Get one-line progress repeatedly (good for incident channels)

cr0x@server:~$ zpool status tank | sed -n '1,12p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Mon Dec 23 01:00:02 2025
        12.3T scanned at 612M/s, 8.1T issued at 403M/s, 43.2T total
        0B repaired, 18.75% done, 2 days 09:14:33 to go
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0

Ce que cela signifie : C’est l’extrait minimal viable. Si l’ETA continue d’augmenter d’heure en heure, il y a probablement de la contention ou des lectures en réessai.

Décision : Si le taux issued est stable et que l’ETA diminue régulièrement, c’est probablement normal ou bridgé. Si cela fluctue énormément, passez aux vérifications par disque.

Task 3: Find which vdev layout you’re dealing with

cr0x@server:~$ zpool status -P tank
  pool: tank
 state: ONLINE
config:

        NAME                                   STATE     READ WRITE CKSUM
        tank                                   ONLINE       0     0     0
          raidz2-0                             ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...A1   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...B2   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...C3   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...D4   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...E5   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...F6   ONLINE       0     0     0

Ce que cela signifie : RAIDZ2 dans un vdev large. La vitesse de scrub sera limitée par le disque le plus lent et par l’overhead parité. Un disque défaillant peut ralentir tout le vdev.

Décision : Si vous avez un vdev RAIDZ très large et que les scrubs sont pénibles, vous pourriez envisager un changement architectural ultérieur (plus de vdevs, largeur réduite). Ne « tunez » pas la physique.

Task 4: Check per-disk latency and utilization during scrub (Linux)

cr0x@server:~$ iostat -x 2 3
Linux 6.6.12 (server)     12/25/2025  _x86_64_    (32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           4.21    0.00    2.73    8.14    0.00   84.92

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s  w_await aqu-sz  %util
sda             112.0   28800.0     0.0   0.00   18.40   257.1      2.0     512.0   4.30   2.10  98.5
sdb             118.0   30208.0     0.0   0.00   17.92   256.0      2.0     512.0   4.10   2.12  97.9
sdc             110.0   28160.0     0.0   0.00   19.30   256.0      2.0     512.0   4.20   2.05  98.2
sdd              15.0    3840.0     0.0   0.00  220.10   256.0      1.0     256.0  10.00   3.90  99.1
sde             115.0   29440.0     0.0   0.00   18.10   256.0      2.0     512.0   4.00   2.08  98.0
sdf             114.0   29184.0     0.0   0.00   18.70   256.0      2.0     512.0   4.20   2.11  98.4

Ce que cela signifie : sdd a un r_await d’environ 220 ms alors que les autres sont à ~18 ms. C’est votre ancre de scrub. Le pool avancera au rythme du plus mauvais performeur dans un vdev RAIDZ.

Décision : Inspectez immédiatement sdd pour erreurs/logs/SMART. Si c’est un problème de câble ou de contrôleur, corrigez cela avant de remplacer le disque.

Task 5: Check kernel logs for resets/timeouts (Linux)

cr0x@server:~$ sudo dmesg -T | egrep -i 'ata|scsi|reset|timeout|error' | tail -n 12
[Wed Dec 24 13:18:44 2025] ata7.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x6 frozen
[Wed Dec 24 13:18:44 2025] ata7.00: failed command: READ FPDMA QUEUED
[Wed Dec 24 13:18:44 2025] ata7: hard resetting link
[Wed Dec 24 13:18:45 2025] ata7: SATA link up 1.5 Gbps (SStatus 113 SControl 300)
[Wed Dec 24 13:18:46 2025] ata7: SATA link up 1.5 Gbps (SStatus 113 SControl 300)
[Wed Dec 24 13:18:46 2025] sd 6:0:0:0: [sdd] tag#17 FAILED Result: hostbyte=DID_OK driverbyte=DRIVER_OK cmd_age=14s
[Wed Dec 24 13:18:46 2025] blk_update_request: I/O error, dev sdd, sector 123456789 op 0x0:(READ) flags 0x0 phys_seg 8 prio class 0

Ce que cela signifie : Reset de lien puis renégociation à 1.5Gbps est le classique « câble/backplane/port défaillant ». Cela peut aussi être un disque en fin de vie, mais les câbles sont moins chers et très courants.

Décision : Traitez comme une panne matérielle. Reseat/remplacez le câble ou changez de port. Puis revérifiez la latence par disque. Si les erreurs persistent, remplacez le disque.

Task 6: Quick SMART health check for the slow device

cr0x@server:~$ sudo smartctl -a /dev/sdd | egrep -i 'Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|UDMA_CRC_Error_Count|SMART overall|Power_On_Hours'
SMART overall-health self-assessment test result: PASSED
  9 Power_On_Hours          0x0032   086   086   000    Old_age   Always       -       31245
  5 Reallocated_Sector_Ct   0x0033   100   100   010    Pre-fail  Always       -       0
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       12
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       3
199 UDMA_CRC_Error_Count    0x003e   200   199   000    Old_age   Always       -       27

Ce que cela signifie : Des secteurs pending et des Offline_Uncorrectable indiquent que le disque a du mal à lire certaines zones. Les erreurs UDMA CRC pointent souvent vers un câble/backplane défaillant. « PASSED » n’est pas une absolution ; c’est du marketing.

Décision : Si des pending/offline uncorrectable existent, planifiez le remplacement. Si les erreurs CRC augmentent, corrigez aussi le chemin physique (câble/backplane/HBA).

Task 7: Identify if the pool is doing repairs (and how much)

cr0x@server:~$ zpool status -v tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Mon Dec 23 01:00:02 2025
        14.8T scanned at 540M/s, 10.2T issued at 372M/s, 43.2T total
        256M repaired, 23.61% done, 2 days 05:01:12 to go
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0

Ce que cela signifie : Un « repaired » non nul lors du scrub signifie que ZFS a trouvé des mismatches de checksum et les a corrigés. C’est le scrub qui fait son travail, mais c’est aussi la preuve d’une corruption quelque part (disque, câblage, contrôleur ou mémoire).

Décision : Si les réparations se répètent d’un scrub à l’autre, investiguez la cause racine. Une réparation ponctuelle après un événement connu peut être acceptable ; des réparations répétées ne le sont pas.

Task 8: Look for ZFS-level I/O and latency indicators (Linux)

cr0x@server:~$ sudo cat /proc/spl/kstat/zfs/arcstats | egrep '^(hits|misses|size|c_max|demand_data_misses|prefetch_data_misses) ' | head
hits                            2876543210
misses                          456789012
size                            17179869184
c_max                           34359738368
demand_data_misses              123456789
prefetch_data_misses            234567890

Ce que cela signifie : Les stats ARC indiquent si les lectures sont servies depuis la mémoire ou vont sur disque. Pendant un scrub, l’ARC peut être moins utile car les lectures du scrub ne sont pas toujours cache-friendly, mais une ARC minuscule avec des workloads lourds peut aggraver la contention.

Décision : Si l’ARC est à la diète et que la machine swappe, corrigez la pression mémoire en priorité. Ne blâmez pas le scrub pour un problème de RAM.

Task 9: Check pool I/O in real time (Linux with zpool iostat)

cr0x@server:~$ zpool iostat -v tank 2 3
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        28.1T  15.1T  3.10K    220   380M  12.1M
  raidz2-0  28.1T  15.1T  3.10K    220   380M  12.1M
    sda          -      -    520     35  63.5M  2.1M
    sdb          -      -    525     36  64.1M  2.1M
    sdc          -      -    518     35  63.2M  2.0M
    sdd          -      -    110     34  12.9M  2.0M
    sde          -      -    523     35  64.0M  2.1M
    sdf          -      -    524     35  64.3M  2.1M
----------  -----  -----  -----  -----  -----  -----

Ce que cela signifie : Encore une fois, sdd sous-performe par rapport aux autres. En RAIDZ, cela peut réduire le débit effectif et augmenter le temps de scrub.

Décision : Si un périphérique montre un débit/ops constamment inférieur, concentrez-vous là-dessus. Ne touchez pas aux tunables globaux tant que la parité des périphériques n’est pas restaurée.

Task 10: Confirm ashift and basic pool properties (performance baseline)

cr0x@server:~$ zdb -C tank | egrep 'ashift|vdev_tree' -n | head -n 8
64:        ashift: 12
120:    vdev_tree:
121:        type: 'root'
122:        id: 0

Ce que cela signifie : ashift: 12 implique des secteurs 4K. Si vous voyez ashift: 9 sur des disques 4K modernes, vous pouvez subir de l’amplification d’écritures et des comportements de performance étranges. Cela ne se verra pas toujours pendant un scrub (majoritairement lecture), mais cela peut empirer la performance générale du pool et l’overhead de resilver/scrub.

Décision : Si ashift est incorrect, la correction est généralement « reconstruire le pool correctement », pas « tuner plus ». Mettez-le sur la feuille de route.

Task 11: Check dataset compression and recordsize (workload interaction)

cr0x@server:~$ zfs get -o name,property,value -s local compression,recordsize tank/vmstore
NAME         PROPERTY     VALUE
tank/vmstore compression  lz4
tank/vmstore recordsize   128K

Ce que cela signifie : Pour des images VM, recordsize est souvent ajusté plus petit (comme 16K) selon les motifs I/O. Un grand recordsize n’est pas « faux », mais si votre workload est aléatoire 4K, vous pouvez générer plus de lectures par octet utile pendant le scrub et un surcoût opérationnel en général.

Décision : Ne changez pas recordsize imprudemment sur des données existantes. Mais si la douleur du scrub se corrèle à un dataset connu pour des I/O aléatoires petits, revoyez la conception du dataset pour la prochaine itération.

Task 12: Check for special vdevs (metadata) and their health

cr0x@server:~$ zpool status tank | egrep -n 'special|log|cache|spares' -A3
15:    special
16:      nvme0n1p2             ONLINE       0     0     0

Ce que cela signifie : Si vous avez un special vdev (souvent NVMe) stockant métadonnées/petits blocs, sa santé et sa latence peuvent dominer le comportement du scrub pour les pools riches en métadonnées. Un special vdev défaillant peut rendre l’ensemble du pool « lent » même si les HDD vont bien.

Décision : Si le scrub est lent sur un workload riche en métadonnées, vérifiez tôt la performance et les erreurs du special vdev.

Task 13: Check the actual device path and link speed (common hidden failure)

cr0x@server:~$ sudo hdparm -I /dev/sdd | egrep -i 'Transport|speed|SATA Version' | head -n 5
Transport: Serial, ATA8-AST, SATA 3.1
SATA Version is:  SATA 3.1, 6.0 Gb/s (current: 1.5 Gb/s)

Ce que cela signifie : Le disque supporte 6.0Gb/s mais est actuellement à 1.5Gb/s. C’est un fort indicateur de problèmes de lien, pas de « ZFS lent ».

Décision : Corrigez le chemin physique. Après réparation, confirmez la négociation à 6.0Gb/s et relancez iostat.

Task 14: Check scrub throttling-related module parameters (Linux OpenZFS)

cr0x@server:~$ sudo systool -m zfs -a 2>/dev/null | egrep 'zfs_scrub_delay|zfs_top_maxinflight|zfs_vdev_scrub_max_active' | head -n 20
  Parameters:
    zfs_scrub_delay        = "4"
    zfs_top_maxinflight    = "32"
    zfs_vdev_scrub_max_active = "2"

Ce que cela signifie : Ces valeurs influencent l’agressivité d’émission d’I/O du scrub. Plus agressif n’est pas toujours mieux ; vous pouvez augmenter la profondeur de queue et la latence pour les applications, et parfois ralentir le scrub à cause du thrash.

Décision : Si le scrub est lent mais sain et que vous avez de la marge (faible latence d’impact, faible util), envisagez un tuning. Si le système est déjà chaud, ne « réparez » pas en augmentant la bagarre I/O.

Task 15: Confirm TRIM and autotrim behavior (SSD pools)

cr0x@server:~$ zpool get autotrim tank
NAME  PROPERTY  VALUE     SOURCE
tank  autotrim  off       default

Ce que cela signifie : Sur des pools SSD, autotrim peut affecter la performance à long terme. Pas directement la vitesse de scrub, mais cela change le comportement du pool sous lectures/écritures soutenues et GC, ce qui peut rendre les scrubs « aléatoirement horribles ».

Décision : Si vous êtes sur SSDs et observez des chutes périodiques de performance, évaluez l’activation d’autotrim dans une fenêtre de changement contrôlée.

Task 16: Check if you’re accidentally scrubbing frequently

cr0x@server:~$ sudo grep -R "zpool scrub" -n /etc/cron* /var/spool/cron 2>/dev/null | head
/etc/cron.monthly/zfs-scrub:4: zpool scrub tank

Ce que cela signifie : Les scrubs mensuels sont courants. Les scrubs hebdomadaires peuvent convenir pour des petits pools, mais sur de grands pools cela peut signifier que vous êtes effectivement toujours en train de scrubber, et les opérateurs finissent par ignorer le signal.

Décision : Choisissez une cadence adaptée au média et au risque. Si un scrub ne se termine jamais avant le suivant, vous avez transformé les vérifications d’intégrité en bruit de fond.

Trois mini-récits en entreprise depuis les tranchées du scrub

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une entreprise de taille moyenne exploitait un cluster de virtualisation sur ZFS. Rien d’exotique : RAIDZ2, gros disques SATA, un pool par nœud. Les scrubs étaient programmés mensuellement et prenaient « un certain temps ». On s’y était habitué.

Puis un mois l’ETA du scrub a commencé à augmenter. Ce n’était pas dramatique au début — juste un jour de plus. La personne d’astreinte a supposé que c’était de la contention de workload : des jobs de fin de trimestre. On a laissé courir. « Les scrubs sont lents, ça finira par passer. »

Deux jours plus tard, la latence des VM a grimpé, redescendu, puis remonté. Zpool status affichait toujours ONLINE, aucune erreur évidente. L’hypothèse a tenu : « c’est occupé ». Personne n’a regardé les stats disque — erreur.

Quand quelqu’un a finalement lancé iostat -x, un disque affichait des r_await de 300–800 ms, alors que les autres étaient à 15–25 ms. SMART montrait des secteurs pending. Le disque ne mourait pas brutalement ; il mourait poliment en traînant le vdev. C’est le pire type car cela ressemble à de la « lenteur normale » jusqu’au moment où ce n’en est plus.

Ils ont remplacé le disque. Le taux de scrub est revenu à la normale immédiatement. La vraie leçon n’était pas « remplacer les disques plus vite ». C’était : ne supposez jamais que la lenteur du scrub est due au workload tant que vous n’avez pas vérifié que tous les périphériques sont sains. Le scrub touche parfois les mauvais secteurs en premier. C’est votre système d’alerte précoce. Servez-vous-en.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une autre organisation avait des fenêtres de maintenance strictes. Ils voulaient que les scrubs se terminent pendant le weekend, coûte que coûte. Quelqu’un a trouvé les tunables de scrub et a décidé de « monter le volume ». Ils ont augmenté la concurrence et réduit les délais. Le scrub est devenu agressif, et le chiffre de débit avait l’air impressionnant — pendant environ une heure.

Puis la latence applicative a grimpé. Les hyperviseurs ont commencé à logger des stalls de stockage. Les utilisateurs se sont plaints lundi matin de « lenteurs aléatoires ». L’équipe a d’abord pointé le réseau (comme toujours), puis ZFS, puis l’hyperviseur. Triangle classique du déni.

Ce qui s’était réellement passé était plus ennuyeux : le pattern I/O du scrub a expulsé le cache du workload et a enfoncé les files disque. Les HDD ont atteint près de 100 % d’util, avec des temps de service élevés. Certaines lectures applicatives sont devenues des monstres de tail-latency. Le scrub lui-même n’a pas fini plus vite au global — car lorsque les queues ont grandi, le débit effectif a chuté et les réessais ont augmenté.

Ils ont annulé le tuning et déplacé les scrubs vers des périodes de faible trafic. L’« optimisation » était réelle, mais son impact système était négatif. L’astuce de performance la plus efficace en stockage reste la planification : ne forcez pas vos utilisateurs.

Blague courte #2 : Le tuning stockage, c’est comme la politique de bureau — si vous poussez trop fort, tout le monde ralentit et, étrangement, c’est quand même votre faute.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la journée

Une équipe fintech utilisait OpenZFS sur Linux pour une charge de type grand livre. Les scrubs étaient traités comme une activité de maintenance formelle : programmés, monitorés et comparés à des baselines historiques. Pas d’héroïsme. Juste des graphiques et de la discipline.

Ils gardaient un runbook simple : après chaque scrub, enregistrer la durée, le débit moyen issued, et les octets réparés. Si « repaired » était non nul, cela déclenchait une vérification approfondie : logs noyau, SMART long test, et revue de tout changement matériel récent.

Un mois, un scrub s’est terminé avec une petite quantité réparée — rien d’alarmant en soi. Mais c’était le deuxième mois consécutif. Leur logique baseline l’a signalé. La personne d’astreinte a creusé et a trouvé des erreurs CRC intermittentes sur un chemin disque. Pas assez pour faire échouer le disque net, mais suffisant pour altérer des bits occasionnellement sous charge. Exactement le type de défaut qui vous ruine la journée six mois plus tard.

Ils ont remplacé le câble backplane et déplacé le disque vers un autre port HBA. Les réparations ont cessé. Pas d’incident, pas de perte de données, pas de rapport dramatique. C’est le genre de victoire qui n’est jamais célébrée parce que rien n’a explosé. Elle devrait l’être.

Erreurs fréquentes : symptôme → cause racine → correction

Cette section est volontairement directe. Ce sont des schémas qui reviennent en production, encore et encore, parce que les humains sont constants.

ETA du scrub qui augmente au fil du temps

  • Symptôme : L’ETA passe de « 12 heures » à « 2 jours » pendant le scrub.
  • Cause racine : Un périphérique réessaie des lectures (problèmes média) ou le lien clignote ; alternativement, la charge applicative a monté.
  • Correction : Lancez iostat -x et zpool iostat -v pour identifier un disque lent ; vérifiez dmesg et SMART. Si aucun disque solitaire n’est lent, corrélez avec la charge et replanifiez le scrub.

Le scrub est « lent » seulement pendant les heures ouvrées

  • Symptôme : Le scrub rampe de 9h à 17h et accélère la nuit.
  • Cause racine : Contention avec les workloads de production ; ZFS et/ou l’ordonnanceur OS priorisent l’I/O de premier plan.
  • Correction : Planifiez les scrubs sur des fenêtres de faible trafic ; préférez le throttling à l’agressivité. Ne montez pas la concurrence sans réflexion.

Un disque montre une attente 10× supérieure aux autres

  • Symptôme : Dans iostat -x, un lecteur a un r_await élevé ou un %util incohérent.
  • Cause racine : Disque mourant, comportement SMR sous stress, câble/backplane défectueux, port négocié à vitesse réduite.
  • Correction : Vérifiez dmesg et SMART, confirmez la vitesse de lien, changez câble/port, remplacez le disque si des secteurs pending ou des uncorrectables apparaissent.

Le scrub provoque des timeouts applicatifs

  • Symptôme : Pics de latence, timeouts, profondeur de file qui grandit ; le scrub semble « DoS » le système.
  • Cause racine : I/O du scrub trop agressive, mauvaise isolation des workloads, trop peu de vdevs, pool HDD servant des I/O aléatoires sans disques suffisants.
  • Correction : Réduisez l’agressivité du scrub ; replanifiez ; ajoutez des vdevs ou déplacez le workload vers SSD/NVMe ; envisagez un special vdev pour les métadonnées. Ne comptez pas sur un seul vdev RAIDZ large pour agir comme un array.

Le scrub rapporte des octets réparés de façon répétée

  • Symptôme : À chaque scrub, des données sont réparées.
  • Cause racine : Source chronique de corruption : disque défectueux, câble défaillant, contrôleur instable, ou problèmes mémoire (oui, la mémoire).
  • Correction : Inspectez le chemin matériel de bout en bout ; lancez des SMART long tests ; vérifiez les logs ECC si disponibles ; considérez une fenêtre de test mémoire contrôlée. Les données réparées sont un cadeau — ne l’ignorez pas.

Le scrub est lent sur un pool SSD « sans raison »

  • Symptôme : Pool NVMe/SSD plus lent que prévu, parfois avec des chutes périodiques.
  • Cause racine : Throttling thermique, GC SSD, mauvais comportement TRIM, problèmes de lien PCIe, ou goulot du special vdev.
  • Correction : Vérifiez températures et vitesse PCIe ; revoyez autotrim ; confirmez le firmware ; assurez-vous que le special vdev n’est pas saturé ou en erreur.

Le scrub ne se termine jamais avant le scrub suivant

  • Symptôme : Toujours en scrub ; les opérateurs finissent par ne plus y prêter attention.
  • Cause racine : Pool surdimensionné pour le média, cadence trop fréquente, ou scrub redémarré par automatisation.
  • Correction : Réduisez la cadence ; assurez-vous que les scrubs ne sont pas relancés inutilement ; envisagez des changements architecturaux (plus de vdevs, média plus rapide) si les vérifications d’intégrité ne finissent pas dans une fenêtre raisonnable.

La vitesse de scrub est bien inférieure aux calculs disques bruts

  • Symptôme : « Nous avons N disques, chacun fait X MB/s, alors pourquoi pas N×X ? »
  • Cause racine : Le scrub lit les blocs alloués, pas forcément séquentiels ; overhead métadonnées ; parité RAIDZ ; fragmentation ; et le pool peut être proche du plein, ce qui aggrave tout.
  • Correction : Comparez avec vos propres baselines historiques, pas les datasheets des vendeurs. Si proche du plein, libérez de l’espace. Si la fragmentation est sévère, envisagez une réorganisation planifiée via réplication vers un pool frais.

Checklists / step-by-step plans

Étape par étape : décider si un scrub lent est « normal »

  1. Capturer le statut actuel. Lancez zpool status -v. Sauvegardez-le dans votre ticket/chat.
  2. Recherchez des erreurs. Des compteurs READ/WRITE/CKSUM non nuls ou des octets « repaired » changent l’urgence.
  3. Mesurez le taux issued. Si issued est stable et dans votre intervalle historique, c’est probablement normal.
  4. Vérifiez la latence par disque. Utilisez iostat -x (Linux) et identifiez les outliers.
  5. Consultez les logs. Une ligne dans dmesg sur des resets peut expliquer des jours de douleur de scrub.
  6. Vérifiez SMART. Secteurs pending, uncorrectable et erreurs CRC décident du remplacement matériel.
  7. Corrélez avec la charge. Si le scrub est lent seulement sous charge, corrigez la planification et/ou le throttling.
  8. Ne tunez qu’ensuite. Et faites un changement à la fois avec un plan de rollback.

Étape par étape : si vous trouvez un disque lent pendant le scrub

  1. Confirmez que c’est constant : iostat -x 2 5 et zpool iostat -v 2 5.
  2. Vérifiez une négociation de lien réduite : hdparm -I pour SATA, ou logs contrôleur pour SAS.
  3. Vérifiez les logs noyau pour resets/timeouts : dmesg -T filtré.
  4. Vérifiez SMART : des secteurs pending/offline uncorrectable signifient vie empruntée.
  5. Changez d’abord les éléments peu coûteux (câble/port) si le chemin pointe vers un problème de lien.
  6. Remplacez le disque si des problèmes média sont présents ou si les erreurs persistent après correction du chemin.
  7. Après remplacement, lancez un autre scrub ou au moins un plan de vérification ciblé conforme à vos standards opérationnels.

Étape par étape : si le scrub est sain mais perturbe la performance

  1. Confirmez qu’aucun périphérique n’est malade (latence outlier, erreurs).
  2. Confirmez si le scrub est déjà bridgé (vérifiez les tunables et la profondeur I/O observée).
  3. Déplacez la planification du scrub vers des périodes de faible trafic ; étalez entre pools/nœuds.
  4. Si vous devez scrubber pendant les heures ouvrées, bridez plutôt qu’accélérez.
  5. Réévaluez la topologie du pool si vous ne pouvez pas compléter les scrubs durant une fenêtre de maintenance.

FAQ

1) Quelle est une vitesse « normale » pour un scrub ZFS ?

Normal, c’est ce que fait votre pool quand il est sain, faiblement chargé et sans erreurs. Utilisez votre propre durée historique de scrub et le débit issued comme baseline. Les spécifications séquentielles des disques ne sont pas une promesse pour le scrub.

2) Pourquoi scanned diffère de issued dans zpool status ?

« Scanned » reflète la progression logique à travers les blocs ; « issued » reflète l’I/O réellement envoyée/achetée aux vdevs. De gros écarts peuvent venir du cache, du readahead ou de l’attente sur des périphériques lents. Si issued est bas et la latence élevée, cherchez un disque traînant.

3) Un scrub lit-il l’espace libre ?

En général, le scrub vérifie les blocs alloués (ce qui est réellement utilisé). Ce n’est pas un scan de surface complet de chaque secteur. C’est pourquoi un disque peut encore cacher des secteurs défectueux qui ne se révéleront qu’à l’écriture ou à une lecture ultérieure.

4) Dois-je arrêter un scrub s’il est lent ?

Si le scrub est sain mais impacte les SLO de production, le mettre en pause/arrêter peut être raisonnable — puis le replanifier. Si vous constatez des erreurs ou des réparations, l’arrêter ne fait que retarder l’information dont vous avez probablement besoin. Traitez le problème matériel sous-jacent.

5) À quelle fréquence devrais-je lancer des scrubs ?

Une cadence courante est mensuelle pour de grands pools HDD, parfois hebdomadaire pour des environnements petits ou à risque élevé. La bonne réponse dépend du média, de la redondance et de la rapidité à laquelle vous voulez découvrir les erreurs latentes. Si la cadence dépasse votre capacité à terminer les scrubs, ajustez — ne normalisez pas « toujours en scrub ».

6) Le scrub a trouvé et réparé des données. Suis-je désormais en sécurité ?

Vous êtes plus en sécurité que si rien n’avait été fait, mais vous n’êtes pas « tiré d’affaire ». Les réparations signifient qu’une corruption a eu lieu sous ZFS. Si les réparations se répètent, réalisez une RCA (root cause analysis) sur disques, câbles, contrôleurs et potentiellement la mémoire.

7) RAIDZ est-il intrinsèquement lent au scrub comparé aux mirrors ?

Les mirrors sont souvent plus rapides et prévisibles pour les lectures car ils peuvent load-balancer et n’ont pas de reconstruction de parité pour lire. RAIDZ peut être très correct quand il est sain, mais des vdevs larges sont plus sensibles à un disque lent et aux patterns I/O aléatoires.

8) Le tuning peut-il rendre les scrubs dramatiquement plus rapides ?

Parfois modestement, si vous avez de la marge et des valeurs par défaut conservatrices. Mais le tuning n’est pas un substitut à plus de plateaux, du meilleur média ou à la correction d’un chemin disque défaillant. De plus : le tuning peut se retourner contre vous en augmentant la latence et en diminuant le débit effectif.

9) Pourquoi le scrub est lent sur un pool majoritairement vide ?

Parce que « vide » ne veut pas dire « simple ». Un pool avec des millions de petits fichiers, beaucoup de métadonnées, des snapshots ou une forte fragmentation peut scrubber lentement même si l’espace utilisé est faible. Le scrub touche les blocs alloués ; les allocations riches en métadonnées ne sont pas des bonbons séquentiels.

10) Quelle est la différence entre scrub et resilver, et pourquoi cela importe pour la lenteur ?

Le scrub vérifie les données existantes et répare la corruption ; le resilver reconstruit des données vers un périphérique remplacé/retourné. Le resilver a souvent une priorité et des patterns différents, et peut être plus orienté écriture. Confondre les deux vous fera mal interpréter attentes de performance et urgence.

Conclusion : étapes pratiques suivantes

Les scrubs lents ne sont pas intrinsèquement effrayants. En fait, un scrub lent sur un grand pool chargé est souvent le signe que ZFS se comporte de façon responsable. Ce qui est inquiétant, c’est la lenteur inexpliquée, surtout si elle vient avec des outliers par disque, des resets noyau ou des réparations récurrentes.

Utilisez cette séquence par défaut :

  1. Lancez zpool status -v et décidez si c’est un événement de fiabilité (erreurs/repairs) ou un problème de planification/perf.
  2. Lancez iostat -x et zpool iostat -v pour trouver le périphérique lent ou confirmer la contention.
  3. Vérifiez dmesg et SMART pour les pannes évidentes du chemin matériel.
  4. Ce n’est qu’ensuite que vous envisagez le tuning et la reprogrammation, et mesurez l’impact par rapport à votre baseline historique.

Une idée paraphrasée de W. Edwards Deming s’applique au travail opérationnel : « Sans données, vous n’êtes qu’une personne avec une opinion. » La lenteur d’un scrub est votre opportunité de collecter des données avant de collecter des pannes.

Debian 13 : Service ne démarre plus après une modification de configuration — corrigez-le en lisant les bonnes lignes de log (cas n°1)

Vous avez modifié une configuration. Vous avez agi de manière responsable. Vous avez même laissé un commentaire comme « temporary » qui sera assurément toujours là en 2027. Maintenant le service ne démarre plus, votre monitoring sonne, et systemctl status se montre évasif.

La bonne nouvelle : Debian 13 avec systemd vous fournit tout ce qu’il faut pour résoudre cela rapidement — à condition d’arrêter de lire les mauvaises lignes de log. La mauvaise nouvelle : la plupart des gens font exactement ça, regardent les trois dernières lignes de sortie, puis commencent des sacrifices rituels à « the cache ». Ne le faites pas. Lisez les bonnes lignes, dans le bon ordre, et vous réparerez cela en quelques minutes.

Cas n°1 : modification de configuration → le service ne démarre plus (ce qui s’est réellement passé)

C’est le schéma le plus courant que je vois sur les systèmes Debian : un service est sain, quelqu’un modifie un fichier de configuration, puis redémarre le service. Le redémarrage échoue. L’astreint lance systemctl status, voit « failed with result ‘exit-code’ », et commence à deviner.

La correction se trouve presque toujours dans les logs, mais pas dans la partie que les gens lisent en premier. La ligne utile est généralement :

  • Plus ancienne que la ligne « Main process exited… »
  • Émise par un processus auxiliaire (comme ExecStartPre) qui a testé la config puis s’est arrêté
  • Ou émise par le démon lui‑même, une seule fois, puis enterrée sous le boilerplate systemd

Pour le cas n°1, imaginez un service typique avec une étape de test de configuration :

  • ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;
  • ExecStart=/usr/sbin/nginx -g daemon on; master_process on;

Le redémarrage échoue non parce que systemd est mystérieux, mais parce que le test de pré‑vol a détecté une erreur de syntaxe, un chemin d’include invalide, ou un problème de permission sur un fichier référencé. Les « bonnes lignes de log » sont celles qui décrivent cet échec de pré‑vol. Votre travail est de les extraire proprement, sans vous noyer dans du bruit sans rapport.

Blague n°1 (courte, pertinente) : Un redémarrage de service, c’est comme un parachute — si vous ignorez l’inspection, vous saurez quand il a marché.

Quelques faits et un peu d’histoire qui expliquent pourquoi les logs ressemblent à ça

Comprendre le pourquoi Debian 13 se comporte ainsi vous rend plus rapide sous pression. Voici des faits concrets qui comptent quand un service refuse de démarrer après une modification de configuration :

  1. systemd est devenu le système d’init par défaut de Debian à partir de Debian 8 (Jessie). Cette décision a standardisé la gestion des services et les attentes en matière de journalisation, mais a aussi déplacé l’endroit où l’on cherche les erreurs.
  2. journald n’est pas un fichier texte. Les logs sont stockés dans un journal binaire et interrogés avec journalctl. Vous pouvez toujours forwarder vers syslog, mais la source canonique est le journal.
  3. systemctl status est un résumé, pas une investigation. Il affiche un extrait tronqué des logs et un état unitaire de haut niveau. Il sert à vous orienter vers des requêtes plus profondes, pas à les remplacer.
  4. Les unités systemd peuvent avoir plusieurs processus avant que le « vrai » démon ne démarre. ExecStartPre, des generators, des scripts wrapper et des fichiers d’environnement peuvent échouer avant même que le PID du service n’existe.
  5. Les codes de sortie sont standardisés, mais souvent trompeurs sans contexte. Un « exit status 1 » peut signifier « erreur de syntaxe », « permission refusée » ou « port déjà utilisé ». Il vous faut le message associé.
  6. Beaucoup de démons sont conçus pour échouer rapidement sur une config invalide. Nginx, Postfix, HAProxy et d’autres refusent volontairement de démarrer si les tests de config échouent — car démarrer avec une config partielle/invalide est pire.
  7. Le packaging Debian tend à ajouter des vérifications de sécurité. Les mainteneurs incluent fréquemment des validations pré‑start dans les unités ou scripts wrapper. C’est un bon point d’ingénierie, mais cela signifie que les erreurs peuvent provenir de scripts que vous n’aviez pas remarqués.
  8. L’ordre des logs peut être trompeur. journald est horodaté, mais des démarrages parallèles et plusieurs processus peuvent s’entrelacer. La « dernière ligne » n’est pas toujours « la cause ».
  9. La limitation de taux est réelle. journald peut rate‑limiter des services bruyants ; la première erreur peut être enregistrée, les 500 suivantes résumées. Si vous ne regardez que le résumé, vous ratez l’indice initial.

Une idée paraphrasée à garder en tête, attribuée correctement : Gene Kim (idée paraphrasée) : la fiabilité s’améliore quand vous créez des boucles de rétroaction rapides et raccourcissez la distance entre le changement et le diagnostic.

Playbook de diagnostic rapide (premières/deuxièmes/troisièmes vérifications)

Ceci est l’ordre qui gagne en production. Il est biaisé pour obtenir la cause racine en moins de cinq minutes, pas pour vous faire sentir occupé.

Premier : confirmez ce que systemd considère comme ayant échoué (vue au niveau de l’unité)

  • Obtenez l’état de l’unité, le code de sortie et la phase qui a échoué (pré‑start vs démarrage principal).
  • Extrait la ligne de commande exacte que systemd a lancée (y compris ExecStartPre).

Deuxième : extrayez la bonne tranche du journal (filtrée par temps et par unité)

  • Interrogez les logs pour cette unité, pour le dernier boot, avec le moins de bruit possible.
  • Puis élargissez la plage temporelle si nécessaire ; n’élargissez pas la portée d’abord.
  • Cherchez la première ligne d’erreur significative, pas la dernière ligne « exited ».

Troisième : lancez manuellement la validation de configuration du démon

  • La plupart des services ont un mode « tester la configuration puis quitter ».
  • Exécutez-le exactement comme le ferait systemd (même utilisateur, même environnement, même chemin de configuration).
  • Si la validation passe manuellement mais échoue sous systemd, suspectez les permissions, les fichiers d’environnement, AppArmor ou des différences de répertoire de travail.

Quatrième : décidez entre corriger, rollback ou contournement temporaire

  • Si c’est une erreur de syntaxe claire : corrigez-la maintenant, puis redémarrez.
  • Si vous n’êtes pas sûr et que la production brûle : faites un rollback vers la dernière config connue bonne et redémarrez.
  • Évitez les contournements « temporaires » comme commenter des étapes de validation sauf si vous comprenez le rayon d’impact.

Tâches pratiques : commandes, sortie attendue et décisions (12+)

Ces tâches sont écrites comme un SRE travaille réellement : lancez une commande, lisez la sortie, prenez une décision. Pas de discours de motivation. Chaque tâche inclut ce que signifie la sortie et ce que vous faites ensuite.

Task 1 : Vérifiez le statut de l’unité (mais lisez‑le correctement)

cr0x@server:~$ systemctl status nginx.service --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 10:14:03 UTC; 42s ago
   Duration: 2.103s
    Process: 21984 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)
        CPU: 29ms

Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57
Dec 30 10:14:03 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 10:14:03 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 10:14:03 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Ce que cela signifie : L’échec s’est produit dans ExecStartPre, avant le démarrage du démon nginx. C’est un échec de test de configuration, pas un crash en cours d’exécution.

Décision : Ne poursuivez pas les ports, les fichiers PID ou les limites du noyau. Corrigez la ligne de configuration référencée (app.conf:57) et relancez le test de configuration.

Task 2 : Affichez uniquement le journal pour cette unité (la dernière tentative, proprement)

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 60
Dec 30 10:14:03 server systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57
Dec 30 10:14:03 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 10:14:03 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 10:14:03 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Ce que cela signifie : Le journal confirme l’erreur de parser exacte. Pas besoin d’inférer.

Décision : Ouvrez le fichier, corrigez la syntaxe, puis testez de nouveau la configuration avant de redémarrer.

Task 3 : Récupérez les logs « depuis le redémarrage » quand le boot est bruyant

cr0x@server:~$ systemctl show -p ActiveEnterTimestampMonotonic nginx.service
ActiveEnterTimestampMonotonic=81234567890
cr0x@server:~$ journalctl -u nginx.service -b --no-pager --since "2 min ago"
Dec 30 10:14:03 server systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57
Dec 30 10:14:03 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 10:14:03 server systemd[1]: nginx.service: Failed with result 'exit-code'.

Ce que cela signifie : Vous avez limité les logs par temps plutôt que de traverser tout un boot.

Décision : Si l’erreur n’est pas dans cette fenêtre, élargissez à 10 minutes ; ne retirez pas encore le filtre unité.

Task 4 : Inspectez l’unité pour les vérifications pré‑start et les fichiers d’environnement

cr0x@server:~$ systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;
ExecStart=/usr/sbin/nginx -g daemon on; master_process on;
ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

Ce que cela signifie : L’échec n’est pas dans le démon long‑cours ; il se situe dans l’étape de validation. De plus, il n’y a pas d’EnvironmentFile= ici, donc moins de variables cachées.

Décision : Exécutez manuellement la commande de pré‑start exacte pour reproduire ; si elle échoue, corrigez la configuration. Si elle réussit, l’échec est environnemental (permissions, AppArmor, chemins d’include).

Task 5 : Lancez manuellement le test de configuration du démon (même commande)

cr0x@server:~$ sudo /usr/sbin/nginx -t -q -g "daemon on; master_process on;"
nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57

Ce que cela signifie : C’est une erreur de parsing déterministe de la configuration. Pas de bizarrerie systemd.

Décision : Corrigez le fichier et la ligne référencés. Ne redémarrez pas en boucle dans l’espoir que « ça se stabilise ». Ce ne sera pas le cas.

Task 6 : Localisez la ligne fautive et validez la structure des includes

cr0x@server:~$ nl -ba /etc/nginx/sites-enabled/app.conf | sed -n '45,70p'
    45  server {
    46      listen 443 ssl;
    47      server_name app.example.internal;
    48      include /etc/nginx/snippets/tls.conf;
    49
    50      location / {
    51          proxy_pass http://127.0.0.1:8080;
    52          proxy_set_header Host $host;
    53      }
    54
    55  }   # end server
    56
    57  }

Ce que cela signifie : Il y a une accolade fermante supplémentaire à la ligne 57.

Décision : Supprimez‑la, enregistrez, relancez le test de configuration. Si vous voyez souvent des déséquilibres d’accolades, adoptez une règle de style : un bloc par fichier, indentation cohérente, et un linter de configuration dans le CI.

Task 7 : Validez à nouveau, puis redémarrez (ne sautez pas l’étape de validation)

cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Ce que cela signifie : Il est maintenant sûr de redémarrer.

Décision : Redémarrez une seule fois. Si le redémarrage échoue maintenant, c’est un problème différent — ne supposez pas que c’est encore la syntaxe de la config.

cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl is-active nginx.service
active

Ce que cela signifie : Le service fonctionne.

Décision : Confirmez qu’il sert du trafic (health check local) et clôturez correctement l’incident.

Task 8 : Quand status est peu utile, affichez les logs complets avec filtrage par priorité

cr0x@server:~$ journalctl -u nginx.service -b -p warning --no-pager
Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57

Ce que cela signifie : Vous avez filtré sur warning et supérieur, donc vous ne lisez pas le « Started… » verbeux.

Décision : Utilisez‑le quand une unité est bavarde. Si rien n’apparaît au niveau warning/error, vous loggez peut‑être ailleurs ou vous avez un échec silencieux avant l’initialisation du logging.

Task 9 : Confirmez quels fichiers de config ont changé récemment (attrapez le vrai coupable)

cr0x@server:~$ sudo find /etc/nginx -type f -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort | tail -n 8
2025-12-30 10:12 /etc/nginx/sites-enabled/app.conf
2025-12-29 18:41 /etc/nginx/nginx.conf
2025-12-10 09:03 /etc/nginx/snippets/tls.conf
2025-11-21 15:22 /etc/nginx/mime.types

Ce que cela signifie : Vous pouvez corréler l’échec de démarrage avec l’édition la plus récente.

Décision : Si l’erreur référence un fichier inclus, vérifiez aussi son mtime. « Je n’ai changé qu’une ligne » n’est rarement toute l’histoire.

Task 10 : Si ce n’est pas une syntaxe, vérifiez les « permission denied » (classique après un durcissement)

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 30
Dec 30 10:20:11 server nginx[22310]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (13: Permission denied)
Dec 30 10:20:11 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
cr0x@server:~$ namei -l /etc/nginx/snippets/tls.conf
f: /etc/nginx/snippets/tls.conf
drwxr-xr-x root root /
drwxr-xr-x root root etc
drwxr-xr-x root root nginx
drwx------ root root snippets
-rw------- root root tls.conf

Ce que cela signifie : Les permissions du répertoire empêchent nginx (qui tourne en www-data après démarrage) ou son test de pré‑start de lire les includes.

Décision : Corrigez les permissions au minimum requis. Habituellement : bit d’exécution sur les répertoires pour la traversée et lecture du fichier pour l’utilisateur ou le groupe du service.

Task 11 : Validez l’utilisateur runtime et l’isolation du service

cr0x@server:~$ systemctl show nginx.service -p User -p Group -p DynamicUser -p ProtectSystem -p ReadWritePaths
User=
Group=
DynamicUser=no
ProtectSystem=no
ReadWritePaths=

Ce que cela signifie : Cette unité particulière n’utilise pas les directives de sandboxing systemd. Si vous voyez ProtectSystem=strict ou des ReadWritePaths serrés, les lectures/écritures de config peuvent être bloquées.

Décision : Si le sandboxing est activé, alignez‑le sur les besoins du démon plutôt que de le désactiver sans réfléchir. Ajoutez des ReadOnlyPaths/ReadWritePaths explicites dans un override.

Task 12 : Interprétez les raisons d’échec du point de vue de systemd (codes de sortie et signaux)

cr0x@server:~$ systemctl show nginx.service -p ExecMainStatus -p ExecMainCode -p Result
ExecMainStatus=1
ExecMainCode=exited
Result=exit-code

Ce que cela signifie : Le processus est sorti normalement avec le statut 1. Pas SIGKILL, pas OOM, pas timeout.

Décision : Concentrez‑vous sur la configuration, les paramètres et les permissions. Si vous voyez ExecMainCode=killed ou Result=timeout, c’est une branche différente.

Task 13 : Si le service flappe, arrêtez la boucle de redémarrages pendant que vous lisez les logs

cr0x@server:~$ sudo systemctl reset-failed nginx.service
cr0x@server:~$ sudo systemctl stop nginx.service
cr0x@server:~$ systemctl status nginx.service --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: inactive (dead)

Ce que cela signifie : Vous empêchez systemd de spammer les redémarrages pendant que vous déboguez. Cela rend aussi le journal plus lisible.

Décision : Faites cela quand Restart=always crée du bruit et de la charge. Puis redémarrez intentionnellement quand vous avez une correction.

Task 14 : Comparez les changements de config en toute sécurité avec les métadonnées dpkg (vérification liée au packaging)

cr0x@server:~$ dpkg -S /etc/nginx/nginx.conf
nginx-common: /etc/nginx/nginx.conf
cr0x@server:~$ sudo ls -l /etc/nginx/nginx.conf*
-rw-r--r-- 1 root root 1492 Dec 29 18:41 /etc/nginx/nginx.conf
-rw-r--r-- 1 root root 1479 Nov 21 15:22 /etc/nginx/nginx.conf.dpkg-dist

Ce que cela signifie : Vous avez peut‑être un fichier par défaut fourni par la distro ou un fichier en attente de fusion. Cela peut interagir avec votre changement.

Décision : Si le service a commencé à échouer après une mise à jour plus une édition de config, examinez .dpkg-dist/.dpkg-old et réconciliez consciemment.

Task 15 : Quand les logs manquent, confirmez la persistance journald et la limitation de taux

cr0x@server:~$ sudo grep -E '^(Storage|SystemMaxUse|RateLimitIntervalSec|RateLimitBurst)=' /etc/systemd/journald.conf | sed '/^#/d;/^$/d'
Storage=auto
RateLimitIntervalSec=30s
RateLimitBurst=1000
cr0x@server:~$ journalctl --disk-usage
Archived and active journals take up 384.0M in the file system.

Ce que cela signifie : Si Storage=volatile, vous perdez les logs au redémarrage. Si la limitation de taux est basse, vous pouvez manquer des erreurs répétées.

Décision : En production, conservez les logs sur disque et taillez-les de façon appropriée. Pour le débogage, augmentez temporairement les limites de taux si un service spamme, mais corrigez ensuite la cause du spam.

Blague n°2 (courte, pertinente) : « Ça marchait hier » n’est pas une preuve ; c’est juste le témoignage d’un témoin à la mémoire douteuse.

Trois mini-récits d’entreprise (et leurs enseignements)

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

L’équipe avait une flotte Debian exécutant un mélange de services web et API. Un après‑midi, une modification de configuration de routine est sortie : mise à jour des suites TLS, standardisation entre environnements. Quelqu’un a redémarré nginx sur un canary. Ça a échoué. Ils ont exécuté nginx -t manuellement ; ça a réussi. L’hypothèse s’est formée instantanément : « systemd est cassé sur cet hôte. »

Ils ont creusé les versions de paquet, les paramètres du noyau, même SELinux (qui n’était d’ailleurs pas activé). Pendant ce temps, le trafic a été drainé du nœud et l’autoscaler s’est alarmé. Ils ont continué à retenter des redémarrages « juste pour voir », ce qui est une excellente façon d’écraser la seule bonne ligne d’erreur avec une pile de redémarrages.

La correction était embarrassante de simplicité : l’unité systemd utilisait un chemin de config différent via un fichier d’environnement. Pas malveillant — juste historique. Le nginx -t manuel testait /etc/nginx/nginx.conf ; systemd testait /etc/nginx/nginx-canary.conf. Le fichier canary incluait un snippet qui n’existait pas sur cet hôte.

La leçon n’est pas « ne pas utiliser de fichiers d’environnement ». C’est : n’assumez jamais que votre reproduction manuelle correspond au service manager. Extrait la commande exacte ExecStartPre/ExecStart avec systemctl cat et exécutez celle‑ci. Si un fichier d’environnement existe, affichez‑le, et arrêtez de deviner.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Un groupe plateforme a décidé de « accélérer les déploiements » en passant de restart à reload quand c’était possible. Reload est moins coûteux : moins de churn de connexions, moins d’erreurs transitoires. Bonne intention. Puis ils l’ont généralisé à plusieurs services avec un script one‑size‑fits‑most.

Un service, un broker de messages, acceptait les signaux de reload mais ne rechargeait la configuration que partiellement. Pour certains paramètres il fallait un redémarrage complet, mais la commande de reload retournait quand même succès. Au fil du temps, la dérive de configuration s’est installée : la configuration en mémoire ne correspondait plus à celle sur disque, et on a perdu confiance dans les deux.

Finalement, un changement de configuration a introduit un paramètre qui aurait échoué à une validation de démarrage frais. Le reload n’a rien fait d’utile, a dit « OK », et le système a continué avec l’ancienne configuration. Quelques jours plus tard, un redémarrage de l’hôte a eu lieu. Le service a dû démarrer à froid, a lu la mauvaise config, et a refusé de démarrer. Cet échec est survenu pendant une fenêtre de maintenance, lieu favori pour rencontrer ses erreurs passées.

La leçon : reload n’est pas un déjeuner gratuit. Si vous choisissez reload comme optimisation, vous devez aussi imposer la validation de config dans le processus de changement et périodiquement effectuer des redémarrages contrôlés pour prouver que la configuration est réellement démarrable.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la situation

Un service interne lié à la finance tournait sur Debian, soutenu par une base de données et un front web. Ils avaient une politique de changement peu glamour : chaque modification de config devait être commitée dans un repo, et l’outil de déploiement exécutait toujours le test de configuration intégré du service avant de toucher systemd. Si le test échouait, le changement n’était tout simplement pas déployé.

Les gens se plaignaient. « Ça nous ralentit. » « Je peux le tester dans ma tête. » Le classique. Puis un jour un ingénieur senior a modifié une config en direct pendant un incident — parce que le service se comportait mal et qu’il fallait une atténuation rapide. L’édition comportait une erreur de quote subtile. Le prochain redémarrage aurait tué le service entièrement.

L’outil de déploiement a refusé d’appliquer le changement sans un test de config passant. C’était le but : des garde‑fous quand le stress rend tout le monde négligent. Ils ont corrigé la quote, retesté, puis redémarré en toute sécurité. Personne n’a été rappelé deux fois.

La leçon : la pratique ennuyeuse n’est pas le repo. C’est la porte de validation automatique plus un chemin de rollback prévisible. Ces deux éléments empêchent qu’une petite erreur devienne une panne.

Erreurs fréquentes : symptôme → cause racine → correction

Voici les récidivistes. Si votre service ne démarre plus après une modification de configuration, vous tomberez probablement dans l’une de ces catégories.

1) Symptom : systemctl status affiche « failed (Result: exit-code) » sans erreur utile

Cause racine : Vous ne voyez que le résumé. La ligne significative est plus ancienne ou tronquée.

Correction : Interrogez directement le journal et élargissez la tranche.

cr0x@server:~$ journalctl -u myservice.service -b --no-pager -n 200
...look for the first real error line...

2) Symptom : le service échoue instantanément après le redémarrage ; les logs mentionnent ExecStartPre

Cause racine : La validation pré‑start a échoué (syntax error, include manquant, directive invalide).

Correction : Exécutez la même validation manuellement et corrigez la configuration avant de redémarrer.

cr0x@server:~$ systemctl cat myservice.service | sed -n '/ExecStartPre/p'
ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;

3) Symptom : le test de config passe manuellement, échoue sous systemd

Cause racine : Chemin de config différent, utilisateur différent, environnement différent ou restrictions du sandbox.

Correction : Extrait la commande et l’environnement exacts de l’unité ; exécutez en tant qu’utilisateur du service.

cr0x@server:~$ systemctl show myservice.service -p Environment -p EnvironmentFiles
Environment=
EnvironmentFiles=/etc/default/myservice (ignore_errors=no)

4) Symptom : « Permission denied » sur includes, certificats, sockets, fichiers PID

Cause racine : Changement de durcissement (chmod/chown), nouveau chemin aux permissions restrictives, ou mismatch de l’utilisateur du service.

Correction : Tracez les permissions du chemin avec namei -l ; corrigez le bit d’exécution sur les répertoires et la lecture des fichiers.

5) Symptom : « Address already in use » après une modification de config

Cause racine : Vous avez changé l’écoute / le port ; un autre service l’occupe ; ou l’ancienne instance ne s’est pas arrêtée proprement.

Correction : Identifiez qui tient le port ; choisissez de revenir au port précédent, d’arrêter le service en conflit, ou de corriger l’activation de socket.

cr0x@server:~$ sudo ss -ltnp | grep ':443 '
LISTEN 0      511          0.0.0.0:443        0.0.0.0:*    users:(("haproxy",pid=1203,fd=7))

6) Symptom : l’unité affiche Result=timeout

Cause racine : Le démon bloque au démarrage (attente DNS, stockage, migrations) ou le timeout systemd est trop agressif pour un cold start.

Correction : Lisez les logs autour du blocage, puis ajustez TimeoutStartSec seulement si le travail de démarrage est légitime et borné.

7) Symptom : après une modification de config, le service « démarre » mais ne fonctionne pas

Cause racine : Vous avez utilisé reload et supposé que tout s’appliquait ; ou la config est acceptée mais sémantiquement incorrecte.

Correction : Exécutez un health check applicatif et confirmez la config active avec l’introspection du service si disponible. Redémarrez si nécessaire.

8) Symptom : le journal n’a aucune entrée pour l’unité

Cause racine : Le service logge vers un fichier (ou stdout est redirigé), journald est volatile, ou l’unité ne s’est jamais exécutée à cause d’un échec de dépendance.

Correction : Vérifiez systemctl list-dependencies et les paramètres de journald ; inspectez les fichiers de logs traditionnels si configurés.

Listes de contrôle / plan étape par étape (corrections sûres et rollback)

Étape par étape : diagnostiquer et corriger sans agiter le système

  1. Arrêtez la boucle de redémarrages si elle est présente. Si l’unité flappe, mettez‑la en pause pour pouvoir lire des logs stables.
  2. Lisez le résumé de l’unité. Identifiez si ExecStartPre a échoué ou si le processus principal est mort.
  3. Interrogez journald par unité et par boot. Ne commencez pas par les logs globaux.
  4. Extrait la commande de démarrage exacte. Lisez systemctl cat et vérifiez les drop‑ins.
  5. Exécutez le test de config du service manuellement. Même arguments, même chemin de config.
  6. Corrigez la plus petite chose qui permet au service de démarrer. Évitez les refontes pendant la réponse à l’incident.
  7. Redémarrez une fois, puis vérifiez au niveau applicatif. « active (running) » n’est pas la même chose que « sert du trafic ».
  8. Notez la ligne de cause racine. Collez la chaîne d’erreur exacte dans la note d’incident. Le vous du futur remerciera le vous du présent.

Plan de rollback : quand vous n’êtes pas sûr que votre correction est correcte

Si vous ne pouvez pas prouver la correction rapidement, faites un rollback. N’« itérez pas en production » pendant que le pager hurle.

  1. Sauvegardez la configuration cassée. Copiez‑la avec un timestamp pour l’analyser plus tard.
  2. Restaurez la dernière configuration connue bonne depuis votre repo ou sauvegardes.
  3. Validez la configuration. Lancez toujours le mode de test du démon.
  4. Redémarrez le service et vérifiez.
  5. Seulement après la récupération : déboguez le changement cassé dans un environnement contrôlé.
cr0x@server:~$ sudo cp -a /etc/nginx/sites-enabled/app.conf /root/app.conf.broken.$(date +%F-%H%M%S)
cr0x@server:~$ sudo cp -a /root/rollback/app.conf /etc/nginx/sites-enabled/app.conf
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl is-active nginx.service
active

Quand vous devez garder un service partiellement up (contrôle des dégâts)

Parfois vous ne pouvez pas tout corriger immédiatement, mais vous pouvez réduire l’impact :

  • Restaurez une configuration minimale qui sert une page de maintenance.
  • Désactivez le virtual host cassé tout en gardant les autres en service.
  • Dirigez temporairement le trafic hors du nœud, corrigez en isolation, puis réintégrez.

N’éliminez pas les étapes de validation pour « faire démarrer » à moins d’être certain que le démon ne démarrera pas dans un état corrompu. Ce chemin mène à une perte de données, et vous n’apprécierez pas le postmortem.

FAQ

1) Pourquoi systemctl status n’est‑il pas suffisant ?

Parce qu’il est volontairement compact. Il montre une petite queue de logs et un résumé d’état de l’unité. Utilisez‑le pour trouver le nom de l’unité, la phase d’échec, puis pivotez vers journalctl -u pour le vrai diagnostic.

2) Quelle est la meilleure commande journalctl pour cette situation ?

Habituellement :

cr0x@server:~$ journalctl -u myservice.service -b --no-pager -n 200

Si c’est verbeux, ajoutez -p warning ou restreignez par temps avec --since.

3) Comment savoir si l’échec est dû à la config ou à l’exécution ?

Cherchez si ExecStartPre a échoué (config/validation) versus le processus principal qui démarre puis meurt (runtime). systemctl status vous indique généralement quel processus a échoué.

4) Pourquoi le test de configuration manuel réussit parfois alors que systemd échoue ?

Environnement différent. systemd peut utiliser un fichier d’environnement, un répertoire de travail différent, du sandboxing, ou un contexte utilisateur différent. Reproduisez toujours en utilisant la ligne de commande exacte du fichier d’unité.

5) Comment voir les drop‑in overrides qui peuvent changer le comportement ?

cr0x@server:~$ systemctl status myservice.service --no-pager
...look for "Drop-In:" lines...
cr0x@server:~$ systemctl cat myservice.service
...includes /etc/systemd/system/myservice.service.d/*.conf if present...

6) Quand dois‑je utiliser reload plutôt que restart ?

Seulement quand le service documente que reload applique les changements que vous avez faits, et que vous disposez d’une étape de validation. Si vous avez un doute, redémarrez pendant une fenêtre sûre ou après avoir drainé le trafic.

7) Que faire s’il n’y a aucun log pour l’unité ?

Alors soit l’unité ne s’est pas exécutée, soit journald ne conserve pas les logs, soit les logs vont ailleurs (comme /var/log/*). Vérifiez les dépendances et les paramètres de journald, et inspectez les logs fichiers si le service les configure.

8) Comment détecter rapidement si c’est un problème de permissions ?

Cherchez « Permission denied » dans le journal, puis tracez le chemin de fichier avec namei -l. Les problèmes de permissions viennent souvent d’un bit d’exécution manquant sur un répertoire ou d’un durcissement qui a oublié l’utilisateur du service.

9) Quelle est la façon la plus sûre d’éviter cette classe de panne ?

Automatisez la validation de configuration (mode test du démon) avant restart/reload, gardez les configs sous contrôle de version, et rendez le rollback trivial. L’objectif est d’attraper la ligne cassée avant qu’elle n’atteigne systemd.

Conclusion : étapes suivantes pour éviter la répétition des incidents

Un service Debian 13 qui échoue après une modification de configuration n’est rarement un mystère. C’est généralement une ligne d’erreur précise que vous n’avez pas extraite proprement. Lisez l’unité pour savoir ce qui a été exécuté. Lisez le journal limité à l’unité pour savoir ce qui a échoué. Puis validez la config manuellement en utilisant la commande exacte que systemd utilise.

Actions pratiques :

  • Ajoutez une étape de test de configuration avant déploiement pour chaque service qui le supporte.
  • Formez votre équipe à traiter systemctl status comme un pointeur, pas comme un diagnostic final.
  • Faites du rollback une opération de première classe (copier, restaurer, valider, redémarrer).
  • Standardisez un runbook « diagnostic rapide » et gardez‑le près de la rotation du pager.

Faites cela, et la prochaine fois qu’un service refusera de démarrer, vous passerez votre temps à corriger le vrai problème — pas à discuter avec un écran de résumé.