Práctica guiada — Iniciar, verificar, romper y reparar el laboratorio

Práctica guiada64 min
Duración
45 a 60 min
Módulo
1/7
Vas a construir
un laboratorio cargado, verificado por tres caminos (script, Dev Tools, Neo4j Browser), luego roto voluntariamente y reparado
Entregable
la salida completa de etat con (healthy) en todas partes, 504 / 609 / 12000 documentos y 872 nodos, más dos líneas explicando el fallo que provocaste

Cómo leer esta página. Cada sección está plegada bajo su título: haz clic en «Mostrar …» para abrirla, y ciérrala cuando termines para mantener la página legible. Orden de lectura: Objetivo, luego En resumen (los comandos a escribir), luego El conjunto de datos (a leer antes de cualquier consulta), luego las consultas Elasticsearch y Neo4j, ordenadas de la más simple (GET _cat/indices, MATCH (n) RETURN n) a la más impresionante, con una explicación después de cada una. El paso a paso detallado, con la salida esperada de cada comando y los fallos a provocar, está en el anexo: anexo A para Windows (PowerShell), anexo B para Linux, macOS, WSL 2 y Git Bash. Abre un solo anexo, el de tu sistema. El anexo C, común, agrupa los casos donde algo falla.

Objetivo

Te unes al equipo que construye el motor de búsqueda de la plataforma de cursos en línea. Tu jefa te entrega el kit del laboratorio: «Mañana por la mañana, quiero un laboratorio que funcione en tu equipo, los datos cargados, y la certeza de que sabes repararlo sin llamarme.» Vas entonces a iniciar la stack, demostrar que los tres motores contienen lo que deben, luego detener un servicio a propósito para ver cómo se lee el fallo en etat, en el navegador y en el journal, y ponerlo de nuevo en marcha. Reconocer «este servicio está detenido» en diez segundos es lo que evita horas de búsqueda en el lugar equivocado.

Los nueve pasos de este esquema se detallan, con la salida esperada de cada comando, en el anexo A (Windows) o el anexo B (Linux, macOS) al final de la página.

En resumen: los comandos del laboratorio

Mostrar los comandos

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

Clonas el kit en una carpeta lab1, verificas que Docker esté listo, inicias los tres servicios (Elasticsearch, Kibana, Neo4j), abres sus tres páginas web, luego cargas los datos. Lanzas importer y charger-graphe dos veces: la segunda no debe cambiar nada en los contadores, es la prueba de que la carga se puede repetir sin duplicados. Al final, etat debe mostrar (healthy) en todas partes, acces 12000 avis 609 cours 504 y nœuds : 872. Empieza por ejecutar este bloque.

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

Verificar las tres URL en el navegador:

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 rechaza .\labo.ps1 («la ejecución de scripts está deshabilitada»): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, responde O, relanza.

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

Verificar las tres URL en el navegador:

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

El conjunto de datos: lo que vas a manipular

Mostrar el conjunto de datos

Antes de escribir una sola consulta, mira los datos. Todo el laboratorio gira en torno a una plataforma de cursos en línea ficticia: un catálogo de cursos, las reseñas dejadas por los estudiantes, el registro del servidor web que sirve las páginas, y los vínculos entre estudiantes, profesores, cursos y competencias. Los mismos datos se cargan en Elasticsearch (para buscar) y en Neo4j (para seguir los vínculos). Están en el kit, en claro, en dos carpetas:

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

Ábrelos tú mismo, toma diez segundos y sabrás exactamente qué estás manipulando:

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

Del lado de Elasticsearch: tres índices, tres tipos de documentos

Un archivo .ndjson (newline-delimited JSON) contiene un objeto JSON por línea. En el kit, las líneas van de dos en dos: una línea de instrucción («guarda lo que sigue en el índice cours bajo el identificador C0001»), luego el documento mismo. Es el formato que el comando importer envía a Elasticsearch en bloque.

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

El primer documento, tal como está en 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"]
}
CampoEjemploQué es
idC0001Identificador del curso. C de curso, luego un número. También es el _id del documento.
titreDocker expliqué simplementEl título. Texto libre, sobre esto se harán las búsquedas por palabras.
descriptionDans ce cours…Un párrafo de presentación. Texto libre también.
categorieDevOpsUna de las seis grandes familias: Cloud, DevOps, Datos, Desarrollo web, IA, Seguridad. 84 cursos cada una.
sujetDockerMás preciso que la categoría: Docker, Kubernetes, Neo4j, Elasticsearch…
niveaudebutantdebutant, intermediaire o avance.
langueenfr o en.
prix129En dólares. 0 para un curso gratuito.
gratuitfalseVerdadero o falso.
duree_heures5Duración total del curso.
tags["docker", "linux", …]Una lista de palabras clave. Un campo puede contener varios valores.
date_publication2024-08-14Una fecha.
note_moyenne4.4Promedio de las calificaciones recibidas, sobre 5.
nb_avis327Número de reseñas recibidas.
professeur{ "id": "P001", "nom": …, "ville": … }Un objeto dentro del objeto: el profesor está descrito directamente en la ficha del curso, con su identificador, su nombre y su ciudad.
competences["Conteneurisation", …]Lista de competencias que cubre el curso.

Índice avis: 609 documentos, uno por reseña dejada por un estudiante

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
}
CampoEjemploQué es
idA00001Identificador de la reseña. A de avis (reseña).
cours_idC0028El curso en cuestión. Es el vínculo hacia el índice cours: este C0028 es el id de un documento de cours.
etudiantNathanNombre del autor.
ville, paysSherbrooke, CanadaDesde dónde escribe.
note3La calificación dada, de 1 a 5.
texteLes vidéos sont bonnes…El comentario. Texto libre.
date2024-06-21Fecha de la reseña.
utile33Número de personas que encontraron útil esta reseña.

Índice acces: 12 000 documentos, una línea por solicitud recibida por el servidor 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"
}
CampoEjemploQué es
idL000001Identificador de la línea. L de línea de registro (log).
@timestamp2026-08-10T08:17:19.000ZFecha y hora exactas de la solicitud. El @ es una convención: es el campo de tiempo que Kibana detecta automáticamente.
methodeGETGET o POST.
chemin/robots.txtLa dirección solicitada en el sitio: /, /cours, /catalogue, /contact, /tarifs, o la página de un curso.
cours_idnull o C0042El curso consultado, si la página se refiere a uno; null (vacío) si no, como aquí para /robots.txt. Vínculo hacia el índice cours.
categorienull o DevOpsCategoría del curso consultado, copiada para simplificar los gráficos.
statut200El código HTTP de la respuesta: 200 OK, 301 y 304 redirección o caché, 404 no encontrado, 500 y 503 error del servidor.
octets108506Tamaño de la respuesta enviada.
duree_ms100Tiempo de respuesta en milisegundos.
ip108.190.166.1Dirección IP del visitante.
paysCAPaís del visitante, código de dos letras.
appareildesktopdesktop, mobile o tablette.
navigateurEdgeChrome, Firefox, Safari, Edge…
referentgoogleDe dónde venía el visitante: direct, google, linkedin, youtube o newsletter.

Los tres índices están vinculados por cours_id: una reseña habla de un curso, una línea de registro consulta un curso. Pero Elasticsearch no hace uniones: cada índice se busca por separado. Para seguir los vínculos, se pasa a Neo4j.

Del lado de Neo4j: cinco tipos de nodos, cinco tipos de relaciones

Neo4j recibe los mismos cursos (mismos identificadores C0001…, mismos títulos), más lo que no está en Elasticsearch: los estudiantes, sus inscripciones, los profesores, las competencias, las ciudades, y los prerrequisitos entre cursos. El modelo cabe en un dibujo:

Las cinco flechas se leen como frases: un estudiante está inscrito en un curso; un profesor enseña un curso; un curso cubre una competencia; un curso es prerrequisito de otro; un estudiante o un profesor vive en una ciudad. En total 872 nodos y 3712 relaciones.

Los datos llegan en forma de archivos CSV clásicos, una línea de encabezado luego una línea por elemento:

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

Mira cómo se traduce esto en un grafo. Los cinco primeros archivos se convierten en nodos; cada línea es un nodo, cada columna una propiedad. Los tres últimos se convierten en relaciones; cada línea conecta dos nodos por sus identificadores. La línea E0001,C0028,2024-04-01,100,3 de inscriptions.csv se convierte en la flecha (Etudiant E0001)-[:INSCRIT_A {date, progression, note}]->(Cours C0028): Nathan Ben Ali está inscrito en el curso C0028, lo terminó al 100 %, y le puso 3. Compara con la primera reseña del índice avis: es el mismo Nathan, el mismo curso C0028, la misma calificación 3.

