Pratique guidée — Démarrer, vérifier, casser et réparer le labo

Pratique guidée67 min
Durée
45 à 60 min
Module
1/7
Tu vas construire
un labo chargé, vérifié par trois chemins (script, Dev Tools, Neo4j Browser), puis volontairement cassé et réparé
Livrable
la sortie complète de etat avec (healthy) partout, 504 / 609 / 12000 documents et 872 nœuds, plus deux lignes expliquant la panne que tu as provoquée

Comment lire cette page. Chaque section est repliée sous son titre : clique sur « Afficher … » pour l'ouvrir, et referme-la quand tu as fini pour garder la page lisible. Ordre de lecture : Objectif, puis En bref (les commandes à taper), puis Le jeu de données (à lire avant toute requête), puis les requêtes Elasticsearch et Neo4j, classées de la plus simple (GET _cat/indices, MATCH (n) RETURN n) à la plus impressionnante, avec une explication après chacune. Le pas à pas détaillé, avec la sortie attendue de chaque commande et les pannes à provoquer, est en annexe : annexe A pour Windows (PowerShell), annexe B pour Linux, macOS, WSL 2 et Git Bash. Ouvre une seule annexe, celle de ton système. L'annexe C, commune, regroupe les cas où ça coince.

Objectif

Tu rejoins l'équipe qui construit le moteur de recherche de la plateforme de cours en ligne. Ta cheffe te tend le kit du labo : « Demain matin, je veux un labo qui tourne sur ton poste, les données chargées, et la certitude que tu sais le dépanner sans m'appeler. » Tu vas donc démarrer la stack, prouver que les trois moteurs contiennent ce qu'ils doivent, puis arrêter un service exprès pour voir comment la panne se lit dans etat, dans le navigateur et dans le journal, et le remettre en route. Reconnaître « ce service est arrêté » en dix secondes, c'est ce qui évite des heures de recherche au mauvais endroit.

Les neuf étapes de ce schéma sont détaillées, avec la sortie attendue de chaque commande, dans l'annexe A (Windows) ou l'annexe B (Linux, macOS) en bas de page.

En bref : les commandes du lab

Afficher les commandes

Kit du labo : https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr

Tu clones le kit dans un dossier lab1, tu vérifies que Docker est prêt, tu démarres les trois services (Elasticsearch, Kibana, Neo4j), tu ouvres leurs trois pages web, puis tu charges les données. Tu lances importer et charger-graphe deux fois : la seconde ne doit rien changer aux compteurs, c'est la preuve que le chargement est rejouable sans doublon. À la fin, etat doit afficher (healthy) partout, acces 12000 avis 609 cours 504 et nœuds : 872. Commence par exécuter ce bloc.

Windows (PowerShell)

powershell
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr.git lab1
cd lab1
ls                       # explorer le contenu : docker-compose.yml, labo.ps1, labo.sh, elasticsearch/, neo4j/, outils/
.\labo.ps1 prerequis
.\labo.ps1 demarrer
.\labo.ps1 etat

Vérifier les trois URL dans le navigateur :

text
Kibana           http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch    http://localhost:9200
Neo4j Browser    http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)
powershell
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer          # 2e passage : mêmes compteurs, rien ne double
.\labo.ps1 charger-graphe
.\labo.ps1 etat              # attendu : acces 12000  avis 609  cours 504  nœuds : 872

Si PowerShell refuse .\labo.ps1 (« l'exécution de scripts est désactivée ») : Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, réponds O, relance.

Linux, macOS, WSL 2, Git Bash

bash
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr.git lab1
cd lab1
ls                       # explorer le contenu : docker-compose.yml, labo.sh, labo.ps1, elasticsearch/, neo4j/, outils/
./labo.sh prerequis
./labo.sh demarrer
./labo.sh etat

Vérifier les trois URL dans le navigateur :

text
Kibana           http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch    http://localhost:9200
Neo4j Browser    http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)
bash
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer           # 2e passage : mêmes compteurs, rien ne double
./labo.sh charger-graphe
./labo.sh etat               # attendu : acces 12000  avis 609  cours 504  nœuds : 872

Le jeu de données : ce que tu vas manipuler

Afficher le jeu de données

Avant de taper une seule requête, regarde les données. Tout le labo tourne autour d'une plateforme de cours en ligne fictive : un catalogue de cours, les avis laissés par les étudiants, le journal du serveur web qui sert les pages, et les liens entre étudiants, professeurs, cours et compétences. Les mêmes données sont chargées dans Elasticsearch (pour chercher) et dans Neo4j (pour suivre les liens). Elles sont dans le kit, en clair, dans deux dossiers :

text
lab1/
├── elasticsearch/donnees/        ← ce qui va dans Elasticsearch (3 fichiers NDJSON)
│   ├── cours.ndjson                 504 cours
│   ├── avis.ndjson                  609 avis
│   └── acces.ndjson              12 000 lignes de journal web
└── neo4j/import/                 ← ce qui va dans Neo4j (8 fichiers CSV)
    ├── cours.csv                    504 cours          → nœuds Cours
    ├── etudiants.csv                300 étudiants      → nœuds Etudiant
    ├── professeurs.csv               30 professeurs    → nœuds Professeur
    ├── competences.csv               22 compétences    → nœuds Competence
    ├── villes.csv                    16 villes         → nœuds Ville
    ├── inscriptions.csv           1 654 inscriptions   → relations INSCRIT_A
    ├── couvre.csv                   994 liens          → relations COUVRE
    └── prerequis.csv                230 liens          → relations PREREQUIS_DE

Ouvre-les toi-même, ça prend dix secondes et tu sauras exactement ce que tu manipules :

powershell
# Windows (PowerShell), depuis le dossier lab1
Get-Content elasticsearch\donnees\cours.ndjson -TotalCount 2
Get-Content elasticsearch\donnees\avis.ndjson  -TotalCount 2
Get-Content elasticsearch\donnees\acces.ndjson -TotalCount 2
Get-Content neo4j\import\cours.csv -TotalCount 3
Get-Content neo4j\import\inscriptions.csv -TotalCount 3
bash
# Linux, macOS, WSL 2, Git Bash, depuis le dossier lab1
head -n 2 elasticsearch/donnees/cours.ndjson
head -n 2 elasticsearch/donnees/avis.ndjson
head -n 2 elasticsearch/donnees/acces.ndjson
head -n 3 neo4j/import/cours.csv
head -n 3 neo4j/import/inscriptions.csv

Côté Elasticsearch : trois index, trois sortes de documents

Un fichier .ndjson (newline-delimited JSON) contient un objet JSON par ligne. Dans le kit, les lignes vont par deux : une ligne d'instruction (« range ce qui suit dans l'index cours sous l'identifiant C0001 »), puis le document lui-même. C'est le format que la commande importer envoie à Elasticsearch en bloc.

text
{"index":{"_index":"cours","_id":"C0001"}}
{"id":"C0001","titre":"Docker expliqué simplement","description":"…","categorie":"DevOps", … }

Index cours : 504 documents, un par cours du catalogue

Le premier document, tel qu'il est dans Elasticsearch :

json
{
  "id": "C0001",
  "titre": "Docker expliqué simplement",
  "description": "Dans ce cours accessible sans prérequis, vous apprenez à automatiser vos applications avec Docker. …",
  "categorie": "DevOps",
  "sujet": "Docker",
  "niveau": "debutant",
  "langue": "en",
  "prix": 129,
  "gratuit": false,
  "duree_heures": 5,
  "tags": ["docker", "linux", "helm", "devops"],
  "date_publication": "2024-08-14",
  "note_moyenne": 4.4,
  "nb_avis": 327,
  "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },
  "competences": ["Conteneurisation", "Intégration continue"]
}
ChampExempleCe que c'est
idC0001Identifiant du cours. C comme cours, puis un numéro. C'est aussi l'_id du document.
titreDocker expliqué simplementLe titre. Texte libre, c'est là-dessus qu'on fera les recherches par mots.
descriptionDans ce cours…Un paragraphe de présentation. Texte libre aussi.
categorieDevOpsUne des six grandes familles : Cloud, DevOps, Données, Développement web, IA, Sécurité. 84 cours chacune.
sujetDockerPlus précis que la catégorie : Docker, Kubernetes, Neo4j, Elasticsearch…
niveaudebutantdebutant, intermediaire ou avance.
langueenfr ou en.
prix129En dollars. 0 pour un cours gratuit.
gratuitfalseVrai ou faux.
duree_heures5Durée totale du cours.
tags["docker", "linux", …]Une liste de mots-clés. Un champ peut contenir plusieurs valeurs.
date_publication2024-08-14Une date.
note_moyenne4.4Moyenne des notes reçues, sur 5.
nb_avis327Nombre d'avis reçus.
professeur{ "id": "P001", "nom": …, "ville": … }Un objet dans l'objet : le professeur est décrit directement dans la fiche du cours, avec son identifiant, son nom et sa ville.
competences["Conteneurisation", …]Liste des compétences que le cours couvre.

Index avis : 609 documents, un par avis laissé par un étudiant

json
{
  "id": "A00001",
  "cours_id": "C0028",
  "etudiant": "Nathan",
  "ville": "Sherbrooke",
  "pays": "Canada",
  "note": 3,
  "texte": "Les vidéos sont bonnes, la partie théorique est dense.",
  "date": "2024-06-21",
  "utile": 33
}
ChampExempleCe que c'est
idA00001Identifiant de l'avis. A comme avis.
cours_idC0028Le cours concerné. C'est le lien vers l'index cours : ce C0028 est l'id d'un document de cours.
etudiantNathanPrénom de l'auteur.
ville, paysSherbrooke, CanadaD'où il écrit.
note3La note donnée, de 1 à 5.
texteLes vidéos sont bonnes…Le commentaire. Texte libre.
date2024-06-21Date de l'avis.
utile33Nombre de personnes qui ont trouvé cet avis utile.

Index acces : 12 000 documents, une ligne par requête reçue par le serveur web

json
{
  "id": "L000001",
  "@timestamp": "2026-08-10T08:17:19.000Z",
  "methode": "GET",
  "chemin": "/robots.txt",
  "cours_id": null,
  "categorie": null,
  "statut": 200,
  "octets": 108506,
  "duree_ms": 100,
  "ip": "108.190.166.1",
  "pays": "CA",
  "appareil": "desktop",
  "navigateur": "Edge",
  "referent": "google"
}
ChampExempleCe que c'est
idL000001Identifiant de la ligne. L comme ligne de journal (log).
@timestamp2026-08-10T08:17:19.000ZDate et heure exactes de la requête. Le @ est une convention : c'est le champ de temps que Kibana repère automatiquement.
methodeGETGET ou POST.
chemin/robots.txtL'adresse demandée sur le site : /, /cours, /catalogue, /contact, /tarifs, ou la page d'un cours.
cours_idnull ou C0042Le cours consulté, si la page en concerne un ; null (vide) sinon, comme ici pour /robots.txt. Lien vers l'index cours.
categorienull ou DevOpsCatégorie du cours consulté, recopiée pour simplifier les graphiques.
statut200Le code HTTP de la réponse : 200 OK, 301 et 304 redirection ou cache, 404 introuvable, 500 et 503 erreur serveur.
octets108506Taille de la réponse envoyée.
duree_ms100Temps de réponse en millisecondes.
ip108.190.166.1Adresse IP du visiteur.
paysCAPays du visiteur, code à deux lettres.
appareildesktopdesktop, mobile ou tablette.
navigateurEdgeChrome, Firefox, Safari, Edge…
referentgoogleD'où venait le visiteur : direct, google, linkedin, youtube ou newsletter.

Les trois index sont liés par cours_id : un avis parle d'un cours, une ligne de journal consulte un cours. Mais Elasticsearch ne fait pas de jointure : chaque index se cherche séparément. Pour suivre les liens, on passe dans Neo4j.

Côté Neo4j : cinq sortes de nœuds, cinq sortes de relations

Neo4j reçoit les mêmes cours (mêmes identifiants C0001…, mêmes titres), plus ce qui n'est pas dans Elasticsearch : les étudiants, leurs inscriptions, les professeurs, les compétences, les villes, et les prérequis entre cours. Le modèle tient en un dessin :

