Module 15 — Diagnostiquer : les douze pannes classiques et leur remède
Le kit Veille tourne, l'index est en place, le graphe est chargé — puis un jour, Kibana affiche « server is not ready yet », LOAD CSV ne trouve plus le fichier ou Elasticsearch refuse l'authentification. Inès a fini par tout classer : douze pannes récurrentes, provoquées à la main, lues dans les journaux, réparées en une commande, vérifiées.
Tableau récapitulatif
| N | Symptôme exact | Cause | Correction |
|---|---|---|---|
| 1 | failed to bind host port ... address already in use | Port 9200, 5601, 7474 ou 7687 déjà pris | docker ps puis docker stop <nom>, ou libérer le port |
| 2 | Conteneur Exited (137), OOMKilled: true | Docker a tué le processus, mémoire dépassée | Augmenter Docker Desktop → Memory ou mem_limit |
| 3 | max virtual memory areas vm.max_map_count [65530] is too low | Linux : paramètre kernel trop bas | sudo sysctl -w vm.max_map_count=262144 |
| 4 | _cluster/health en yellow ou red | Répliques non assignées, ou shard primaire perdu | GET _cluster/allocation/explain puis correction |
| 5 | cluster_block_exception, index read-only / allow delete (api) | Watermark disque atteint | PUT news/_settings {"index.blocks.read_only_allow_delete": null} |
| 6 | security_exception ... unable to authenticate user [elastic] | ELASTIC_PASSWORD modifié après création du volume | ./lab.sh reset puis ./lab.sh up |
| 7 | Kibana server is not ready yet | kibana_system ou Elasticsearch pas prêt | ./lab.sh logs setup puis relancer ./lab.sh up |
| 8 | mapper_parsing_exception ou mapper cannot be changed | Type de champ incompatible | _reindex vers un nouvel index avec le bon mapping |
| 9 | Couldn't load the external resource at: file:/import/news.csv | Fichier absent dans neo4j/import/ | ./lab.sh import-news |
| 10 | CALL { ... } IN TRANSACTIONS can only be executed in an implicit transaction | Exécuté dans une transaction explicite | Préfixer par :auto dans Neo4j Browser |
| 11 | There is no procedure with the name apoc.periodic.iterate registered | APOC non chargé | Vérifier NEO4J_PLUGINS, redémarrer Neo4j |
| 12 | The client is unauthorized due to authentication failure (Neo4j) | Mot de passe changé dans .env mais volume existant | ./lab.sh reset puis ./lab.sh up |
Le reste du module reprend chaque ligne : comment la provoquer volontairement dans le kit (quand c'est sans danger), ce que vous lirez dans les journaux, la commande de correction, et comment vérifier que c'est vraiment réglé.
1. Port déjà occupé
Provoquer. Ouvrez un terminal et gardez un serveur factice sur 9200 :
docker run --rm -p 9200:80 --name faux-serveur nginx
Dans un second terminal, lancez ./lab.sh up.
Message exact.
Error response from daemon: driver failed programming external connectivity on endpoint veille-es
(...): failed to bind host port for 0.0.0.0:9200:172.19.0.2:9200/tcp: address already in use
Cause. Un autre programme (souvent un ancien conteneur Elasticsearch, parfois un service local) tient déjà le port 9200. Docker ne peut pas mapper deux fois le même port hôte.
Réparer.
docker ps
docker stop faux-serveur
./lab.sh doctor
./lab.sh up
Sous Linux et macOS, sudo lsof -iTCP:9200 -sTCP:LISTEN identifie le processus si ce n'est pas un conteneur. Sous Windows PowerShell, netstat -ano | findstr :9200 donne le PID.
Vérifier. ./lab.sh status doit lister les trois conteneurs Up (healthy) avec les ports 0.0.0.0:9200->9200/tcp en face de veille-es.
2. Conteneur tué par manque de mémoire (Exited (137))
Provoquer. Réduisez la RAM allouée à Docker Desktop en dessous de 4 Go (Settings → Resources → Memory). Redémarrez Docker puis lancez ./lab.sh up. Sur une machine limitée, ajoutez temporairement mem_limit: 1g sur le service elasticsearch dans docker-compose.yml.
Message exact.
docker ps -a
CONTAINER IMAGE STATUS
veille-es docker.elastic.co/.../elasticsearch:9.5.3 Exited (137) 12 seconds ago
docker inspect -f '{{.State.OOMKilled}}' veille-es
true
137 vaut 128 + 9 : le noyau a envoyé SIGKILL (signal 9) au conteneur, presque toujours parce que la limite mémoire a été atteinte. OOMKilled: true confirme.
Cause. Le processus Elasticsearch (heap de 1 Go + surcoût JVM + page cache) a dépassé la limite mémoire du conteneur ou de Docker Desktop.
Réparer. Augmentez Docker Desktop → Settings → Resources → Memory à 6 Go (8 Go si vous ajoutez OpenSearch). Sur Linux via WSL2, éditez %UserProfile%\.wslconfig :
[wsl2]
memory=8GB
puis wsl --shutdown et rouvrez Docker Desktop. Enfin :
./lab.sh down
./lab.sh up
Vérifier. docker stats --no-stream doit montrer veille-es autour d'1,3 Go de résident (le heap plus les buffers). ./lab.sh logs elasticsearch --tail=20 doit se terminer par started.
3. vm.max_map_count trop bas (Linux natif)
Provoquer. Sur une machine Linux sans Docker Desktop (Docker Engine directement) :
sudo sysctl -w vm.max_map_count=65530
./lab.sh reset
./lab.sh up
Message exact dans ./lab.sh logs elasticsearch :
ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
Cause. Elasticsearch utilise mmap intensivement pour les index Lucene. Le noyau Linux limite par défaut le nombre de zones mémoire mappables à 65 530 — insuffisant. Docker Desktop règle la valeur tout seul dans sa distribution WSL2 interne ; sur Linux natif, c'est à l'administrateur.
Réparer.
sudo sysctl -w vm.max_map_count=262144
echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-elasticsearch.conf
./lab.sh up
La deuxième ligne rend le réglage permanent après redémarrage.
Vérifier.
sysctl vm.max_map_count
./lab.sh logs elasticsearch --tail=20
La valeur affichée est 262144 et le journal se termine par started.
4. Cluster yellow ou red
Provoquer. Sur un cluster à un seul nœud (le kit), créez un index avec une réplique — impossible à assigner :
PUT test_yellow
{
"settings": { "number_of_shards": 1, "number_of_replicas": 1 }
}
GET _cluster/health
Message exact.
{
"cluster_name": "veille",
"status": "yellow",
"unassigned_shards": 1,
...
}
GET _cluster/allocation/explain détaille la raison :
"explanation": "the shard cannot be allocated to the same node on which a copy of the shard already exists"
Cause. yellow = shard primaire présent, réplique introuvable (souvent : pas assez de nœuds). red = shard primaire lui-même absent (nœud tombé, disque corrompu). Le corpus news est configuré number_of_replicas: 0 pour rester green sur un seul nœud.
Réparer. Sur un cluster mono-nœud, ramenez les répliques à zéro :
PUT test_yellow/_settings
{
"index": { "number_of_replicas": 0 }
}
Pour un red réel, GET _cluster/allocation/explain donne la raison exacte : disque plein, nœud disparu, corruption. Le remède dépend du diagnostic — la commande, elle, est toujours la même première étape.
Vérifier. GET _cluster/health retourne "status": "green", "unassigned_shards": 0.
5. Index en lecture seule (watermark disque)
Provoquer. Le kit désactive le seuil pour l'atelier. Simulez la conséquence à la main :
PUT news/_settings
{
"index.blocks.read_only_allow_delete": true
}
POST news/_doc
{ "headline": "test" }
Message exact.
{
"error": {
"type": "cluster_block_exception",
"reason": "index [news] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"
},
"status": 429
}
Cause. En production, Elasticsearch surveille le disque. À 95 % occupé (seuil flood_stage), il ajoute automatiquement le blocage read_only_allow_delete sur tous les index pour empêcher la corruption. Une fois posé, ce blocage ne se retire pas tout seul même si le disque redescend — c'est délibéré.
Réparer.
PUT news/_settings
{
"index.blocks.read_only_allow_delete": null
}
null supprime le paramètre. Le blocage tombe immédiatement, l'écriture repasse. En production, faites d'abord la place sur le disque (supprimer d'anciens index, augmenter le volume) avant de lever le blocage.
Vérifier. Rejouez le POST news/_doc {"headline":"test"} : la réponse est {"result": "created"} et non plus cluster_block_exception.
6. 401 après changement de ELASTIC_PASSWORD
Provoquer. Éditez .env, remplacez ELASTIC_PASSWORD=veille2026 par ELASTIC_PASSWORD=nouveau2026, puis :
./lab.sh down
./lab.sh up
Message exact dans le premier appel à Kibana (./lab.sh logs kibana) :
[error][elasticsearch-service] Unable to retrieve version information from Elasticsearch nodes.
security_exception: [security_exception] Reason: unable to authenticate user [elastic] for REST request [/_nodes?filter_path=nodes.*.version%2Cnodes.*.http.publish_address%2Cnodes.*.ip]
Cause. Le mot de passe de l'utilisateur elastic est écrit dans le volume es-data au tout premier démarrage. Une fois écrit, la variable d'environnement ELASTIC_PASSWORD ne le modifie plus : Elasticsearch la lit uniquement à l'initialisation d'un nouveau nœud. ./lab.sh down conserve les volumes ; le mot de passe stocké reste l'ancien, celui que vous tapez dans .env est le nouveau, ils ne coïncident plus.
Réparer. Deux options.
Option A (rapide, sans repartir de zéro) — mettre à jour le mot de passe via l'API, en s'authentifiant avec l'ancien :
docker exec veille-es curl -s -u elastic:veille2026 \
-H 'Content-Type: application/json' \
-X POST http://localhost:9200/_security/user/elastic/_password \
-d '{"password":"nouveau2026"}'
Option B (radicale) — repartir de zéro :
./lab.sh reset
./lab.sh up
Vérifier.
./lab.sh es _cluster/health
La réponse JSON arrive au lieu d'un security_exception.
7. Kibana : « Kibana server is not ready yet »
Provoquer. Interrompez Elasticsearch pendant que Kibana démarre :
./lab.sh down
docker compose up -d kibana # sans elasticsearch en amont
Puis ouvrez http://localhost:5601.
Message exact dans le navigateur :
Kibana server is not ready yet
Dans ./lab.sh logs kibana :
[error][elasticsearch-service] Unable to retrieve version information from Elasticsearch nodes.
Cause. Kibana ne rend pas la main tant qu'il n'a pas parlé à Elasticsearch avec les identifiants de kibana_system. Trois raisons possibles : Elasticsearch pas démarré, setup en échec (donc kibana_system a l'ancien mot de passe), ou Kibana démarré avant les autres. Les healthchecks du kit protègent normalement contre ce cas ; docker compose up -d kibana seul contourne les dépendances.
Réparer.
./lab.sh logs setup
./lab.sh down
./lab.sh up
./lab.sh up respecte l'ordre : elasticsearch → setup → kibana → neo4j, avec attente service_healthy entre chaque étape. Si ./lab.sh logs setup montre un échec (mot de passe désynchronisé, service Elasticsearch mort), un ./lab.sh reset suivi d'./lab.sh up remet tout au propre.
Vérifier. curl -s http://localhost:5601/api/status | grep 'available' renvoie "level":"available".
8. Conflit de mapping (mapper_parsing_exception, mapper cannot be changed)
Provoquer.
PUT test_mapping
{
"mappings": {
"properties": {
"date": { "type": "date", "format": "yyyy-MM-dd" }
}
}
}
POST test_mapping/_doc
{ "date": "hier" }
Puis, deuxième cas :
PUT test_mapping/_mapping
{
"properties": {
"date": { "type": "text" }
}
}
Messages exacts.
{"error":{"type":"mapper_parsing_exception","reason":"failed to parse field [date] of type [date] in document with id ..."}}
{"error":{"type":"illegal_argument_exception","reason":"mapper [date] cannot be changed from type [date] to [text]"}}
Cause. Elasticsearch fige le type d'un champ dès la première indexation. On peut ajouter un champ, jamais en changer le type. Passer d'un date à un text (ou d'un text à un keyword) demande de créer un nouvel index avec le bon mapping et de recopier les documents avec _reindex.
Réparer.
PUT test_mapping_v2
{
"mappings": {
"properties": {
"date": { "type": "text" }
}
}
}
POST _reindex
{
"source": { "index": "test_mapping" },
"dest": { "index": "test_mapping_v2" }
}
Puis basculer les lecteurs et les écritures vers test_mapping_v2, idéalement via un alias (POST _aliases), et supprimer l'ancien.
Vérifier.
GET test_mapping_v2/_mapping
Le champ date est bien de type text dans la réponse.
9. LOAD CSV : « Couldn't load the external resource »
Provoquer.
./lab.sh reset
./lab.sh up
./lab.sh cypher 11-charger-news.cypher
Sans ./lab.sh import-news au préalable, le fichier neo4j/import/news.csv n'existe pas.
Message exact dans la sortie de cypher-shell :
Couldn't load the external resource at: file:/import/news.csv
Cause. LOAD CSV WITH HEADERS FROM 'file:///news.csv' cherche le fichier dans le répertoire /import du conteneur Neo4j — répertoire monté sur ./neo4j/import/ de l'hôte. Le fichier news.csv n'est écrit que par ./lab.sh import-news, qui télécharge d'abord le corpus, l'indexe dans Elasticsearch, puis produit le CSV pour Neo4j.
Réparer.
./lab.sh import-news
./lab.sh cypher 11-charger-news.cypher
Vérifier.
ls neo4j/import/
news.csv (environ 25 Mo) est présent, et apoc.meta.stats() dans cypher-shell compte les 200 853 articles, 41 catégories et 23 082 auteurs.
10. CALL { ... } IN TRANSACTIONS en transaction explicite
Provoquer. Ouvrez Neo4j Browser (http://localhost:7474) et exécutez, sans préfixe :auto :
LOAD CSV WITH HEADERS FROM 'file:///news.csv' AS ligne
CALL {
WITH ligne
MERGE (a:Article {id: toInteger(ligne.id)})
} IN TRANSACTIONS OF 1000 ROWS
Message exact.
A query with 'CALL { ... } IN TRANSACTIONS' can only be executed in an implicit transaction, but tried to execute in an explicit transaction.
Cause. Neo4j Browser enveloppe par défaut chaque requête dans une transaction explicite (BEGIN/COMMIT) pour offrir le bouton d'annulation. Or CALL { } IN TRANSACTIONS — la syntaxe qui gère les grosses charges par lots — a besoin d'une transaction implicite, celle que crée automatiquement le serveur pour chaque commande soumise. Les deux modes sont incompatibles.
Réparer. Préfixez la requête par :auto dans Neo4j Browser :
:auto
LOAD CSV WITH HEADERS FROM 'file:///news.csv' AS ligne
CALL {
WITH ligne
MERGE (a:Article {id: toInteger(ligne.id)})
} IN TRANSACTIONS OF 1000 ROWS
:auto est une commande du Browser qui bascule la requête en mode implicite. Depuis cypher-shell, ./lab.sh cypher 11-charger-news.cypher fait le même travail sans préfixe : le fichier est exécuté en mode implicite par défaut.
Vérifier. La requête aboutit et apoc.meta.stats() retourne le compte d'articles attendu.
11. Procédure APOC inconnue
Provoquer. Retirez temporairement le plugin :
docker exec veille-neo4j sh -c 'rm -f /plugins/apoc-*.jar'
docker restart veille-neo4j
Puis, dans cypher-shell :
CALL apoc.periodic.iterate(
"MATCH (a:Article) RETURN a",
"SET a.vu = true",
{batchSize: 1000}
);
Message exact.
There is no procedure with the name `apoc.periodic.iterate` registered for this database instance.
Please ensure you've spelled the procedure name correctly and that the procedure is properly deployed.
Cause. APOC est une bibliothèque de procédures externes. Elle n'est pas incluse par défaut dans Neo4j Community : le kit la télécharge au premier démarrage via NEO4J_PLUGINS=["apoc"] et la conserve dans le volume neo4j-plugins. Si le volume est vide, corrompu, ou si le téléchargement a échoué au premier up (accès réseau bloqué), les procédures apoc.* ne sont pas enregistrées.
Réparer.
./lab.sh logs neo4j | grep -i apoc
./lab.sh down
./lab.sh up
Le journal montre Loading APOC plugins au démarrage. Si la ligne est absente, vérifiez que docker-compose.yml contient bien NEO4J_PLUGINS=["apoc"] et que le volume neo4j-plugins est déclaré. En dernier recours, ./lab.sh reset puis ./lab.sh up force un nouveau téléchargement.
Vérifier.
CALL apoc.help("apoc.periodic.iterate");
La procédure est listée avec sa signature.
12. Mot de passe Neo4j changé dans .env mais refusé
Provoquer. Éditez .env, remplacez NEO4J_PASSWORD=veille2026 par NEO4J_PASSWORD=nouveau2026, puis :
./lab.sh down
./lab.sh up
Tentez de vous connecter dans Neo4j Browser avec nouveau2026.
Message exact.
The client is unauthorized due to authentication failure.
Dans ./lab.sh logs neo4j :
[o.n.k.a.p.SecurityLogFilter] Failed authentication attempt for 'neo4j' from ...
Cause. Symétrique du cas 6. Neo4j écrit le hachage du mot de passe dans le volume neo4j-data au premier démarrage. NEO4J_AUTH=neo4j/${NEO4J_PASSWORD} n'est lu qu'à ce moment-là ; ensuite, le mot de passe est celui du volume, pas celui du .env.
Réparer. Deux options.
Option A — changer le mot de passe via cypher-shell en s'authentifiant avec l'ancien :
docker exec -it veille-neo4j cypher-shell -u neo4j -p veille2026 \
"ALTER USER neo4j SET PASSWORD 'nouveau2026' CHANGE NOT REQUIRED;"
Option B — repartir de zéro :
./lab.sh reset
./lab.sh up
Le volume neo4j-data disparaît, la variable NEO4J_AUTH est relue au premier démarrage.
Vérifier.
docker exec veille-neo4j cypher-shell -u neo4j -p nouveau2026 \
"RETURN 1 AS ok;"
La colonne ok renvoie 1 sans erreur d'authentification.
Méthode générale en cinq étapes
Quand la panne n'est dans aucune des douze ci-dessus, appliquez la même séquence :
- Lire le statut.
./lab.sh status— quels conteneurs sontUp (healthy), lesquels ontExited (code)? Le code donne déjà l'essentiel (0 = normal, 137 = OOM, 143 = SIGTERM, 1 = erreur applicative). - Lire les journaux du service concerné.
./lab.sh logs <service> --tail=200. Le message d'erreur exact est presque toujours dans les dernières lignes. Copiez-le mot pour mot. - Isoler le service. Testez chaque brique séparément :
./lab.sh es _cluster/health, puis Kibana (/api/status), puis Neo4j (cypher-shell "RETURN 1;"). Le premier qui échoue est la source. - Réinitialiser au bon niveau. Trois niveaux :
docker restart <conteneur>, puis./lab.sh down/up(garde les volumes), puis./lab.sh reset/up(efface tout, dernier recours). N'appliquez jamais 3 sans avoir essayé 1 et 2. - Demander de l'aide avec les bonnes informations. Copiez-collez dans l'ordre : la commande tapée, la sortie de
./lab.sh status, les vingt dernières lignes de./lab.sh logs <service>,./lab.sh doctor, votre OS etdocker version --format '{{.Server.Version}}'. Ces cinq blocs permettent à n'importe qui de reproduire votre situation en trente secondes.
Le seul travail d'un journal est de dire la vérité. Chaque fois que vous êtes tenté de « redémarrer pour voir », relisez d'abord les vingt dernières lignes. Neuf fois sur dix, la cause y est écrite en toutes lettres — souvent avec la commande de correction.
À vous
Exercice 1 — Reproduire et lire
Provoquez la panne 5 (index en lecture seule) sur un index de test, écrivez sur un fichier panne5.txt : le message exact renvoyé, la commande qui corrige, la commande qui vérifie que c'est réparé.
Solution
PUT test_ro
{"settings":{"index":{"number_of_shards":1,"number_of_replicas":0}}}
PUT test_ro/_settings
{"index.blocks.read_only_allow_delete": true}
POST test_ro/_doc
{"x": 1}
Message :
cluster_block_exception, index [test_ro] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];
Correction :
PUT test_ro/_settings
{"index.blocks.read_only_allow_delete": null}
Vérification :
POST test_ro/_doc
{"x": 2}
Réponse : {"result": "created"}. Le blocage est levé.
Exercice 2 — Diagnostic à froid
Un collègue vous montre son écran : Kibana affiche « Kibana server is not ready yet » depuis trois minutes. Il a déjà relancé son navigateur trois fois. Écrivez la séquence exacte de trois commandes à exécuter dans l'ordre, sans deviner, pour identifier la cause.
Solution
./lab.sh status
./lab.sh logs setup --tail=50
./lab.sh logs elasticsearch --tail=50
status dit si veille-es et veille-setup sont en bon état ; logs setup révèle un échec de synchronisation du mot de passe kibana_system ; logs elasticsearch révèle un problème plus profond (heap saturé, watermark, port). Si les trois passent, un ./lab.sh logs kibana --tail=50 termine le diagnostic. La règle : ne jamais relancer avant d'avoir lu.
Exercice 3 — Le message à copier
Rédigez, comme si vous ouvriez une question sur un canal d'aide interne, le message parfait pour la panne 12 (mot de passe Neo4j refusé après changement). Cinq blocs, dans l'ordre de l'étape 5 de la méthode générale.
Solution
Bonjour, Neo4j Browser me refuse l'authentification depuis le changement de mot de passe dans .env.
Commande tapée :
docker exec veille-neo4j cypher-shell -u neo4j -p nouveau2026 "RETURN 1;"
./lab.sh status :
veille-es Up 8 minutes (healthy) 0.0.0.0:9200->9200/tcp
veille-kibana Up 7 minutes (healthy) 0.0.0.0:5601->5601/tcp
veille-neo4j Up 8 minutes (healthy) 0.0.0.0:7474->7474/tcp, 0.0.0.0:7687->7687/tcp
./lab.sh logs neo4j --tail=20 :
...
[o.n.k.a.p.SecurityLogFilter] Failed authentication attempt for 'neo4j' from 172.19.0.5
...
./lab.sh doctor : tout OK, ports libres, mémoire 8 Go.
OS : Windows 11 + WSL2 Ubuntu, Docker Desktop 29.0.2.
Trente secondes plus tard, la personne qui aide sait que c'est la panne 12 et pointe vers ./lab.sh reset.
Points à retenir
- Douze pannes couvrent la quasi-totalité des blocages du kit : port, mémoire,
vm.max_map_count, cluster color, watermark, mot de passe Elasticsearch, Kibana pas prêt, mapping figé,LOAD CSV,:auto, APOC, mot de passe Neo4j. - Cinq d'entre elles se corrigent avec une seule commande ; deux (mots de passe 6 et 12) exigent
./lab.sh resetparce que les identifiants vivent dans le volume au premier démarrage. - Le code de sortie d'un conteneur donne déjà l'essentiel :
137= OOM,143= SIGTERM,1= erreur applicative. - La méthode générale tient en cinq étapes — statut, journaux, isolation, réinitialisation graduée, aide informée — et fonctionne aussi pour les pannes qui ne figurent pas dans le tableau.
./lab.sh doctordétecte à l'avance les pannes 1 (ports), 2 (mémoire), 3 (vm.max_map_count) ; passez-le d'abord.- Le journal a toujours raison : les vingt dernières lignes de
./lab.sh logs <service>contiennent presque toujours la cause exacte. - Un bon message d'aide (commande, statut, journaux, doctor, OS) fait gagner trente minutes à celui qui aide et à celui qui demande.
Si ça ne marche pas
Cette section renvoie au tableau récapitulatif ci-dessus : trouvez le symptôme exact dans la colonne « Symptôme », suivez la ligne. Si votre message d'erreur n'y figure pas, appliquez la méthode générale en cinq étapes et copiez-collez les cinq blocs à quelqu'un de l'équipe. Neuf fois sur dix, la réponse arrive avant que vous n'ayez fini d'écrire.