NodoPropiedadesEjemplo
Coursid, titre, categorie, sujet, niveau, prix, duree_heures, date_publicationC0001, «Docker expliqué simplement», 129 $
Etudiantid, prenom, nom, interet, inscription_leE0001, Nathan Ben Ali, interés DevOps
Professeurid, prenom, nom, specialite, annees_experienceP001, Karim Caron, DevOps, 23 años
Competenceid, nomK01, Conteneurisation
Villenom, pays, latitude, longitudeMontréal, Canadá
RelaciónDe → haciaPropiedadesSentido
INSCRIT_AEtudiant → Coursdate, progression, noteEl estudiante toma este curso
ENSEIGNEProfesseur → CoursEste profesor dicta este curso (uno solo por curso)
COUVRECours → CompetenceEste curso enseña esta competencia
PREREQUIS_DECours → CoursHay que haber tomado el primero antes que el segundo
HABITEEtudiant o Professeur → VilleDónde vive la persona

Los identificadores, para orientarse

Todos los datos usan la misma convención: una letra, luego un número. Cuando ves un identificador, sabes de inmediato de qué se habla.

PrefijoCosaEjemploDónde se encuentra
CCursoC0001Elasticsearch cours, avis.cours_id, acces.cours_id, Neo4j Cours
AReseñaA00001Elasticsearch avis
LLínea de registroL000001Elasticsearch acces
EEstudianteE0001Neo4j Etudiant
PProfesorP001cours.professeur.id en Elasticsearch, Neo4j Professeur
KCompetenciaK01Neo4j Competence

Recuerda un solo hilo conductor para todo lo que sigue: el curso C0001, «Docker expliqué simplement», dictado por P001, Karim Caron. Lo volverás a encontrar en Elasticsearch (consulta E9), en Neo4j (consultas N7 y N13), y servirá para demostrar que ambos motores contienen exactamente lo mismo.

Primeras consultas: mostrar los datos, de lo más simple a lo más impresionante

Los contadores de etat demuestran que los datos están ahí; las consultas siguientes los hacen ver. Todas son de solo lectura: puedes repetirlas tantas veces como quieras, nada se modificará. Son idénticas en Windows y en Linux, todo ocurre en el navegador.

La regla de esta sección: una sola novedad por consulta. Empezamos por «muéstrame lo que hay», sin ningún parámetro, y añadimos una noción en cada paso. Después de cada consulta, una explicación corta; cuando hay que profundizar, un bloque desplegable «Para entender bien». Pega las consultas una por una, en orden, y lee la respuesta antes de pasar a la siguiente.

Elasticsearch, en Kibana Dev Tools

Mostrar las 15 consultas de Elasticsearch (E1 a E15)

Abre http://localhost:5601, luego menú ManagementDev Tools (o «Outils de développement»). El panel de la izquierda es un editor: pega una consulta, coloca el cursor sobre ella, luego Ctrl+Enter o el triángulo ▶. La respuesta aparece a la derecha, con el código HTTP (200 - OK) abajo.

Una imagen para tener en mente durante todo lo que sigue: Elasticsearch es un gran armario.

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

Las consultas E1 a E4 miran las etiquetas de los cajones. A partir de E5, se abren los cajones. No confundas nunca las dos cosas: es el error número uno de los principiantes.

E1. Listar los cajones

text
GET _cat/indices

Qué pide la consulta: «Elasticsearch, muéstrame la lista de todos tus cajones.» Respuesta en la máquina del curso:

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

Cuatro líneas = cuatro cajones. Tres son tuyos: avis, acces, cours. El cuarto, el que empieza con un punto (.internal.alerts-security…), no es tuyo: Kibana lo creó solo para su uso interno (la gestión de alertas de seguridad). Está vacío (0 fichas), no molesta, no lo elimines. En lo que sigue, lo excluiremos de la visualización.

Lo molesto aquí: hay números por todas partes y ningún título de columna. Es como una tabla de Excel sin línea de encabezado. Lo corregimos en E2.

Para leer la consulta misma, palabra por palabra:

ParteQué significa
GET«Quiero leer algo.» No modifico nada, no elimino nada, solo miro. Sin riesgo.
_cat«Respóndeme en tabla de texto», legible por un humano, no en JSON. El guion bajo al principio indica un comando del propio Elasticsearch, no un nombre de índice.
indices«… la tabla de los índices.» Es el plural en inglés de índice.
Para entender bien: qué es un índice, y leer una línea valor por valor

Un índice es un cajón: un lugar donde se guardan fichas parecidas. Todas las fichas «curso» van al cajón cours, todas las fichas «reseña» al cajón avis, y cada línea del registro del servidor web es una ficha del cajón acces. Si conoces SQL, el índice es lo que SQL llama una tabla:

ElasticsearchEn la imagenSQL
Índice coursEl cajón «cursos»Tabla cours
Documento JSONUna ficha en el cajónFila
CampoUna casilla en la ficha (título, precio…)Columna
_id del documentoEl número escrito arriba de la fichaClave primaria

Los tres cajones del laboratorio:

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)

Dos trampas de vocabulario. El singular es un índice, y el plural es los índices en español (Elasticsearch escribe indices, el plural en inglés). Y no es un «indicio» en el sentido policial: es un índice como el que está al final de un libro, la lista que dice en qué página está cada palabra. Elasticsearch hace exactamente eso, a gran escala.

Ahora, tomemos una sola línea de la respuesta E1 y leámosla valor por valor, de izquierda a derecha:

text
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb
ValorQué significa
greenEl cajón está en buen estado. Verde = todo va bien.
openEl cajón está abierto: se puede leer y escribir en él. (Lo contrario sería close.)
avisEl nombre del cajón.
W9j_JrwJT4mdzpWcS5k7xgUn número de serie técnico, generado por Elasticsearch. Nunca lo usarás.
1El cajón está en un solo fragmento (un shard primario). Un índice grande puede dividirse en varios fragmentos repartidos en varias máquinas; aquí, no.
0Cero copias de seguridad (replica). Normal en un laboratorio de una sola máquina: una copia no tendría dónde ir.
609El cajón contiene 609 fichas. Es el valor que se mira primero.
0Cero fichas marcadas «para tirar» pendientes de limpieza.
74.6kbEspacio total ocupado en el disco.
74.6kbEspacio ocupado solo por el fragmento primario (idéntico: solo hay un fragmento y sin copia).
74.6kbTamaño de los datos mismos.

Tres veces 74.6kb porque, con un solo fragmento y cero copias, las tres formas de medir dan el mismo resultado. En un cluster real con copias, store.size sería mayor que pri.store.size.

En resumen, tus tres cajones:

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

E2. La misma lista, con los títulos de columnas: ?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

Misma respuesta que en E1, más una línea de títulos arriba. Es todo lo que hace ?v. Ahora ya no tienes que adivinar: la columna docs.count es el número de fichas (609, 0, 12000, 504), la columna store.size es el espacio en el disco.

?v, ¿qué significa exactamente?

  • El ? dice: «lo que sigue son opciones». Separa el comando (_cat/indices) de sus ajustes.
  • La v es la opción verbose, «hablador»: «muestra también los títulos de las columnas».

Entonces ?v = «respóndeme con los encabezados, para que entienda lo que leo». Toma la costumbre de ponerlo siempre en los comandos _cat. Sin ?v, tienes números; con ?v, tienes información.

Para entender bien: cada título de columna, en una frase
ColumnaEn claro
healthSalud del cajón. green: todo bien. yellow: las fichas están ahí, pero faltan algunas copias de seguridad. red: algunas fichas son inaccesibles.
statusopen: utilizable. close: cerrado, no se puede leer ni escribir.
indexEl nombre del cajón.
uuidNúmero de serie técnico. Nunca se usa a mano.
priEn cuántos fragmentos (primary shards) está dividido el cajón. 1 en el laboratorio.
repCuántas copias de seguridad (replicas) de cada fragmento. 0 en el laboratorio.
docs.countNúmero de fichas. La columna a mirar primero.
docs.deletedFichas marcadas «para tirar» aún no limpiadas del disco.
store.sizeEspacio total en el disco.
pri.store.sizeEspacio de los fragmentos primarios solos, sin las copias.
dataset.sizeTamaño de los datos mismos.

Por qué todo es green en el laboratorio: rep vale 0, así que no hay ninguna copia que hacer, así que ninguna copia puede faltar. En un cluster real, rep 1 con una sola máquina daría yellow, porque la copia no tendría dónde ir.

E3. La etiqueta de un solo cajón

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

Qué pide la consulta: «Muéstrame la etiqueta del cajón cours.» Una sola novedad: el nombre del cajón añadido después de _cat/indices/. Una sola línea en la respuesta, la de cours, con sus 504 fichas. Lo que sigue a la última / sirve de filtro.

