À 02:13, votre pager (ou votre client) vous indique que quelque chose est cassé. Vous vous connectez en SSH, lancez journalctl -xe et vous êtes accueilli par une cascade de messages qui semblent tous également suspects. La moitié sont « normales ». L’autre moitié sont « pas normales, mais pas la cause ». Quelque part dans ce flot se trouve la ligne qui compte : la première erreur qui a fait tout le reste hurler.
Il s’agit de trouver cette ligne rapidement, de prouver qu’elle est causale (pas seulement du bruit), et d’acheminer le résultat dans un e‑mail ennuyeux et fiable vers vous-même. Pas une démonstration de « plateforme d’analyse de logs ». Une habitude de production.
Un modèle mental pratique : symptômes, déclencheurs et le premier événement fautif
Les journaux d’événements ne forment pas un récit. Ils forment une foule. Votre travail est de trouver la personne qui a donné le premier coup.
Trois catégories à séparer
- Symptômes : nouvelles tentatives, temporisations, « connection reset », « failed to send », « context deadline exceeded », « upstream unavailable ». Ce sont les services en aval qui expriment leur douleur.
- Déclencheurs : crash d’un processus, expiration d’un certificat, montage du système de fichiers en lecture seule, fluctuation réseau, kill OOM du noyau. Ce sont typiquement proches de la cause racine.
- Bruit : contrôles de santé périodiques, tâches cron, sondes de vivacité, redémarrages attendus, logs de debug activés en production, avertissements de dépréciation. Le bruit n’est pas « sans importance » ; il n’est juste pas utile pour l’instant.
Quand les gens disent « les logs sont trop bruyants », cela signifie généralement : ils n’ont jamais conçu de méthode pour transformer un flux de logs en chaîne causale. Le bruit, c’est l’information que vous ne savez pas indexer.
La règle du « premier événement fautif »
La plupart des incidents suivent une de ces formes :
- Un échec brutal : par ex. erreur d’E/S disque → système de fichiers remonté en lecture seule → panique de la base de données → erreurs de l’API.
- Un échec progressif : par ex. pic de latence → file d’attente de requêtes qui grossit → saturation du pool de threads → temporisations généralisées.
- Une mauvaise configuration : par ex. rotation de certificat manquée → handshakes TLS échouent → contrôles de santé tombent → autoscaler multiplie les actions.
- Le mensonge du « rien n’a changé » : une dépendance a changé hors de votre contrôle (heure, DNS, déploiement en amont, plan de contrôle cloud).
Le geste le plus efficace est d’identifier le premier horodatage où la réalité a divergé de la normale, puis de marcher en avant et observer l’expansion du rayon d’impact. Marchez en arrière aussi, mais l’analyse rétrograde sert surtout à éliminer les fausses pistes.
Les journaux ne prennent sens que si vous alignez les horloges
Si les horloges de votre flotte dérivent, la corrélation est une fiction. Corrigez NTP/chrony avant de vous lancer dans du parsing astucieux. Le temps est la clé primaire des opérations.
Une idée paraphrasée qui vaut d’être punaisée à votre écran (et vous verrez pourquoi dans le playbook) : idée paraphrasée
— John Allspaw, sur la valeur d’une réponse aux incidents fondée sur des preuves et l’apprentissage à partir du comportement réel des systèmes.
Faits et histoire intéressants qui rendent les journaux modernes étranges
- Syslog précède la plupart de votre pile. Il a été conçu dans les années 1980 pour des messages texte simples, pas pour des payloads JSON et des trace IDs.
- Les timestamps RFC 3164 de syslog n’incluent pas l’année. C’est pourquoi certains parseurs devinent et se trompent parfois autour du Nouvel An.
- Le journal de systemd stocke des champs structurés. Vous pouvez filtrer par
_SYSTEMD_UNIT,PID,_COMMet plus, sans regex sur le texte libre. - Le ring buffer du noyau n’est pas une archive fiable.
dmesgest un tampon circulaire ; sous contrainte, les messages les plus importants peuvent être écrasés en premier. - Les niveaux de logs sont des constructions sociales. De nombreuses applications traitent « ERROR » comme « je veux attirer l’attention » et « WARN » comme « je veux aussi attirer l’attention, mais avec moins de honte ».
- rsyslog et syslog‑ng ont été conçus pour le débit. Ils peuvent perdre des messages sous charge s’ils sont mal configurés ou si l’I/O disque flanche ; les « logs manquants » peuvent être un symptôme.
- Les alertes e‑mail précèdent les webhooks de plusieurs décennies. SMTP est ennuyeux, omniprésent et reste l’un des mécanismes de notification les plus fiables quand les API sont en feu.
- La limitation de débit de journald est à double tranchant. Elle peut sauver votre disque lors d’une défaillance spammée, mais elle peut aussi masquer la fréquence d’un motif d’erreur.
- Les défaillances de stockage chuchotent souvent en premier. Erreurs UDMA CRC, réinitialisations de lien ou erreurs de checksum ZFS peuvent apparaître bien avant une « erreur d’E/S » manifeste.
Et oui, vos logs contiendront au moins une ligne techniquement correcte mais émotionnellement inutile.
Blague 1 : Si votre plan de réponse aux incidents est « tail les logs et ressentir des émotions », félicitations — vous venez d’inventer le débogage artisanal.
Playbook de diagnostic rapide : premières / deuxièmes / troisièmes vérifications
Voici la séquence que j’utilise quand je ne sais pas encore ce qui cloche. Elle est optimisée pour « trouver rapidement le goulot d’étranglement », pas pour construire un récit parfait. Vous pourrez faire le récit parfait plus tard, lors du postmortem, quand le système sera stable et que votre café sera légal.
Première étape : établir la fenêtre d’incident et le rayon d’impact
- Choisir une fenêtre temporelle. Commencez par « quand les utilisateurs ont remarqué » et remontez de 10–30 minutes en arrière.
- Identifier les hôtes et unités affectés. Est‑ce un nœud, une zone de disponibilité, un service, ou tout le cluster ?
- Rechercher la première erreur sévère. Noyau, stockage, OOM, crash de service, échec TLS.
Deuxième étape : vérifier les « trois gros » contraintes de ressources
- Saturation CPU ou throttling ? Recherchez pics de charge, latence du scheduler, throttling cgroup.
- Pression mémoire ? OOM kills, swap, tempêtes de reclaim.
- I/O et stockage ? Latence disque, erreurs système de fichiers, réinitialisations de contrôleur, événements ZFS, profondeur de queue.
Troisième étape : confirmer la causalité par la corrélation
- Corréler les horodatages. L’erreur applicative suit‑elle l’événement noyau/stockage ?
- Trouver la « première occurrence ». Le message le plus ancien du motif est souvent le déclencheur.
- Valider avec un second signal. Métriques si vous en avez ; sinon, utilisez d’autres logs (auth, kernel, stockage).
Quand vous êtes bloqué
- Basculez du grep au filtrage par origine : unité, PID, exécutable, ID de conteneur.
- Réduisez la portée : un hôte, une minute, une unité.
- Regardez le noyau et la couche stockage même si vous « savez » que c’est un bug applicatif. Vous ne savez pas.
Tâches pratiques (commandes, sorties, décisions)
Ce sont des tâches réelles que vous pouvez exécuter sur une machine Linux avec systemd. Chaque tâche inclut : la commande, ce que signifie la sortie, et la décision suivante. Le but est de transformer la « soupe de logs » en une séquence de décisions.
Tâche 1 : Confirmer la sanity de l’horloge (car la corrélation en dépend)
cr0x@server:~$ timedatectl
Local time: Tue 2026-02-05 02:21:18 UTC
Universal time: Tue 2026-02-05 02:21:18 UTC
RTC time: Tue 2026-02-05 02:21:18
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Signification : Vous voulez « System clock synchronized: yes » et NTP actif.
Décision : Si les horloges ne sont pas synchronisées, corrigez d’abord le temps (chrony/systemd-timesyncd) sinon votre corrélation de logs mentira.
Tâche 2 : Délimiter rapidement la fenêtre d’incident
cr0x@server:~$ journalctl --since "2026-02-05 01:45:00" --until "2026-02-05 02:15:00" -p err..alert --no-pager
Feb 05 01:52:11 server kernel: blk_update_request: I/O error, dev sdb, sector 1946152832 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
Feb 05 01:52:11 server kernel: Buffer I/O error on dev sdb1, logical block 243269104, async page read
Feb 05 01:52:12 server systemd[1]: postgresql.service: Main process exited, code=killed, status=9/KILL
Feb 05 01:52:12 server systemd[1]: postgresql.service: Failed with result 'signal'.
Signification : Ceci montre seulement les événements de haute gravité. L’erreur d’I/O noyau apparaît avant que PostgreSQL ne meure. Cet ordre importe.
Décision : Traitez le stockage comme suspect immédiatement ; ne perdez pas 45 minutes à « tuner Postgres » alors que votre disque retourne des erreurs d’E/S.
Tâche 3 : Identifier la première occurrence d’un motif
cr0x@server:~$ journalctl --since "2026-02-05 01:00:00" | grep -m1 -E "I/O error, dev sdb|Buffer I/O error"
Feb 05 01:52:11 server kernel: blk_update_request: I/O error, dev sdb, sector 1946152832 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
Signification : -m1 s’arrête à la première correspondance, vous donnant la première apparition connue dans ce flux.
Décision : Utilisez cet horodatage comme point pivot. Tout ce qui suit est potentiellement une conséquence.
Tâche 4 : Filtrer les logs par unité systemd au lieu de tout greper
cr0x@server:~$ journalctl -u postgresql.service --since "2026-02-05 01:45:00" --no-pager | tail -n 12
Feb 05 01:52:10 server postgres[21455]: LOG: could not read block 243269104 in file "base/16384/2619": read only 0 of 8192 bytes
Feb 05 01:52:10 server postgres[21455]: LOG: unexpected pageaddr 0/0 in log segment 0000000100000000000000A3, offset 0
Feb 05 01:52:11 server postgres[21455]: PANIC: could not read from log segment 0000000100000000000000A3 at offset 0: read only 0 of 8192 bytes
Feb 05 01:52:12 server systemd[1]: postgresql.service: Main process exited, code=killed, status=9/KILL
Feb 05 01:52:12 server systemd[1]: postgresql.service: Failed with result 'signal'.
Signification : La base de données rapporte des lectures partielles. Ce n’est pas un problème de configuration ; c’est soit le stockage, soit le noyau.
Décision : Cessez de supposer « bug base de données ». Escaladez vers l’investigation du stockage ; envisagez le basculement.
Tâche 5 : Vérifier les OOM kills (l’assassin silencieux)
cr0x@server:~$ journalctl -k --since "2026-02-05 01:45:00" | grep -E "Out of memory|oom-kill|Killed process" | tail -n 5
Feb 05 01:47:03 server kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/api.service,task=java,pid=18802,uid=1001
Feb 05 01:47:03 server kernel: Killed process 18802 (java) total-vm:7421860kB, anon-rss:4823112kB, file-rss:0kB, shmem-rss:0kB, UID:1001 pgtables:12544kB oom_score_adj:0
Signification : Le noyau a tué un processus pour pression mémoire, et il nomme le cgroup (/system.slice/api.service).
Décision : Si l’OOM correspond au début de l’incident, traitez la pression mémoire comme le déclencheur. S’il survient plus tard, il peut être une conséquence des retries et de la croissance des files.
Tâche 6 : Vérifier la limitation de débit de journald (car les logs manquants posent problème)
cr0x@server:~$ journalctl -u systemd-journald --since "2026-02-05 01:45:00" --no-pager | tail -n 8
Feb 05 01:51:58 server systemd-journald[412]: Suppressed 1529 messages from postgresql.service
Feb 05 01:52:01 server systemd-journald[412]: Suppressed 941 messages from kernel
Signification : Vous perdez du détail pendant la partie la plus chaude de l’incident. Le motif reste pertinent, mais les comptes et séquences peuvent être incomplets.
Décision : Si la suppression est importante, collectez d’autres preuves (compteurs noyau, outils de stockage). Envisagez d’ajuster les limites de débit après l’incident, pas pendant.
Tâche 7 : Repérer les problèmes de stockage/lien dans dmesg/journal
cr0x@server:~$ dmesg -T | egrep -i "ata[0-9]|nvme|reset|link is down|I/O error|blk_update_request" | tail -n 12
[Tue Feb 5 01:52:10 2026] ata2.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x6 frozen
[Tue Feb 5 01:52:10 2026] ata2.00: failed command: READ FPDMA QUEUED
[Tue Feb 5 01:52:11 2026] blk_update_request: I/O error, dev sdb, sector 1946152832 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
[Tue Feb 5 01:52:11 2026] ata2: hard resetting link
[Tue Feb 5 01:52:12 2026] ata2: SATA link up 6.0 Gbps (SStatus 133 SControl 300)
Signification : Réinitialisations de lien + lectures échouées = territoire classique « disque, câble, contrôleur ou backplane ».
Décision : Planifiez un remplacement matériel ou une migration. Si c’est une VM avec disques virtuels, considérez l’instabilité du stockage sous‑jacente et impliquez votre fournisseur/équipe plateforme.
Tâche 8 : Confirmer les signaux de santé du système de fichiers
cr0x@server:~$ journalctl -k --since "2026-02-05 01:45:00" | egrep -i "EXT4-fs error|XFS.*corruption|Remounting filesystem read-only|I/O error" | tail -n 10
Feb 05 01:52:11 server kernel: EXT4-fs error (device sdb1): ext4_find_entry:1456: inode #131104: comm postgres: reading directory lblock 0
Feb 05 01:52:11 server kernel: Aborting journal on device sdb1-8.
Feb 05 01:52:11 server kernel: EXT4-fs (sdb1): Remounting filesystem read-only
Signification : Le système de fichiers a été remonté en lecture seule. Les applications vont échouer de façons étranges à partir de maintenant.
Décision : Arrêtez de « redémarrer des services ». Vous devez basculer, remonter ou réparer. Les redémarrages ajoutent du bruit et peuvent aggraver la corruption.
Tâche 9 : Si vous utilisez ZFS, vérifier les événements et erreurs du pool
cr0x@server:~$ sudo zpool status -x
pool: tank
state: DEGRADED
status: One or more devices has experienced an error resulting in data corruption.
action: Replace the device using 'zpool replace'.
scan: scrub repaired 0B in 0 days 00:12:44 with 3 errors on Tue Feb 5 01:55:21 2026
config:
NAME STATE READ WRITE CKSUM
tank DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
sdb FAULTED 5 0 3 too many errors
sdc ONLINE 0 0 0
errors: Permanent errors have been detected in the following files:
/tank/pg/wal/0000000100000000000000A3
Signification : Ce n’est pas « peut‑être ». ZFS vous dit qu’un périphérique est en faute et peut nommer les fichiers affectés.
Décision : Remplacez le disque défaillant, restaurez les données impactées depuis une réplique/sauvegarde, et traitez les erreurs applicatives comme des symptômes en aval tant que le contraire n’est pas prouvé.
Tâche 10 : Voir quels services ont vacillé (les tempêtes de redémarrage créent de faux coupables)
cr0x@server:~$ systemctl list-units --type=service --state=failed
UNIT LOAD ACTIVE SUB DESCRIPTION
postgresql.service loaded failed failed PostgreSQL RDBMS
api.service loaded failed failed Example API
LOAD = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state.
SUB = The low-level unit activation state.
Signification : C’est un inventaire rapide de « ce qui est visiblement cassé ». Cela ne dit pas pourquoi.
Décision : Utilisez‑le pour délimiter le rayon d’impact, puis inspectez les logs de chaque unité autour du premier horodatage de défaillance.
Tâche 11 : Extraire les champs structurés de journald (arrêtez de regexer quand vous pouvez filtrer)
cr0x@server:~$ journalctl -u api.service --since "2026-02-05 01:45:00" -o json | head -n 3
{"_SYSTEMD_UNIT":"api.service","PRIORITY":"3","MESSAGE":"DB connection failed: timeout","_PID":"18890","_COMM":"java","__REALTIME_TIMESTAMP":"1738720565000000"}
{"_SYSTEMD_UNIT":"api.service","PRIORITY":"3","MESSAGE":"DB connection failed: timeout","_PID":"18890","_COMM":"java","__REALTIME_TIMESTAMP":"1738720566000000"}
{"_SYSTEMD_UNIT":"api.service","PRIORITY":"4","MESSAGE":"Retrying in 500ms","_PID":"18890","_COMM":"java","__REALTIME_TIMESTAMP":"1738720566000000"}
Signification : Vous pouvez parser de façon programmatique des champs comme unit, PID, priority, timestamp. C’est ainsi qu’on construit des alertes fiables.
Décision : Si vous écrivez de l’automatisation, préférez la sortie JSON. Le texte est pour les humains ; les champs structurés sont pour les machines.
Tâche 12 : Compter les rafales d’erreurs pour voir si vous gérez une tempête
cr0x@server:~$ journalctl -u api.service --since "2026-02-05 01:45:00" --until "2026-02-05 02:00:00" -p err --no-pager | wc -l
842
Signification : 842 messages de niveau erreur en 15 minutes n’est pas « quelques échecs ». C’est un problème systémique ou une boucle de retries agressive.
Décision : Si le comptage est énorme, concentrez‑vous sur l’identification de la dépendance en amont qui échoue et envisagez d’atténuer les retries ou d’activer des coupe‑circuits.
Tâche 13 : Corrélation inter‑hôtes rapide et rustique (quand vous n’avez pas de logging centralisé)
cr0x@server:~$ for h in app01 app02 db01; do echo "== $h =="; ssh $h "journalctl --since '2026-02-05 01:50:00' --until '2026-02-05 01:55:00' -p err --no-pager | head -n 3"; done
== app01 ==
Feb 05 01:52:13 app01 api[18890]: DB connection failed: timeout
Feb 05 01:52:14 app01 api[18890]: DB connection failed: timeout
Feb 05 01:52:15 app01 api[18890]: DB connection failed: timeout
== app02 ==
Feb 05 01:52:13 app02 api[19011]: DB connection failed: timeout
Feb 05 01:52:14 app02 api[19011]: DB connection failed: timeout
Feb 05 01:52:15 app02 api[19011]: DB connection failed: timeout
== db01 ==
Feb 05 01:52:11 db01 kernel: blk_update_request: I/O error, dev sdb, sector 1946152832 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
Feb 05 01:52:11 db01 kernel: EXT4-fs (sdb1): Remounting filesystem read-only
Feb 05 01:52:12 db01 systemd[1]: postgresql.service: Main process exited, code=killed, status=9/KILL
Signification : Les nœuds applicatifs montrent des temporisations ; le nœud BDD montre des erreurs de stockage. Voilà votre chaîne, avec timestamps.
Décision : Cessez de dépanner les applications. Initiez le basculement/la récupération BDD, puis réparez le stockage sous‑jacent.
Tâche 14 : Extraire la ligne de « vraie erreur » avec son contexte (10 lignes autour)
cr0x@server:~$ journalctl --since "2026-02-05 01:45:00" --no-pager | grep -n -E "Remounting filesystem read-only|blk_update_request: I/O error" | head -n 1
1823:Feb 05 01:52:11 server kernel: EXT4-fs (sdb1): Remounting filesystem read-only
cr0x@server:~$ journalctl --since "2026-02-05 01:45:00" --no-pager | sed -n '1813,1833p'
Feb 05 01:52:10 server kernel: ata2.00: failed command: READ FPDMA QUEUED
Feb 05 01:52:11 server kernel: blk_update_request: I/O error, dev sdb, sector 1946152832 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
Feb 05 01:52:11 server kernel: Buffer I/O error on dev sdb1, logical block 243269104, async page read
Feb 05 01:52:11 server kernel: EXT4-fs error (device sdb1): ext4_find_entry:1456: inode #131104: comm postgres: reading directory lblock 0
Feb 05 01:52:11 server kernel: Aborting journal on device sdb1-8.
Feb 05 01:52:11 server kernel: EXT4-fs (sdb1): Remounting filesystem read-only
Feb 05 01:52:12 server systemd[1]: postgresql.service: Main process exited, code=killed, status=9/KILL
Feb 05 01:52:13 server api[18890]: DB connection failed: timeout
Signification : Le contexte transforme une seule ligne effrayante en une séquence causale : échec de lecture ATA → erreur d’E/S bloc → arrêt du journal du système de fichiers → remontage en lecture seule → base de données meurt → applications temporisent.
Décision : Voilà votre ancre temporelle d’incident. Utilisez‑la pour conduire l’action (basculement) et rédiger plus tard un postmortem propre.
Tâche 15 : Quand la « vraie erreur » est TLS/certificats, trouvez‑la une fois, pas 5 000 fois
cr0x@server:~$ journalctl -u nginx.service --since "2026-02-05 00:00:00" --no-pager | grep -m1 -E "certificate|SSL_do_handshake|PEM_read_bio"
Feb 05 01:03:44 server nginx[901]: [emerg] SSL_CTX_use_PrivateKey_file("/etc/nginx/tls/site.key") failed (SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch)
Signification : Une ligne explique la panne : mismatch clef/cert. Tout le reste, ce sont des clients incapables de se connecter.
Décision : Corrigez la paire clé/cert, rechargez, puis ajoutez une validation pré‑déploiement pour que cela ne se reproduise pas.
Tâche 16 : Détecter les échecs d’« optimisation » comme une rotation de logs trop agressive ou tmpfs plein
cr0x@server:~$ journalctl --since "2026-02-05 01:00:00" -p warning --no-pager | grep -E "No space left on device|failed to write|ENOSPC" | head -n 5
Feb 05 01:11:02 server systemd-journald[412]: Failed to write entry (24 items, 882 bytes), ignoring: No space left on device
Feb 05 01:11:03 server rsyslogd[777]: action 'action-0-builtin:omfile' suspended (module 'builtin:omfile'), retry 0. There is no space left on device.
Signification : Votre système de journalisation ne peut plus écrire. Cela signifie que vous perdez des preuves pendant un incident.
Décision : Libérez de l’espace immédiatement ; puis réévaluez où les logs sont stockés, la politique de rotation et si des changements « économie de disque » sabotaient l’observabilité.
Trois mini-récits d’entreprise issus des tranchées des logs
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
L’entreprise était en pleine migration : nouveau cluster Kubernetes, nouveau service mesh, tout neuf. Une API orientée client a commencé à renvoyer des 502 de façon intermittente. L’ingénieur on‑call a fait ce que nous faisons tous sous pression : il a cherché « error » dans les logs et a vu une tempête de messages « upstream reset » dans le proxy.
L’hypothèse s’est formée instantanément : « bug du service mesh ». Une salle de crise a été montée. Les gens ont ajusté les timeouts du proxy, redéployé les sidecars, basculé des feature flags. Le taux d’erreur a bougé, ce qui donnait l’impression de progrès, mais c’était en réalité une variance aléatoire prise pour du contrôle.
Finalement, quelqu’un a vérifié les logs noyau sur un nœud. Pas les logs applicatifs, pas les logs du proxy. Le nœud rapportait des flappings de lien NIC et des retransmissions TCP. Le proxy était innocent ; il était juste le premier composant assez honnête pour se plaindre. Quand l’équipe a corrélé les horodatages, chaque reset upstream s’alignait sur un cycle lien down/up.
La cause racine était banale : un port de switch top‑of‑rack configuré avec une mauvaise vitesse/duplex après une maintenance de routine. L’« incident mesh » était un incident réseau déguisé en problème de proxy.
La leçon n’était pas « blâmez toujours le réseau ». C’était : ne laissez pas le premier message d’erreur plausible devenir votre cause racine. Les logs vous disent ce qu’un composant a observé, pas ce qui a causé la réalité à se briser.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux
Une équipe plateforme essayait de réduire l’utilisation disque sur une flotte de nœuds de base de données. Quelqu’un a remarqué que journald consommait une quantité surprenante d’espace. La solution semblait propre : limiter agressivement la taille du journal et activer une limitation de débit plus stricte. Moins de disque, moins d’écritures, SSDs plus heureux. Tout le monde aime une victoire.
Deux semaines plus tard, un nœud a subi des pics intermittents de latence de stockage. PostgreSQL a commencé à logger des fsync lents et des blocages d’écriture WAL occasionnels. La couche applicative a vu des temporisations. L’on‑call a essayé de reconstituer la timeline, mais les messages noyau autour du pic manquaient. Journald avait supprimé beaucoup, et le journal limité avait fait passer les avertissements initiaux hors de portée.
Sans la preuve initiale, l’équipe s’est concentrée sur la configuration de la base de données. Ils ont ajusté checkpoints, paramètres WAL, ratios dirty du noyau. Certains changements ont amélioré les performances en régime et ont donné à tous la sensation rassurante « d’agir ». Mais les pics ont persisté.
Plus tard, pendant une fenêtre de maintenance, quelqu’un a lancé un scrub sur le pool de stockage et a trouvé des erreurs de checksum qui augmentaient. Un disque se dégradait lentement, et le système l’avait signalé. Ces avertissements étaient les premiers à être supprimés car ils étaient « avant l’incident ».
L’optimisation n’a pas causé la panne matérielle. Elle a fait perdre à l’équipe le seul signal bon marché et précoce qui aurait pu raccourcir l’incident. Économiser de l’espace, c’est bien ; économiser l’espace au prix de la rétention forensique ne l’est pas.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Un service de paiements avait une habitude que personne ne célébrait : chaque déploiement exigeait une petite « liste de vérification de sanity des journaux ». Pas un gros audit de sécurité. Juste des vérifications ennuyeuses — synchronisation du temps, espace disque, santé de journald, et une simple transaction bout en bout dans un environnement canari.
Un matin, le canari a échoué. Les logs montraient des erreurs de handshake TLS. L’ingénieur de garde n’a pas greppé à l’aveugle ; il a filtré par unité et cherché la première erreur liée au certificat. C’était immédiat : le service avait chargé une nouvelle chaîne de certificats, mais un fichier de certificat intermédiaire manquait. Le service démarrait toujours. Il refusait simplement les clients nécessitant la chaîne complète.
La correction a tenu en une ligne : installer l’intermédiaire manquant et recharger. Pas d’incident, pas d’impact client. La checklist l’a attrapé avant qu’il ne s’échappe. L’équipe est passée à autre chose, légèrement agacée que « rien ne soit arrivé », ce qui est la bonne réaction émotionnelle à un processus bien conçu.
Cette pratique ne semblait pas innovante. Elle l’était. La plupart des pannes sont évitées par des choses qui ressemblent à de la paperasserie jusqu’au moment où vous en avez besoin.
S’envoyer un e‑mail : une chaîne d’alerte de qualité production depuis les logs
La journalisation centralisée est excellente. Parfois vous ne l’avez pas. Parfois c’est justement ce qui est en panne. L’e‑mail, malgré ses défauts, est résilient, à dépendance minimale et disponible depuis presque n’importe où. « S’envoyer un e‑mail » n’est pas primitif ; c’est pragmatique.
Le truc est d’éviter deux modes d’échec :
- Spam d’alertes : vous allez l’ignorer, puis manquer le message qui comptait.
- Silence d’alerte : le script échoue, SMTP échoue, DNS échoue, ou journald supprime des événements et vous ne savez rien.
Principes de conception pour des alertes log‑vers‑e‑mail
- Alerter sur les déclencheurs, pas sur les symptômes. « EXT4 remounted read-only » vaut bien mieux que « API timeout ».
- Dédupliquer agressivement. L’e‑mail n’est pas une série temporelle métrique.
- Inclure le contexte. Envoyez la première ligne d’erreur plus une petite fenêtre de lignes autour.
- Rendre testable. Vous devez pouvoir lancer le script et obtenir une sortie unique et déterministe.
- Échouer bruyamment. Si la chaîne d’alerte ne peut pas envoyer, loggez cet échec quelque part que vous verrez.
Une implémentation simple et robuste : timer systemd + curseur journalctl
Nous allons utiliser le curseur de journald pour se souvenir où nous en étions. Cela évite de « renvoyer le monde » et est plus fiable que de deviner des fenêtres temporelles.
1) Créer un script d’alerte
Ce script recherche des événements de haute gravité et une poignée de motifs déclencheurs spécifiques (stockage, OOM, remontage en lecture seule, mismatch de certificat). Il envoie un e‑mail par exécution, dédupliqué, avec la « découverte » principale.
cr0x@server:~$ sudo tee /usr/local/sbin/journal-alert-email.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
STATE_DIR="/var/lib/journal-alert"
CURSOR_FILE="$STATE_DIR/cursor"
HOST="$(hostname -f 2>/dev/null || hostname)"
TO_ADDR="oncall@example.com"
FROM_ADDR="journal-alert@$HOST"
SUBJECT_PREFIX="[journal-alert]"
TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT
mkdir -p "$STATE_DIR"
chmod 700 "$STATE_DIR"
# Pull new high-priority logs since last cursor.
# We intentionally include kernel + system units; customize for your environment.
if [[ -f "$CURSOR_FILE" ]]; then
journalctl --after-cursor "$(cat "$CURSOR_FILE")" -p err..alert -o short-iso --no-pager > "$TMP" || true
else
journalctl --since "10 minutes ago" -p err..alert -o short-iso --no-pager > "$TMP" || true
fi
# Update cursor regardless; prevents loops if one bad message repeats.
journalctl -n 1 -o json --no-pager | sed -n 's/.*"__CURSOR":"\([^"]*\)".*/\1/p' > "$CURSOR_FILE" || true
chmod 600 "$CURSOR_FILE"
# If no new events, exit quietly.
if [[ ! -s "$TMP" ]]; then
exit 0
fi
# Prefer trigger patterns; fall back to first error line.
TRIGGER_LINE="$(grep -m1 -E \
'Remounting filesystem read-only|blk_update_request: I/O error|Buffer I/O error|oom-kill|Killed process|X509_check_private_key|SSL_CTX_use_PrivateKey_file|No space left on device|Permanent errors have been detected' \
"$TMP" || true)"
if [[ -z "$TRIGGER_LINE" ]]; then
TRIGGER_LINE="$(head -n 1 "$TMP")"
fi
# Add some context around the trigger if present in TMP.
CONTEXT="$(awk -v needle="$TRIGGER_LINE" '
BEGIN{found=0}
$0==needle{found=1; start=NR-5; end=NR+10}
{lines[NR]=$0}
END{
if(found){
for(i=(start<1?1:start); i<=end; i++) if(i in lines) print lines[i]
} else {
for(i=1;i<=20;i++) if(i in lines) print lines[i]
}
}' "$TMP")"
SUBJECT="$SUBJECT_PREFIX $HOST $(echo "$TRIGGER_LINE" | cut -c1-120)"
BODY=$(cat <<EOT
Host: $HOST
Time: $(date -Is)
Trigger:
$TRIGGER_LINE
Context:
$CONTEXT
EOT
)
# Send email. Requires a local MTA or msmtp configured.
printf "%s\n" "$BODY" | /usr/bin/mail -s "$SUBJECT" -r "$FROM_ADDR" "$TO_ADDR"
EOF
sudo chmod 750 /usr/local/sbin/journal-alert-email.sh
Ce que cela signifie : Cela utilise journald comme source de vérité et un curseur pour l’état. Il envoie un message par exécution, ce qui le rend naturellement limité en débit.
Décision : Si vous ne pouvez pas garantir un MTA local fonctionnel, utilisez msmtp ou un relais. Ne comptez pas sur « l’ordinateur portable de quelqu’un » pour la livraison des alertes.
2) Configurer un envoi de mail minimal (exemple : msmtp)
Sur beaucoup de systèmes, mail peut fonctionner avec un MTA local. Si vous n’en avez pas, msmtp est un choix courant. C’est un exemple de configuration ; adaptez‑le à votre environnement.
cr0x@server:~$ sudo tee /etc/msmtprc >/dev/null <<'EOF'
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
account relay
host smtp.relay.local
port 587
from journal-alert@server.example
user journal-alert@server.example
passwordeval "cat /etc/msmtp-password"
account default : relay
EOF
sudo chmod 600 /etc/msmtprc
Ce que cela signifie : Les identifiants sont séparés du script. Le mailer logue dans un fichier que vous pouvez auditer.
Décision : Si vous ne pouvez pas sécuriser les identifiants correctement, ne les déployez pas. Utilisez un relais local avec auth par IP, ou un service SMTP interne.
3) Créer un service systemd + timer minimal
cr0x@server:~$ sudo tee /etc/systemd/system/journal-alert-email.service >/dev/null <<'EOF'
[Unit]
Description=Send email alerts for high-severity journal events
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/journal-alert-email.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
EOF
cr0x@server:~$ sudo tee /etc/systemd/system/journal-alert-email.timer >/dev/null <<'EOF'
[Unit]
Description=Run journal email alert script every minute
[Timer]
OnBootSec=2min
OnUnitActiveSec=1min
AccuracySec=10s
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now journal-alert-email.timer
Ce que cela signifie : Toutes les minutes, le timer exécute le script une fois. Si l’hôte était down, Persistent=true exécute les intervalles manqués au démarrage (utile pour attraper les échecs en début de boot).
Décision : Si vous craignez le volume d’e‑mails, augmentez l’intervalle à 2–5 minutes et dédupliquez dans le script. Ne l’exécutez pas toutes les 5 secondes. Vous apprendrez juste à détester votre propre boîte de réception.
4) Tester la chaîne de bout en bout
cr0x@server:~$ sudo systemctl start journal-alert-email.service
cr0x@server:~$ sudo systemctl status journal-alert-email.service --no-pager
● journal-alert-email.service - Send email alerts for high-severity journal events
Loaded: loaded (/etc/systemd/system/journal-alert-email.service; static)
Active: inactive (dead) since Tue 2026-02-05 02:23:20 UTC; 2s ago
Process: 22901 ExecStart=/usr/local/sbin/journal-alert-email.sh (code=exited, status=0/SUCCESS)
Signification : status=0/SUCCESS signifie que le script s’est exécuté. Il peut n’avoir rien envoyé s’il n’y avait pas de nouvelles erreurs.
Décision : Si vous avez besoin d’un e‑mail de test, baissez temporairement le seuil ou générez une ligne de log d’erreur contrôlée dans une unité/environnement sûr.
Blague 2 : Les alertes e‑mail sont comme les détecteurs de fumée : vous ne les remarquez que quand elles dérangent, et c’est exactement à ce moment‑là qu’elles font leur travail.
Listes de contrôle / plan étape par étape
Checklist : trouver la vraie erreur en 10 minutes
- Définir la fenêtre : définir
sinceetuntil; étendre vers l’arrière si nécessaire. - Récupérer d’abord la haute gravité :
journalctl -p err..alertpour la fenêtre. - Scanner les déclencheurs : I/O noyau, système de fichiers lecture seule, OOM, crash de service, erreurs TLS/certificat, ENOSPC.
- Trouver la première ligne déclencheuse : utiliser
grep -m1ou des scans basés sur le curseur. - Ajouter le contexte : récupérer 10 lignes avant/après ou filtrer par unité.
- Corréler entre les couches : logs unitaires applicatifs + systemd + noyau.
- Décider de l’action : basculement, rollback, redémarrage (rarement), ou escalade vers hardware/réseau/plateforme.
- Préserver les preuves : copier les tranches de journal et les logs noyau pertinents avant que le système ne redémarre ou ne tourne.
Checklist : alerting log en production par e‑mail
- Choisir des motifs déclencheurs pour lesquels vous voulez réellement être réveillé.
- Implémenter l’état curseur pour éviter les doublons et les problèmes de dérive temporelle.
- Dédupliquer dans une exécution (envoyer un seul e‑mail avec le déclencheur principal + contexte).
- Utiliser un chemin mail fiable (relais local ou SMTP authentifié).
- Exécuter depuis un timer systemd et surveiller les échecs du service d’alerte lui‑même.
- Tester trimestriellement (oui, faites‑le réellement) : les chaînes d’alerte pourrissent en silence.
Checklist : triage axé stockage (parce que le stockage ment en devenant lent)
- Rechercher dans les logs noyau les réinitialisations de lien, erreurs d’E/S, remontages en lecture seule.
- Vérifier les événements RAID/ZFS/MD si applicable.
- Confirmer que « l’erreur applicative » a commencé après les avertissements de stockage.
- Basculez ou réduisez la charge avant d’essayer des réparations.
- Après stabilisation, lancez scrubs/checks et remplacez les composants marginaux.
Erreurs courantes : symptôme → cause racine → correction
1) « Tout expire »
Symptôme : logs applicatifs pleins de temporisations ; reverse proxy montre des erreurs upstream.
Cause racine : nœud de base de données remonté en lecture seule après erreurs d’E/S ; l’application est innocente.
Correction : vérifier les logs noyau + système de fichiers, basculer la base de données, remplacer disque/câble/contrôleur défaillant ; arrêter de redémarrer les services applicatifs.
2) « Le service redémarre sans cesse, c’est forcément un crash loop »
Symptôme : systemd montre des redémarrages répétés ; les logs applicatifs se terminent brusquement.
Cause racine : OOM killer du noyau terminant le processus (souvent dû à fuite mémoire, pics de trafic ou limites cgroup).
Correction : confirmer l’OOM dans les logs noyau, augmenter la mémoire ou corriger la fuite, ajuster les limites cgroup, ajouter de la pression en retour / coupe‑circuits.
3) « Pas d’erreurs dans les logs, donc ce n’est pas l’hôte »
Symptôme : vous ne voyez pas les messages noyau/stockage attendus pendant l’incident.
Cause racine : suppression par journald, rotation agressive ou disque plein empêchant l’écriture des logs.
Correction : vérifier journald pour suppression et ENOSPC, augmenter la rétention, garantir de la marge sur les partitions de logs, et alerter sur « pipeline de logs malsain ».
4) « On a fixé en augmentant les timeouts »
Symptôme : augmenter les timeouts réduit temporairement le taux d’erreur.
Cause racine : dépendance lente (latence stockage, problèmes DNS, BDD surchargée) était le déclencheur ; les timeouts n’ont fait que déplacer la douleur.
Correction : identifier le premier trigger de latence/E/S, réduire la charge ou basculer ; ne tuner les timeouts qu’après stabilisation pour éviter les tempêtes de retries.
5) « La rotation des certificats est automatisée, TLS ne peut pas être en cause »
Symptôme : échecs de handshake soudains après un déploiement ; clients signalent des erreurs de chaîne de certificats.
Cause racine : clé/cert mismatch, intermédiaire manquant, permissions erronées ou processus qui n’a pas rechargé.
Correction : chercher la première ligne d’erreur TLS/clé, valider la paire cert/clé en CI, imposer des checks de reload et alerter sur les expirations imminentes.
6) « C’est un problème réseau » (à chaque fois)
Symptôme : resets intermittents, retransmissions, échecs aléatoires.
Cause racine : parfois le réseau, oui. D’autres fois : starvation CPU provoquant des ACKs retardés, blocages disque gelant des processus, ou exhaustion de conntrack.
Correction : corroborer avec les logs noyau et les signaux de pression sur l’hôte. Ne cherchez pas un bouc émissaire. Prouvez.
7) « On va juste greper ERROR »
Symptôme : correspondances à l’infini, pas de clarté, focus erroné.
Cause racine : mauvais usage des niveaux de sévérité et absence de filtrage structuré (unit/PID/champs).
Correction : filtrez par unité et priorité, puis cherchez les motifs déclencheurs ; utilisez les champs JSON pour l’automatisation.
FAQ
1) Qu’est‑ce que la « vraie erreur » dans un journal d’événements ?
C’est l’événement à signal fort le plus ancien qui explique les défaillances en aval : la première erreur d’E/S, le premier kill OOM, le premier mismatch de clé TLS, le premier « remontage en lecture seule ». Pas le 50ᵉ timeout.
2) Pourquoi ne pas tout envoyer vers une plateforme de logs et chercher là‑bas ?
Vous devriez, quand vous le pouvez. Mais la réponse aux incidents ne peut pas dépendre d’un système qui peut être dégradé par le même incident. Les outils locaux‑first sont votre canot de sauvetage.
3) Pourquoi journalctl -xe semble inutile pendant les incidents ?
Parce qu’il mélange sévérité, portée et temps d’une façon excellente pour « ce qui vient de se passer » mais mauvaise pour « qu’est‑ce qui a causé l’incident ». Utilisez des fenêtres bornées, des priorités et des filtres d’unités.
4) Comment éviter la fatigue d’alerte en m’envoyant des e‑mails ?
Alertez sur des déclencheurs, dédupliquez, envoyez un seul e‑mail par exécution et gardez l’objet court mais spécifique. Si vous n’agiriez pas à partir de l’e‑mail, ne l’envoyez pas.
5) Dois‑je parser les logs avec des regex ou des champs structurés ?
Pour les sources journald, préférez les champs structurés quand c’est possible. Utilisez la regex pour matcher le contenu des messages, mais ne construisez pas une chaîne fragile qui dépend uniquement d’un phrasé exact.
6) Et si mon système utilise des fichiers syslog classiques au lieu de journald ?
La méthode reste la même : bornez la fenêtre, priorisez la haute gravité, trouvez le premier déclencheur, ajoutez le contexte et corrélez entre les couches. Vos commandes changent (par ex. grep, awk, zgrep sur les fichiers rotatés), mais les décisions restent.
7) Comment savoir si une erreur applicative est cause ou effet ?
Vérifiez l’ordre et l’indépendance. Si des événements noyau/stockage/oom précèdent les erreurs applicatives, l’application est probablement en aval. Si les erreurs applicatives apparaissent seules avec des logs noyau stables, l’application peut être le déclencheur.
8) Quel est l’ensemble minimal de motifs déclencheurs qui valent la peine d’alerter ?
Commencez par : remontage système de fichiers en lecture seule, erreurs I/O noyau, OOM kills, disque plein/ENOSPC, crashes de service répétés, erreurs de chargement TLS/certificat. Élargissez seulement quand vous avez prouvé qu’une alerte est actionnable.
9) Et pour les conteneurs — journald aide‑t‑il encore ?
Oui, si les logs de conteneur sont routés vers journald ou si des unités systemd gèrent le runtime. Si vous utilisez un driver de logging différent, collectez des signaux équivalents depuis le runtime et corrélez toujours avec les logs noyau.
10) Combien de temps dois‑je conserver les logs sur un serveur ?
Assez longtemps pour inclure les « avertissements précoces » qui précèdent les incidents : jours à semaines, selon le budget disque. Si vous ne pouvez pas vous permettre la rétention, vous ne pourrez pas déboguer la prochaine panne sournoise non plus.
Conclusion : prochaines étapes à faire avant le déjeuner
Si vous ne faites rien d’autre, faites ces trois choses :
- Adoptez la règle du « premier événement fautif ». Trouvez la première ligne déclencheuse et construisez la chaîne causale vers l’avant.
- Utilisez le filtrage journald comme un adulte. Priorités, filtres d’unités, champs structurés. La regex est un outil, pas un mode de vie.
- Branchez une petite alerte e‑mail pour les déclencheurs qui comptent. Basée sur un curseur, dédupliquée, avec contexte. Vous vous remercierez à 02:13.
Puis, quand le prochain incident surviendra, vous ne lirez plus les logs comme des feuilles de thé. Vous extrayez des preuves, prenez une décision et passez à autre chose. C’est le métier.