The lab kit — https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr — cloned into
lab3in lesson 03. Every command in this lesson is typed from that folder. Grafana is described on port 3000; if you choseGRAFANA_PORT=3001, replace 3000 with 3001 everywhere.
Starting the lab is turning the key: the engine (Docker) launches the ten services, the gauges light up one by one, and after a minute the dashboard is alive. The nine web interfaces are the nine dials of that dashboard: each shows the same lab from a different angle. Prometheus is the raw meter; Grafana is the windshield, the comfortable view of the same numbers; Alertmanager and the webhook are the warning lights; the API is the road the car is driving on.
demarrer chains four phases. It downloads the six public images (Prometheus, Alertmanager, Grafana, Alloy, node-exporter, cAdvisor: about 1.5 GB the first time, nothing afterwards); it builds the four local images (api, charge, webhook from python:3.13-slim, and loki to add a busybox binary used by the health check); it creates a Docker network (labo-observabilite_labo) and three volumes (grafana-data, prometheus-data, loki-data); then it starts the ten containers and waits for each one to answer its health check (healthcheck). Some services depend on others: charge only starts once the API is healthy, grafana and alloy wait for Prometheus and Loki.
The three volumes are the lab's memory. arreter stops the containers but keeps the volumes: Prometheus series, Loki logs and Grafana settings survive, and demarrer resumes where you were. reinitialiser deletes the volumes: a brand-new lab. Documentation: Docker Compose — Volumes and Docker Compose — Healthchecks and depends_on.
| Command | What it does | When to use it |
|---|---|---|
prerequis | Checks Docker, Compose, memory, processors, the nine ports | Before the first demarrer (lesson 03) |
demarrer | Downloads, builds, creates network and volumes, starts the 10 services, waits until they are ready | At the start of each session |
etat | Table of containers, Prometheus targets, series, alerts, state of each interface | All the time: it is your first reflex |
journal <service> | The last 100 log lines of a service | When a service does not answer, or to read the API logs |
casser <scenario> | Triggers an outage: api, erreurs, lenteur, disque | Practice of this module and following modules |
reparer | Cancels every outage | After each casser |
arreter | Stops the containers, keeps the data | At the end of a session |
reinitialiser | Deletes containers and volumes, after confirmation | To start from scratch |
The nine interfaces, and what to look at there:
| Interface | URL | What you look at |
|---|---|---|
| Prometheus | http://localhost:9090 | The Query page to type a query, Alerts for the rules, Status → Target health for the 8 targets |
| Alertmanager | http://localhost:9093 | The alerts received, grouped by alertname and service; the silences |
| Grafana | http://localhost:3000 | The three provisioned dashboards, Explore, the three data sources |
| Catalog API | http://localhost:8000 | /cours, /cours/C0001, /sante, /metrics: the observed service, seen from the outside |
| webhook | http://localhost:8090 | The list of alerts received from Alertmanager, refreshed every 10 s |
| Loki | http://localhost:3100/ready | Answers ready; Loki has no interface, it is queried through Grafana |
| Alloy | http://localhost:12345 | Its diagnostic interface: the components of config.alloy and their state |
| node-exporter | http://localhost:9100/metrics | The raw metrics page of the host machine |
| cAdvisor | http://localhost:8080 | The per-container metrics; /metrics for the raw page |
Start. From lab3:
.\labo.ps1 demarrer./labo.sh demarrerOn the course machine, at first startup (download and build lines are cut):
== Téléchargement des images (long la première fois : ~1,5 Go) ==
Image labo-webhook:1.0.0 Skipped Image can be built
Image labo-api:1.0.0 Skipped Image can be built
Image labo-charge:1.0.0 Skipped Image can be built
Image labo-loki:3.7.7 Skipped Image can be built
…
== Construction des images du labo (api, charge, webhook, loki) et démarrage ==
…
Network labo-observabilite_labo Created
Volume labo-observabilite_grafana-data Created
Volume labo-observabilite_prometheus-data Created
Volume labo-observabilite_loki-data Created
…
Container labo-prometheus Healthy
Container labo-api Healthy
Container labo-charge Starting
Container labo-charge Started
Container labo-loki Healthy
Container labo-alloy Started
Container labo-grafana Started
== Attente que chaque service soit prêt ==
prometheus prêt (0 s)
alertmanager prêt (0 s)
loki prêt (0 s)
alloy .. prêt (6 s)
node-exporter prêt (0 s)
cadvisor prêt (0 s)
api prêt (0 s)
webhook prêt (0 s)
charge prêt (0 s)
grafana ... prêt (9 s)
Le labo est prêt.
Grafana http://localhost:3000 (utilisateur admin · mot de passe aiopsatlas2026)
Prometheus http://localhost:9090 (Status → Target health, puis onglet Graph)
Alertmanager http://localhost:9093
API catalogue http://localhost:8000/cours · http://localhost:8000/metrics
Webhook http://localhost:8090 (les alertes reçues)
Loki http://localhost:3100/ready · Alloy http://localhost:12345
node-exporter http://localhost:9100/metrics · cAdvisor http://localhost:8080
Étape suivante : .\labo.ps1 etat (laissez tourner 2 minutes pour avoir des courbes)What to see: the four Skipped Image can be built (local images are not downloaded, they are built), the three volumes created, labo-charge Starting only after labo-api Healthy, then the ten prêt (ready). On the course machine, demarrer took 44 seconds once the images were cached. The last line tells you to wait two minutes: Prometheus needs a few scrapes to draw a curve.
Read etat.
.\labo.ps1 etat
== Conteneurs ==
NAME SERVICE STATUS
labo-alertmanager alertmanager Up About a minute (healthy)
labo-alloy alloy Up 48 seconds (healthy)
labo-api api Up About a minute (healthy)
labo-cadvisor cadvisor Up About a minute (healthy)
labo-charge charge Up About a minute (healthy)
labo-grafana grafana Up 48 seconds (healthy)
labo-loki loki Up About a minute (healthy)
labo-node-exporter node-exporter Up About a minute (healthy)
labo-prometheus prometheus Up About a minute (healthy)
labo-webhook webhook Up About a minute (healthy)
== Supervision ==
✔ Prometheus répond — cibles up : 8/8
séries en mémoire : 9038
alertes : 0 active(s), 0 en attente (pending)
✔ Alertmanager répond (http://localhost:9093)
✔ Grafana répond (http://localhost:3000)
✔ Loki répond (http://localhost:3100)
✔ API catalogue répond — version 1.0.0, 64 cours
✔ Webhook répond — 0 alerte(s) reçue(s) (http://localhost:8090)
Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.What to see: two blocks. Conteneurs (containers): ten lines, all Up … (healthy). Supervision: Prometheus reads its 8 targets, stores 9038 series (this number climbs during the first minutes, then stabilizes around 12,000 to 15,000 on the course machine), no alert; the API knows 64 courses; the webhook received nothing. The last line, Labo : 10/10 services, 8/8 cibles up, 0 alertes actives., is the one you must find again at the end of every practice.
Prometheus: Query, Alerts, Status. Open http://localhost:9090. The top menu has three entries: Query, Alerts, Status. Under Status, the submenu offers Target health, Rule health, Service discovery, Runtime & build information, TSDB status, Command-line flags, Configuration, Alertmanager discovery. Go to Target health: eight blocks, one per job, each with 1 / 1 up, the URL read (Endpoint), the labels, the date of the last scrape and the UP state. On the course machine:
api
1 / 1 up
Endpoint Labels Last scrape State
http://api:8000/metrics instance="api:8000" job="api" service="api" 6.014s ago UPGo back to Query, type up in the input field and click Execute (or Enter). Under the field, three tabs: Table, Graph, Explain. In Table, eight lines of the form up{instance="localhost:9090", job="prometheus"} 1, all at 1. Under the tabs, a line such as Load time: 40ms Result series: 8 tells you how many series answered. Switch to Graph: eight flat lines at 1. Module 2 will spend the rest of its time on this page.
Grafana: three ready-made dashboards. Open http://localhost:3000, user admin, password aiopsatlas2026. The home page lists Recent dashboards: the three dashboards provisioned by the kit, in the Labo observabilite folder. The main menu (icon at the top left) gives Dashboards, Explore, Alerting, Connections → Data sources. In Connections → Data sources, three sources: Prometheus (default), Loki, Alertmanager. Grafana's API confirms the same list:
uid=api-catalogue titre=« API catalogue — signaux dorés » panneaux=23 dossier=Labo observabilite
uid=hote-conteneurs titre=« Hôte et conteneurs » panneaux=16 dossier=Labo observabilite
uid=journaux-api titre=« Journaux de l'API » panneaux=6 dossier=Labo observabilite
source Prometheus type=prometheus uid=prometheus default=True
source Loki type=loki uid=loki default=False
source Alertmanager type=alertmanager uid=alertmanager default=FalseOpen API catalogue — signaux dorés (catalog API — golden signals): the four golden signals (traffic, errors, latency, saturation) of the API, plus two business metrics (enrollments, courses viewed). The curves are flat at first: it takes two minutes of load. What to see: nothing you see here is stored in Grafana. Each panel sends a PromQL query to Prometheus at the moment you look. Grafana is the windshield, not the engine.
Loki is read in the same place. Open Explore in the menu, choose the Loki source in the selector at the top, type {service="api"} in the query field and run it with Run query: the API's JSON lines scroll by, most recent first, each with its labels. Loki knows only four labels on this lab, obtained from http://localhost:3100/loki/api/v1/labels: code, conteneur, niveau, service. service and conteneur come from Docker, niveau and code are extracted from the JSON by Alloy. Switch the selector to Prometheus, type up, Run query: the eight series from step 3 appear, as a table and as a curve. Explore is where you try a query before turning it into a panel (lesson 07).
The catalog API, from the outside. Open the following four URLs in the browser, or with Invoke-RestMethod / curl:
http://localhost:8000/sante → {"etat":"ok","version":"1.0.0","cours":64}
http://localhost:8000/cours/C0001 → {"id":"C0001","titre":"Introduction à Python","categorie":"programmation","niveau":"debutant","prix":89,"duree_heures":6,"professeur":"Karim Haddad","tags":["code","algorithmes"],"note":4.8,"inscrits":2319}
http://localhost:8000/cours/C9999 → 404 {"detail":"cours C9999 introuvable"}
http://localhost:8000/metrics → 271 lignes de texte, les métriques de la leçon 02What to see: /sante is the route Docker calls for the healthcheck; /cours/C0001 is one of the 64 courses; /cours/C9999 returns a clean 404, which the API counts in http_requetes_total{code="404",route="/cours/{id}"} and writes to a WARNING log; /metrics is what Prometheus reads. Each response carries an x-id-requete header (for example c5c49525ea53), the same identifier as in the JSON log of that request.
Alertmanager and the webhook: the warning lights, off. Open http://localhost:9093: the menu offers Alerts, Silences, Status, Settings, and a New Silence button. In normal operation, the Alerts page is empty. Open http://localhost:8090: a page « Alertes reçues d'Alertmanager » (alerts received from Alertmanager) with a six-column table (Reçue à, Alerte, Sévérité, État, Service, Résumé — received at, alert, severity, state, service, summary) and the line Aucune alerte reçue pour l'instant. (no alert received yet); the page refreshes every 10 seconds. The raw format is at http://localhost:8090/alertes.json ([]) and the service state at http://localhost:8090/sante:
{"etat":"ok","alertes_en_memoire":0,"notifications":0,"alertes":0}What to see: two empty pages, and that is what we want. This module's practice fills them with casser api.
Loki, Alloy, node-exporter, cAdvisor: behind the scenes. Four pages without a comfortable interface:
http://localhost:3100/ready → ready
http://localhost:12345/-/ready → Alloy is ready.
http://localhost:9100/metrics → 1578 lignes (node_cpu_seconds_total, node_memory_MemTotal_bytes…)
http://localhost:8080/metrics → 3444 lignes (container_memory_working_set_bytes{name="labo-api"}…)What to see: Loki and Alloy answer in one word; they work for Grafana, not for you directly. The two exporters expose the same page shape as the API (# HELP, # TYPE, one line per series), with many more lines. Open http://localhost:8080 without /metrics: cAdvisor redirects to /containers/, a small page showing the whole machine (Usage Overview, CPU Total Usage, Memory Total Usage, Network Throughput) and a Docker Containers link to the detail of each container.
Read a log.
.\labo.ps1 journal apilabo-api | {"horodatage": "2026-09-15T19:33:25.839+00:00", "niveau": "INFO", "id_requete": "46bb533b33e9", "methode": "GET", "route": "/cours/{id}", "code": 200, "duree_ms": 13.9, "message": "GET /cours/C0038 -> 200"}
labo-api | {"horodatage": "2026-09-15T19:33:25.843+00:00", "niveau": "INFO", "id_requete": "5c0d4e124807", "methode": "GET", "route": "/cours", "code": 200, "duree_ms": 30.4, "message": "GET /cours -> 200"}
labo-api | {"horodatage": "2026-09-15T19:33:25.916+00:00", "niveau": "INFO", "id_requete": "5ccaee4d1a2a", "methode": "GET", "route": "/sante", "code": 200, "duree_ms": 1.0, "message": "GET /sante -> 200"}Then journal charge:
labo-charge | {"horodatage": "2026-09-15T19:33:22.848+00:00", "niveau": "INFO", "message": "résumé des 30 dernières secondes", "requetes": {"201": 28, "total": 264, "200": 210, "404": 21, "422": 2, "500": 3}}What to see: the API writes one line per request; the load generator writes a summary every 30 seconds, about 260 requests (nearly 9 per second), including some twenty 404s and a few deliberate 500s. These numbers are the ones you will find again in Prometheus with rate() in module 2.
Stop, then restart without losing anything.
.\labo.ps1 arreter Container labo-webhook Stopping
Container labo-alertmanager Stopping
…
Container labo-api Stopped
Container labo-prometheus Stopped
✔ conteneurs arrêtés — vos données sont conservées ; .\labo.ps1 demarrer pour reprendredocker compose ps -a then shows ten Exited lines; etat ends with Labo : 0/10 services, ?/? cibles up, ? alertes actives. (the ? because Prometheus no longer answers). Run .\labo.ps1 demarrer again: this time, no download, no build, about thirty seconds. In Prometheus, type up and switch to Graph with a one-hour range: the curve stops during the shutdown and resumes, with the earlier history intact. The prometheus-data volume kept the series.
demarrer stops on a service. The message is explicit: Un service n'a pas démarré. Regardez son journal : .\labo.ps1 journal <service> (a service did not start, look at its log). Do it. The most frequent case is a port taken between prerequis and demarrer (another program launched in between): Docker then refuses to publish the port, and the container's log is empty because it never started. Run prerequis again.
journal without a service name. .\labo.ps1 journal alone displays:
usage : .\labo.ps1 journal <prometheus|alertmanager|loki|alloy|node-exporter|cadvisor|api|webhook|charge|grafana>The ten valid names are there; they are the service names in docker-compose.yml, not the container names (api, not labo-api). Likewise, .\labo.ps1 casser tout answers usage : .\labo.ps1 casser <api|erreurs|lenteur|disque>.
Grafana answers Unauthorized. If you call Grafana's API without being logged in, for example http://localhost:3000/api/search in a private tab, you get:
{"extra":null,"message":"Unauthorized","messageId":"auth.unauthorized","statusCode":401,"traceID":""}This is normal: API pages require a session or a password. Log in first at http://localhost:3000 (admin / aiopsatlas2026), then reload.
Grafana dashboards display "No data". Two causes, in this order: the lab has been running for less than two minutes (wait); or the time range at the top right points to a period when the lab was stopped (set it back to "Last 15 minutes" or "Last 1 hour").
etat displays Labo : 0/10 services, ?/? cibles up, ? alertes actives. The lab is stopped. .\labo.ps1 demarrer.
Under PowerShell, demarrer displays red NativeCommandError lines around Container labo-api Stopping. These are Docker Compose's progress messages, written to the error output; when labo.ps1 is itself launched from another script or with 2>&1, PowerShell 5.1 dresses them up as errors. The lab did start: check with etat.
demarrer downloads six images, builds four local images, creates a network and three volumes, then launches ten containers and waits for their health checks; 44 seconds on the course machine once the images are cached. etat has two blocks: the containers (ten Up … (healthy)) and the supervision (8/8 targets, the number of series, the alerts, the state of each interface); its nominal last line is Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.. Prometheus (9090) has three pages: Query with its Table, Graph, Explain tabs; Alerts; Status → Target health with the 8 UP targets. Grafana (3000, admin / aiopsatlas2026) has three dashboards provisioned in the Labo observabilite folder and three data sources, Prometheus by default; it stores no measurement itself. The API (8000) answers on /sante, /cours/C0001, /metrics, and a clean 404 on an unknown course, with an x-id-requete header on every response. Alertmanager (9093) and the webhook (8090) are empty in normal operation. journal <service> takes the service name, not the container name. arreter keeps the volumes: the series survive, the up curve stops then resumes.
grafana/provisioning/. This is why the Labo observabilite folder is marked "managed by classic file provisioning" in the API.docker compose ps, logs, stop: the commands that labo.ps1 and labo.sh chain for you; journal api is docker compose logs --tail 100 api.etat, in Target health, in Alertmanager and in the webhook, before fixing it.