Haz lo mismo para los otros dos cajones:

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

«Muéstrame la etiqueta del cajón de las reseñas»: 609 en docs.count. «Muéstrame la etiqueta del cajón de los registros de acceso»: 12000. En Dev Tools, cuando varias consultas están pegadas seguidas, solo se envía aquella donde está el cursor: colócalo en la línea deseada antes de Ctrl+Enter.

E4. El resumen limpio de los tres cajones

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

Qué pide la consulta: «Muéstrame una tabla resumen de los cajones acces, avis y cours. Ordénalos por nombre, y solo muestra su nombre, su salud, su número de fichas y su tamaño.» Cuatro líneas, cuatro columnas, todo lo necesario y nada más. Es la consulta a tener a mano para verificar el laboratorio de un vistazo.

Parece complicada porque es larga, pero es solo E3 con tres opciones más. Descomposición, parte por parte:

ParteEn claro
GETPido información, no modifico nada.
_cat/indicesLas etiquetas de los cajones, en tabla legible.
/acces,avis,coursSolo estos tres cajones (nombres separados por comas, sin espacio). El cajón interno de Kibana desaparece.
?vCon los títulos de las columnas.
&s=indexs de sort: ordena las líneas por orden alfabético de la columna index.
&h=index,health,docs.count,store.sizeh de headers: solo muestra estas columnas, en este orden.

Una regla y solo una para las opciones: la primera empieza con ?, todas las siguientes con &. Por eso se lee ?v&s=…&h=….

Y las cuatro columnas elegidas:

ColumnaEn claro
indexEl nombre del cajón.
healthSu salud: green todo funciona; yellow las fichas existen pero faltan copias de seguridad; red algunas fichas no están disponibles.
docs.countCuántas fichas contiene.
store.sizeCuánto espacio ocupa en el disco.

En una frase: tus tres cajones están abiertos, saludables, y contienen exactamente las fichas esperadas, 12 000, 609 y 504.

Para entender bien: por qué es la consulta a recordar

Sin h=, Elasticsearch devuelve once columnas, la mayoría de las cuales no te enseñan nada en el día a día. Sin s=, el orden de las líneas es arbitrario y cambia de una llamada a otra (mira E2: avis aparecía antes que acces). Al nombrar los tres cajones, también excluyes el de Kibana. El resultado cabe en cuatro líneas y se compara de un vistazo con los números esperados. Es exactamente lo que hace el comando etat del kit por detrás.

Variante útil si también quieres ver que los cajones están bien abiertos: añade status en la lista 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

Balance de E1 a E4: aún no has leído ninguna ficha. Solo miraste las etiquetas de los cajones. Sabes que hay 504 cursos, pero todavía no viste un solo título de curso. Es lo que hacemos ahora.

E5. Contar las fichas de un cajón

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

Qué pide la consulta: «Cuenta las fichas del cajón cours.» Respuesta: 504. Mira bien la forma de la consulta, es nueva: ya no le hablamos al armario (_cat/…), le hablamos a un cajón. El nombre del cajón viene primero (cours), luego lo que queremos hacerle (_count), separados por una /. Todas las consultas siguientes tendrán esta forma: nombre-del-cajon/_accion.

Otro cambio: la respuesta ya no es una tabla de texto sino JSON, con llaves y comillas. Es la forma normal de las respuestas de Elasticsearch; _cat era la excepción. Ignora la parte _shards, solo dice «el fragmento del cajón respondió, nada falló».

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

E6. Abrir el cajón y leer las fichas

text
GET cours/_search

Qué pide la consulta: «Abre el cajón cours y muéstrame las fichas que hay dentro.» Es la primera vez que ves un curso real: su título, su precio, su profesor.

La respuesta es larga: es normal, contiene diez fichas completas. Elasticsearch solo te da las 10 primeras, aunque el cajón contenga 504; es una protección, para no enviarte 504 fichas de golpe sin que lo hayas pedido. Mira la estructura más que el contenido:

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 significa «busca»; sin más precisión, significa «dame fichas, cualquiera». Los dos números a identificar: "total": { "value": 504 } (hay 504 fichas en el cajón) y la lista hits que solo contiene 10 (las que se te muestran). Cada ficha está en _source, con todas sus casillas: titre, prix, categorie

Haz lo mismo con los otros dos cajones: GET avis/_search («abre el cajón avis, muéstrame las 10 primeras reseñas») y GET acces/_search (las 10 primeras líneas de registro). En SQL: SELECT * FROM cours LIMIT 10.

La diferencia esencial, para no volver a confundir nunca.

GET _cat/indices/cours?v mira la etiqueta del cajón: su estado, su número de fichas, su tamaño. Una línea. No ves ningún curso.

GET cours/_search abre el cajón y lee las fichas: títulos, precios, categorías, profesores. Diez fichas. Ves los cursos mismos.

La primera responde «hay 504 cursos». La segunda responde «aquí hay cursos».

Para entender bien: leer una respuesta de búsqueda, clave por clave
ClaveEn claro
tookEl tiempo que tardó Elasticsearch, en milisegundos (aquí 1 ms).
hits.total.valueEl número total de fichas que coinciden: 504. Aunque solo se te muestren 10, te dice cuántas hay en total.
hits.hitsLa lista de fichas que se te muestran: las 10 primeras por defecto.
_indexEl cajón de donde viene la ficha (cours).
_idEl número escrito arriba de la ficha (C0001, C0002…).
_scoreUna nota de relevancia. Vale 1 en todas partes aquí porque no se buscó nada preciso: todas las fichas valen lo mismo.
_sourceLa ficha misma, tal como fue guardada en el cajón, con todas sus casillas.

La palabra hit significa «golpe», como en el tiro: una ficha «alcanzada» por la consulta. El campo professeur es una ficha dentro de la ficha (un JSON dentro del JSON): el profesor está escrito directamente en la ficha del curso, con su nombre y su ciudad. SQL no hace esto naturalmente, haría falta una segunda tabla y una unión.

E7. Mostrar más de diez

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

Qué pide la consulta: «Abre el cajón cours y muéstrame 20 fichas en lugar de las 10 habituales.»

Una sola novedad, pero importante: la consulta ahora tiene dos partes. La primera línea (GET cours/_search) dice qué hacer; el bloque entre llaves debajo, llamado el cuerpo, da precisiones. Aquí la precisión es "size": 20: «tamaño del lote = 20». Eso es todo. size simplemente significa «cuántas fichas quieres que se te muestren».

Verifica en la respuesta: hits.total.value sigue valiendo 504 (el cajón no cambió), pero la lista hits ahora contiene 20 fichas en lugar de 10. En SQL: LIMIT 20.

Para entender bien: ¿un GET con un cuerpo?

En HTTP clásico, GET no tiene cuerpo; Elasticsearch lo acepta de todos modos porque es práctico en Dev Tools. Si una herramienta lo rechaza, POST cours/_search con el mismo cuerpo hace exactamente lo mismo. Otro límite a conocer: size no puede superar 10 000 de golpe (parámetro index.max_result_window); para recorrer más, se pagina. Para nuestros 504 cursos, "size": 504 funcionaría, pero la respuesta tendría miles de líneas: no es así como se leen datos, las agregaciones (E13) están hechas para eso.

E8. Elegir los campos a mostrar

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 } }
]

Qué pide la consulta: «Muéstrame 3 fichas, pero solo las casillas titre y prix de cada ficha.» Novedad: _source con una lista de campos. En lugar de la ficha entera, solo se conservan las casillas que nos interesan. La respuesta se vuelve legible de un vistazo. En SQL: SELECT titre, prix FROM cours LIMIT 3. Usaremos _source en casi todas las consultas siguientes, justamente para mantener respuestas cortas.

E9. Un documento preciso, por su identificador

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" },

  }
}

No hay búsqueda aquí: se pide el documento cuyo identificador es C0001, y Elasticsearch lo devuelve directamente ("found": true). Es el acceso más rápido que existe. En SQL: SELECT * FROM cours WHERE id = 'C0001'. Recuerda este curso, «Docker expliqué simplement», de Karim Caron: lo volveremos a encontrar en Neo4j más adelante, para demostrar que ambos motores contienen los mismos datos.

Para entender bien: _id e id, dos cosas diferentes

_id (con guion bajo) es el identificador técnico del documento en Elasticsearch; id (sin guion) es un campo ordinario dentro de _source. El kit los hizo idénticos a propósito (C0001 en ambos lados) para que sea legible. Es también lo que hace que importer se pueda relanzar: enviar dos veces un documento con el mismo _id reemplaza el primero en lugar de crear un segundo, de ahí _version: 6 (el documento fue reescrito seis veces en la máquina del curso, sin nunca duplicarse).

