Aller au contenu principal

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

NSymptôme exactCauseCorrection
1failed to bind host port ... address already in usePort 9200, 5601, 7474 ou 7687 déjà prisdocker ps puis docker stop <nom>, ou libérer le port
2Conteneur Exited (137), OOMKilled: trueDocker a tué le processus, mémoire dépasséeAugmenter Docker Desktop → Memory ou mem_limit
3max virtual memory areas vm.max_map_count [65530] is too lowLinux : paramètre kernel trop bassudo sysctl -w vm.max_map_count=262144
4_cluster/health en yellow ou redRépliques non assignées, ou shard primaire perduGET _cluster/allocation/explain puis correction
5cluster_block_exception, index read-only / allow delete (api)Watermark disque atteintPUT news/_settings {"index.blocks.read_only_allow_delete": null}
6security_exception ... unable to authenticate user [elastic]ELASTIC_PASSWORD modifié après création du volume./lab.sh reset puis ./lab.sh up
7Kibana server is not ready yetkibana_system ou Elasticsearch pas prêt./lab.sh logs setup puis relancer ./lab.sh up
8mapper_parsing_exception ou mapper cannot be changedType de champ incompatible_reindex vers un nouvel index avec le bon mapping
9Couldn't load the external resource at: file:/import/news.csvFichier absent dans neo4j/import/./lab.sh import-news
10CALL { ... } IN TRANSACTIONS can only be executed in an implicit transactionExécuté dans une transaction explicitePréfixer par :auto dans Neo4j Browser
11There is no procedure with the name apoc.periodic.iterate registeredAPOC non chargéVérifier NEO4J_PLUGINS, redémarrer Neo4j
12The 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 : elasticsearchsetupkibananeo4j, 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 :

  1. Lire le statut. ./lab.sh status — quels conteneurs sont Up (healthy), lesquels ont Exited (code) ? Le code donne déjà l'essentiel (0 = normal, 137 = OOM, 143 = SIGTERM, 1 = erreur applicative).
  2. 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.
  3. 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.
  4. 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.
  5. 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 et docker version --format '{{.Server.Version}}'. Ces cinq blocs permettent à n'importe qui de reproduire votre situation en trente secondes.
Le journal a raison

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 reset parce 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 doctor dé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.

Pour aller plus loin