Les cinq flèches se lisent comme des phrases : un étudiant est inscrit à un cours ; un professeur enseigne un cours ; un cours couvre une compétence ; un cours **est prérequis d'**un autre cours ; un étudiant ou un professeur habite une ville. Au total 872 nœuds et 3 712 relations.

Les données arrivent sous forme de fichiers CSV classiques, une ligne d'en-tête puis une ligne par élément :

text
cours.csv          id,titre,categorie,sujet,niveau,prix,duree_heures,date_publication,professeur_id
                   C0001,Docker expliqué simplement,DevOps,Docker,debutant,129,5,2024-08-14,P001

etudiants.csv      id,prenom,nom,ville,pays,inscription_le,interet
                   E0001,Nathan,Ben Ali,Sherbrooke,Canada,2023-05-15,DevOps

professeurs.csv    id,prenom,nom,ville,pays,specialite,annees_experience
                   P001,Karim,Caron,Gatineau,Canada,DevOps,23

competences.csv    id,nom
                   K01,Conteneurisation

villes.csv         nom,pays,latitude,longitude
                   Montréal,Canada,45.5019,-73.5674

inscriptions.csv   etudiant_id,cours_id,date,progression,note
                   E0001,C0028,2024-04-01,100,3

couvre.csv         cours_id,competence_id
                   C0001,K01

prerequis.csv      prerequis_id,cours_id
                   C0001,C0003

Regarde comment ça se traduit en graphe. Les cinq premiers fichiers deviennent des nœuds ; chaque ligne est un nœud, chaque colonne une propriété. Les trois derniers deviennent des relations ; chaque ligne relie deux nœuds par leurs identifiants. La ligne E0001,C0028,2024-04-01,100,3 de inscriptions.csv devient la flèche (Etudiant E0001)-[:INSCRIT_A {date, progression, note}]->(Cours C0028) : Nathan Ben Ali est inscrit au cours C0028, l'a terminé à 100 %, et lui a mis 3. Compare avec le premier avis de l'index avis : c'est le même Nathan, le même cours C0028, la même note 3.

NœudPropriétésExemple
Coursid, titre, categorie, sujet, niveau, prix, duree_heures, date_publicationC0001, « Docker expliqué simplement », 129 $
Etudiantid, prenom, nom, interet, inscription_leE0001, Nathan Ben Ali, intérêt DevOps
Professeurid, prenom, nom, specialite, annees_experienceP001, Karim Caron, DevOps, 23 ans
Competenceid, nomK01, Conteneurisation
Villenom, pays, latitude, longitudeMontréal, Canada
RelationDe → versPropriétésSens
INSCRIT_AEtudiant → Coursdate, progression, noteL'étudiant suit ce cours
ENSEIGNEProfesseur → CoursCe professeur donne ce cours (un seul par cours)
COUVRECours → CompetenceCe cours apprend cette compétence
PREREQUIS_DECours → CoursIl faut avoir suivi le premier avant le second
HABITEEtudiant ou Professeur → VilleOù la personne vit

Les identifiants, pour s'y retrouver

Toutes les données utilisent la même convention : une lettre, puis un numéro. Quand tu vois un identifiant, tu sais tout de suite de quoi on parle.

PréfixeChoseExempleOù on le trouve
CCoursC0001Elasticsearch cours, avis.cours_id, acces.cours_id, Neo4j Cours
AAvisA00001Elasticsearch avis
LLigne de journalL000001Elasticsearch acces
EÉtudiantE0001Neo4j Etudiant
PProfesseurP001cours.professeur.id dans Elasticsearch, Neo4j Professeur
KCompétenceK01Neo4j Competence

Retiens un seul fil pour toute la suite : le cours C0001, « Docker expliqué simplement », enseigné par P001, Karim Caron. Tu le retrouveras dans Elasticsearch (requête E9), dans Neo4j (requêtes N7 et N13), et il servira à prouver que les deux moteurs contiennent bien la même chose.

Premières requêtes : montrer les données, du plus simple au plus impressionnant

Les compteurs de etat prouvent que les données sont là ; les requêtes ci-dessous les font voir. Elles sont toutes en lecture seule : tu peux les rejouer autant de fois que tu veux, rien ne sera modifié. Elles sont identiques sous Windows et sous Linux, tout se passe dans le navigateur.

La règle de cette section : une seule nouveauté par requête. On commence par « montre-moi ce qu'il y a », sans aucun paramètre, et on ajoute une notion à chaque pas. Après chaque requête, une explication courte ; quand il faut creuser, un bloc dépliable « Pour bien comprendre ». Colle les requêtes une par une, dans l'ordre, et lis la réponse avant de passer à la suivante.

Elasticsearch, dans Kibana Dev Tools

Afficher les 15 requêtes Elasticsearch (E1 à E15)

Ouvre http://localhost:5601, puis menu ManagementDev Tools (ou « Outils de développement »). Le volet de gauche est un éditeur : colle une requête, place le curseur dessus, puis Ctrl+Entrée ou le triangle ▶. La réponse s'affiche à droite, avec le code HTTP (200 - OK) en bas.

Une image à garder en tête pour toute la suite : Elasticsearch est une grande armoire.

text
L'armoire  = Elasticsearch
Un tiroir  = un index          (le tiroir « cours », le tiroir « avis », le tiroir « acces »)
Une fiche  = un document       (une fiche par cours, une fiche par avis, une fiche par ligne de journal)

_cat/indices  = lire les étiquettes collées sur les tiroirs : nom, état, nombre de fiches, épaisseur
_search       = ouvrir un tiroir et lire les fiches qui sont dedans

Les requêtes E1 à E4 regardent les étiquettes des tiroirs. À partir de E5, on ouvre les tiroirs. Ne confonds jamais les deux : c'est l'erreur numéro un des débutants.

E1. Lister les tiroirs

text
GET _cat/indices

Ce que la requête demande : « Elasticsearch, montre-moi la liste de tous tes tiroirs. » Réponse sur la machine du cours :

text
green open avis                                            W9j_JrwJT4mdzpWcS5k7xg 1 0   609 0  74.6kb  74.6kb  74.6kb
green open .internal.alerts-security.alerts-default-000001 YJq3VGeRQKqD0fqJW6Fw3w 1 0     0 0    249b    249b    249b
green open acces                                           aii68fsfQXKyt5wqkE1mPA 1 0 12000 0   1.5mb   1.5mb   1.5mb
green open cours                                           pmq403ZgSZWeHJY9uNw1Qw 1 0   504 0 183.6kb 183.6kb 183.6kb