E10. La primera búsqueda real

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" } },

  ]
}

Novedad: query, la parte del cuerpo que dice qué buscar. match es la búsqueda básica: «los documentos cuyo campo titre contiene la palabra kubernetes». Siete cursos responden, y por primera vez _score ya no es 1: es la relevancia, y los resultados están ordenados del más relevante al menos relevante. En SQL, el equivalente aproximado sería WHERE titre LIKE '%kubernetes%', pero mira bien: escribimos kubernetes en minúsculas y encontramos «Kubernetes» con mayúscula. LIKE no lo habría hecho.

Para entender bien: qué hace match

En el momento en que se indexa un curso, Elasticsearch divide su título en palabras, las convierte a minúsculas, quita los acentos y reduce cada palabra a su raíz («conteneurs» se convierte en «conteneur»): es el análisis, hecho aquí por el analizador french definido en el mapping del kit. Cuando buscas, tu consulta recibe el mismo tratamiento, luego Elasticsearch compara palabra por palabra. Resultado: mayúsculas, acentos y plurales ya no cuentan. El _score sube cuando la palabra es rara en el índice y frecuente en el documento. El módulo 3 le dedica varias lecciones; aquí, recuerda solo: match busca palabras, no secuencias de caracteres.

E11. La falta de ortografía perdonada

Primero la prueba de que, por defecto, una falta real no encuentra nada. «kubrenetes» (dos letras invertidas):

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

Luego la misma consulta con una línea más, fuzziness, que permite una o dos letras de diferencia:

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 } }
]

Los 7 cursos de Kubernetes vuelven, a pesar de la falta. Es el momento que siempre sorprende en el aula, y es la razón de ser de Elasticsearch en una barra de búsqueda: el usuario escribe mal, el motor entiende de todos modos. Un LIKE '%kubrenetes%' en SQL nunca habría devuelto nada. Nota la sintaxis: cuando match necesita opciones, el valor del campo se convierte en un objeto { "query": …, "fuzziness": … } en lugar de una simple cadena.

Para entender bien: AUTO

fuzziness cuenta las modificaciones (una letra añadida, quitada, cambiada o intercambiada con su vecina) que se toleran entre la palabra escrita y la palabra indexada. AUTO adapta la tolerancia a la longitud de la palabra: 0 faltas para una palabra de 1 o 2 letras, 1 falta de 3 a 5 letras, 2 faltas más allá. «kubrenetes» tiene 10 letras, así que se admiten 2 faltas; solo tiene una (el intercambio reer): encontrada. El _score es un poco más bajo que en E10 (4,45 contra 5,09): Elasticsearch penaliza levemente las coincidencias aproximadas, lo que mantiene las coincidencias exactas en primer lugar.

E12. Ordenar: el ranking de los cursos

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"]
}

Dos novedades, que se entienden al leer. range con gte (greater than or equal): solo los cursos que tienen al menos 10 reseñas, para descartar las calificaciones basadas en un solo voto. sort: ordenar por note_moyenne descendente, luego por nb_avis descendente para desempatar. En SQL: WHERE nb_avis >= 10 ORDER BY note_moyenne DESC, nb_avis DESC LIMIT 5. Cuando ordenas tú mismo, _score se vuelve null: la relevancia ya no importa, es tu orden el que cuenta.

E13. Contar por categoría sin leer una sola línea

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 }
    ]
  }
}

Novedad: aggs (agregaciones). terms sobre categorie hace un paquete (bucket) por valor distinto y cuenta los documentos dentro. size: 0 dice «no me devuelvas ningún documento, solo los contadores»: la respuesta es minúscula e instantánea, incluso con millones de líneas. En SQL: SELECT categorie, COUNT(*) FROM cours GROUP BY categorie. Resultado: seis categorías de 84 cursos cada una. par_categorie es un nombre que eliges; solo sirve para encontrar el resultado en la respuesta.

Para entender bien: por qué es la base de Kibana

Cada gráfico de Kibana, cada gráfico circular, cada histograma es una agregación como esta, ejecutada por Elasticsearch y dibujada por Kibana. Cuando construyas un dashboard en el módulo 4, harás clic en «campo: categorie, agregación: terms» y Kibana enviará exactamente esta consulta. Saber leerla es saber lo que hace el dashboard por detrás.

E14. Una agregación dentro de una agregación

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

Misma consulta que E13, con un segundo aggs dentro del primero. En cada paquete de categoría, se vuelven a hacer paquetes por niveau. Resultado: para «Cloud», 45 principiante, 28 avanzado, 11 intermedio; y así para las seis categorías, en una sola pasada. En SQL: GROUP BY categorie, niveau, pero la respuesta ya viene jerarquizada, lista para un gráfico apilado.

E15. Los errores del servidor en los 12 000 accesos

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 }
    ]
  }
}

Nada nuevo: se combina range (E12) y terms (E13) sobre el índice acces. De las 12 000 líneas de registro, 178 tienen un código HTTP ≥ 500: 122 errores 500 y 56 503. Es la pregunta que un responsable de plataforma hace cada mañana, y se responde en un milisegundo. En SQL: SELECT statut, COUNT(*) FROM acces WHERE statut >= 500 GROUP BY statut. Es exactamente lo que pondrás en un dashboard en el módulo 4.

Neo4j, en Neo4j Browser

Mostrar las 16 consultas de Neo4j (N1 a N16)

Abre http://localhost:7474. Pantalla de conexión: URL neo4j://localhost:7687, usuario neo4j, contraseña aiopsatlas2026. Arriba, una barra de edición que empieza con neo4j$: pega una sola consulta, luego Ctrl+Enter o el triángulo ▶. El resultado aparece en un recuadro debajo, con pestañas a la izquierda: Graph (un dibujo, cuando el resultado contiene nodos), Table (filas y columnas) y Text.

N1. Mostrar todo

cypher
MATCH (n) RETURN n

El resultado es una nube de burbujas coloreadas, conectadas por flechas, que puedes mover con el mouse. Neo4j Browser muestra como máximo 300 nodos a la vez (un mensaje arriba del resultado lo indica); los 872 están ahí, solo dibuja una parte para mantenerse legible.

Es la consulta más simple de Cypher, el lenguaje de Neo4j. MATCH significa «encuentra», (n) designa un nodo cualquiera (los paréntesis dibujan un círculo, como una burbuja) al que se le da el nombre n, y RETURN n significa «muéstramelo». En SQL, no hay equivalente: sería «SELECT * de todas las tablas a la vez», lo que SQL no sabe hacer.

Para entender bien: nodos, etiquetas, relaciones

Un grafo está hecho de dos cosas: nodos (las burbujas) y relaciones (las flechas entre burbujas). Cada nodo lleva una etiqueta que dice qué es (Cours, Etudiant…) y propiedades (título, precio…). Cada relación lleva un tipo (INSCRIT_A, ENSEIGNE…) y una dirección.

Neo4jSQL
Etiqueta CoursTabla cours
NodoFila
PropiedadColumna
Relación INSCRIT_ATabla de unión inscriptions + uniones

El grafo del laboratorio:

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

Los 504 cursos son los mismos que los 504 documentos del índice de Elasticsearch cours: son dos motores que guardan los mismos datos de dos formas, cada uno para responder preguntas diferentes.

N2. Limitar el número de resultados

cypher
MATCH (n) RETURN n LIMIT 25

Una sola novedad: LIMIT 25, la misma palabra que en SQL. Veinticinco burbujas en lugar de trescientas: por fin se ve algo. Pasa el mouse sobre una burbuja: sus propiedades se muestran abajo del recuadro. Haz doble clic en ella: sus vecinos se despliegan.

N3. Contar en lugar de mostrar

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

count(n) cuenta en lugar de dibujar; AS total nombra la columna. Sin dibujo: el resultado pasa automáticamente a vista Table, ya que un número no se dibuja. 872, la cifra de etat. En SQL: SELECT COUNT(*).

N4. Un solo tipo de nodo

cypher
MATCH (c:Cours) RETURN c LIMIT 5

Novedad: :Cours después del nombre de la variable. Es la etiqueta: «solo los nodos que son cursos». Cinco burbujas, todas del mismo color. En SQL: SELECT * FROM cours LIMIT 5. Por costumbre, se llama a la variable por la inicial de la etiqueta (c para Cours, e para Etudiant), pero n también funcionaría.

N5. Columnas en lugar de burbujas

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

Novedad: c.titre, c.prix. El punto da acceso a una propiedad del nodo. Cuando se devuelven propiedades en lugar de nodos enteros, Neo4j Browser pasa a vista Table. En SQL: SELECT titre, prix FROM cours LIMIT 5. Compara con E8: mismos títulos, mismos precios, mismo orden. Para conocer todas las propiedades de un curso: MATCH (c:Cours) RETURN keys(c) LIMIT 1 responde prix, duree_heures, date_publication, sujet, niveau, categorie, id, titre.

