Incident 2026-03-30 — Saturation I/O Uptime Kuma¶
Date : 2026-03-30 ~14:00
Durée : ~5–10 minutes
Services impactés : LXC 101 (Uptime Kuma, Homepage, Portainer, Zensical)
Déclencheur : Suppression de l'historique des events dans l'interface Uptime Kuma
1. CHRONOLOGIE¶
| Heure | Événement |
|---|---|
| ~13:55 | Suppression "clear history" depuis UI Uptime Kuma |
| ~13:55–13:58 | Load monte à 300%+, iowait très élevé, IOR/s uptime-kuma en MB/s |
| ~13:58 | LXC 101 freeze – impossible à shutdown depuis Portainer |
| ~13:58 | pkill uptime-kuma sur host PVE → process redémarre automatiquement |
| 13:58:11 | vzreboot:101 Proxmox (TASK OK) |
| 13:59 | LXC 101 restart, containers up |
| 13:59–14:00 | Uptime Kuma error log : DB pool aborted (3x "Error: aborted" + "Unable to acquire a connection") |
2. ANALYSE TECHNIQUE¶
2.1 Mécanisme réel — SQLite write lock contention (bug connu Uptime Kuma)¶
sdc est en parfait état (SMART sain, 210 GB libres sur 228 GB, 0 erreur ATA). La durée et l'intensité de l'incident ne peuvent pas s'expliquer par une lenteur du disque : une DB de 300 MB sur un SSD 850 EVO serait traitée en ~1 seconde en séquentiel, ~10 secondes au pire en random 4K.
La cause réelle est un bug applicatif connu et documenté dans Uptime Kuma :
GitHub issue #3248 (juin 2023) : "Clearing the Events on a http(s) monitor hangs Uptime Kuma for over a minute. Load average jumps to 4.5" — Raspberry Pi 4, symptômes identiques.
GitHub issues #4041, #2751, #4709, #5667 : variantes du même problème — DB croissante →SQLITE_BUSY,KnexTimeoutError, instance inaccessible.
Statut : non résolu dans la branche v1 / v2 à ce jour.
Uptime Kuma utilise better-sqlite3 via knex avec un pool de connexions SQLite = 1 (SQLite n'autorise qu'un seul writer simultané). Lors du "Clear History" :
DELETE FROM heartbeat WHERE ...— 37 jours × ~40 moniteurs × 1440 min = ~2,1 millions de lignes à supprimer (~280 MB)- Le
DELETEtient le write lock SQLite pendant toute sa durée - Pendant ce maintien du lock, tous les beats (40+ monitors toutes les 60s) font une queue sur ce lock unique →
SQLITE_BUSY+ retry en boucle - Le pool de connexions knex s'épuise →
"Unable to acquire a connection"(visible danserror.log) - Les retry loops de tous les monitors génèrent une charge CPU explosive (300%+ dans
top) - L'iowait élevé vient du lock contention SQLite (threads bloqués sur le lock), pas de la lenteur physique du disque
Pourquoi la majorité des users ne crashe pas complètement :
| Facteur | Setup classique | Ce setup |
|---|---|---|
| Isolation CPU | Uptime Kuma seul ou VM dédiée | LXC partagé, pas de CPU limit |
| Services affectés | Uptime Kuma seul ralentit | Homepage, Portainer, Zensical en CPU starvation |
| DB size | Rétention configurée (7–30j) | Pas de config rétention → 37 jours × 40 monitors |
| Hardware | SSD NVMe ou disque sain | sdb avec secteurs défaillants (allonge les I/O waits) |
Un Raspberry Pi (issue #3248) voit 1–2 minutes de hang puis recovery. Ici : le CPU starvation s'étend à tout le LXC, rendant Portainer inaccessible pour l'intervention.
Estimation DB avant delete : - ~2,1M rows × 150 bytes ≈ 305 MB → visible dans les IOR/s élevés - Après delete + checkpoint : 26 MB (kuma.db actuel) - WAL résiduel : 1,3 MB (checkpoint interrompu par pkill)
2.2 Pourquoi le système gèle (LXC inaccessible)¶
- Tous les containers Docker de LXC 101 tournent dans le même LXC sans CPU limits
- Uptime Kuma monopolise les CPU cores du LXC → Homepage, Portainer, Zensical en starvation
- Le LXC lui-même devient inaccessible depuis Portainer (API calls timeout)
pkill uptime-kumalibère immédiatement les CPU → système récupère en quelques secondes
2.3 Preuves¶
# sdc : parfaitement sain, non impliqué dans la défaillance
Reallocated_Sector_Ct : 0
Uncorrectable_Error_Cnt : 0
ATA Error Count : 0
Espace libre : 210 GB / 228 GB (4% utilisé)
Total LBAs Written : 31 TB sur lifetime → dans les specs 850 EVO (150 TBW)
# sdb : erreurs hardware préexistantes (NON liées à cet incident)
Reallocated_Sector_Ct : 3
Uncorrectable_Error_Cnt : 3
ATA Error Count : 3 (dont 1 UNC à LBA 0x00402000 = ~2.0 GB)
# dm-10 (LXC 101) filesystem state
FS Error count : 9 ← INCHANGÉ depuis le 23/03 (pré-existant, aucune nouvelle erreur aujourd'hui)
Last error time : Mon Mar 23 18:55:08 2026
# kuma error.log (post-pkill shutdown)
3× "Error: aborted" ← pool SQLite interrompu pendant le shutdown
1× "Unable to acquire a connection" ← pool épuisé (conséquence du thundering herd)
2.4 État pool LVM au moment de l'incident¶
pool data: 48.42% ← sain (était 92.6% le 23/03, remediation effectuée)
vm-101-disk-0: 50.78%
vm-104-disk-0: 87.30% ← ⚠️ toujours élevé
3. RELATION AVEC L'INCIDENT DU 23/03¶
| Critère | Incident 23/03 | Incident 30/03 |
|---|---|---|
| Disque impliqué | sdb (pool thin LVM) — défaillance physique réelle |
sdc — sain, non défaillant |
| Cause racine | Pool thin 92.6% + backup COW + secteur UNC sdb | Bug applicatif : Node.js event loop block + SQLite thundering herd |
| Mécanisme | EIO hardware → ext4 s'arrête | Lock contention SQLite → CPU starvation LXC |
| Erreurs kernel | 9+3+1 nouvelles erreurs EIO ext4 | Zéro — compteurs inchangés depuis le 23/03 |
| Erreurs disque | OUI (secteurs défaillants sdb) | NON (sdc parfaitement sain) |
| SMART sdb | UNC à LBA 4,202,496 → cause des EIO | Inchangé, toujours critique |
Verdict : DIFFÉRENT du 23/03 — confirme partiellement, précise et corrige.
- ✅ Confirme : une opération intensive peut rendre le LXC inaccessible
- ✅ Confirme : le NVMe est nécessaire pour sdb (raison inchangée — secteurs défaillants réels)
- 🔎 Précise : le 30/03 est une défaillance applicative Uptime Kuma, pas un problème disque
- 🔎 Précise : même pattern que l'incident Gramps (pic I/O inattendu d'une opération app-level)
- ✏️ Corrige :
sdcn'est pas insuffisant ni un SPOF — c'estbetter-sqlite3synchrone qui bloque - ❌ N'invalide pas : les secteurs défaillants sdb restent critiques et non résolus
4. PROBLÈMES PERSISTANTS NON RÉSOLUS¶
4.1 sdb — Secteurs défaillants (CRITIQUE)¶
Samsung SSD 850 EVO 250GB (sdb) — 32 486 h d'utilisation
Reallocated_Sector_Ct : 3 (secteurs remappés)
Uncorrectable_Error_Cnt : 3
ATA Error Count : 3 (dont UNC à LBA 4 202 496 = ~2.0 GB depuis début disque)
Warning: SMART ATA Error Log Structure error: invalid SMART checksum ← firmware/HW dégradé
Conséquence : LXC 100, 101, 103 ont des filesystems avec erreurs ext4 non corrigées.
dm-10 (LXC 101) : 9 erreurs, state "clean with errors", orphan_present
→ First error: 2026-03-20 20:42:40 — ext4_do_writepages (EIO)
→ Last error: 2026-03-23 18:55:08 — ext4_journal_check_start (EIO)
dm-6 (LXC 100) : 3 erreurs (journal_check_start, 23/03)
dm-8 (LXC 103) : 1 erreur (journal_check_start, 23/03)
e2fsck OBLIGATOIRE sur ces 3 volumes avant le remplacement de sdb.
4.2 vm-104-disk-0 à 87.3% (ATTENTION)¶
LXC 104 occupait déjà 87.28% lors du rapport 23/03. Aucune amélioration.
À surveiller avant la migration NVMe.
4.3 kuma.db-wal non checkpointé¶
kuma.db-wal = 1.3 MB après l'incident (checkpoint interrompu par le kill).
SQLite le retraitera au prochain démarrage propre — aucune action requise, mais noter que la DB peut être légèrement incohérente si Uptime Kuma a crashé au mauvais moment.
5. ACTIONS REQUISES¶
🔴 Immédiat (avant installation NVMe)¶
-
e2fsck sur LXC 100, 101, 103 — à faire conteneur STOPPÉ :
-
Uptime Kuma — mitigation applicative : ne pas utiliser "Clear All History" en production. Si nécessaire, préférer la rétention configurable (ex: 30 jours) plutôt qu'un delete massif ponctuel.
🟠 Court terme (avec installation NVMe)¶
-
Migrer sdb → NVMe : copie LVM complète + remount. Les 3 secteurs défaillants et l'UNC error disparaissent. Cible principale du remplacement hardware.
-
CPU limits LXC 101 : configurer un cgroup CPU limit pour que uptime-kuma ne puisse pas saturer tous les cores du LXC et bloquer les autres containers.
🟡 Long terme¶
-
Rétention Uptime Kuma : configurer une rétention automatique des heartbeats (30–90 jours) pour que la DB reste petite et que les deletes futurs soient négligeables.
-
Isolation des services : envisager de sortir Uptime Kuma de LXC 101 dans son propre LXC si les incidents applicatifs affectent Homepage/Portainer.
6. SCRIPT DE DIAGNOSTIC DE RÉFÉRENCE¶
# État santé disques
smartctl -a /dev/sdb | grep -E "Reallocated|Uncorrectable|CRC|ATA Error|overall"
smartctl -a /dev/sdc | grep -E "Reallocated|Uncorrectable|CRC|ATA Error|overall"
# Erreurs ext4 filesystems LXC
for dev in dm-6 dm-8 dm-10; do
echo "=== $dev ===" && tune2fs -l /dev/$dev | grep -E "FS Error|error time|error func|state"
done
# Pool LVM
lvs --options lv_name,data_percent,lv_size pve
# Erreurs kernel récentes
dmesg -T | grep -E "EXT4|I/O err|UNC|blk_update" -i | tail -20