Quatre lignes = quatre tiroirs. Trois sont à toi : avis, acces, cours. Le quatrième, celui dont le nom commence par un point (.internal.alerts-security…), n'est pas à toi : Kibana l'a créé tout seul pour son usage interne (la gestion d'alertes de sécurité). Il est vide (0 fiches), il ne gêne pas, ne le supprime pas. Dans la suite, on l'écartera de l'affichage.

Ce qui est pénible ici : il y a des chiffres partout et aucun titre de colonne. C'est comme un tableau Excel sans ligne d'en-tête. On corrige ça en E2.

Pour lire la requête elle-même, mot par mot :

MorceauCe que ça veut dire
GET« Je veux lire quelque chose. » Je ne modifie rien, je ne supprime rien, je regarde. Sans risque.
_cat« Réponds-moi en tableau texte », lisible par un humain, pas en JSON. Le tiret bas au début signale une commande d'Elasticsearch lui-même, pas un nom d'index.
indices« … le tableau des index. » C'est le pluriel anglais d'index.
Pour bien comprendre : c'est quoi un index, et lire une ligne valeur par valeur

Un index, c'est un tiroir : un endroit où l'on range des fiches qui se ressemblent. Toutes les fiches « cours » vont dans le tiroir cours, toutes les fiches « avis » dans le tiroir avis, et chaque ligne du journal du serveur web est une fiche du tiroir acces. Si tu connais SQL, l'index est ce que SQL appelle une table :

ElasticsearchDans l'imageSQL
Index coursLe tiroir « cours »Table cours
Document JSONUne fiche dans le tiroirLigne
ChampUne case sur la fiche (titre, prix…)Colonne
_id du documentLe numéro écrit en haut de la ficheClé primaire

Les trois tiroirs du labo :

text
Elasticsearch
├── index cours  → 504 documents     (un document = un cours du catalogue)
├── index avis   → 609 documents     (un document = un avis laissé par un étudiant)
└── index acces  → 12 000 documents  (un document = une ligne de journal du serveur web)

Deux pièges de vocabulaire. Le singulier est un index, et le pluriel est des index en français (Elasticsearch écrit indices, le pluriel anglais). Et ce n'est pas un « indice » au sens mathématique ou policier : c'est un index comme celui à la fin d'un livre, la liste qui dit à quelle page se trouve chaque mot. Elasticsearch fait exactement ça, en très grand.

Maintenant, prenons une seule ligne de la réponse E1 et lisons-la valeur par valeur, de gauche à droite :

text
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb
ValeurCe que ça veut dire
greenLe tiroir est en bon état. Vert = tout va bien.
openLe tiroir est ouvert : on peut lire et écrire dedans. (Le contraire serait close.)
avisLe nom du tiroir.
W9j_JrwJT4mdzpWcS5k7xgUn numéro de série technique, généré par Elasticsearch. Tu ne t'en serviras jamais.
1Le tiroir est en un seul morceau (un shard principal). Un gros index peut être découpé en plusieurs morceaux répartis sur plusieurs machines ; ici, non.
0Zéro copie de sécurité (replica). Normal dans un labo à une seule machine : une copie n'aurait nulle part où aller.
609Le tiroir contient 609 fiches. C'est la valeur qu'on regarde en premier.
0Zéro fiche marquée « à jeter » en attente de nettoyage.
74.6kbPlace totale prise sur le disque.
74.6kbPlace prise par le morceau principal seul (identique : il n'y a qu'un morceau et pas de copie).
74.6kbTaille des données elles-mêmes.

Trois fois 74.6kb parce que, avec un seul morceau et zéro copie, les trois façons de mesurer donnent le même résultat. Sur un vrai cluster avec des copies, store.size serait plus grand que pri.store.size.

En résumé, tes trois tiroirs :

text
avis   →    609 fiches  →  74,6 Ko
acces  → 12 000 fiches  →   1,5 Mo
cours  →    504 fiches  → 183,6 Ko

E2. La même liste, avec les titres de colonnes : ?v

text
GET _cat/indices?v
text
health status index                                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   avis                                            W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.6kb         74.6kb       74.6kb
green  open   .internal.alerts-security.alerts-default-000001 YJq3VGeRQKqD0fqJW6Fw3w   1   0          0            0       249b           249b         249b
green  open   acces                                           aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   cours                                           pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Même réponse qu'en E1, plus une ligne de titres au-dessus. C'est tout ce que fait ?v. Maintenant tu n'as plus à deviner : la colonne docs.count est le nombre de fiches (609, 0, 12000, 504), la colonne store.size est la place sur le disque.

?v, ça veut dire quoi, exactement ?

  • Le ? dit : « ce qui suit, ce sont des options ». Il sépare la commande (_cat/indices) de ses réglages.
  • Le v est l'option verbose, « bavard » : « affiche aussi les titres des colonnes ».

Donc ?v = « réponds-moi avec les en-têtes, pour que je comprenne ce que je lis ». Prends l'habitude de le mettre toujours sur les commandes _cat. Sans ?v, tu as des chiffres ; avec ?v, tu as de l'information.

Pour bien comprendre : chaque titre de colonne, en une phrase
ColonneEn clair
healthSanté du tiroir. green : tout va bien. yellow : les fiches sont là, mais certaines copies de sécurité manquent. red : certaines fiches sont inaccessibles.
statusopen : utilisable. close : fermé, on ne peut ni lire ni écrire.
indexLe nom du tiroir.
uuidNuméro de série technique. On ne s'en sert jamais à la main.
priEn combien de morceaux (primary shards) le tiroir est découpé. 1 dans le labo.
repCombien de copies de sécurité (replicas) de chaque morceau. 0 dans le labo.
docs.countNombre de fiches. La colonne à regarder en premier.
docs.deletedFiches marquées « à jeter » pas encore nettoyées du disque.
store.sizePlace totale sur le disque.
pri.store.sizePlace des morceaux principaux seuls, sans les copies.
dataset.sizeTaille des données elles-mêmes.

Pourquoi tout est green dans le labo : rep vaut 0, donc il n'y a aucune copie à faire, donc aucune copie ne peut manquer. Sur un vrai cluster, rep 1 avec une seule machine donnerait yellow, parce que la copie n'a nulle part où aller.

E3. L'étiquette d'un seul tiroir

text
GET _cat/indices/cours?v
text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Ce que la requête demande : « Montre-moi l'étiquette du tiroir cours. » Une seule nouveauté : le nom du tiroir ajouté après _cat/indices/. Une seule ligne en réponse, celle de cours, avec ses 504 fiches. Ce qui suit le dernier / sert de filtre.

Fais pareil pour les deux autres tiroirs :

text
GET _cat/indices/avis?v
GET _cat/indices/acces?v

« Montre-moi l'étiquette du tiroir des avis » : 609 dans docs.count. « Montre-moi l'étiquette du tiroir des journaux d'accès » : 12000. Dans Dev Tools, quand plusieurs requêtes sont collées à la suite, seule celle où se trouve le curseur est envoyée : place-le sur la ligne voulue avant Ctrl+Entrée.

E4. Le résumé propre des trois tiroirs

text
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,docs.count,store.size
text
index health docs.count store.size
acces green       12000      1.5mb
avis  green         609     74.6kb
cours green         504    183.6kb

Ce que la requête demande : « Montre-moi un tableau résumé des tiroirs acces, avis et cours. Trie-les par nom, et n'affiche que leur nom, leur santé, leur nombre de fiches et leur taille. » Quatre lignes, quatre colonnes, tout ce qu'il faut et rien de plus. C'est la requête à garder sous la main pour vérifier le labo en un coup d'œil.

Elle a l'air compliquée parce qu'elle est longue, mais c'est juste E3 avec trois options de plus. Décomposition, morceau par morceau :

MorceauEn clair
GETJe demande une information, je ne modifie rien.
_cat/indicesLes étiquettes des tiroirs, en tableau lisible.
/acces,avis,coursSeulement ces trois tiroirs (noms séparés par des virgules, sans espace). Le tiroir interne de Kibana disparaît.
?vAvec les titres de colonnes.
&s=indexs comme sort : trie les lignes par ordre alphabétique de la colonne index.
&h=index,health,docs.count,store.sizeh comme headers : n'affiche que ces colonnes, dans cet ordre.

Une règle et une seule pour les options : la première commence par ?, toutes les suivantes par &. C'est pour ça qu'on lit ?v&s=…&h=….

Et les quatre colonnes choisies :

ColonneEn clair
indexLe nom du tiroir.
healthSa santé : green tout fonctionne ; yellow les fiches existent mais des copies de sécurité manquent ; red certaines fiches sont indisponibles.
docs.countCombien de fiches il contient.
store.sizeCombien de place il prend sur le disque.

En une phrase : tes trois tiroirs sont ouverts, en bonne santé, et contiennent exactement les fiches attendues, 12 000, 609 et 504.

Pour bien comprendre : pourquoi c'est la requête à retenir

Sans h=, Elasticsearch renvoie onze colonnes dont la plupart ne t'apprennent rien au quotidien. Sans s=, l'ordre des lignes est arbitraire et change d'un appel à l'autre (regarde E2 : avis arrivait avant acces). En nommant les trois tiroirs, tu écartes aussi celui de Kibana. Le résultat tient sur quatre lignes et se compare d'un coup d'œil avec les chiffres attendus. C'est exactement ce que fait la commande etat du kit en coulisses.

Variante utile si tu veux aussi voir que les tiroirs sont bien ouverts : ajoute status dans la liste h= :

text
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,status,docs.count,store.size
text
index health status docs.count store.size
acces green  open        12000      1.5mb
avis  green  open          609     74.6kb
cours green  open          504    183.6kb

Bilan de E1 à E4 : tu n'as encore lu aucune fiche. Tu as seulement regardé les étiquettes sur les tiroirs. Tu sais qu'il y a 504 cours, mais tu n'as pas encore vu un seul titre de cours. C'est ce qu'on fait maintenant.

E5. Compter les fiches d'un tiroir

text
GET cours/_count
json
{
  "count": 504,
  "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }
}

Ce que la requête demande : « Compte les fiches du tiroir cours. » Réponse : 504. Regarde bien la forme de la requête, elle est nouvelle : on ne parle plus à l'armoire (_cat/…), on parle à un tiroir. Le nom du tiroir vient en premier (cours), puis ce qu'on veut lui faire (_count), séparés par un /. Toutes les requêtes qui suivent auront cette forme : nom-du-tiroir/_action.

Autre changement : la réponse n'est plus un tableau texte mais du JSON, avec des accolades et des guillemets. C'est la forme normale des réponses d'Elasticsearch ; _cat était l'exception. Ignore la partie _shards, elle dit seulement « le morceau du tiroir a répondu, rien n'a échoué ».

Essaie GET avis/_count (609) et GET acces/_count (12000). En SQL : SELECT COUNT(*) FROM cours.

E6. Ouvrir le tiroir et lire les fiches

text
GET cours/_search

Ce que la requête demande : « Ouvre le tiroir cours et montre-moi les fiches qui sont dedans. » C'est la première fois que tu vois un vrai cours : son titre, son prix, son professeur.

La réponse est longue : c'est normal, elle contient dix fiches complètes. Elasticsearch ne te donne que les 10 premières, même si le tiroir en contient 504 ; c'est une protection, pour ne pas t'envoyer 504 fiches d'un coup sans que tu l'aies demandé. Regarde la structure plutôt que le contenu :

json
{
  "took": 1,
  "timed_out": false,
  "_shards": {  },
  "hits": {
    "total": { "value": 504, "relation": "eq" },
    "max_score": 1,
    "hits": [
      {
        "_index": "cours",
        "_id": "C0001",
        "_score": 1,
        "_source": {
          "id": "C0001",
          "titre": "Docker expliqué simplement",
          "categorie": "DevOps",
          "niveau": "debutant",
          "prix": 129,
          "tags": ["docker", "linux", "helm", "devops"],
          "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },

        }
      },
 9 autres documents
    ]
  }
}

_search veut dire « cherche » ; sans autre précision, ça veut dire « donne-moi des fiches, n'importe lesquelles ». Les deux chiffres à repérer : "total": { "value": 504 } (il y a 504 fiches dans le tiroir) et la liste hits qui n'en contient que 10 (celles qu'on te montre). Chaque fiche est dans _source, avec toutes ses cases : titre, prix, categorie

Fais pareil avec les deux autres tiroirs : GET avis/_search (« ouvre le tiroir avis, montre-moi les 10 premiers avis ») et GET acces/_search (les 10 premières lignes de journal). En SQL : SELECT * FROM cours LIMIT 10.

La différence essentielle, à ne plus jamais confondre.

GET _cat/indices/cours?v regarde l'étiquette du tiroir : son état, son nombre de fiches, sa taille. Une ligne. Tu ne vois aucun cours.

GET cours/_search ouvre le tiroir et lit les fiches : titres, prix, catégories, professeurs. Dix fiches. Tu vois les cours eux-mêmes.

La première répond « il y a 504 cours ». La seconde répond « voici des cours ».

Pour bien comprendre : lire une réponse de recherche, clé par clé
CléEn clair
tookLe temps qu'Elasticsearch a mis, en millisecondes (ici 1 ms).
hits.total.valueLe nombre total de fiches qui correspondent : 504. Même si on ne t'en montre que 10, il te dit combien il y en a en tout.
hits.hitsLa liste des fiches qu'on te montre : les 10 premières par défaut.
_indexLe tiroir d'où vient la fiche (cours).
_idLe numéro écrit en haut de la fiche (C0001, C0002…).
_scoreUne note de pertinence. Vaut 1 partout ici parce qu'on n'a rien cherché de précis : toutes les fiches se valent.
_sourceLa fiche elle-même, telle qu'elle a été rangée dans le tiroir, avec toutes ses cases.

Le mot hit veut dire « touche », comme au tir : une fiche « touchée » par la requête. Le champ professeur est une fiche dans la fiche (un JSON dans le JSON) : le professeur est écrit directement sur la fiche du cours, avec son nom et sa ville. SQL ne fait pas ça naturellement, il faudrait une deuxième table et une jointure.

E7. En afficher plus que dix

json
GET cours/_search
{
  "size": 20
}

Ce que la requête demande : « Ouvre le tiroir cours et montre-moi 20 fiches au lieu des 10 habituelles. »

Une seule nouveauté, mais importante : la requête a maintenant deux parties. La première ligne (GET cours/_search) dit quoi faire ; le bloc entre accolades en dessous, qu'on appelle le corps, donne des précisions. Ici la précision est "size": 20 : « taille du lot = 20 ». C'est tout. size veut simplement dire « combien de fiches tu veux qu'on te montre ».

Vérifie dans la réponse : hits.total.value vaut toujours 504 (le tiroir n'a pas changé), mais la liste hits contient maintenant 20 fiches au lieu de 10. En SQL : LIMIT 20.

Pour bien comprendre : un GET avec un corps ?

En HTTP classique, GET n'a pas de corps ; Elasticsearch l'accepte quand même parce que c'est pratique dans Dev Tools. Si un outil refuse, POST cours/_search avec le même corps fait exactement la même chose. Autre limite à connaître : size ne peut pas dépasser 10 000 d'un coup (paramètre index.max_result_window) ; pour parcourir plus, on pagine. Pour nos 504 cours, "size": 504 marcherait, mais la réponse ferait des milliers de lignes : ce n'est pas comme ça qu'on lit des données, les agrégations (E13) sont faites pour ça.

E8. Choisir les champs à afficher

json
GET cours/_search
{
  "size": 3,
  "_source": ["titre", "prix"]
}
json
"hits": [
  { "_id": "C0001", "_source": { "titre": "Docker expliqué simplement", "prix": 129 } },
  { "_id": "C0002", "_source": { "titre": "Docker avancé : industrialiser des conteneurs en production", "prix": 29 } },
  { "_id": "C0003", "_source": { "titre": "Docker : le guide complet", "prix": 19 } }
]

Ce que la requête demande : « Montre-moi 3 fiches, mais seulement les cases titre et prix de chaque fiche. » Nouveauté : _source avec une liste de champs. Au lieu de la fiche entière, on ne garde que les cases qui nous intéressent. La réponse devient lisible d'un coup d'œil. En SQL : SELECT titre, prix FROM cours LIMIT 3. On utilisera _source dans presque toutes les requêtes suivantes, justement pour garder des réponses courtes.

E9. Un document précis, par son identifiant

text
GET cours/_doc/C0001
json
{
  "_index": "cours",
  "_id": "C0001",
  "_version": 6,
  "found": true,
  "_source": {
    "id": "C0001",
    "titre": "Docker expliqué simplement",
    "categorie": "DevOps",
    "sujet": "Docker",
    "niveau": "debutant",
    "prix": 129,
    "note_moyenne": 4.4,
    "nb_avis": 327,
    "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },

  }
}

Pas de recherche ici : on demande le document dont l'identifiant est C0001, et Elasticsearch le rend directement ("found": true). C'est l'accès le plus rapide qui existe. En SQL : SELECT * FROM cours WHERE id = 'C0001'. Retiens ce cours, « Docker expliqué simplement », par Karim Caron : on le retrouvera dans Neo4j tout à l'heure, pour prouver que les deux moteurs contiennent les mêmes données.

Pour bien comprendre : _id et id, deux choses différentes

_id (avec un tiret bas) est l'identifiant technique du document dans Elasticsearch ; id (sans tiret) est un champ ordinaire à l'intérieur de _source. Le kit les a volontairement rendus identiques (C0001 des deux côtés) pour que ce soit lisible. C'est aussi ce qui rend importer rejouable : envoyer deux fois un document avec le même _id remplace le premier au lieu d'en créer un second, d'où _version: 6 (le document a été réécrit six fois sur la machine du cours, sans jamais être dupliqué).

E10. La première vraie recherche

json
GET cours/_search
{
  "query": { "match": { "titre": "kubernetes" } },
  "_source": ["titre"]
}
json
"hits": {
  "total": { "value": 7, "relation": "eq" },
  "hits": [
    { "_score": 5.0897474, "_source": { "titre": "Les bases de Kubernetes" } },
    { "_score": 5.0897474, "_source": { "titre": "Kubernetes en pratique" } },
    { "_score": 5.0897474, "_source": { "titre": "Kubernetes pour les débutants" } },

  ]
}

Nouveauté : query, la partie du corps qui dit quoi chercher. match est la recherche de base : « les documents dont le champ titre contient le mot kubernetes ». Sept cours répondent, et pour la première fois _score n'est plus 1 : c'est la pertinence, et les résultats sont classés du plus pertinent au moins pertinent. En SQL, l'équivalent approximatif serait WHERE titre LIKE '%kubernetes%', mais regarde bien : on a tapé kubernetes en minuscules et on trouve « Kubernetes » avec une majuscule. LIKE ne l'aurait pas fait.

Pour bien comprendre : ce que fait match

Au moment où un cours est indexé, Elasticsearch découpe son titre en mots, les met en minuscules, retire les accents et ramène chaque mot à sa racine (« conteneurs » devient « conteneur ») : c'est l'analyse, faite ici par l'analyseur french défini dans le mapping du kit. Quand tu cherches, ta requête subit le même traitement, puis Elasticsearch compare mot à mot. Résultat : majuscules, accents et pluriels ne comptent plus. Le _score monte quand le mot est rare dans l'index et fréquent dans le document. Le module 3 y consacre plusieurs leçons ; ici, retiens seulement : match cherche des mots, pas des suites de caractères.

E11. La faute de frappe pardonnée

D'abord la preuve que, par défaut, une vraie faute ne trouve rien. « kubrenetes » (deux lettres inversées) :

json
GET cours/_count
{
  "query": { "match": { "titre": "kubrenetes" } }
}
json
{ "count": 0,  }

Puis la même requête avec une seule ligne de plus, fuzziness, qui autorise une ou deux lettres d'écart :

json
GET cours/_search
{
  "query": { "match": { "titre": { "query": "kubrenetes", "fuzziness": "AUTO" } } },
  "size": 3,
  "_source": ["titre", "niveau", "prix"]
}
json
"total": { "value": 7, "relation": "eq" },
"hits": [
  { "_score": 4.4535294, "_source": { "titre": "Les bases de Kubernetes", "niveau": "debutant", "prix": 0 } },
  { "_score": 4.4535294, "_source": { "titre": "Kubernetes en pratique", "niveau": "debutant", "prix": 129 } },
  { "_score": 4.4535294, "_source": { "titre": "Kubernetes pour les débutants", "niveau": "debutant", "prix": 29 } }
]

Les 7 cours Kubernetes reviennent, malgré la faute. C'est le moment qui surprend toujours en salle, et c'est la raison d'être d'Elasticsearch dans une barre de recherche : l'utilisateur tape mal, le moteur comprend quand même. Un LIKE '%kubrenetes%' en SQL n'aurait jamais rien rendu. Remarque la syntaxe : quand match a besoin d'options, la valeur du champ devient un objet { "query": …, "fuzziness": … } au lieu d'une simple chaîne.

Pour bien comprendre : AUTO

fuzziness compte les modifications (une lettre ajoutée, retirée, changée ou échangée avec sa voisine) qu'on tolère entre le mot tapé et le mot indexé. AUTO adapte la tolérance à la longueur du mot : 0 faute pour un mot de 1 ou 2 lettres, 1 faute de 3 à 5 lettres, 2 fautes au-delà. « kubrenetes » a 10 lettres, donc 2 fautes admises ; il n'en a qu'une (l'échange reer) : trouvé. Le _score est un peu plus bas qu'en E10 (4,45 contre 5,09) : Elasticsearch pénalise légèrement les correspondances approximatives, ce qui garde les correspondances exactes en tête.

E12. Trier : le palmarès des cours

json
GET cours/_search
{
  "query": { "range": { "nb_avis": { "gte": 10 } } },
  "sort": [{ "note_moyenne": "desc" }, { "nb_avis": "desc" }],
  "size": 5,
  "_source": ["titre", "note_moyenne", "nb_avis", "categorie"]
}

Deux nouveautés, qui se comprennent à la lecture. range avec gte (greater than or equal) : seulement les cours qui ont au moins 10 avis, pour écarter les notes basées sur un seul vote. sort : classer par note_moyenne décroissante, puis par nb_avis décroissant pour départager les égalités. En SQL : WHERE nb_avis >= 10 ORDER BY note_moyenne DESC, nb_avis DESC LIMIT 5. Quand on trie soi-même, _score devient null : la pertinence ne sert plus, c'est ton ordre qui compte.

E13. Compter par catégorie sans lire une seule ligne

json
GET cours/_search
{
  "size": 0,
  "aggs": {
    "par_categorie": { "terms": { "field": "categorie" } }
  }
}
json
"aggregations": {
  "par_categorie": {
    "buckets": [
      { "key": "Cloud", "doc_count": 84 },
      { "key": "DevOps", "doc_count": 84 },
      { "key": "Données", "doc_count": 84 },
      { "key": "Développement web", "doc_count": 84 },
      { "key": "IA", "doc_count": 84 },
      { "key": "Sécurité", "doc_count": 84 }
    ]
  }
}

Nouveauté : aggs (agrégations). terms sur categorie fait un paquet (bucket) par valeur distincte et compte les documents dedans. size: 0 dit « ne me renvoie aucun document, seulement les compteurs » : la réponse est minuscule et instantanée, même sur des millions de lignes. En SQL : SELECT categorie, COUNT(*) FROM cours GROUP BY categorie. Résultat : six catégories de 84 cours chacune. par_categorie est un nom que tu choisis ; il sert seulement à retrouver le résultat dans la réponse.

Pour bien comprendre : pourquoi c'est la base de Kibana

Chaque graphique de Kibana, chaque camembert, chaque histogramme est une agrégation comme celle-là, exécutée par Elasticsearch et dessinée par Kibana. Quand tu construiras un tableau de bord au module 4, tu cliqueras sur « champ : categorie, agrégation : terms » et Kibana enverra exactement cette requête. Savoir la lire, c'est savoir ce que fait le tableau de bord en coulisses.

E14. Une agrégation dans une agrégation

json
GET cours/_search
{
  "size": 0,
  "aggs": {
    "par_categorie": {
      "terms": { "field": "categorie" },
      "aggs": { "par_niveau": { "terms": { "field": "niveau" } } }
    }
  }
}

Même requête qu'E13, avec un second aggs à l'intérieur du premier. Dans chaque paquet de catégorie, on refait des paquets par niveau. Résultat : pour « Cloud », 45 débutant, 28 avancé, 11 intermédiaire ; et ainsi de suite pour les six catégories, en une seule passe. En SQL : GROUP BY categorie, niveau, mais la réponse est déjà hiérarchisée, prête pour un graphique empilé.

E15. Les erreurs serveur dans les 12 000 accès

json
GET acces/_search
{
  "size": 0,
  "query": { "range": { "statut": { "gte": 500 } } },
  "aggs": { "codes": { "terms": { "field": "statut" } } }
}
json
"hits": { "total": { "value": 178, "relation": "eq" } },
"aggregations": {
  "codes": {
    "buckets": [
      { "key": 500, "doc_count": 122 },
      { "key": 503, "doc_count": 56 }
    ]
  }
}

Rien de nouveau : on combine range (E12) et terms (E13) sur l'index acces. Parmi les 12 000 lignes de journal, 178 ont un code HTTP ≥ 500 : 122 erreurs 500 et 56 503. C'est la question qu'un responsable de plateforme pose chaque matin, et elle est répondue en une milliseconde. En SQL : SELECT statut, COUNT(*) FROM acces WHERE statut >= 500 GROUP BY statut. C'est exactement ce que tu mettras sur un tableau de bord au module 4.

Neo4j, dans Neo4j Browser

Afficher les 16 requêtes Neo4j (N1 à N16)

Ouvre http://localhost:7474. Écran de connexion : URL neo4j://localhost:7687, utilisateur neo4j, mot de passe aiopsatlas2026. En haut, une barre d'édition qui commence par neo4j$ : colle une seule requête, puis Ctrl+Entrée ou le triangle ▶. Le résultat apparaît dans un cadre dessous, avec des onglets à gauche : Graph (un dessin, quand le résultat contient des nœuds), Table (des lignes et des colonnes) et Text.

N1. Tout afficher

cypher
MATCH (n) RETURN n

Le résultat est un nuage de bulles colorées, reliées par des flèches, que tu peux déplacer à la souris. Neo4j Browser affiche au plus 300 nœuds à la fois (un message en haut du résultat le signale) ; les 872 sont bien là, il n'en dessine qu'une partie pour rester lisible.

C'est la requête la plus simple de Cypher, le langage de Neo4j. MATCH veut dire « trouve », (n) désigne un nœud quelconque (les parenthèses dessinent un rond, comme une bulle) auquel on donne le nom n, et RETURN n veut dire « montre-le-moi ». En SQL, il n'y a pas d'équivalent : ce serait « SELECT * de toutes les tables à la fois », ce que SQL ne sait pas faire.

Pour bien comprendre : nœuds, étiquettes, relations

Un graphe est fait de deux choses : des nœuds (les bulles) et des relations (les flèches entre bulles). Chaque nœud porte une étiquette qui dit ce qu'il est (Cours, Etudiant…) et des propriétés (titre, prix…). Chaque relation porte un type (INSCRIT_A, ENSEIGNE…) et une direction.

Neo4jSQL
Étiquette CoursTable cours
NœudLigne
PropriétéColonne
Relation INSCRIT_ATable de liaison inscriptions + jointures

Le graphe du labo :

text
Neo4j
├── 504 nœuds Cours
├── 300 nœuds Etudiant
├──  30 nœuds Professeur
├──  22 nœuds Competence
└──  16 nœuds Ville
     = 872 nœuds, reliés par 3 712 relations

Les 504 cours sont les mêmes que les 504 documents de l'index Elasticsearch cours : ce sont deux moteurs qui rangent les mêmes données de deux façons, chacun pour répondre à des questions différentes.

N2. Limiter le nombre de résultats

cypher
MATCH (n) RETURN n LIMIT 25

Une seule nouveauté : LIMIT 25, même mot qu'en SQL. Vingt-cinq bulles au lieu de trois cents : on voit enfin quelque chose. Survole une bulle : ses propriétés s'affichent en bas du cadre. Double-clique dessus : ses voisins se déplient.

N3. Compter au lieu d'afficher

cypher
MATCH (n) RETURN count(n) AS total
text
total
872

count(n) compte au lieu de dessiner ; AS total nomme la colonne. Plus de dessin : le résultat passe tout seul en vue Table, puisqu'un chiffre ne se dessine pas. 872, le chiffre de etat. En SQL : SELECT COUNT(*).

N4. Une seule sorte de nœud

cypher
MATCH (c:Cours) RETURN c LIMIT 5

Nouveauté : :Cours après le nom de variable. C'est l'étiquette : « seulement les nœuds qui sont des cours ». Cinq bulles, toutes de la même couleur. En SQL : SELECT * FROM cours LIMIT 5. Par habitude, on appelle la variable par l'initiale de l'étiquette (c pour Cours, e pour Etudiant), mais n marcherait aussi.

N5. Des colonnes plutôt que des bulles

cypher
MATCH (c:Cours) RETURN c.titre, c.prix LIMIT 5
text
c.titre                                                        c.prix
"Docker expliqué simplement"                                   129.0
"Docker avancé : industrialiser des conteneurs en production"  29.0
"Docker : le guide complet"                                    19.0

Nouveauté : c.titre, c.prix. Le point donne accès à une propriété du nœud. Quand on renvoie des propriétés au lieu de nœuds entiers, Neo4j Browser passe en vue Table. En SQL : SELECT titre, prix FROM cours LIMIT 5. Compare avec E8 : mêmes titres, mêmes prix, même ordre. Pour connaître toutes les propriétés d'un cours : MATCH (c:Cours) RETURN keys(c) LIMIT 1 répond prix, duree_heures, date_publication, sujet, niveau, categorie, id, titre.

N6. Compter par étiquette

cypher
MATCH (n) RETURN labels(n)[0] AS type, count(*) AS nombre ORDER BY nombre DESC
text
type          nombre
"Cours"       504
"Etudiant"    300
"Professeur"  30
"Competence"  22
"Ville"       16

labels(n) renvoie la liste des étiquettes du nœud (un nœud peut en avoir plusieurs ; ici une seule, d'où le [0], le premier élément). Le count(*) se regroupe automatiquement par tout ce qui n'est pas une agrégation : pas besoin d'écrire GROUP BY, Cypher le déduit. ORDER BY nombre DESC trie. La somme des cinq lignes fait 872. En SQL, il faudrait cinq SELECT COUNT(*) et des UNION.

N7. Un nœud précis, par son identifiant

cypher
MATCH (c:Cours {id: 'C0001'}) RETURN c.titre, c.prix, c.niveau
text
c.titre                       c.prix  c.niveau
"Docker expliqué simplement"  129.0   "debutant"

Nouveauté : les accolades {id: 'C0001'} dans le motif. Elles filtrent sur une propriété, comme un WHERE id = 'C0001'. Et c'est le même cours qu'en E9 dans Elasticsearch : même titre, même prix. Voilà la preuve que les deux moteurs contiennent bien les mêmes données ; ce qui change, c'est ce qu'on peut leur demander.

N8. Filtrer avec WHERE

cypher
MATCH (c:Cours) WHERE c.titre CONTAINS 'Kubernetes' RETURN c.titre ORDER BY c.titre
text
c.titre
"Kubernetes : de zéro à la production"
"Kubernetes : le guide complet"
"Kubernetes avancé : superviser des conteneurs en production"
"Kubernetes en pratique"
"Kubernetes expliqué simplement"
"Kubernetes pour les débutants"
"Les bases de Kubernetes"

WHERE s'écrit comme en SQL et CONTAINS cherche une suite de caractères. Sept cours, les mêmes qu'en E10. Mais essaie CONTAINS 'kubernetes' en minuscules : zéro résultat. Et CONTAINS 'kubrenetes' : zéro aussi. Neo4j compare des caractères, exactement ; il ne connaît ni la casse, ni les mots, ni les fautes. C'est précisément pour ça que le labo a les deux moteurs : la barre de recherche, c'est Elasticsearch ; les liens entre les choses, c'est Neo4j.

N9. Trier

cypher
MATCH (c:Cours) RETURN c.titre, c.prix ORDER BY c.prix DESC LIMIT 5
text
c.titre                                                  c.prix
"Helm par la pratique : superviser un pipeline CI/CD"    199.0
"Atelier GitHub Actions : vos applications"              199.0
"Docker pour les débutants"                              199.0

Rien de neuf : ORDER BY … DESC LIMIT 5, comme en SQL. Les cinq cours les plus chers, tous à 199 $.

N10. Compter par catégorie

cypher
MATCH (c:Cours) RETURN c.categorie AS categorie, count(*) AS nombre ORDER BY nombre DESC
text
categorie            nombre
"DevOps"             84
"Données"            84
"IA"                 84
"Développement web"  84
"Sécurité"           84
"Cloud"              84

Exactement le résultat de l'agrégation E13 dans Elasticsearch : six catégories de 84. Même donnée, deux moteurs, deux syntaxes. Jusqu'ici, Neo4j n'a rien fait que SQL ne sache faire. Ça change à la requête suivante.

N11. La première relation

cypher
MATCH (p:Professeur)-[r:ENSEIGNE]->(c:Cours) RETURN p, r, c LIMIT 30

Passe en vue Graph : des bulles « professeur » reliées par des flèches ENSEIGNE à des bulles « cours ». Attrape un professeur avec la souris, tu vois tous ses cours suivre.

Nouveauté : la flèche. (p:Professeur)-[r:ENSEIGNE]->(c:Cours) se lit littéralement « un professeur, qui enseigne, un cours ». Les parenthèses sont des nœuds, les crochets sont la relation, -> donne le sens. C'est un dessin ASCII de ce qu'on cherche, et Neo4j trouve tous les endroits du graphe qui ressemblent à ce dessin. En SQL, ce serait SELECT * FROM professeurs JOIN cours ON cours.professeur_id = professeurs.id, et ça ne se dessinerait pas.

Pour bien comprendre : pourquoi les relations changent tout

En SQL, une relation entre deux lignes n'existe pas vraiment : elle est recalculée à chaque requête par une jointure, qui compare des identifiants. Avec dix jointures enchaînées, ça devient lent et illisible. Dans Neo4j, la relation est stockée comme une flèche physique entre deux nœuds : suivre une flèche coûte le même prix quel que soit le nombre de nœuds dans la base. C'est ce qui rend possibles les requêtes N14 à N16, qui enchaînent plusieurs sauts sans effort.

N12. Compter les relations par type

cypher
MATCH ()-[r]->() RETURN type(r) AS relation, count(*) AS nombre ORDER BY nombre DESC
text
relation        nombre
"INSCRIT_A"     1654
"COUVRE"        994
"ENSEIGNE"      504
"HABITE"        330
"PREREQUIS_DE"  230

() est un nœud quelconque dont on ne garde même pas le nom ; [r] une relation de n'importe quel type ; type(r) son type. Cinq types de relations, 3 712 au total : le chiffre affiché par le panneau Database information de Neo4j Browser. On lit le modèle du labo en une ligne : des étudiants inscrits à des cours, des cours qui couvrent des compétences, des professeurs qui enseignent des cours, des gens qui habitent des villes, et des cours prérequis d'autres cours. Le ENSEIGNE à 504 dit qu'il y a exactement une relation par cours : chaque cours a un et un seul professeur.

N13. Suivre une relation à partir d'un nœud précis

cypher
MATCH (p:Professeur {id: 'P001'})-[:ENSEIGNE]->(c:Cours)
RETURN p.prenom + ' ' + p.nom AS professeur, count(c) AS nb_cours
text
professeur     nb_cours
"Karim Caron"  11

On combine N7 (le filtre {id: 'P001'}) et N11 (la flèche). P001, c'est le professeur de « Docker expliqué simplement » vu en E9 et N7 ; il enseigne 11 cours. Le + colle des chaînes de caractères, comme en SQL avec ||. Remarque [:ENSEIGNE] sans nom de variable : quand on n'a pas besoin de la relation dans le RETURN, on ne la nomme pas.

N14. Le chemin de formation

cypher
MATCH (debut:Cours {id: 'C0111'}), (fin:Cours {id: 'C0110'}),
      chemin = shortestPath((debut)-[:PREREQUIS_DE*]-(fin))
RETURN length(chemin) AS sauts, [n IN nodes(chemin) | n.titre] AS parcours
text
sauts  parcours
5      ["Neo4j : de zéro à la production", "Neo4j en pratique", "Neo4j par la pratique : interroger des journaux applicatifs", "Neo4j avancé : modéliser des journaux applicatifs", "Neo4j : le guide complet", "Maîtriser Neo4j"]

Première requête qui ne ressemble plus à rien en SQL. [:PREREQUIS_DE*] avec l'astérisque veut dire « en suivant cette relation autant de fois qu'il faut ». shortestPath demande le plus court des chemins possibles entre les deux cours. La réponse : 5 sauts, et la liste des six titres à suivre, dans l'ordre, pour aller du premier au dernier cours de la filière Neo4j. C'est un plan de formation calculé à la volée. En SQL, il faudrait une requête récursive de plusieurs dizaines de lignes, et elle serait lente.

Pour bien comprendre : la ligne RETURN

nodes(chemin) donne la liste des nœuds traversés. [n IN nodes(chemin) | n.titre] se lit « pour chaque nœud n de cette liste, garde son titre » : c'est une façon compacte de transformer une liste de nœuds en liste de titres. length(chemin) compte les relations traversées (5 relations pour 6 nœuds). Le module 6 détaille ces fonctions ; ici l'important est le résultat : Neo4j a trouvé un itinéraire dans le graphe.

N15. « Les étudiants qui ont suivi ce cours ont aussi suivi… »

cypher
MATCH (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(e:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN autre.titre AS recommandation, autre.categorie AS categorie,
       count(DISTINCT e) AS etudiants_communs
ORDER BY etudiants_communs DESC, recommandation
LIMIT 5
text
recommandation                                                  categorie  etudiants_communs
"Agents IA par la pratique : évaluer un pipeline de prédiction"  "IA"       3
"Atelier NLP : des agents autonomes"                             "IA"       2
"Deep learning par la pratique : orchestrer un assistant …"      "IA"       2

Le moteur de recommandation d'un site marchand, en cinq lignes. Lis le motif de gauche à droite : on part du cours x, on remonte la flèche INSCRIT_A (elle pointe vers x, d'où le <-) jusqu'aux étudiants e qui le suivent, puis on redescend une autre flèche INSCRIT_A vers les autres cours autre de ces mêmes étudiants. WHERE autre <> x écarte le cours de départ lui-même. count(DISTINCT e) compte les étudiants en commun, sans doublon. Les cours en tête sont ceux que les étudiants de C0213 suivent le plus souvent en parallèle : ce sont les recommandations. En SQL : deux jointures sur la table d'inscriptions, un GROUP BY, et une requête qu'on relit trois fois avant de la comprendre.

N16. Le même résultat, dessiné

cypher
MATCH chemin = (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN chemin LIMIT 50

Passe en vue Graph : le cours de départ au centre, ses étudiants autour, et les cours qu'ils partagent en périphérie. C'est la requête N15 sans le comptage : on renvoie les chemins entiers plutôt que des colonnes, et Neo4j Browser les dessine. C'est l'image à montrer quand quelqu'un demande « à quoi sert une base de graphe ».

Le message à faire passer. Quinze requêtes Elasticsearch, seize requêtes Cypher, et on a vu, dans l'ordre : lister, compter, afficher, filtrer, trier, regrouper, ce que SQL fait aussi ; puis pardonner une faute de frappe, agréger 12 000 lignes en une milliseconde, calculer un plus court chemin et produire des recommandations, ce que SQL fait mal ou pas du tout. Le même cours « Docker expliqué simplement » est apparu dans les deux moteurs : mêmes données, questions différentes. C'est exactement pourquoi ce labo existe, et tout le reste du cours détaille comment chacune de ces requêtes fonctionne.

Défi bonus (optionnel)

Refais l'étape 7 de ton annexe avec docker compose stop neo4j : que devient la ligne Neo4j répond — nœuds : 872 dans etat ? Que dit Neo4j Browser, déjà connecté, quand tu relances la requête N3 (MATCH (n) RETURN count(n)) ? Quelle est la dernière ligne de journal neo4j ? Relance avec docker compose start neo4j, vérifie que les 872 nœuds sont toujours là sans rien recharger, et note lequel des deux services redémarre le plus vite.

Annexe A — Pas à pas détaillé sous Windows (PowerShell)

Afficher le pas à pas Windows (A.0 à A.11)

Toutes les commandes de cette annexe se tapent dans PowerShell (Windows Terminal, ou PowerShell 7), avec .\labo.ps1 …. Les sorties reproduites sont celles de la machine du cours, sous Windows 11 et Docker Desktop.

A.0 — Avant de commencer

  • Avoir lu les quatre leçons du module : 01, 02, 03 et 04.
  • Docker Desktop lancé (icône verte), le kit cloné, un terminal PowerShell ouvert dans le dossier du kit (celui qui contient docker-compose.yml et labo.ps1).
  • Si PowerShell refuse d'exécuter .\labo.ps1 (« l'exécution de scripts est désactivée sur ce système ») : Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, réponds O, relance.
  • Le labo peut être démarré ou pas : la pratique commence par les prérequis. À la fin, .\labo.ps1 etat doit afficher (healthy) partout, "status":"green", acces 12000 avis 609 cours 504 et nœuds : 872.
  • Toutes les requêtes de cette pratique : elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Aucune n'écrit dans le cluster.

A.1 — Dérouler la liste de contrôle des prérequis

Avant d'allumer quoi que ce soit, fais parler le script.

powershell
.\labo.ps1 prerequis

Point de contrôle : que des coches vertes et la phrase finale.

text
== Prérequis ==
  ✔ docker : Docker version 29.3.1, build c2be9cc
  ✔ le démon Docker répond
  ✔ docker compose : 5.1.1
  ✔ mémoire disponible pour Docker : 31 Go
  ✔ processeurs : 20
  ✔ port 9200 : utilisé par le labo lui-même
  ✔ port 5601 : utilisé par le labo lui-même
  ✔ port 7474 : utilisé par le labo lui-même
  ✔ port 7687 : utilisé par le labo lui-même

Tout est prêt. Lancez : .\labo.ps1 demarrer

Chez toi, versions et mémoire diffèrent, et si le labo n'a jamais tourné les quatre dernières lignes disent port 9200 : libre. Les deux lectures sont bonnes.

Si tu vois autre chose : une croix contient son remède dans la phrase (Docker Desktop pas lancé, mémoire sous 4 Go, port pris) ; corrige, relance, n'avance qu'avec la ligne verte. Pour la mémoire : Docker Desktop → SettingsResources, ou le fichier %UserProfile%\.wslconfig si Docker Desktop utilise WSL 2.

A.2 — Démarrer et lire les trois blocs

Lance le démarrage et, cette fois, lis ce qui défile au lieu d'attendre la fin.

powershell
.\labo.ps1 demarrer

Point de contrôle : trois blocs == … == puis trois prêt.

text
== Téléchargement des images (long la première fois : ~6 Go, ~12 Go avec OpenSearch) ==

== Démarrage ==

== Attente que chaque service soit prêt ==
  elasticsearch            prêt (0 s)
  kibana                   prêt (0 s)
  neo4j                   ..... prêt (15 s)

Le labo est prêt.
  Kibana                 http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
  Elasticsearch          http://localhost:9200
  Neo4j Browser          http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)

Étape suivante : .\labo.ps1 importer   puis   .\labo.ps1 charger-graphe

Le premier bloc est vide si les images sont déjà là ; le deuxième contient les lignes de Compose (Container labo-elasticsearch Started, ou Running s'il tournait déjà) ; le troisième ajoute un point toutes les trois secondes jusqu'à prêt (… s). Labo froid : une à deux minutes ; labo déjà en marche : prêt (0 s).

Si tu vois autre chose : unhealthy, exited ou délai dépassé.\labo.ps1 journal <service> et le catalogue de la leçon 04 ; cause la plus fréquente sous Windows : la mémoire allouée à Docker Desktop (Exited (137)).

A.3 — Relever les quatre chiffres de etat

Tu vas taper etat plusieurs fois ; apprends d'abord à en extraire les quatre chiffres qui comptent.

powershell
.\labo.ps1 etat

Point de contrôle : sortie réelle de la machine du cours, où le profil OpenSearch du module 5 est actif. Chez toi, les deux lignes labo-opensearch… et ✔ OpenSearch sont remplacées par — OpenSearch non démarré (profil optionnel : .\labo.ps1 demarrer opensearch).

text
== Conteneurs ==
NAME                         STATUS                    PORTS
labo-elasticsearch           Up 10 hours (healthy)     0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up 10 hours (healthy)     0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 37 seconds (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)     0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours               0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Note les quatre chiffres attendus : 12000, 609, 504, 872. Sur un labo tout neuf tu lis index : aucun index du labo et nœuds : 0 : normal, l'étape A.4 les remplit.

Si tu vois autre chose : ✘ Kibana ne répond pas encore dans la minute qui suit demarrer → Kibana finit de créer ses index internes ; retape etat trente secondes plus tard.

A.4 — Charger les données, puis recharger pour prouver que rien ne bouge

Charge les index puis le graphe, et relance les deux commandes une seconde fois : les compteurs ne doivent pas changer d'une unité.

powershell
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer
.\labo.ps1 charger-graphe

Point de contrôle : au second passage, importer signale que les index existent déjà et rend les mêmes compteurs :

text
== Import dans elasticsearch ==
— index cours existe déjà — conservé
  ✔ données cours chargées
— index avis existe déjà — conservé
  ✔ données avis chargées
— index acces existe déjà — conservé
  ✔ données acces chargées

index docs.count store.size
acces      12000      1.5mb
avis         609     74.8kb
cours        504    183.6kb

Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.

et charger-graphe donne le même bilan qu'au premier :

text
== Chargement du graphe Neo4j ==
  ✔ contraintes et index en place
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16

Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.

Les documents portent leur propre _id (C0001, A00001…), donc un second _bulk remplace chaque document au lieu de l'ajouter ; côté Neo4j, tout est en MERGE. Seul le store.size peut bouger de quelques kilo-octets (Lucene garde un moment les anciennes versions) ; les docs.count, jamais.

Si tu vois autre chose : un docs.count différent de 504 / 609 / 12000 → quelqu'un a écrit dans ces index ; .\labo.ps1 reinitialiser puis demarrer, importer, charger-graphe te rend l'état de référence.

A.5 — Vérifier Elasticsearch depuis Dev Tools

Le script dit que tout est bon ; vérifie-le sans lui. Ouvre http://localhost:5601, menu ManagementOutils de développement, et envoie ces requêtes une par une (Ctrl + Entrée ou le bouton ▶ « Cliquer pour envoyer la requête »).

text
GET _cluster/health

Point de contrôle : "status": "green", "unassigned_shards": 0, et 200 - OK en bas à droite du panneau de réponse.

json
{
  "cluster_name": "labo",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 53,
  "active_shards": 53,

  "unassigned_shards": 0,

  "active_shards_percent_as_number": 100
}
text
GET _cat/indices/cours,avis,acces?v&s=index

Point de contrôle : trois lignes green, pri 1, rep 0, et les trois compteurs dans docs.count.

text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   acces aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   avis  W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.8kb         74.8kb       74.8kb
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Termine par GET cours/_count, GET avis/_count, GET acces/_count : "count": 504, 609, 12000. Tu as les mêmes chiffres par deux chemins indépendants : le script (curl dans le conteneur) et Dev Tools (via Kibana). S'ils divergent un jour, c'est le chemin qui est en cause, pas les données.

Si tu vois autre chose : "status": "yellow" → un index a des réplicas non alloués, impossible avec les mappings du kit (number_of_replicas: 0) ; GET _cat/indices?v&health=yellow désigne le coupable, en général un index créé à la main.

A.6 — Compter le graphe dans Neo4j Browser

Même exercice pour Neo4j. Ouvre http://localhost:7474, connecte-toi (neo4j / aiopsatlas2026, URL localhost:7687), tape dans l'éditeur neo4j$ et clique Run :

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label

Point de contrôle : un cadre avec deux vues, Table et Raw (pas de Graph : la requête renvoie des chiffres, pas des nœuds), deux colonnes label et count(*), cinq lignes triées, et en bas à droite Started streaming 5 records after … ms and completed after … ms.

text
label          count(*)
"Competence"   22
"Cours"        504
"Etudiant"     300
"Professeur"   30
"Ville"        16

22 + 504 + 300 + 30 + 16 = 872, le chiffre de etat. Le panneau Database information (icône Database overview, la première de la barre latérale) affiche Nodes (872) et Relationships (3,712).

Si tu vois autre chose : une sixième étiquette inconnue → des nœuds créés hors kit (le module 6 t'apprendra à les supprimer proprement) ; Nodes (0) → tu as sauté charger-graphe, retour à l'étape A.4.

A.7 — Casser Kibana volontairement et observer la panne

Tu sais à quoi ressemble un labo sain ; provoque une panne dont tu connais la cause pour apprendre à la lire. Arrête uniquement Kibana, avec Compose (pas arreter, qui stopperait tout) :

powershell
docker compose stop kibana
text
 Container labo-kibana Stopping
 Container labo-kibana Stopped

Puis les trois gestes de la leçon 04, dans l'ordre : etat, le navigateur, journal.

Point de contrôle 1, .\labo.ps1 etat : la ligne labo-kibana a disparu du bloc == Conteneurs == (Compose n'affiche par défaut que les conteneurs en cours d'exécution) et le bloc == Services == marque une croix :

text
== Conteneurs ==
NAME                         STATUS                   PORTS
labo-elasticsearch           Up 10 hours (healthy)    0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-neo4j                   Up 3 minutes (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)    0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours              0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✘ Kibana ne répond pas encore
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Pour voir quand même le conteneur arrêté : docker compose ps -a affiche labo-kibana Exited (0) 31 seconds ago. Le 0 dit « arrêt propre, demandé » ; un 137 dirait « tué, mémoire ».

Point de contrôle 2, le navigateur : recharge http://localhost:5601. Pas de « Kibana server is not ready yet » (cette phrase vient de Kibana, or il n'y a plus de Kibana pour la dire) mais l'erreur de connexion du navigateur lui-même : dans Chrome ou Edge, « Ce site est inaccessible », code ERR_CONNECTION_REFUSED. Personne n'écoute sur le port 5601. À retenir : page Kibana qui s'excuse = Kibana tourne mais attend Elasticsearch ; erreur du navigateur = Kibana ne tourne pas.

Point de contrôle 3, .\labo.ps1 journal kibana : les cent dernières lignes se terminent par un arrêt propre, daté à la seconde où tu as tapé stop :

text
labo-kibana  | [2026-09-09T23:44:06.893+00:00][INFO ][root] SIGTERM received - initiating shutdown
labo-kibana  | [2026-09-09T23:44:06.894+00:00][INFO ][root] Kibana is shutting down
labo-kibana  | [2026-09-09T23:44:06.902+00:00][INFO ][plugins-system.standard] Stopping all plugins.

labo-kibana  | [2026-09-09T23:44:07.265+00:00][INFO ][plugins-system.standard] All plugins stopped.

SIGTERM received signe un arrêt demandé (par toi, par docker compose stop, par un redémarrage de Docker Desktop). Aucune ligne ERROR ni FATAL : rien à réparer, il faut juste relancer. Les dizaines de lignes at OperatorSubscriber… entre les deux sont une pile d'appels émise par un plugin pendant l'arrêt : du bruit.

Si tu vois autre chose : no configuration file provided: not found → tu n'es pas dans le dossier du kit ; no such service: kibana → tu as tapé le nom du conteneur (labo-kibana) au lieu du service Compose (kibana).

A.8 — Réparer Kibana et prouver qu'Elasticsearch n'a rien vu

Relance le service et attends que son healthcheck repasse au vert : 40 à 60 secondes, le temps de se reconnecter à Elasticsearch et de vérifier ses index internes.

powershell
docker compose start kibana
text
 Container labo-elasticsearch Waiting
 Container labo-elasticsearch Healthy
 Container labo-kibana Starting
 Container labo-kibana Started

Compose a d'abord vérifié qu'Elasticsearch était Healthy (le depends_on … service_healthy de la leçon 03), puis a démarré Kibana. Surveille toutes les cinq secondes :

powershell
docker inspect --format '{{.State.Health.Status}}' labo-kibana

Point de contrôle : starting pendant 40 à 50 secondes, puis healthy (sur la machine du cours : starting de 0 à 45 s, healthy à 50 s). etat remontre alors la ligne :

text
labo-kibana                  Up 52 seconds (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp

  ✔ Kibana répond (http://localhost:5601)

et journal kibana se termine par les lignes qu'on veut voir :

text
labo-kibana  | [2026-09-09T23:47:49.408+00:00][INFO ][http.server.Kibana] http server running at http://0.0.0.0:5601
labo-kibana  | [2026-09-09T23:47:50.374+00:00][INFO ][status] Kibana is now available

Rouvre Dev Tools (si tu tombes sur « Kibana server is not ready yet », tu as été plus rapide que le healthy : attends dix secondes) et envoie la requête que etat lui-même utilise :

text
GET _cluster/health?filter_path=status,number_of_nodes
json
{
  "status": "green",
  "number_of_nodes": 1
}

Elasticsearch a répondu green pendant toute la panne : Kibana est une fenêtre sur les données, pas les données. Arrêter Kibana n'a rien effacé ni réindexé.

Si tu vois autre chose : unhealthy après deux minutes → journal kibana et cherche ECONNREFUSED (Elasticsearch tombé entre-temps) ; port is already allocated → un autre programme a pris le port 5601 pendant l'arrêt (leçon 04, panne 1).

A.9 — À toi de jouer : provoquer et expliquer une 404

Tu reconnais un service arrêté ; apprends maintenant à reconnaître une requête fausse sur un service sain, la confusion la plus fréquente en salle. Dans Dev Tools, écris une requête de recherche qui renvoie 404 avec "type": "index_not_found_exception", puis explique en une phrase pourquoi le cluster reste green.

Indice : Elasticsearch ne devine jamais le nom d'un index. Choisis-en un qui n'existe pas, avec le préfixe pratique- ; rien à créer, rien à écrire.

Solution
text
GET pratique-inexistant/_search

Réponse, avec le badge 404 - Not Found en bas à droite du panneau de sortie :

json
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [pratique-inexistant]",
        "resource.type": "index_or_alias",
        "resource.id": "pratique-inexistant",
        "index_uuid": "_na_",
        "index": "pratique-inexistant"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [pratique-inexistant]",

  },
  "status": 404
}

Explication attendue : le 404 est une réponse normale et complète d'Elasticsearch : « j'ai compris ta requête, mais cette ressource n'existe pas ». Le service tourne, le cluster reste green ; il n'y a que le nom à corriger (GET _cat/indices?v donne la liste). À l'étape A.7, il n'y avait aucune réponse du tout.

Deux variantes à essayer : GET pratique-inexistant/_count?ignore_unavailable=true renvoie 200 et "count": 0 (tu demandes d'ignorer l'index absent) ; GET cours/_serch renvoie 400 avec "no handler found for uri [/cours/_serch] and method [GET]". Un 400 : « je ne comprends pas la requête » ; un 404 : « je comprends, mais ça n'existe pas ».

A.10 — Vérification finale

Une seule commande prouve que tout est fait : etat, avec Kibana revenu et les quatre chiffres.

powershell
.\labo.ps1 etat
text
== Conteneurs ==
NAME                         STATUS                        PORTS
labo-elasticsearch           Up 10 hours (healthy)         0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up About a minute (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 7 minutes (healthy)        0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)         0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours                   0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

(Sans le profil OpenSearch, les deux conteneurs labo-opensearch… n'apparaissent pas et la dernière ligne dit — OpenSearch non démarré … : c'est l'état attendu jusqu'au module 5.)

  • prerequis se termine par Tout est prêt.
  • labo-elasticsearch, labo-kibana et labo-neo4j sont Up … (healthy), Kibana compris.
  • etat affiche "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, inchangés après le second importer / charger-graphe.
  • Les mêmes compteurs ont été relus dans Dev Tools et dans Neo4j Browser.
  • Tu sais dire ce que montrent etat, le navigateur et journal kibana quand Kibana est arrêté, et en quoi ça diffère d'un 404.
  • Tu as gardé la sortie de etat ci-dessus (copie ou capture) comme livrable.

A.11 — Nettoyage

Cette pratique ne crée rien : ni index, ni nœud, ni objet Kibana. Deux choses à garantir : que Kibana tourne (sinon docker compose start kibana depuis le dossier du kit), et qu'aucun index de travail ne traîne :

text
GET _cat/indices/pratique-*?v

Réponse attendue : la ligne d'en-tête seule (health status index uuid pri rep docs.count …). Ne touche pas à cours, avis, acces ni au graphe : ils servent à tous les modules suivants.

Annexe B — Pas à pas détaillé sous Linux, macOS, WSL 2 et Git Bash

Afficher le pas à pas Linux, macOS, WSL 2 et Git Bash (B.0 à B.11)

Toutes les commandes de cette annexe se tapent dans un terminal bash (ou zsh), avec ./labo.sh …. Les sorties sont identiques à celles de Windows au nom du script près : le kit est le même, seuls les lanceurs changent.

B.0 — Avant de commencer

  • Avoir lu les quatre leçons du module : 01, 02, 03 et 04.
  • Docker lancé (Docker Desktop sur macOS et WSL 2, le service docker sur Linux : docker info doit répondre), le kit cloné, un terminal ouvert dans le dossier du kit (celui qui contient docker-compose.yml et labo.sh).
  • Si bash répond Permission denied sur ./labo.sh : chmod +x labo.sh, une seule fois.
  • Le labo peut être démarré ou pas : la pratique commence par les prérequis. À la fin, ./labo.sh etat doit afficher (healthy) partout, "status":"green", acces 12000 avis 609 cours 504 et nœuds : 872.
  • Toutes les requêtes de cette pratique : elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Aucune n'écrit dans le cluster.

B.1 — Dérouler la liste de contrôle des prérequis

Avant d'allumer quoi que ce soit, fais parler le script.

bash
./labo.sh prerequis

Point de contrôle : que des coches vertes et la phrase finale.

text
== Prérequis ==
  ✔ docker : Docker version 29.3.1, build c2be9cc
  ✔ le démon Docker répond
  ✔ docker compose : 5.1.1
  ✔ mémoire disponible pour Docker : 31 Go
  ✔ processeurs : 20
  ✔ port 9200 : utilisé par le labo lui-même
  ✔ port 5601 : utilisé par le labo lui-même
  ✔ port 7474 : utilisé par le labo lui-même
  ✔ port 7687 : utilisé par le labo lui-même

Tout est prêt. Lancez : ./labo.sh demarrer

Chez toi, versions et mémoire diffèrent, et si le labo n'a jamais tourné les quatre dernières lignes disent port 9200 : libre. Les deux lectures sont bonnes.

Si tu vois autre chose : une croix contient son remède dans la phrase (Docker pas lancé, mémoire sous 4 Go, port pris) ; corrige, relance, n'avance qu'avec la ligne verte. Sous Linux natif, la mémoire est celle de la machine ; sous macOS et WSL 2, c'est celle allouée dans Docker Desktop → SettingsResources.

B.2 — Démarrer et lire les trois blocs

Lance le démarrage et, cette fois, lis ce qui défile au lieu d'attendre la fin.

bash
./labo.sh demarrer

Point de contrôle : trois blocs == … == puis trois prêt.

text
== Téléchargement des images (long la première fois : ~6 Go, ~12 Go avec OpenSearch) ==

== Démarrage ==

== Attente que chaque service soit prêt ==
  elasticsearch            prêt (0 s)
  kibana                   prêt (0 s)
  neo4j                   ..... prêt (15 s)

Le labo est prêt.
  Kibana                 http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
  Elasticsearch          http://localhost:9200
  Neo4j Browser          http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)

Étape suivante : ./labo.sh importer   puis   ./labo.sh charger-graphe

Le premier bloc est vide si les images sont déjà là ; le deuxième contient les lignes de Compose (Container labo-elasticsearch Started, ou Running s'il tournait déjà) ; le troisième ajoute un point toutes les trois secondes jusqu'à prêt (… s). Labo froid : une à deux minutes ; labo déjà en marche : prêt (0 s).

Si tu vois autre chose : unhealthy, exited ou délai dépassé./labo.sh journal <service> et le catalogue de la leçon 04 ; causes les plus fréquentes : la mémoire (Exited (137)) et, sous Linux natif, vm.max_map_count trop bas (sudo sysctl -w vm.max_map_count=262144, puis ./labo.sh demarrer).

B.3 — Relever les quatre chiffres de etat

Tu vas taper etat plusieurs fois ; apprends d'abord à en extraire les quatre chiffres qui comptent.

bash
./labo.sh etat

Point de contrôle : sortie réelle de la machine du cours, où le profil OpenSearch du module 5 est actif. Chez toi, les deux lignes labo-opensearch… et ✔ OpenSearch sont remplacées par — OpenSearch non démarré (profil optionnel : ./labo.sh demarrer opensearch).

text
== Conteneurs ==
NAME                         STATUS                    PORTS
labo-elasticsearch           Up 10 hours (healthy)     0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up 10 hours (healthy)     0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 37 seconds (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)     0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours               0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Note les quatre chiffres attendus : 12000, 609, 504, 872. Sur un labo tout neuf tu lis index : aucun index du labo et nœuds : 0 : normal, l'étape B.4 les remplit.

Si tu vois autre chose : ✘ Kibana ne répond pas encore dans la minute qui suit demarrer → Kibana finit de créer ses index internes ; retape etat trente secondes plus tard.

B.4 — Charger les données, puis recharger pour prouver que rien ne bouge

Charge les index puis le graphe, et relance les deux commandes une seconde fois : les compteurs ne doivent pas changer d'une unité.

bash
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer
./labo.sh charger-graphe

Point de contrôle : au second passage, importer signale que les index existent déjà et rend les mêmes compteurs :

text
== Import dans elasticsearch ==
— index cours existe déjà — conservé
  ✔ données cours chargées
— index avis existe déjà — conservé
  ✔ données avis chargées
— index acces existe déjà — conservé
  ✔ données acces chargées

index docs.count store.size
acces      12000      1.5mb
avis         609     74.8kb
cours        504    183.6kb

Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.

et charger-graphe donne le même bilan qu'au premier :

text
== Chargement du graphe Neo4j ==
  ✔ contraintes et index en place
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16

Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.

Les documents portent leur propre _id (C0001, A00001…), donc un second _bulk remplace chaque document au lieu de l'ajouter ; côté Neo4j, tout est en MERGE. Seul le store.size peut bouger de quelques kilo-octets (Lucene garde un moment les anciennes versions) ; les docs.count, jamais.

Si tu vois autre chose : un docs.count différent de 504 / 609 / 12000 → quelqu'un a écrit dans ces index ; ./labo.sh reinitialiser puis demarrer, importer, charger-graphe te rend l'état de référence.

B.5 — Vérifier Elasticsearch depuis Dev Tools

Le script dit que tout est bon ; vérifie-le sans lui. Ouvre http://localhost:5601, menu ManagementOutils de développement, et envoie ces requêtes une par une (Ctrl + Entrée, ou Cmd + Entrée sur macOS, ou le bouton ▶ « Cliquer pour envoyer la requête »).

text
GET _cluster/health

Point de contrôle : "status": "green", "unassigned_shards": 0, et 200 - OK en bas à droite du panneau de réponse.

json
{
  "cluster_name": "labo",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 53,
  "active_shards": 53,

  "unassigned_shards": 0,

  "active_shards_percent_as_number": 100
}
text
GET _cat/indices/cours,avis,acces?v&s=index

Point de contrôle : trois lignes green, pri 1, rep 0, et les trois compteurs dans docs.count.

text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   acces aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   avis  W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.8kb         74.8kb       74.8kb
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Termine par GET cours/_count, GET avis/_count, GET acces/_count : "count": 504, 609, 12000. Tu as les mêmes chiffres par deux chemins indépendants : le script (curl dans le conteneur) et Dev Tools (via Kibana). S'ils divergent un jour, c'est le chemin qui est en cause, pas les données.

Sous bash, tu as même un troisième chemin, sans navigateur :

bash
curl -s 'http://localhost:9200/_cat/indices/cours,avis,acces?v&s=index'

Si tu vois autre chose : "status": "yellow" → un index a des réplicas non alloués, impossible avec les mappings du kit (number_of_replicas: 0) ; GET _cat/indices?v&health=yellow désigne le coupable, en général un index créé à la main.

B.6 — Compter le graphe dans Neo4j Browser

Même exercice pour Neo4j. Ouvre http://localhost:7474, connecte-toi (neo4j / aiopsatlas2026, URL localhost:7687), tape dans l'éditeur neo4j$ et clique Run :

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label

Point de contrôle : un cadre avec deux vues, Table et Raw (pas de Graph : la requête renvoie des chiffres, pas des nœuds), deux colonnes label et count(*), cinq lignes triées, et en bas à droite Started streaming 5 records after … ms and completed after … ms.

text
label          count(*)
"Competence"   22
"Cours"        504
"Etudiant"     300
"Professeur"   30
"Ville"        16

22 + 504 + 300 + 30 + 16 = 872, le chiffre de etat. Le panneau Database information (icône Database overview, la première de la barre latérale) affiche Nodes (872) et Relationships (3,712).

Si tu vois autre chose : une sixième étiquette inconnue → des nœuds créés hors kit (le module 6 t'apprendra à les supprimer proprement) ; Nodes (0) → tu as sauté charger-graphe, retour à l'étape B.4.

B.7 — Casser Kibana volontairement et observer la panne

Tu sais à quoi ressemble un labo sain ; provoque une panne dont tu connais la cause pour apprendre à la lire. Arrête uniquement Kibana, avec Compose (pas arreter, qui stopperait tout) :

bash
docker compose stop kibana
text
 Container labo-kibana Stopping
 Container labo-kibana Stopped

Puis les trois gestes de la leçon 04, dans l'ordre : etat, le navigateur, journal.

Point de contrôle 1, ./labo.sh etat : la ligne labo-kibana a disparu du bloc == Conteneurs == (Compose n'affiche par défaut que les conteneurs en cours d'exécution) et le bloc == Services == marque une croix :

text
== Conteneurs ==
NAME                         STATUS                   PORTS
labo-elasticsearch           Up 10 hours (healthy)    0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-neo4j                   Up 3 minutes (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)    0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours              0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✘ Kibana ne répond pas encore
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Pour voir quand même le conteneur arrêté : docker compose ps -a affiche labo-kibana Exited (0) 31 seconds ago. Le 0 dit « arrêt propre, demandé » ; un 137 dirait « tué, mémoire ».

Point de contrôle 2, le navigateur : recharge http://localhost:5601. Pas de « Kibana server is not ready yet » (cette phrase vient de Kibana, or il n'y a plus de Kibana pour la dire) mais l'erreur de connexion du navigateur lui-même : dans Chrome, « Ce site est inaccessible », code ERR_CONNECTION_REFUSED ; dans Firefox, « Impossible de se connecter ». En ligne de commande, curl -s http://localhost:5601 || echo REFUSE affiche REFUSE : personne n'écoute sur le port 5601. À retenir : page Kibana qui s'excuse = Kibana tourne mais attend Elasticsearch ; erreur du navigateur = Kibana ne tourne pas.

Point de contrôle 3, ./labo.sh journal kibana : les cent dernières lignes se terminent par un arrêt propre, daté à la seconde où tu as tapé stop :

text
labo-kibana  | [2026-09-09T23:44:06.893+00:00][INFO ][root] SIGTERM received - initiating shutdown
labo-kibana  | [2026-09-09T23:44:06.894+00:00][INFO ][root] Kibana is shutting down
labo-kibana  | [2026-09-09T23:44:06.902+00:00][INFO ][plugins-system.standard] Stopping all plugins.

labo-kibana  | [2026-09-09T23:44:07.265+00:00][INFO ][plugins-system.standard] All plugins stopped.

SIGTERM received signe un arrêt demandé (par toi, par docker compose stop, par un redémarrage de Docker). Aucune ligne ERROR ni FATAL : rien à réparer, il faut juste relancer. Les dizaines de lignes at OperatorSubscriber… entre les deux sont une pile d'appels émise par un plugin pendant l'arrêt : du bruit.

Si tu vois autre chose : no configuration file provided: not found → tu n'es pas dans le dossier du kit ; no such service: kibana → tu as tapé le nom du conteneur (labo-kibana) au lieu du service Compose (kibana).

B.8 — Réparer Kibana et prouver qu'Elasticsearch n'a rien vu

Relance le service et attends que son healthcheck repasse au vert : 40 à 60 secondes, le temps de se reconnecter à Elasticsearch et de vérifier ses index internes.

bash
docker compose start kibana
text
 Container labo-elasticsearch Waiting
 Container labo-elasticsearch Healthy
 Container labo-kibana Starting
 Container labo-kibana Started

Compose a d'abord vérifié qu'Elasticsearch était Healthy (le depends_on … service_healthy de la leçon 03), puis a démarré Kibana. Surveille toutes les cinq secondes :

bash
docker inspect --format '{{.State.Health.Status}}' labo-kibana

ou, pour ne pas retaper, watch -n 5 docker inspect --format '{{.State.Health.Status}}' labo-kibana (Ctrl + C pour sortir ; watch est absent de macOS par défaut, retape la commande à la main).

Point de contrôle : starting pendant 40 à 50 secondes, puis healthy (sur la machine du cours : starting de 0 à 45 s, healthy à 50 s). etat remontre alors la ligne :

text
labo-kibana                  Up 52 seconds (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp

  ✔ Kibana répond (http://localhost:5601)

et journal kibana se termine par les lignes qu'on veut voir :

text
labo-kibana  | [2026-09-09T23:47:49.408+00:00][INFO ][http.server.Kibana] http server running at http://0.0.0.0:5601
labo-kibana  | [2026-09-09T23:47:50.374+00:00][INFO ][status] Kibana is now available

Rouvre Dev Tools (si tu tombes sur « Kibana server is not ready yet », tu as été plus rapide que le healthy : attends dix secondes) et envoie la requête que etat lui-même utilise :

text
GET _cluster/health?filter_path=status,number_of_nodes
json
{
  "status": "green",
  "number_of_nodes": 1
}

Elasticsearch a répondu green pendant toute la panne : Kibana est une fenêtre sur les données, pas les données. Arrêter Kibana n'a rien effacé ni réindexé.

Si tu vois autre chose : unhealthy après deux minutes → journal kibana et cherche ECONNREFUSED (Elasticsearch tombé entre-temps) ; port is already allocated → un autre programme a pris le port 5601 pendant l'arrêt (leçon 04, panne 1).

B.9 — À toi de jouer : provoquer et expliquer une 404

Tu reconnais un service arrêté ; apprends maintenant à reconnaître une requête fausse sur un service sain, la confusion la plus fréquente en salle. Dans Dev Tools, écris une requête de recherche qui renvoie 404 avec "type": "index_not_found_exception", puis explique en une phrase pourquoi le cluster reste green.

Indice : Elasticsearch ne devine jamais le nom d'un index. Choisis-en un qui n'existe pas, avec le préfixe pratique- ; rien à créer, rien à écrire.

Solution
text
GET pratique-inexistant/_search

Réponse, avec le badge 404 - Not Found en bas à droite du panneau de sortie :

json
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [pratique-inexistant]",
        "resource.type": "index_or_alias",
        "resource.id": "pratique-inexistant",
        "index_uuid": "_na_",
        "index": "pratique-inexistant"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [pratique-inexistant]",

  },
  "status": 404
}

La même chose en ligne de commande, pour voir le code HTTP nu : curl -s -o /dev/null -w '%{http_code}\n' http://localhost:9200/pratique-inexistant/_search affiche 404.

Explication attendue : le 404 est une réponse normale et complète d'Elasticsearch : « j'ai compris ta requête, mais cette ressource n'existe pas ». Le service tourne, le cluster reste green ; il n'y a que le nom à corriger (GET _cat/indices?v donne la liste). À l'étape B.7, il n'y avait aucune réponse du tout.

Deux variantes à essayer : GET pratique-inexistant/_count?ignore_unavailable=true renvoie 200 et "count": 0 (tu demandes d'ignorer l'index absent) ; GET cours/_serch renvoie 400 avec "no handler found for uri [/cours/_serch] and method [GET]". Un 400 : « je ne comprends pas la requête » ; un 404 : « je comprends, mais ça n'existe pas ».

B.10 — Vérification finale

Une seule commande prouve que tout est fait : etat, avec Kibana revenu et les quatre chiffres.

bash
./labo.sh etat
text
== Conteneurs ==
NAME                         STATUS                        PORTS
labo-elasticsearch           Up 10 hours (healthy)         0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up About a minute (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 7 minutes (healthy)        0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)         0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours                   0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

(Sans le profil OpenSearch, les deux conteneurs labo-opensearch… n'apparaissent pas et la dernière ligne dit — OpenSearch non démarré … : c'est l'état attendu jusqu'au module 5.)

  • prerequis se termine par Tout est prêt.
  • labo-elasticsearch, labo-kibana et labo-neo4j sont Up … (healthy), Kibana compris.
  • etat affiche "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, inchangés après le second importer / charger-graphe.
  • Les mêmes compteurs ont été relus dans Dev Tools et dans Neo4j Browser.
  • Tu sais dire ce que montrent etat, le navigateur et journal kibana quand Kibana est arrêté, et en quoi ça diffère d'un 404.
  • Tu as gardé la sortie de etat ci-dessus (copie ou capture) comme livrable.

B.11 — Nettoyage

Cette pratique ne crée rien : ni index, ni nœud, ni objet Kibana. Deux choses à garantir : que Kibana tourne (sinon docker compose start kibana depuis le dossier du kit), et qu'aucun index de travail ne traîne :

text
GET _cat/indices/pratique-*?v

Réponse attendue : la ligne d'en-tête seule (health status index uuid pri rep docs.count …). Ne touche pas à cours, avis, acces ni au graphe : ils servent à tous les modules suivants.

Annexe C — Si ça coince (tous systèmes)

Afficher les cas qui coincent
  • docker compose stop kibana répond no configuration file provided: not found → Compose cherche docker-compose.yml dans le dossier courant. cd vers la racine du kit (celle qui contient labo.sh et labo.ps1) et recommence. Le script se repositionne tout seul ; les commandes docker compose tapées à la main, non.

  • Après stop, etat ne montre plus labo-kibana et tu crois l'avoir supprimé → Non : docker compose ps masque les conteneurs arrêtés. docker compose ps -a le liste en Exited (0), et docker compose start kibana le relance avec ses données. Un conteneur vraiment supprimé n'apparaîtrait pas même avec -a ; demarrer le recréerait.

  • Kibana reste starting puis passe unhealthy, et journal kibana répète Unable to retrieve version information from Elasticsearch nodes. connect ECONNREFUSED 172.x.x.x:9200 → Kibana est revenu mais Elasticsearch est tombé entre-temps (souvent Exited (137), la mémoire). Répare Elasticsearch d'abord (leçon 04, panne 2) ; Kibana se reconnecte seul.

  • Dev Tools renvoie 400 au lieu du 404 attendu, avec no handler found for uri [/pratique-inexistant/_serch] and method [GET] → La faute porte sur l'API (_serch), pas sur l'index. Elasticsearch valide d'abord la route, puis l'index : corrige en _search et le 404 apparaît.

  • Windows seulement — sous PowerShell 5.1, demarrer affiche en rouge docker : Image docker.elastic.co/kibana/kibana:9.5.3 Pulling … NativeCommandError mais finit par Le labo est prêt → Ça n'arrive que si tu rediriges la sortie (2>&1, | Tee-Object) : Compose écrit sa progression sur le flux d'erreur et PowerShell 5.1 l'habille en exception. Pas une erreur ; lance le script sans redirection, ou passe à PowerShell 7.

  • Windows seulement — .\labo.ps1 est refusé : « l'exécution de scripts est désactivée sur ce système »Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, réponds O, relance. Une seule fois par machine.

  • Linux natif seulement — Elasticsearch sort en Exited (78) et journal elasticsearch dit max virtual memory areas vm.max_map_count [65530] is too lowsudo sysctl -w vm.max_map_count=262144 puis ./labo.sh demarrer. Pour que ça survive au redémarrage : ajoute vm.max_map_count=262144 dans /etc/sysctl.conf.

  • macOS et bash — ./labo.sh répond Permission deniedchmod +x labo.sh, une seule fois. Si bash: ./labo.sh: /bin/bash^M: bad interpreter, le fichier a des fins de ligne Windows : git config core.autocrlf input puis re-clone, ou sed -i '' 's/\r$//' labo.sh.