N6. Contar por etiqueta

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) devuelve la lista de etiquetas del nodo (un nodo puede tener varias; aquí solo una, de ahí el [0], el primer elemento). El count(*) se agrupa automáticamente por todo lo que no sea una agregación: no hace falta escribir GROUP BY, Cypher lo deduce. ORDER BY nombre DESC ordena. La suma de las cinco líneas da 872. En SQL, harían falta cinco SELECT COUNT(*) y UNION.

N7. Un nodo preciso, por su identificador

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"

Novedad: las llaves {id: 'C0001'} en el patrón. Filtran sobre una propiedad, como un WHERE id = 'C0001'. Y es el mismo curso que en E9 en Elasticsearch: mismo título, mismo precio. Ahí está la prueba de que ambos motores contienen exactamente los mismos datos; lo que cambia es lo que se les puede preguntar.

N8. Filtrar con 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 se escribe como en SQL y CONTAINS busca una secuencia de caracteres. Siete cursos, los mismos que en E10. Pero prueba con CONTAINS 'kubernetes' en minúsculas: cero resultados. Y CONTAINS 'kubrenetes': cero también. Neo4j compara caracteres, exactamente; no conoce ni mayúsculas, ni palabras, ni faltas. Es precisamente por eso que el laboratorio tiene los dos motores: la barra de búsqueda es Elasticsearch; los vínculos entre las cosas, es Neo4j.

N9. Ordenar

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

Nada nuevo: ORDER BY … DESC LIMIT 5, como en SQL. Los cinco cursos más caros, todos a 199 $.

N10. Contar por categoría

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

Exactamente el resultado de la agregación E13 en Elasticsearch: seis categorías de 84. Mismo dato, dos motores, dos sintaxis. Hasta aquí, Neo4j no ha hecho nada que SQL no sepa hacer. Eso cambia en la siguiente consulta.

N11. La primera relación

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

Pasa a vista Graph: burbujas «profesor» conectadas por flechas ENSEIGNE a burbujas «curso». Toma un profesor con el mouse, verás todos sus cursos seguirlo.

Novedad: la flecha. (p:Professeur)-[r:ENSEIGNE]->(c:Cours) se lee literalmente «un profesor, que enseña, un curso». Los paréntesis son nodos, los corchetes son la relación, -> da el sentido. Es un dibujo ASCII de lo que se busca, y Neo4j encuentra todos los lugares del grafo que se parecen a ese dibujo. En SQL, sería SELECT * FROM professeurs JOIN cours ON cours.professeur_id = professeurs.id, y eso no se dibujaría.

Para entender bien: por qué las relaciones lo cambian todo

En SQL, una relación entre dos filas no existe realmente: se recalcula en cada consulta mediante una unión, que compara identificadores. Con diez uniones encadenadas, se vuelve lento e ilegible. En Neo4j, la relación está almacenada como una flecha física entre dos nodos: seguir una flecha cuesta lo mismo sin importar cuántos nodos haya en la base. Es lo que hace posibles las consultas N14 a N16, que encadenan varios saltos sin esfuerzo.

N12. Contar las relaciones por tipo

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

() es un nodo cualquiera del que ni siquiera se conserva el nombre; [r] una relación de cualquier tipo; type(r) su tipo. Cinco tipos de relaciones, 3712 en total: la cifra que muestra el panel Database information de Neo4j Browser. Se lee el modelo del laboratorio en una línea: estudiantes inscritos en cursos, cursos que cubren competencias, profesores que enseñan cursos, personas que viven en ciudades, y cursos prerrequisitos de otros cursos. El ENSEIGNE en 504 dice que hay exactamente una relación por curso: cada curso tiene uno y solo un profesor.

N13. Seguir una relación a partir de un nodo preciso

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

Se combina N7 (el filtro {id: 'P001'}) y N11 (la flecha). P001 es el profesor de «Docker expliqué simplement» visto en E9 y N7; enseña 11 cursos. El + concatena cadenas de caracteres, como en SQL con ||. Nota [:ENSEIGNE] sin nombre de variable: cuando no se necesita la relación en el RETURN, no se le da nombre.

N14. El camino de formación

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"]

Primera consulta que ya no se parece en nada a SQL. [:PREREQUIS_DE*] con el asterisco significa «siguiendo esta relación tantas veces como haga falta». shortestPath pide el más corto de los caminos posibles entre los dos cursos. La respuesta: 5 saltos, y la lista de los seis títulos a seguir, en orden, para ir del primer al último curso de la trayectoria de Neo4j. Es un plan de formación calculado al vuelo. En SQL, haría falta una consulta recursiva de varias decenas de líneas, y sería lenta.

Para entender bien: la línea RETURN

nodes(chemin) da la lista de nodos atravesados. [n IN nodes(chemin) | n.titre] se lee «para cada nodo n de esta lista, guarda su título»: es una forma compacta de transformar una lista de nodos en una lista de títulos. length(chemin) cuenta las relaciones atravesadas (5 relaciones para 6 nodos). El módulo 6 detalla estas funciones; aquí lo importante es el resultado: Neo4j encontró un itinerario en el grafo.

N15. «Los estudiantes que tomaron este curso también tomaron…»

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

El motor de recomendación de un sitio de comercio electrónico, en cinco líneas. Lee el patrón de izquierda a derecha: se parte del curso x, se sube la flecha INSCRIT_A (apunta hacia x, de ahí el <-) hasta los estudiantes e que lo toman, luego se baja otra flecha INSCRIT_A hacia los otros cursos autre de esos mismos estudiantes. WHERE autre <> x excluye el curso de partida mismo. count(DISTINCT e) cuenta los estudiantes en común, sin duplicados. Los cursos en cabeza son los que los estudiantes de C0213 toman con más frecuencia en paralelo: son las recomendaciones. En SQL: dos uniones sobre la tabla de inscripciones, un GROUP BY, y una consulta que se relee tres veces antes de entenderla.

N16. El mismo resultado, dibujado

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

Pasa a vista Graph: el curso de partida en el centro, sus estudiantes alrededor, y los cursos que comparten en la periferia. Es la consulta N15 sin el conteo: se devuelven los caminos enteros en lugar de columnas, y Neo4j Browser los dibuja. Es la imagen para mostrar cuando alguien pregunta «¿para qué sirve una base de grafos?».

El mensaje a transmitir. Quince consultas de Elasticsearch, dieciséis consultas Cypher, y vimos, en orden: listar, contar, mostrar, filtrar, ordenar, agrupar, lo que SQL también hace; luego perdonar una falta de ortografía, agregar 12 000 líneas en un milisegundo, calcular un camino más corto y producir recomendaciones, lo que SQL hace mal o nada en absoluto. El mismo curso «Docker expliqué simplement» apareció en ambos motores: mismos datos, preguntas diferentes. Es exactamente por eso que existe este laboratorio, y todo el resto del curso detalla cómo funciona cada una de estas consultas.

Desafío bonus (opcional)

Repite el paso 7 de tu anexo con docker compose stop neo4j: ¿qué pasa con la línea Neo4j répond — nœuds : 872 en etat? ¿Qué dice Neo4j Browser, ya conectado, cuando relanzas la consulta N3 (MATCH (n) RETURN count(n))? ¿Cuál es la última línea de journal neo4j? Vuelve a lanzar con docker compose start neo4j, verifica que los 872 nodos sigan ahí sin recargar nada, y anota cuál de los dos servicios se reinicia más rápido.

Anexo A — Paso a paso detallado en Windows (PowerShell)

Mostrar el paso a paso Windows (A.0 a A.11)

Todos los comandos de este anexo se escriben en PowerShell (Windows Terminal, o PowerShell 7), con .\labo.ps1 …. Las salidas reproducidas son las de la máquina del curso, en Windows 11 y Docker Desktop.

A.0 — Antes de empezar

  • Haber leído las cuatro lecciones del módulo: 01, 02, 03 y 04.
  • Docker Desktop iniciado (icono verde), el kit clonado, una terminal PowerShell abierta en la carpeta del kit (la que contiene docker-compose.yml y labo.ps1).
  • Si PowerShell rechaza ejecutar .\labo.ps1 («la ejecución de scripts está deshabilitada en este sistema»): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, responde O, relanza.
  • El laboratorio puede estar iniciado o no: la práctica empieza por los requisitos previos. Al final, .\labo.ps1 etat debe mostrar (healthy) en todas partes, "status":"green", acces 12000 avis 609 cours 504 y nœuds : 872.
  • Todas las consultas de esta práctica: elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Ninguna escribe en el cluster.

A.1 — Recorrer la lista de verificación de requisitos previos

Antes de encender nada, haz hablar al script.

powershell
.\labo.ps1 prerequis

Punto de control: solo marcas verdes y la frase final.

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

En tu caso, versiones y memoria difieren, y si el laboratorio nunca corrió, las últimas cuatro líneas dirán port 9200 : libre. Ambas lecturas son buenas.

Si ves otra cosa: una cruz contiene su remedio en la frase (Docker Desktop no iniciado, memoria bajo 4 GB, puerto ocupado); corrige, relanza, avanza solo con la línea verde. Para la memoria: Docker Desktop → SettingsResources, o el archivo %UserProfile%\.wslconfig si Docker Desktop usa WSL 2.

A.2 — Iniciar y leer los tres bloques

Lanza el inicio y, esta vez, lee lo que va pasando en lugar de esperar el final.

powershell
.\labo.ps1 demarrer

Punto de control: tres bloques == … == luego tres 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

El primer bloque está vacío si las imágenes ya están ahí; el segundo contiene las líneas de Compose (Container labo-elasticsearch Started, o Running si ya estaba corriendo); el tercero añade un punto cada tres segundos hasta prêt (… s). Laboratorio frío: uno a dos minutos; laboratorio ya en marcha: prêt (0 s).

Si ves otra cosa: unhealthy, exited o délai dépassé.\labo.ps1 journal <service> y el catálogo de la lección 04; causa más frecuente en Windows: la memoria asignada a Docker Desktop (Exited (137)).

A.3 — Anotar las cuatro cifras de etat

Vas a escribir etat varias veces; primero aprende a extraer las cuatro cifras que cuentan.

powershell
.\labo.ps1 etat

Punto de control: salida real de la máquina del curso, donde el perfil OpenSearch del módulo 5 está activo. En tu caso, las dos líneas labo-opensearch… y ✔ OpenSearch se reemplazan por — 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}

Anota las cuatro cifras esperadas: 12000, 609, 504, 872. En un laboratorio recién estrenado lees index : aucun index du labo y nœuds : 0: normal, el paso A.4 los llena.

Si ves otra cosa: ✘ Kibana ne répond pas encore en el minuto siguiente a demarrer → Kibana está terminando de crear sus índices internos; vuelve a escribir etat treinta segundos después.

A.4 — Cargar los datos, luego recargar para probar que nada se mueve

Carga los índices luego el grafo, y relanza los dos comandos una segunda vez: los contadores no deben cambiar en una unidad.

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

Punto de control: en el segundo pase, importer indica que los índices ya existen y devuelve los mismos contadores:

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.

y charger-graphe da el mismo balance que el primero:

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.

Los documentos llevan su propio _id (C0001, A00001…), así que un segundo _bulk reemplaza cada documento en lugar de añadirlo; del lado de Neo4j, todo está en MERGE. Solo el store.size puede moverse unos kilobytes (Lucene guarda un momento las versiones antiguas); los docs.count, nunca.

Si ves otra cosa: un docs.count diferente de 504 / 609 / 12000 → alguien escribió en estos índices; .\labo.ps1 reinitialiser luego demarrer, importer, charger-graphe te devuelve al estado de referencia.

A.5 — Verificar Elasticsearch desde Dev Tools

El script dice que todo está bien; verifícalo sin él. Abre http://localhost:5601, menú ManagementOutils de développement, y envía estas consultas una por una (Ctrl + Enter o el botón ▶ «Cliquer pour envoyer la requête»).

text
GET _cluster/health

Punto de control: "status": "green", "unassigned_shards": 0, y 200 - OK en la parte inferior derecha del panel de respuesta.

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

Punto de control: tres líneas green, pri 1, rep 0, y los tres contadores en 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

Termina con GET cours/_count, GET avis/_count, GET acces/_count: "count": 504, 609, 12000. Tienes las mismas cifras por dos caminos independientes: el script (curl dentro del contenedor) y Dev Tools (vía Kibana). Si algún día divergen, es el camino el que falla, no los datos.

Si ves otra cosa: "status": "yellow" → un índice tiene réplicas no asignadas, imposible con los mappings del kit (number_of_replicas: 0); GET _cat/indices?v&health=yellow señala al culpable, generalmente un índice creado a mano.

A.6 — Contar el grafo en Neo4j Browser

Mismo ejercicio para Neo4j. Abre http://localhost:7474, conéctate (neo4j / aiopsatlas2026, URL localhost:7687), escribe en el editor neo4j$ y haz clic en Run:

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

Punto de control: un recuadro con dos vistas, Table y Raw (sin Graph: la consulta devuelve números, no nodos), dos columnas label y count(*), cinco líneas ordenadas, y abajo a la derecha 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, la cifra de etat. El panel Database information (icono Database overview, el primero de la barra lateral) muestra Nodes (872) y Relationships (3,712).

Si ves otra cosa: una sexta etiqueta desconocida → nodos creados fuera del kit (el módulo 6 te enseñará a eliminarlos correctamente); Nodes (0) → te saltaste charger-graphe, vuelve al paso A.4.

A.7 — Romper Kibana voluntariamente y observar el fallo

Sabes cómo se ve un laboratorio sano; provoca un fallo cuya causa conoces para aprender a leerlo. Detén únicamente Kibana, con Compose (no arreter, que detendría todo):

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

Luego los tres gestos de la lección 04, en orden: etat, el navegador, journal.

Punto de control 1, .\labo.ps1 etat: la línea labo-kibana desapareció del bloque == Conteneurs == (Compose solo muestra por defecto los contenedores en ejecución) y el bloque == Services == marca una cruz:

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}

Para ver de todos modos el contenedor detenido: docker compose ps -a muestra labo-kibana Exited (0) 31 seconds ago. El 0 dice «detención limpia, solicitada»; un 137 diría «matado, memoria».

Punto de control 2, el navegador: recarga http://localhost:5601. No hay «Kibana server is not ready yet» (esta frase viene de Kibana, pero ya no hay Kibana para decirla) sino el error de conexión del navegador mismo: en Chrome o Edge, «Este sitio no está disponible», código ERR_CONNECTION_REFUSED. Nadie escucha en el puerto 5601. Para recordar: página de Kibana que se disculpa = Kibana está corriendo pero espera a Elasticsearch; error del navegador = Kibana no está corriendo.

Punto de control 3, .\labo.ps1 journal kibana: las últimas cien líneas terminan con una detención limpia, fechada al segundo en que escribiste 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 marca una detención solicitada (por ti, por docker compose stop, por un reinicio de Docker Desktop). Ninguna línea ERROR ni FATAL: nada que reparar, solo hay que relanzar. Las decenas de líneas at OperatorSubscriber… entre medio son una pila de llamadas emitida por un plugin durante la detención: ruido.

Si ves otra cosa: no configuration file provided: not found → no estás en la carpeta del kit; no such service: kibana → escribiste el nombre del contenedor (labo-kibana) en lugar del servicio Compose (kibana).

A.8 — Reparar Kibana y probar que Elasticsearch no vio nada

Relanza el servicio y espera a que su healthcheck vuelva a verde: 40 a 60 segundos, el tiempo de reconectarse a Elasticsearch y verificar sus índices internos.

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

Compose primero verificó que Elasticsearch estuviera Healthy (el depends_on … service_healthy de la lección 03), luego inició Kibana. Vigila cada cinco segundos:

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

Punto de control: starting durante 40 a 50 segundos, luego healthy (en la máquina del curso: starting de 0 a 45 s, healthy a los 50 s). etat vuelve entonces a mostrar la línea:

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

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

y journal kibana termina con las líneas que queremos ver:

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

Vuelve a abrir Dev Tools (si te encuentras con «Kibana server is not ready yet», fuiste más rápido que el healthy: espera diez segundos) y envía la consulta que etat mismo usa:

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

Elasticsearch respondió green durante todo el fallo: Kibana es una ventana hacia los datos, no los datos. Detener Kibana no borró ni reindexó nada.

Si ves otra cosa: unhealthy después de dos minutos → journal kibana y busca ECONNREFUSED (Elasticsearch se cayó mientras tanto); port is already allocated → otro programa tomó el puerto 5601 durante la detención (lección 04, fallo 1).

A.9 — A ti te toca: provocar y explicar un 404

Reconoces un servicio detenido; aprende ahora a reconocer una consulta incorrecta sobre un servicio sano, la confusión más frecuente en el aula. En Dev Tools, escribe una consulta de búsqueda que devuelva 404 con "type": "index_not_found_exception", luego explica en una frase por qué el cluster sigue en green.

Pista: Elasticsearch nunca adivina el nombre de un índice. Elige uno que no exista, con el prefijo pratique-; nada que crear, nada que escribir.

Solución
text
GET pratique-inexistant/_search

Respuesta, con la insignia 404 - Not Found en la parte inferior derecha del panel de salida:

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
}

Explicación esperada: el 404 es una respuesta normal y completa de Elasticsearch: «entendí tu consulta, pero este recurso no existe». El servicio está corriendo, el cluster sigue en green; solo hay que corregir el nombre (GET _cat/indices?v da la lista). En el paso A.7, no había ninguna respuesta en absoluto.

Dos variantes para probar: GET pratique-inexistant/_count?ignore_unavailable=true devuelve 200 y "count": 0 (pides ignorar el índice ausente); GET cours/_serch devuelve 400 con "no handler found for uri [/cours/_serch] and method [GET]". Un 400: «no entiendo la consulta»; un 404: «entiendo, pero eso no existe».

A.10 — Verificación final

Un solo comando demuestra que todo está hecho: etat, con Kibana de vuelta y las cuatro cifras.

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}

(Sin el perfil OpenSearch, los dos contenedores labo-opensearch… no aparecen y la última línea dice — OpenSearch non démarré …: es el estado esperado hasta el módulo 5.)

  • prerequis termina con Tout est prêt.
  • labo-elasticsearch, labo-kibana y labo-neo4j están Up … (healthy), Kibana incluido.
  • etat muestra "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, sin cambios después del segundo importer / charger-graphe.
  • Los mismos contadores se volvieron a leer en Dev Tools y en Neo4j Browser.
  • Sabes decir qué muestran etat, el navegador y journal kibana cuando Kibana está detenido, y en qué se diferencia de un 404.
  • Guardaste la salida de etat de arriba (copia o captura) como entregable.

A.11 — Limpieza

Esta práctica no crea nada: ni índice, ni nodo, ni objeto de Kibana. Dos cosas que garantizar: que Kibana esté corriendo (si no, docker compose start kibana desde la carpeta del kit), y que ningún índice de trabajo quede pendiente:

text
GET _cat/indices/pratique-*?v

Respuesta esperada: solo la línea de encabezado (health status index uuid pri rep docs.count …). No toques cours, avis, acces ni el grafo: sirven para todos los módulos siguientes.

Anexo B — Paso a paso detallado en Linux, macOS, WSL 2 y Git Bash

Mostrar el paso a paso Linux, macOS, WSL 2 y Git Bash (B.0 a B.11)

Todos los comandos de este anexo se escriben en una terminal bash (o zsh), con ./labo.sh …. Las salidas son idénticas a las de Windows salvo por el nombre del script: el kit es el mismo, solo cambian los lanzadores.

B.0 — Antes de empezar

  • Haber leído las cuatro lecciones del módulo: 01, 02, 03 y 04.
  • Docker iniciado (Docker Desktop en macOS y WSL 2, el servicio docker en Linux: docker info debe responder), el kit clonado, una terminal abierta en la carpeta del kit (la que contiene docker-compose.yml y labo.sh).
  • Si bash responde Permission denied en ./labo.sh: chmod +x labo.sh, una sola vez.
  • El laboratorio puede estar iniciado o no: la práctica empieza por los requisitos previos. Al final, ./labo.sh etat debe mostrar (healthy) en todas partes, "status":"green", acces 12000 avis 609 cours 504 y nœuds : 872.
  • Todas las consultas de esta práctica: elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Ninguna escribe en el cluster.

B.1 — Recorrer la lista de verificación de requisitos previos

Antes de encender nada, haz hablar al script.

bash
./labo.sh prerequis

Punto de control: solo marcas verdes y la frase final.

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

En tu caso, versiones y memoria difieren, y si el laboratorio nunca corrió, las últimas cuatro líneas dirán port 9200 : libre. Ambas lecturas son buenas.

Si ves otra cosa: una cruz contiene su remedio en la frase (Docker no iniciado, memoria bajo 4 GB, puerto ocupado); corrige, relanza, avanza solo con la línea verde. En Linux nativo, la memoria es la de la máquina; en macOS y WSL 2, es la asignada en Docker Desktop → SettingsResources.

B.2 — Iniciar y leer los tres bloques

Lanza el inicio y, esta vez, lee lo que va pasando en lugar de esperar el final.

bash
./labo.sh demarrer

Punto de control: tres bloques == … == luego tres 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

El primer bloque está vacío si las imágenes ya están ahí; el segundo contiene las líneas de Compose (Container labo-elasticsearch Started, o Running si ya estaba corriendo); el tercero añade un punto cada tres segundos hasta prêt (… s). Laboratorio frío: uno a dos minutos; laboratorio ya en marcha: prêt (0 s).

Si ves otra cosa: unhealthy, exited o délai dépassé./labo.sh journal <service> y el catálogo de la lección 04; causas más frecuentes: la memoria (Exited (137)) y, en Linux nativo, vm.max_map_count demasiado bajo (sudo sysctl -w vm.max_map_count=262144, luego ./labo.sh demarrer).

B.3 — Anotar las cuatro cifras de etat

Vas a escribir etat varias veces; primero aprende a extraer las cuatro cifras que cuentan.

bash
./labo.sh etat

Punto de control: salida real de la máquina del curso, donde el perfil OpenSearch del módulo 5 está activo. En tu caso, las dos líneas labo-opensearch… y ✔ OpenSearch se reemplazan por — 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}

Anota las cuatro cifras esperadas: 12000, 609, 504, 872. En un laboratorio recién estrenado lees index : aucun index du labo y nœuds : 0: normal, el paso B.4 los llena.

Si ves otra cosa: ✘ Kibana ne répond pas encore en el minuto siguiente a demarrer → Kibana está terminando de crear sus índices internos; vuelve a escribir etat treinta segundos después.

B.4 — Cargar los datos, luego recargar para probar que nada se mueve

Carga los índices luego el grafo, y relanza los dos comandos una segunda vez: los contadores no deben cambiar en una unidad.

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

Punto de control: en el segundo pase, importer indica que los índices ya existen y devuelve los mismos contadores:

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.

y charger-graphe da el mismo balance que el primero:

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.

Los documentos llevan su propio _id (C0001, A00001…), así que un segundo _bulk reemplaza cada documento en lugar de añadirlo; del lado de Neo4j, todo está en MERGE. Solo el store.size puede moverse unos kilobytes (Lucene guarda un momento las versiones antiguas); los docs.count, nunca.

Si ves otra cosa: un docs.count diferente de 504 / 609 / 12000 → alguien escribió en estos índices; ./labo.sh reinitialiser luego demarrer, importer, charger-graphe te devuelve al estado de referencia.

B.5 — Verificar Elasticsearch desde Dev Tools

El script dice que todo está bien; verifícalo sin él. Abre http://localhost:5601, menú ManagementOutils de développement, y envía estas consultas una por una (Ctrl + Enter, o Cmd + Enter en macOS, o el botón ▶ «Cliquer pour envoyer la requête»).

text
GET _cluster/health

Punto de control: "status": "green", "unassigned_shards": 0, y 200 - OK en la parte inferior derecha del panel de respuesta.

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

Punto de control: tres líneas green, pri 1, rep 0, y los tres contadores en 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

Termina con GET cours/_count, GET avis/_count, GET acces/_count: "count": 504, 609, 12000. Tienes las mismas cifras por dos caminos independientes: el script (curl dentro del contenedor) y Dev Tools (vía Kibana). Si algún día divergen, es el camino el que falla, no los datos.

En bash, tienes incluso un tercer camino, sin navegador:

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

Si ves otra cosa: "status": "yellow" → un índice tiene réplicas no asignadas, imposible con los mappings del kit (number_of_replicas: 0); GET _cat/indices?v&health=yellow señala al culpable, generalmente un índice creado a mano.

B.6 — Contar el grafo en Neo4j Browser

Mismo ejercicio para Neo4j. Abre http://localhost:7474, conéctate (neo4j / aiopsatlas2026, URL localhost:7687), escribe en el editor neo4j$ y haz clic en Run:

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

Punto de control: un recuadro con dos vistas, Table y Raw (sin Graph: la consulta devuelve números, no nodos), dos columnas label y count(*), cinco líneas ordenadas, y abajo a la derecha 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, la cifra de etat. El panel Database information (icono Database overview, el primero de la barra lateral) muestra Nodes (872) y Relationships (3,712).

Si ves otra cosa: una sexta etiqueta desconocida → nodos creados fuera del kit (el módulo 6 te enseñará a eliminarlos correctamente); Nodes (0) → te saltaste charger-graphe, vuelve al paso B.4.

B.7 — Romper Kibana voluntariamente y observar el fallo

Sabes cómo se ve un laboratorio sano; provoca un fallo cuya causa conoces para aprender a leerlo. Detén únicamente Kibana, con Compose (no arreter, que detendría todo):

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

Luego los tres gestos de la lección 04, en orden: etat, el navegador, journal.

Punto de control 1, ./labo.sh etat: la línea labo-kibana desapareció del bloque == Conteneurs == (Compose solo muestra por defecto los contenedores en ejecución) y el bloque == Services == marca una cruz:

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}

Para ver de todos modos el contenedor detenido: docker compose ps -a muestra labo-kibana Exited (0) 31 seconds ago. El 0 dice «detención limpia, solicitada»; un 137 diría «matado, memoria».

Punto de control 2, el navegador: recarga http://localhost:5601. No hay «Kibana server is not ready yet» (esta frase viene de Kibana, pero ya no hay Kibana para decirla) sino el error de conexión del navegador mismo: en Chrome, «Este sitio no está disponible», código ERR_CONNECTION_REFUSED; en Firefox, «No se puede establecer conexión». En línea de comandos, curl -s http://localhost:5601 || echo REFUSE muestra REFUSE: nadie escucha en el puerto 5601. Para recordar: página de Kibana que se disculpa = Kibana está corriendo pero espera a Elasticsearch; error del navegador = Kibana no está corriendo.

Punto de control 3, ./labo.sh journal kibana: las últimas cien líneas terminan con una detención limpia, fechada al segundo en que escribiste 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 marca una detención solicitada (por ti, por docker compose stop, por un reinicio de Docker). Ninguna línea ERROR ni FATAL: nada que reparar, solo hay que relanzar. Las decenas de líneas at OperatorSubscriber… entre medio son una pila de llamadas emitida por un plugin durante la detención: ruido.

Si ves otra cosa: no configuration file provided: not found → no estás en la carpeta del kit; no such service: kibana → escribiste el nombre del contenedor (labo-kibana) en lugar del servicio Compose (kibana).

B.8 — Reparar Kibana y probar que Elasticsearch no vio nada

Relanza el servicio y espera a que su healthcheck vuelva a verde: 40 a 60 segundos, el tiempo de reconectarse a Elasticsearch y verificar sus índices internos.

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

Compose primero verificó que Elasticsearch estuviera Healthy (el depends_on … service_healthy de la lección 03), luego inició Kibana. Vigila cada cinco segundos:

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

o, para no volver a escribir, watch -n 5 docker inspect --format '{{.State.Health.Status}}' labo-kibana (Ctrl + C para salir; watch no está en macOS por defecto, vuelve a escribir el comando a mano).

Punto de control: starting durante 40 a 50 segundos, luego healthy (en la máquina del curso: starting de 0 a 45 s, healthy a los 50 s). etat vuelve entonces a mostrar la línea:

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

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

y journal kibana termina con las líneas que queremos ver:

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

Vuelve a abrir Dev Tools (si te encuentras con «Kibana server is not ready yet», fuiste más rápido que el healthy: espera diez segundos) y envía la consulta que etat mismo usa:

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

Elasticsearch respondió green durante todo el fallo: Kibana es una ventana hacia los datos, no los datos. Detener Kibana no borró ni reindexó nada.

Si ves otra cosa: unhealthy después de dos minutos → journal kibana y busca ECONNREFUSED (Elasticsearch se cayó mientras tanto); port is already allocated → otro programa tomó el puerto 5601 durante la detención (lección 04, fallo 1).

B.9 — A ti te toca: provocar y explicar un 404

Reconoces un servicio detenido; aprende ahora a reconocer una consulta incorrecta sobre un servicio sano, la confusión más frecuente en el aula. En Dev Tools, escribe una consulta de búsqueda que devuelva 404 con "type": "index_not_found_exception", luego explica en una frase por qué el cluster sigue en green.

Pista: Elasticsearch nunca adivina el nombre de un índice. Elige uno que no exista, con el prefijo pratique-; nada que crear, nada que escribir.

Solución
text
GET pratique-inexistant/_search

Respuesta, con la insignia 404 - Not Found en la parte inferior derecha del panel de salida:

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
}

Lo mismo en línea de comandos, para ver el código HTTP desnudo: curl -s -o /dev/null -w '%{http_code}\n' http://localhost:9200/pratique-inexistant/_search muestra 404.

Explicación esperada: el 404 es una respuesta normal y completa de Elasticsearch: «entendí tu consulta, pero este recurso no existe». El servicio está corriendo, el cluster sigue en green; solo hay que corregir el nombre (GET _cat/indices?v da la lista). En el paso B.7, no había ninguna respuesta en absoluto.

Dos variantes para probar: GET pratique-inexistant/_count?ignore_unavailable=true devuelve 200 y "count": 0 (pides ignorar el índice ausente); GET cours/_serch devuelve 400 con "no handler found for uri [/cours/_serch] and method [GET]". Un 400: «no entiendo la consulta»; un 404: «entiendo, pero eso no existe».

B.10 — Verificación final

Un solo comando demuestra que todo está hecho: etat, con Kibana de vuelta y las cuatro cifras.

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}

(Sin el perfil OpenSearch, los dos contenedores labo-opensearch… no aparecen y la última línea dice — OpenSearch non démarré …: es el estado esperado hasta el módulo 5.)

  • prerequis termina con Tout est prêt.
  • labo-elasticsearch, labo-kibana y labo-neo4j están Up … (healthy), Kibana incluido.
  • etat muestra "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, sin cambios después del segundo importer / charger-graphe.
  • Los mismos contadores se volvieron a leer en Dev Tools y en Neo4j Browser.
  • Sabes decir qué muestran etat, el navegador y journal kibana cuando Kibana está detenido, y en qué se diferencia de un 404.
  • Guardaste la salida de etat de arriba (copia o captura) como entregable.

B.11 — Limpieza

Esta práctica no crea nada: ni índice, ni nodo, ni objeto de Kibana. Dos cosas que garantizar: que Kibana esté corriendo (si no, docker compose start kibana desde la carpeta del kit), y que ningún índice de trabajo quede pendiente:

text
GET _cat/indices/pratique-*?v

Respuesta esperada: solo la línea de encabezado (health status index uuid pri rep docs.count …). No toques cours, avis, acces ni el grafo: sirven para todos los módulos siguientes.

Anexo C — Si algo falla (todos los sistemas)

Mostrar los casos donde algo falla
  • docker compose stop kibana responde no configuration file provided: not found → Compose busca docker-compose.yml en la carpeta actual. cd hacia la raíz del kit (la que contiene labo.sh y labo.ps1) y vuelve a intentar. El script se reposiciona solo; los comandos docker compose escritos a mano, no.

  • Después de stop, etat ya no muestra labo-kibana y crees haberlo eliminado → No: docker compose ps oculta los contenedores detenidos. docker compose ps -a lo lista en Exited (0), y docker compose start kibana lo relanza con sus datos. Un contenedor realmente eliminado no aparecería ni siquiera con -a; demarrer lo recrearía.

  • Kibana se queda en starting y luego pasa a unhealthy, y journal kibana repite Unable to retrieve version information from Elasticsearch nodes. connect ECONNREFUSED 172.x.x.x:9200 → Kibana volvió pero Elasticsearch se cayó mientras tanto (a menudo Exited (137), la memoria). Repara Elasticsearch primero (lección 04, fallo 2); Kibana se reconecta solo.

  • Dev Tools devuelve 400 en lugar del 404 esperado, con no handler found for uri [/pratique-inexistant/_serch] and method [GET] → El error está en la API (_serch), no en el índice. Elasticsearch valida primero la ruta, luego el índice: corrige a _search y aparece el 404.

  • Solo Windows — en PowerShell 5.1, demarrer muestra en rojo docker : Image docker.elastic.co/kibana/kibana:9.5.3 Pulling … NativeCommandError pero termina con Le labo est prêt → Solo ocurre si rediriges la salida (2>&1, | Tee-Object): Compose escribe su progreso en el flujo de error y PowerShell 5.1 lo viste como excepción. No es un error; lanza el script sin redirección, o pasa a PowerShell 7.

  • Solo Windows — .\labo.ps1 es rechazado: «la ejecución de scripts está deshabilitada en este sistema»Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, responde O, relanza. Una sola vez por máquina.

  • Solo Linux nativo — Elasticsearch sale con Exited (78) y journal elasticsearch dice max virtual memory areas vm.max_map_count [65530] is too lowsudo sysctl -w vm.max_map_count=262144 luego ./labo.sh demarrer. Para que sobreviva al reinicio: añade vm.max_map_count=262144 en /etc/sysctl.conf.

  • macOS y bash — ./labo.sh responde Permission deniedchmod +x labo.sh, una sola vez. Si bash: ./labo.sh: /bin/bash^M: bad interpreter, el archivo tiene finales de línea de Windows: git config core.autocrlf input luego vuelve a clonar, o sed -i '' 's/\r$//' labo.sh.