Πώς να διαβάσεις αυτή τη σελίδα. Κάθε ενότητα είναι διπλωμένη κάτω από τον τίτλο της: κάνε κλικ στο «Εμφάνιση …» για να την ανοίξεις, και ξανακλείσ' την όταν τελειώσεις για να κρατήσεις τη σελίδα ευανάγνωστη. Σειρά ανάγνωσης: Στόχος, μετά Εν συντομία (οι εντολές να πληκτρολογήσεις), μετά Το σύνολο δεδομένων (να διαβαστεί πριν από κάθε ερώτημα), μετά τα ερωτήματα Prometheus (P1 έως P12) και Grafana Explore (G1 έως G8), ταξινομημένα από το πιο απλό (
up,{service="api"}) στο πιο εκφραστικό, με μια εξήγηση μετά από καθένα. Το λεπτομερές βήμα προς βήμα, με την αναμενόμενη έξοδο κάθε εντολής και τη βλάβη που πρέπει να προκαλέσεις, βρίσκεται σε παράρτημα: παράρτημα A για Windows (PowerShell), παράρτημα B για Linux, macOS, WSL 2 και Git Bash. Άνοιξε ένα μόνο παράρτημα, αυτό του συστήματός σου. Το παράρτημα C, κοινό, συγκεντρώνει τις περιπτώσεις όπου κάτι κολλάει. Όλες οι έξοδοι αυτής της σελίδας καταγράφηκαν στο εργαστήριο του μαθήματος· οι τιμές που εξαρτώνται από τη στιγμή (μετρητές, διάρκειες, χρονοσφραγίδες) θα είναι διαφορετικές σε σένα, οι μορφές θα είναι ίδιες.
Μπαίνεις στην ομάδα που λειτουργεί τον κατάλογο μαθημάτων μιας διαδικτυακής πλατφόρμας. Η προϊσταμένη σου σου δίνει το kit του εργαστηρίου: «Αύριο το πρωί, θέλω τη στοίβα παρατηρησιμότητας να τρέχει στον σταθμό σου, το API μέσα, και την απόδειξη ότι ξέρεις να διαβάζεις μια βλάβη χωρίς να με καλέσεις.» Θα ξεκινήσεις λοιπόν τις δέκα υπηρεσίες, θα αποδείξεις ότι ο Prometheus διαβάζει σωστά τους οκτώ στόχους του και ότι το Loki λαμβάνει τις καταγραφές του API, θα πληκτρολογήσεις δώδεκα ερωτήματα PromQL και οκτώ ερωτήματα LogQL για να μάθεις να διαβάζεις τι μετρά το εργαστήριο, και μετά θα σταματήσεις το API επίτηδες. Θα δεις τη βλάβη να διαδίδεται: το etat τη βλέπει σε δύο δευτερόλεπτα, ο Prometheus βάζει τον στόχο σε DOWN, η ειδοποίηση APIInjoignable περνά από pending σε firing, ο Alertmanager τη στέλνει στο webhook. Μετά επιδιορθώνεις και βλέπεις την ειδοποίηση να σβήνει. Να αναγνωρίζεις «αυτή η υπηρεσία είναι σταματημένη» σε δέκα δευτερόλεπτα, και να ξέρεις πού να την ψάξεις, είναι αυτό που αποφεύγει ώρες αναζήτησης στο λάθος σημείο.
Τα εννέα βήματα αυτού του σχήματος αναλύονται, με την αναμενόμενη έξοδο κάθε εντολής, στο παράρτημα A (Windows) ή στο παράρτημα B (Linux, macOS) στο κάτω μέρος της σελίδας.
Kit του εργαστηρίου: https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr
Κλωνοποιείς το kit σε έναν φάκελο lab3, ελέγχεις ότι το Docker είναι έτοιμο, ξεκινάς τις δέκα υπηρεσίες, ανοίγεις τις σελίδες web, πληκτρολογείς τα ερωτήματα, μετά σπας και επιδιορθώνεις. Στο τέλος, το etat πρέπει να εμφανίζει Labo : 10/10 services, 8/8 cibles up, 0 alertes actives., το API πρέπει να γνωρίζει 64 cours, και το webhook πρέπει να έχει λάβει δύο γνωστοποιήσεις για την APIInjoignable: μία firing, μία resolved. Ξεκίνα εκτελώντας αυτό το μπλοκ.
Windows (PowerShell)
git clone https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr.git lab3
cd lab3
ls # explorer le contenu : docker-compose.yml, labo.ps1, labo.sh, api/, prometheus/, grafana/, modules/
.\labo.ps1 prerequis
.\labo.ps1 demarrer
.\labo.ps1 etat # attendu : Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.Περίμενε δύο λεπτά (τον χρόνο να έχει ο Prometheus μερικές μετρήσεις), μετά άνοιξε τις σελίδες στον περιηγητή:
Prometheus http://localhost:9090 (Status → Target health : 8 cibles UP ; onglet Graph pour P1 à P12)
Grafana http://localhost:3000 (utilisateur admin · mot de passe aiopsatlas2026 ; menu → Explore, source Loki pour G1 à G8)
Alertmanager http://localhost:9093 (vide au départ)
API catalogue http://localhost:8000/cours · http://localhost:8000/metrics
Webhook http://localhost:8090 (vide au départ)Τα ερωτήματα P1 έως P12 βρίσκονται επίσης στο modules\01-le-labo\requetes.txt, και τα G1 έως G8 στο modules\01-le-labo\requetes-logql.txt: άνοιξέ τα σε έναν επεξεργαστή κειμένου και κάνε αντιγραφή-επικόλληση. Μετά η βλάβη:
.\labo.ps1 casser api # arrête le conteneur de l'API ; la charge continue de frapper dans le vide
.\labo.ps1 etat # attendu : 9/10 services, 7/8 cibles up ; regarde aussi Targets, Alerts, 9093 et 8090
.\labo.ps1 reparer # redémarre l'API
.\labo.ps1 etat # attendu : Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.Αν το PowerShell αρνείται το .\labo.ps1 («l'exécution de scripts est désactivée», η εκτέλεση σεναρίων είναι απενεργοποιημένη): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, απάντησε O, ξαναεκτέλεσε. Αν η θύρα 3000 είναι ήδη κατειλημμένη στον υπολογιστή σου, $env:GRAFANA_PORT = '3001' πριν από το demarrer, και αντικατάστησε το 3000 με 3001 στα URL του Grafana.
Linux, macOS, WSL 2, Git Bash
git clone https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr.git lab3
cd lab3
ls # explorer le contenu : docker-compose.yml, labo.sh, labo.ps1, api/, prometheus/, grafana/, modules/
./labo.sh prerequis
./labo.sh demarrer
./labo.sh etat # attendu : Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.Περίμενε δύο λεπτά, μετά άνοιξε τις σελίδες στον περιηγητή:
Prometheus http://localhost:9090 (Status → Target health : 8 cibles UP ; onglet Graph pour P1 à P12)
Grafana http://localhost:3000 (utilisateur admin · mot de passe aiopsatlas2026 ; menu → Explore, source Loki pour G1 à G8)
Alertmanager http://localhost:9093 (vide au départ)
API catalogue http://localhost:8000/cours · http://localhost:8000/metrics
Webhook http://localhost:8090 (vide au départ)Τα ερωτήματα βρίσκονται στο modules/01-le-labo/requetes.txt (P1 έως P12) και στο modules/01-le-labo/requetes-logql.txt (G1 έως G8). Μετά η βλάβη:
./labo.sh casser api # arrête le conteneur de l'API ; la charge continue de frapper dans le vide
./labo.sh etat # attendu : 9/10 services, 7/8 cibles up ; regarde aussi Targets, Alerts, 9093 et 8090
./labo.sh reparer # redémarre l'API
./labo.sh etat # attendu : Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.Αν η θύρα 3000 είναι κατειλημμένη: GRAFANA_PORT=3001 ./labo.sh demarrer, και μετά 3001 στα URL του Grafana.
Πριν πληκτρολογήσεις ένα και μόνο ερώτημα, κοίτα τι παρατηρεί το εργαστήριο. Όλα περιστρέφονται γύρω από ένα API καταλόγου μαθημάτων: μια μικρή υπηρεσία web γραμμένη σε Python (FastAPI) που σερβίρει 64 μαθήματα και καταγράφει εγγραφές. Μια δεύτερη υπηρεσία, το charge, παίζει τον ρόλο των χρηστών: καλεί το API συνεχώς, με επιτυχημένα αιτήματα, μερικά εκούσια 404 και, μία φορά στις εκατό, ένα σφάλμα 500 που το API κατασκευάζει το ίδιο. Όλα όσα θα διαβάσεις στον Prometheus και στο Loki προέρχονται από αυτές τις δύο υπηρεσίες. Το υπόλοιπο kit, ξεκάθαρα:
lab3/
├── api/
│ ├── app.py l'API catalogue (FastAPI) : 6 routes publiques, 3 routes /admin
│ └── donnees/cours.json 64 cours → servis par GET /cours et GET /cours/{id}
├── charge/charge.py le générateur de trafic : GET /cours, /cours/{id}, POST /inscriptions, GET /lent, des 404
├── prometheus/
│ ├── prometheus.yml 8 cibles lues toutes les 15 s (scrape_interval: 15s)
│ └── regles/alertes.yml 10 règles d'alerte ; APIInjoignable est la première
├── alertmanager/alertmanager.yml groupe les alertes et les envoie au webhook (group_wait: 10s)
├── alloy/config.alloy lit les journaux des conteneurs et les pousse dans Loki
├── grafana/provisioning/ 3 tableaux de bord et 3 sources de données, créés au démarrage
└── modules/01-le-labo/
├── requetes.txt P1 à P12, à coller dans Prometheus
└── requetes-logql.txt G1 à G8, à coller dans Grafana ExploreΆνοιξε τα δεδομένα εσύ ο ίδιος, παίρνει δέκα δευτερόλεπτα:
# Windows (PowerShell), depuis le dossier lab3
Get-Content api\donnees\cours.json -TotalCount 16
Invoke-RestMethod "http://localhost:8000/cours?limite=2"
Invoke-RestMethod http://localhost:8000/cours/C0001
(Invoke-WebRequest http://localhost:8000/metrics -UseBasicParsing).Content -split "`n" | Select-String "^http_requetes_total"# Linux, macOS, WSL 2, Git Bash, depuis le dossier lab3
head -n 16 api/donnees/cours.json
curl -s "http://localhost:8000/cours?limite=2"
curl -s http://localhost:8000/cours/C0001
curl -s http://localhost:8000/metrics | grep "^http_requetes_total"Το api/donnees/cours.json είναι ένας πίνακας JSON 64 αντικειμένων, ένα ανά μάθημα, αναγνωριστικά C0001 έως C0064. Το πρώτο, όπως το επιστρέφει το API στο GET /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}| Πεδίο | Παράδειγμα | Τι είναι |
|---|---|---|
id | C0001 | Αναγνωριστικό του μαθήματος, C ακολουθούμενο από τέσσερα ψηφία, από C0001 έως C0064 |
titre | Introduction à Python | Εμφανιζόμενος τίτλος |
categorie | programmation | Μία από τις εννέα κατηγορίες: cloud, donnees, gestion, ia, outils, programmation, securite, systemes, web |
niveau | debutant | debutant, intermediaire ή avance |
prix | 89 | Τιμή σε δολάρια, ακέραιος |
duree_heures | 6 | Συνολική διάρκεια, σε ώρες |
professeur | Karim Haddad | Ένας από τους δέκα καθηγητές του καταλόγου |
tags | ["code","algorithmes"] | Λίστα λέξεων-κλειδιών |
note | 4.8 | Μέση βαθμολογία στα 5 |
inscrits | 2319 | Αριθμός εγγεγραμμένων κατά τη φόρτωση· οι εγγραφές που γίνονται κατά το εργαστήριο μετριούνται ξεχωριστά, στη μετρική inscriptions_total |
Οι διαδρομές του API και τι απαντούν στο εργαστήριο του μαθήματος:
| Διαδρομή | Πραγματική απόκριση | Τι κάνει |
|---|---|---|
GET /sante | {"etat":"ok","version":"1.0.0","cours":64} | Ο έλεγχος υγείας που καλεί το Docker· το etat διαβάζει version και cours από εδώ |
GET /cours?limite=2 | {"total":64,"page":1,"limite":2,"cours":[…]} | Η σελιδοποιημένη λίστα· φίλτρα categorie και niveau (?categorie=cloud → "total":6) |
GET /cours/C0001 | το παραπάνω έγγραφο | Μια καρτέλα· το API μετρά κάθε προβολή στο cours_consultes_total{cours_id="C0001"} |
GET /cours/C9999 | 404 {"detail":"cours C9999 introuvable"} | Ένα καθαρό 404: η υπηρεσία είναι υγιής, ο πόρος δεν υπάρχει |
POST /inscriptions | 201 (ή 404 αν το μάθημα δεν υπάρχει, 422 αν το σώμα είναι άκυρο) | Καλείται από το charge· αυξάνει το inscriptions_total{cours_id="…"} |
GET /lent | {"attente_ms":305} | Μια επίτηδες αργή διαδρομή (300 έως 900 ms) για να τροφοδοτεί το ιστόγραμμα καθυστέρησης |
GET /admin/etat | {"taux_erreurs":0.01,"lenteur_ms":0,"inscriptions_enregistrees":855,…} | Οι ρυθμίσεις βλάβης· το casser erreurs και το casser lenteur τις αλλάζουν, το reparer τις επαναφέρει |
GET /metrics | περίπου 270 γραμμές κειμένου | Αυτό που διαβάζει ο Prometheus κάθε 15 δευτερόλεπτα |
Κάθε απόκριση φέρει μια κεφαλίδα x-id-requete (για παράδειγμα x-id-requete: c5c49525ea53): είναι το ίδιο αναγνωριστικό με το πεδίο id_requete της γραμμής log που γράφτηκε για αυτό το αίτημα. Θα σου χρησιμεύσει στο ερώτημα G7.
/metrics, μια πραγματική γραμμή ανά τύποΗ σελίδα http://localhost:8000/metrics είναι κείμενο, μια σειρά ανά γραμμή, με πριν από αυτήν δύο γραμμές σχολίου # HELP (σε τι χρησιμεύει η μετρική) και # TYPE (ο τύπος της). Στο εργαστήριο του μαθήματος, έχει περίπου 270 γραμμές. Οι τέσσερις μετρικές που θα ερωτήσεις, αντιγραμμένες από τη σελίδα:
# HELP http_requetes_total Nombre de requêtes HTTP reçues, par méthode, route normalisée et code de réponse.
# TYPE http_requetes_total counter
http_requetes_total{code="200",methode="GET",route="/cours"} 3247.0
http_requetes_total{code="500",methode="GET",route="/cours"} 32.0
# HELP requetes_en_cours Nombre de requêtes HTTP en cours de traitement à cet instant.
# TYPE requetes_en_cours gauge
requetes_en_cours 1.0
# HELP http_duree_requete_seconds Durée de traitement des requêtes HTTP, en secondes, par route normalisée.
# TYPE http_duree_requete_seconds histogram
http_duree_requete_seconds_bucket{le="0.005",route="/cours"} 32.0
http_duree_requete_seconds_bucket{le="0.01",route="/cours"} 74.0
http_duree_requete_seconds_bucket{le="0.025",route="/cours"} 1566.0
http_duree_requete_seconds_bucket{le="0.05",route="/cours"} 3248.0
http_duree_requete_seconds_bucket{le="0.1",route="/cours"} 3276.0
http_duree_requete_seconds_bucket{le="0.25",route="/cours"} 3278.0
http_duree_requete_seconds_bucket{le="0.5",route="/cours"} 3279.0
http_duree_requete_seconds_bucket{le="1.0",route="/cours"} 3279.0
http_duree_requete_seconds_bucket{le="2.0",route="/cours"} 3279.0
http_duree_requete_seconds_bucket{le="+Inf",route="/cours"} 3279.0
http_duree_requete_seconds_count{route="/cours"} 3279.0
http_duree_requete_seconds_sum{route="/cours"} 85.42293146001248
# HELP api_info Informations sur l'API (toujours 1) ; la version est dans le label.
# TYPE api_info gauge
api_info{version="1.0.0"} 1.0| Τύπος | Μετρική του εργαστηρίου | Πώς να τη διαβάσεις |
|---|---|---|
| counter (μετρητής) | http_requetes_total | Μόνο ανεβαίνει: 3247 αποκρίσεις 200 στο /cours από την εκκίνηση του API. Μόνο η ταχύτητά της έχει νόημα (P5) |
| gauge | requetes_en_cours | Ανεβαίνει και κατεβαίνει: 1 αίτημα υπό επεξεργασία τη στιγμή της ανάγνωσης. Διαβάζεται ως έχει (P11) |
| histogram (ιστόγραμμα) | http_duree_requete_seconds | Σωρευτικά καλάθια: 3248 αιτήματα στο /cours πήραν λιγότερο από 50 ms (le="0.05"), 3279 συνολικά (+Inf = _count). _sum / _count = μέση διάρκεια (εδώ 26 ms). Το P10 βγάζει από εδώ ένα p95 |
| info (ένα gauge στο 1) | api_info | Η τιμή είναι πάντα 1· η πληροφορία βρίσκεται στο label version="1.0.0" |
Δεν υπάρχει summary σε αυτό το API: ο Prometheus το αποθαρρύνει προς όφελος του ιστογράμματος, που συναθροίζεται μεταξύ instances.
Μια σειρά είναι ένα όνομα μετρικής πλέον ένα σύνολο ζευγών label="τιμή". Δύο προελεύσεις:
| Label | Τοποθετείται από | Τιμές στο εργαστήριο |
|---|---|---|
methode | το API | GET, POST |
route | το API | /sante, /cours, /cours/{id}, /inscriptions, /lent, /admin/etat, inconnue (κάθε URL που δεν υπάρχει) |
code | το API | 200, 201, 404, 422, 500 |
le | το API, μόνο στο ιστόγραμμα | 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, +Inf |
cours_id | το API, στο inscriptions_total και στο cours_consultes_total | C0001 έως C0064 |
job | ο Prometheus, σύμφωνα με το prometheus.yml | prometheus, api, node-exporter, cadvisor, alertmanager, grafana, loki, alloy |
instance | ο Prometheus | η διεύθυνση που διαβάζεται: api:8000, localhost:9090, grafana:3000… |
service | ο Prometheus, προστέθηκε με το χέρι στο prometheus.yml για το job api | api |
Πρόσεξε τη διαδρομή /cours/{id}: το API κανονικοποιεί το URL πριν μετρήσει. Το /cours/C0001 και το /cours/C0043 πέφτουν στην ίδια σειρά. Χωρίς αυτό, θα υπήρχαν 64 σειρές ανά κωδικό αντί για μία, και 64 φορές περισσότερες γραμμές στο /metrics.
Το API γράφει μια γραμμή ημερολογίου ανά επεξεργασμένο αίτημα, σε JSON. Το Alloy διαβάζει την έξοδο κάθε container labo-* και την προωθεί στο Loki. Μια πραγματική γραμμή, διαβασμένη με .\labo.ps1 journal api (ή ./labo.sh journal api):
{"horodatage": "2026-09-15T19:33:14.781+00:00", "niveau": "ERROR", "id_requete": "dc1c2ff189a6", "methode": "GET", "route": "/cours/{id}", "code": 500, "duree_ms": 0.1, "message": "GET /cours/C0019 -> 500"}| Πεδίο | Παράδειγμα | Τι είναι |
|---|---|---|
horodatage | 2026-09-15T19:33:14.781+00:00 | Ημερομηνία και ώρα UTC, στο χιλιοστό του δευτερολέπτου |
niveau | ERROR | INFO (2xx), WARNING (4xx), ERROR (5xx) |
id_requete | dc1c2ff189a6 | Το αναγνωριστικό που επιστρέφεται στην κεφαλίδα x-id-requete της απόκρισης |
methode, route, code | GET, /cours/{id}, 500 | Οι ίδιες τιμές με τα labels του http_requetes_total: είναι η γέφυρα μεταξύ μετρικών και καταγραφών |
duree_ms | 0.1 | Διάρκεια επεξεργασίας, σε χιλιοστά του δευτερολέπτου |
message | GET /cours/C0019 -> 500 | Η αναγνώσιμη φράση, με το πραγματικό URL αυτή τη φορά (C0019, όχι {id}) |
Το Loki δεν διαβάζει το JSON γραμμή προς γραμμή τη στιγμή του ερωτήματος, εκτός αν του το ζητήσεις (| json, ερώτημα G5). Αυτό που ευρετηριάζει είναι labels που τοποθετεί το Alloy κατά την άφιξη:
| Label Loki | Τιμές | Τοποθετείται από |
|---|---|---|
service | api, charge, webhook, prometheus, alertmanager, grafana, loki, alloy, node-exporter, cadvisor | το Alloy, σύμφωνα με το όνομα της υπηρεσίας Compose |
conteneur | labo-api, labo-charge… | το Alloy, σύμφωνα με το όνομα του container |
niveau | INFO, WARNING, ERROR | το Alloy, εξαγόμενο από το πεδίο niveau του JSON |
code | 200, 201, 404, 422, 500 | το Alloy, εξαγόμενο από το πεδίο code του JSON |
detected_level | info, warn, error | το ίδιο το Loki, που μαντεύει το επίπεδο· μπορείς να το αγνοήσεις |
Κράτα δύο πράγματα που θα ξαναβρείς παντού: τη σειρά http_requetes_total{code="500",route="/cours"}, στο 32 στο /metrics τη στιγμή της καταγραφής, που θα ξαναδείς στο 32 στο P4· και το αναγνωριστικό dc1c2ff189a6, αυτό της παραπάνω γραμμής σφάλματος, που θα ξαναβρείς στο G2 και μετά θα πας να το ψάξεις μόνος σου στο G7. Οι μετρικές μετρούν, οι καταγραφές αφηγούνται· και οι δύο μιλούν για το ίδιο αίτημα.
Οι δύο ενότητες που ακολουθούν περιέχουν είκοσι ερωτήματα: δώδεκα για τον Prometheus (P1 έως P12), οκτώ για το Loki μέσα από το Grafana Explore (G1 έως G8). Είναι ταξινομημένα από το πιο απλό στο πιο εκφραστικό, και καθένα προσθέτει μόνο μία καινοτομία σε σχέση με το προηγούμενο. Αν ένα ερώτημα σου φαίνεται σκοτεινό, είναι σχεδόν πάντα γιατί το προηγούμενο δεν είναι ακόμη σαφές: γύρνα πίσω αντί να συνεχίσεις.
Πληκτρολόγησε κάθε ερώτημα εσύ ο ίδιος, σύγκρινε το αποτέλεσμα με αυτό της σελίδας, διάβασε την εξήγηση, και μετά πέρασε στο επόμενο. Οι αριθμοί θα είναι διαφορετικοί σε σένα: οι μετρητές ανεβαίνουν από την εκκίνηση του δικού σου API, όχι αυτού του μαθήματος. Οι μορφές (ο αριθμός σειρών, τα labels, η τάξη μεγέθους) πρέπει να είναι οι ίδιες. Ο Prometheus και το Loki χρησιμοποιούν την ίδια αρχική ιδέα, ένα σύνολο labels μέσα σε άγκιστρα, και είναι επίτηδες: αυτό που μαθαίνεις στο P2 χρησιμεύει στο G1.
Άνοιξε το http://localhost:9090. Φτάνεις στη σελίδα Query (το πάνω μενού προτείνει Query, Alerts, Status). Επικόλλησε ένα ερώτημα στο πεδίο, πάτησε Enter ή κάνε κλικ στο Execute. Το αποτέλεσμα εμφανίζεται από κάτω στην καρτέλα Table (μια γραμμή ανά σειρά, η τιμή δεξιά)· η καρτέλα Graph σχεδιάζει τις ίδιες σειρές στον χρόνο. Μείνε στο Table για αυτή την ενότητα, εκτός αν λέγεται διαφορετικά. Κάτω από τις καρτέλες, μια γραμμή του τύπου Load time: 40ms Result series: 8 σου λέει πόσες σειρές απάντησαν.
Η εικόνα που πρέπει να κρατήσεις στο μυαλό: ο Prometheus είναι ένα σημειωματάριο μετρήσεων. Κάθε 15 δευτερόλεπτα, περνά μπροστά από καθέναν από τους οκτώ στόχους του, διαβάζει τη σελίδα /metrics τους και σημειώνει κάθε τιμή με την ώρα. Ένα ερώτημα PromQL είναι μια ερώτηση που τίθεται σε αυτό το σημειωματάριο.
upup{instance="localhost:9090", job="prometheus"} 1
up{instance="alloy:12345", job="alloy"} 1
up{instance="api:8000", job="api", service="api"} 1
up{instance="cadvisor:8080", job="cadvisor"} 1
up{instance="alertmanager:9093", job="alertmanager"} 1
up{instance="loki:3100", job="loki"} 1
up{instance="node-exporter:9100", job="node-exporter"} 1
up{instance="grafana:3000", job="grafana"} 1Result series: 8, όλες στο 1.
Τι ζητά το ερώτημα: «Δώσε μου την τελευταία τιμή της μετρικής up για όλους τους στόχους.»
Μηδέν παράμετροι: μόνο ένα όνομα μετρικής. Η up δεν εκτίθεται από κανέναν στόχο· είναι ο Prometheus που την κατασκευάζει σε κάθε scrape: 1 αν η σελίδα /metrics απάντησε, 0 αλλιώς. Οκτώ σειρές γιατί το prometheus.yml δηλώνει οκτώ jobs. Κάθε γραμμή διαβάζεται: το όνομα της μετρικής, μετά μέσα σε άγκιστρα τα labels που τοποθέτησε ο Prometheus (job και instance σε όλες, service επιπλέον στο API), μετά η τιμή. Ισοδύναμο SQL: SELECT * FROM up. Αυτό που κάνει ο Prometheus και δεν κάνει η SQL: παρήγαγε ο ίδιος αυτόν τον πίνακα χτυπώντας οκτώ πόρτες.
Το εργαστήριο έχει δέκα containers αλλά ο Prometheus διαβάζει μόνο οκτώ: το charge και το webhook δεν εκθέτουν σελίδα /metrics σε αυτή την έκδοση του kit, άρα δεν είναι στόχοι. Το etat τα μετρά ξεχωριστά: 10/10 services (τα containers) και 8/8 cibles up (τα scrapes). Αν κάποια μέρα το up επιστρέψει 7 σειρές αντί για 8, δεν είναι ότι ένας στόχος έπεσε (θα ήταν στο 0): είναι ότι ένα job εξαφανίστηκε από τη ρύθμιση. Το kit έχει μια ειδοποίηση για αυτό, την CibleAbsente.
up{job="api"}up{instance="api:8000", job="api", service="api"} 1Τι ζητά το ερώτημα: «Η τιμή της up, μόνο για τις σειρές των οποίων το label job ισούται με api.»
Μία μόνο καινοτομία: ο επιλογέας {job="api"}. Τα άγκιστρα φιλτράρουν πάνω στα labels, όπως ένα WHERE job = 'api'. Τα εισαγωγικά είναι υποχρεωτικά γύρω από την τιμή: το up{job=api} απορρίπτεται με parse error: unexpected identifier "api" in label matching, expected string. Αυτή ακριβώς η έκφραση, up{job="api"} == 0, είναι αυτή που παρακολουθεί ο κανόνας APIInjoignable· θα τη δεις να περνά στο 0 στο παράρτημα.
http_requetes_totalhttp_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/sante", service="api"} 91
http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/cours/{id}", service="api"} 2714
http_requetes_total{code="404", instance="api:8000", job="api", methode="GET", route="/cours/{id}", service="api"} 209
http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/lent", service="api"} 314
http_requetes_total{code="404", instance="api:8000", job="api", methode="GET", route="inconnue", service="api"} 312
http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 3241
http_requetes_total{code="201", instance="api:8000", job="api", methode="POST", route="/inscriptions", service="api"} 855
http_requetes_total{code="404", instance="api:8000", job="api", methode="POST", route="/inscriptions", service="api"} 46
http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 32
http_requetes_total{code="422", instance="api:8000", job="api", methode="POST", route="/inscriptions", service="api"} 51
http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/cours/{id}", service="api"} 27
http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/admin/etat", service="api"} 14
http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/lent", service="api"} 1
http_requetes_total{code="500", instance="api:8000", job="api", methode="POST", route="/inscriptions", service="api"} 7Result series: 14 στο εργαστήριο του μαθήματος (ο ακριβής αριθμός εξαρτάται από τους συνδυασμούς που το charge έχει ήδη παραγάγει· ανεβαίνει έως 16 με τον χρόνο).
Τι ζητά το ερώτημα: «Όλες οι σειρές του μετρητή http_requetes_total, με την τρέχουσα τιμή τους.»
Τίποτα νέο στη σύνταξη: ένα όνομα, όπως στο P1. Αυτό που είναι νέο είναι τι διαβάζεις. Σύγκρινε με τη σελίδα /metrics: η γραμμή http_requetes_total{code="200",methode="GET",route="/cours"} 3247.0 έγινε http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 3241. Ο Prometheus πρόσθεσε τρία labels (instance, job, service) και η τιμή διαφέρει κατά μερικές μονάδες: η σελίδα διαβάστηκε σε άλλη στιγμή. Μια σειρά ανά συνδυασμό (methode, route, code): αυτό είναι που ονομάζουμε πληθικότητα της μετρικής, εδώ 14.
Η ουσιώδης διαφορά μεταξύ
/metricsκαι Prometheus. Η σελίδα/metricsείναι η κατάσταση του API τη στιγμή που την ανοίγεις, χωρίς ιστορικό. Ο Prometheus κρατά όλες τις αναγνώσεις, μία κάθε 15 δευτερόλεπτα, και αυτό είναι που επιτρέπει το P5: για να υπολογίσεις μια ταχύτητα χρειάζεσαι τουλάχιστον δύο σημεία.
http_requetes_total{code="500"}http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 32
http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/cours/{id}", service="api"} 27
http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/lent", service="api"} 1
http_requetes_total{code="500", instance="api:8000", job="api", methode="POST", route="/inscriptions", service="api"} 7Τι ζητά το ερώτημα: «Οι σειρές του http_requetes_total των οποίων το label code ισούται με 500.»
Τίποτα νέο: είναι το P2 εφαρμοσμένο στο P3. Τέσσερις σειρές, μία ανά επηρεασμένη διαδρομή. Το κόκκινο νήμα είναι εδώ: route="/cours" στο 32, η τιμή που διαβάστηκε στο /metrics. Το 500 είναι συμβολοσειρά, όχι αριθμός: το http_requetes_total{code=500} απορρίπτεται (parse error: unexpected character inside braces: '5'). Αυτά τα 500 δεν είναι βλάβη: το charge προκαλεί επίτηδες ένα σφάλμα στα εκατό (taux_erreurs: 0.01 στο /admin/etat) για να μην είναι ποτέ κενές οι καμπύλες σφαλμάτων.
Απολογισμός P1 έως P4: δεν έχεις υπολογίσει ακόμη τίποτα. Διάβασες στιγμιαίες τιμές και έμαθες να τις φιλτράρεις ανά label.
rate(http_requetes_total[1m]){code="200", instance="api:8000", job="api", methode="GET", route="/sante", service="api"} 0.11112345816201799
{code="200", instance="api:8000", job="api", methode="GET", route="/cours/{id}", service="api"} 3.000333370374486
{code="404", instance="api:8000", job="api", methode="GET", route="/cours/{id}", service="api"} 0.2666962995888432
{code="200", instance="api:8000", job="api", methode="GET", route="/lent", service="api"} 0.42226914101566837
{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 3.733748194243805
{code="201", instance="api:8000", job="api", methode="POST", route="/inscriptions", service="api"} 0.8667629736637403
{code="500", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 0.0222246916324036
…Result series: 14, σε αιτήματα ανά δευτερόλεπτο.
Τι ζητά το ερώτημα: «Για κάθε σειρά του μετρητή, κατά πόσο αυξήθηκε ανά δευτερόλεπτο, κατά μέσο όρο, στο τελευταίο λεπτό;»
Μία μόνο καινοτομία, σε δύο αδιαχώριστα κομμάτια: το [1m] μετατρέπει τη σειρά σε εύρος (όλες οι τιμές του τελευταίου λεπτού, αντί μόνο της τελευταίας), και το rate() υπολογίζει την κλίση αυτού του εύρους. Κοίτα το αποτέλεσμα: το όνομα της μετρικής εξαφανίστηκε από τα άγκιστρα, γιατί δεν είναι πλέον http_requetes_total, είναι μια παράγωγη ταχύτητα. Το /cours λαμβάνει 3,7 αιτήματα ανά δευτερόλεπτο, το /sante 0,11 (ένα κάθε 9 δευτερόλεπτα: είναι το healthcheck του Docker). Ισοδύναμο SQL: δεν υπάρχει απλό· θα χρειάζονταν δύο αναγνώσεις, μια αφαίρεση και μια διαίρεση με τον χρόνο που πέρασε. Το rate() το κάνει για κάθε σειρά, και διορθώνει επιπλέον τους μηδενισμούς όταν το API επανεκκινεί.
Χωρίς το εύρος, ο Prometheus αρνείται: το rate(http_requetes_total) δίνει parse error: expected type range vector in call to function "rate", got instant vector. Θα διαβάσεις αυτό το μήνυμα συχνά· σημαίνει «λείπει το […]».
3241 αιτήματα 200 στο /cours δεν λέει τίποτα: από πότε; Αν το API τρέχει μία ώρα, είναι ήρεμο· αν τρέχει ένα λεπτό, είναι επίθεση. Ένας μετρητής αξίζει μόνο για την κλίση του. Γι' αυτό σε έναν πίνακα ελέγχου, δεν θα δεις ποτέ το http_requetes_total ακατέργαστο, αλλά πάντα rate(http_requetes_total[…]). Το παράθυρο [1m] εξομαλύνει σε ένα λεπτό· το [5m] εξομαλύνει περισσότερο (πιο ήρεμη καμπύλη, πιο αργή αντίδραση). Το kit χρησιμοποιεί [5m] στις ειδοποιήσεις του και [1m] εδώ για να βλέπεις κάτι να κινείται. Πρακτικός κανόνας: το παράθυρο πρέπει να περιέχει τουλάχιστον δύο scrapes, άρα εδώ τουλάχιστον [30s]· το rate(http_requetes_total[10s]) επιστρέφει κενό αποτέλεσμα.
sum by (route) (rate(http_requetes_total[1m])){route="/sante"} 0.11112345816201799
{route="/cours/{id}"} 3.3114790532281364
{route="/lent"} 0.42226914101566837
{route="inconnue"} 0.3778197577508612
{route="/cours"} 3.7559728858762087
{route="/inscriptions"} 0.9334370485609511
{route="/admin/etat"} 0.0444493832648072Result series: 7.
Τι ζητά το ερώτημα: «Πάρε τις ταχύτητες του P5 και άθροισέ τις κρατώντας μόνο το label route.»
Μία μόνο καινοτομία: η συνάθροιση sum by (route) (…). Λιώνει όλα τα άλλα labels (code, methode, instance…) και αθροίζει ό,τι μένει. Το /cours/{id} περνά από τρεις σειρές (200, 404, 500) σε μία: 3,00 + 0,27 + 0,04 = 3,31. Ισοδύναμο SQL: SELECT route, SUM(vitesse) FROM … GROUP BY route. Οι παρενθέσεις γύρω από το route είναι υποχρεωτικές: το sum by route (…) απορρίπτεται (parse error: unexpected identifier "route" in grouping opts, expected "(").
sum by (code) (rate(http_requetes_total[1m])){code="200"} 7.311923547060784
{code="404"} 0.6445160573397044
{code="201"} 0.8667629736637403
{code="500"} 0.0888987665296144
{code="422"} 0.0444493832648072Τι ζητά το ερώτημα: «Το ίδιο άθροισμα με το P6, αλλά ομαδοποιημένο ανά κωδικό HTTP.»
Τίποτα νέο: το P6 με ένα άλλο label. Αυτό κάνει ο πίνακας (panel) «Réponses par code» (Αποκρίσεις ανά κωδικό) του πίνακα ελέγχου «API catalogue — signaux dorés» στο Grafana. Πέντε κωδικοί, πέντε γραμμές· τα 500 στο 0,09 ανά δευτερόλεπτο, δηλαδή λίγο πάνω από ένα σφάλμα κάθε δώδεκα δευτερόλεπτα.
sum(rate(http_requetes_total[1m])){} 8.956550727858652Τι ζητά το ερώτημα: «Άθροισε όλες τις ταχύτητες, χωρίς να κρατήσεις κανένα label.»
Μία μόνο καινοτομία: το sum(…) χωρίς by. Αποτέλεσμα: μια μοναδική σειρά, με ένα κενό σύνολο labels ({}), η τιμή 8,96 αιτήματα ανά δευτερόλεπτο. Είναι το πρώτο από τα τέσσερα χρυσά σήματα, η κίνηση. Έλεγξε: το άθροισμα των επτά γραμμών του P6 κάνει πράγματι 8,96.
Απολογισμός P5 έως P8: ξέρεις να μετατρέπεις έναν μετρητή σε ταχύτητα, και μετά να ομαδοποιείς αυτή την ταχύτητα όπως θέλεις. Τα τρία τέταρτα των πινάκων ελέγχου Prometheus δεν κάνουν παρά αυτό.
sum(rate(http_requetes_total{code=~"5.."}[1m])) / sum(rate(http_requetes_total[1m])){} 0.009925558312655085Τι ζητά το ερώτημα: «Η ταχύτητα των αποκρίσεων των οποίων ο κωδικός αρχίζει με 5, διαιρεμένη με την ταχύτητα όλων των αποκρίσεων.»
Δύο καινοτομίες, αλλά μικρές. Πρώτα το =~: ένας επιλογέας με κανονική έκφραση, "5.." = ένα 5 ακολουθούμενο από δύο οποιουσδήποτε χαρακτήρες, άρα όλα τα 5xx. Μετά η διαίρεση δύο αποτελεσμάτων: ο Prometheus διαιρεί τις σειρές που έχουν τα ίδια labels, και εδώ οι δύο πλευρές έχουν ένα κενό σύνολο {}, άρα ζευγαρώνουν. Αποτέλεσμα: 0,0099, δηλαδή 1 %· είναι η τιμή που έχει ρυθμιστεί στο /admin/etat (taux_erreurs: 0.01). Δεύτερο χρυσό σήμα, τα σφάλματα. Ο κανόνας ειδοποίησης TauxErreursEleve του kit ενεργοποιείται όταν αυτή η ίδια έκφραση, υπολογισμένη σε 5 λεπτά, ξεπερνά το 0,05.
histogram_quantile(0.95, sum by (le) (rate(http_duree_requete_seconds_bucket[5m]))){} 0.09797705555555555Τι ζητά το ερώτημα: «Από τα καλάθια του ιστογράμματος διάρκειας, όλες οι διαδρομές μαζί, κάτω από ποια τιμή πέφτει το 95 % των αιτημάτων των 5 τελευταίων λεπτών;»
Μία μόνο καινοτομία: το histogram_quantile(0.95, …). Θέλει ως είσοδο τα καλάθια _bucket αθροισμένα ανά le (γι' αυτό το sum by (le) είναι υποχρεωτικό: χωρίς αυτό, υπολογίζει ένα ποσοστημόριο ανά διαδρομή και το αποτέλεσμα δεν έχει πλέον το νόημα που περίμενες). Αποτέλεσμα: 0,098 δευτερόλεπτα, άρα το 95 % των αιτημάτων σερβίρεται σε λιγότερο από 98 ms. Είναι το τρίτο χρυσό σήμα, η καθυστέρηση, και η μετρική που παρακολουθεί η ειδοποίηση LatenceP95Elevee (κατώφλι: 0,5 s). Κοίτα την κατανομή των καλαθιών του /cours στο σύνολο δεδομένων: 3248 αιτήματα από τα 3279 κάτω από 50 ms, αλλά το /lent (300 έως 900 ms) τραβά το συνολικό p95 προς τα πάνω.
Κάθε γραμμή _bucket{le="0.05"} μετρά τα αιτήματα που πήραν το πολύ 0,05 δευτερόλεπτα (le = less or equal). Τα καλάθια είναι σωρευτικά: το le="0.1" περιέχει επίσης όλα όσα ήταν στο le="0.05". Το τελευταίο, le="+Inf", περιέχει τα πάντα, και ισούται πάντα με _count. Το histogram_quantile ψάχνει το καλάθι όπου η σωρευτική καμπύλη ξεπερνά το 95 % και παρεμβάλλει στο εσωτερικό του. Η ακρίβεια εξαρτάται λοιπόν από την επιλογή των καλαθιών: μεταξύ 0.05 και 0.1, ο Prometheus υποθέτει ομοιόμορφη κατανομή. Γι' αυτό το αποτέλεσμα, 0.0979…, δεν είναι μετρημένη τιμή αλλά εκτίμηση.
requetes_en_coursrequetes_en_cours{instance="api:8000", job="api", service="api"} 0Τι ζητά το ερώτημα: «Η τελευταία τιμή του gauge requetes_en_cours.»
Τίποτα νέο στη σύνταξη, είναι το P1. Αυτό που είναι νέο είναι ο τύπος: ένα gauge διαβάζεται ως έχει, χωρίς rate(). Μηδέν ή ένα, ανάλογα με τη στιγμή: το API επεξεργάζεται κάθε αίτημα σε λίγα χιλιοστά του δευτερολέπτου, είναι σπάνιο να πιάσεις ένα σε εξέλιξη. Είναι το τέταρτο χρυσό σήμα, ο κορεσμός: αν αυτή η τιμή ανέβαινε στο 50, το API θα ήταν υπερφορτωμένο. Το rate(requetes_en_cours[1m]) δεν προκαλεί σφάλμα, αλλά επιστρέφει έναν αριθμό που δεν σημαίνει τίποτα· ο Prometheus δεν σε προστατεύει από αυτή τη σύγχυση.
ALERTSEmpty query resultΤι ζητά το ερώτημα: «Οι ειδοποιήσεις που είναι αυτή τη στιγμή pending ή firing.»
Τίποτα νέο: ένα όνομα μετρικής, όπως στο P1. Η ALERTS είναι, όπως η up, κατασκευασμένη από τον Prometheus: μια σειρά ανά ενεργή ειδοποίηση, με τα labels alertname και alertstate. Σε ένα υγιές εργαστήριο, το αποτέλεσμα είναι κενό: Empty query result. Δεν είναι σφάλμα, είναι η καλύτερη δυνατή απάντηση. Θα το ξαναπληκτρολογήσεις κατά τη βλάβη του παραρτήματος και θα δεις να εμφανίζεται ALERTS{alertname="APIInjoignable", alertstate="pending", …} και μετά alertstate="firing".
Απολογισμός P9 έως P12: τα τέσσερα χρυσά σήματα (κίνηση P8, σφάλματα P9, καθυστέρηση P10, κορεσμός P11) χωρούν σε τέσσερα ερωτήματα, και οι ειδοποιήσεις είναι μια μετρική όπως οι άλλες.
Το μήνυμα που πρέπει να περάσει. Αυτό που κάνει και η SQL: φιλτράρισμα ανά στήλη (
{job="api"}=WHERE), ομαδοποίηση και άθροιση (sum by (route)=GROUP BY), διαίρεση δύο συναθροίσεων. Αυτό που κάνει μόνο ο Prometheus: πήγε να πάρει ο ίδιος τα δεδομένα κάθε 15 δευτερόλεπτα από οκτώ υπηρεσίες, μετατρέπει οποιονδήποτε μετρητή σε ταχύτητα με μια συνάρτηση (rate), εκτιμά ένα ποσοστημόριο από καλάθια (histogram_quantile), και εκθέτει τις δικές του ειδοποιήσεις ως μετρική (ALERTS).
Άνοιξε το http://localhost:3000 (χρήστης admin, κωδικός aiopsatlas2026). Στο κύριο μενού (εικονίδιο πάνω αριστερά), κάνε κλικ στο Explore. Στην κορυφή της σελίδας, ο επιλογέας πηγής δεδομένων προτείνει Prometheus, Loki και Alertmanager: διάλεξε Loki. Δεξιά του πεδίου ερωτήματος, δύο τρόποι: Builder (μενού) και Code (πληκτρολογείς). Πέρασε σε Code, επικόλλησε το ερώτημα, μετά Run query (ή Shift+Enter). Οι καταγραφές εμφανίζονται κάτω, η πιο πρόσφατη γραμμή πρώτη. Πάνω δεξιά, ο επιλογέας περιόδου είναι στο Last 1 hour από προεπιλογή: κράτησέ τον. Αυτά τα βήματα είναι ίδια σε Windows και σε Linux, είναι ο περιηγητής που δουλεύει.
Η εικόνα που πρέπει να κρατήσεις στο μυαλό: το Loki είναι μια ντουλάπα ημερολογίων πλοήγησης, ένα ντοσιέ ανά συνδυασμό labels. Δεν διαβάζει το περιεχόμενο των γραμμών για να τις ταξινομήσει, μόνο την ετικέτα του ντοσιέ. Ένα ερώτημα LogQL αρχίζει λοιπόν πάντα με την επιλογή ενός ντοσιέ, μέσα σε άγκιστρα, και μετά ενδεχομένως με το φιλτράρισμα των γραμμών μέσα του.
{service="api"}2026-09-15 19:33:30.416 {"horodatage": "2026-09-15T19:33:30.416+00:00", "niveau": "INFO", "id_requete": "875939d9cdac", "methode": "GET", "route": "/cours", "code": 200, "duree_ms": 11.5, "message": "GET /cours -> 200"}
2026-09-15 19:33:30.186 {"horodatage": "2026-09-15T19:33:30.186+00:00", "niveau": "INFO", "id_requete": "6d6e21e6d17b", "methode": "GET", "route": "/cours/{id}", "code": 200, "duree_ms": 14.3, "message": "GET /cours/C0001 -> 200"}
2026-09-15 19:33:30.142 {"horodatage": "2026-09-15T19:33:30.142+00:00", "niveau": "INFO", "id_requete": "42a64fb435bf", "methode": "GET", "route": "/cours", "code": 200, "duree_ms": 32.3, "message": "GET /cours -> 200"}
…Τι ζητά το ερώτημα: «Όλες οι γραμμές ημερολογίου των οποίων το label service ισούται με api.»
Μηδέν καινοτομία σε σχέση με το P2: ένας επιλογέας μέσα σε άγκιστρα. Η διαφορά είναι το αποτέλεσμα: γραμμές κειμένου, όχι αριθμοί. Το Grafana εμφανίζει την ώρα (μετατρεπόμενη στη ζώνη ώρας σου) και μετά την ακατέργαστη γραμμή· κάνε κλικ σε μια γραμμή για να ξεδιπλώσεις τα labels της: service="api", conteneur="labo-api", niveau="INFO", code="200", detected_level="info". Εννέα γραμμές ανά δευτερόλεπτο περίπου, όσες ανακοίνωνε το P8: ένα αίτημα, μια γραμμή. Τα άγκιστρα είναι υποχρεωτικά: το service="api" μόνο του απορρίπτεται (parse error at line 1, col 1: syntax error: unexpected IDENTIFIER), και το {service="api" χωρίς κλείσιμο επίσης (syntax error: unexpected $end, expecting } or ,).
Ένα label είναι μια ακριβής συμβολοσειρά. Το {service="API"} με κεφαλαία δεν επιστρέφει καμία γραμμή, χωρίς σφάλμα: το ντοσιέ δεν υπάρχει. Το ίδιο για το {app="api"}: το label ονομάζεται service σε αυτό το εργαστήριο, όχι app. Όταν ένα ερώτημα LogQL επιστρέφει μηδέν γραμμές, έλεγξε πρώτα το όνομα και τα πεζά/κεφαλαία του label· στο Explore, ο τρόπος Builder σου καταγράφει τα υπάρχοντα labels και τις τιμές τους, είναι ο ασφαλέστερος τρόπος να τα ανακαλύψεις.
{service="api", niveau="ERROR"}2026-09-15 19:33:28.931 {"horodatage": "2026-09-15T19:33:28.931+00:00", "niveau": "ERROR", "id_requete": "f5e09e6ed543", "methode": "POST", "route": "/inscriptions", "code": 500, "duree_ms": 0.0, "message": "POST /inscriptions -> 500"}
2026-09-15 19:33:16.381 {"horodatage": "2026-09-15T19:33:16.381+00:00", "niveau": "ERROR", "id_requete": "18f95992a8fa", "methode": "POST", "route": "/inscriptions", "code": 500, "duree_ms": 0.0, "message": "POST /inscriptions -> 500"}
2026-09-15 19:33:14.781 {"horodatage": "2026-09-15T19:33:14.781+00:00", "niveau": "ERROR", "id_requete": "dc1c2ff189a6", "methode": "GET", "route": "/cours/{id}", "code": 500, "duree_ms": 0.1, "message": "GET /cours/C0019 -> 500"}
…Τι ζητά το ερώτημα: «Οι γραμμές του API των οποίων το label niveau ισούται με ERROR.»
Μία μόνο καινοτομία: δύο labels στον επιλογέα, χωρισμένα με κόμμα, είναι ένα ΚΑΙ. Το label niveau δεν βρίσκεται στη γραμμή αρχικά: είναι το Alloy που το εξήγαγε από το πεδίο JSON niveau πριν το στείλει στο Loki, και αυτό είναι που κάνει αυτό το ερώτημα γρήγορο. Το κόκκινο νήμα είναι εδώ, τρίτη γραμμή: id_requete: dc1c2ff189a6, η γραμμή του συνόλου δεδομένων. Πολύ λιγότερες γραμμές από ό,τι στο G1: περίπου μία κάθε δώδεκα δευτερόλεπτα, όπως έλεγε το P7 για τα 500.
{service="api", code="500"}2026-09-15 19:33:28.931 {"horodatage": "2026-09-15T19:33:28.931+00:00", "niveau": "ERROR", "id_requete": "f5e09e6ed543", "methode": "POST", "route": "/inscriptions", "code": 500, …}
2026-09-15 19:33:16.381 {"horodatage": "2026-09-15T19:33:16.381+00:00", "niveau": "ERROR", "id_requete": "18f95992a8fa", "methode": "POST", "route": "/inscriptions", "code": 500, …}
2026-09-15 19:33:14.781 {"horodatage": "2026-09-15T19:33:14.781+00:00", "niveau": "ERROR", "id_requete": "dc1c2ff189a6", "methode": "GET", "route": "/cours/{id}", "code": 500, …}
…Τι ζητά το ερώτημα: «Οι γραμμές του API των οποίων το label code ισούται με 500.»
Τίποτα νέο: το G2 με ένα άλλο label. Οι ίδιες γραμμές με το G2, γιατί σε αυτό το API κάθε 500 είναι ένα ERROR και αντίστροφα. Είναι η εκδοχή ημερολογίου του P4: εκεί που ο Prometheus σου λέει «32 σφάλματα στο /cours», το Loki σου δείχνει ποια, με το πραγματικό URL (/cours/C0019) και το αναγνωριστικό του αιτήματος. Πρόσεξε ότι το code είναι εδώ συμβολοσειρά ("500") γιατί είναι label· στο JSON της γραμμής, είναι αριθμός (500). Το G6 θα σου δείξει τη διαφορά.
{service="api"} |= "inscriptions"2026-09-15 19:33:29.738 {"horodatage": "2026-09-15T19:33:29.738+00:00", "niveau": "INFO", "id_requete": "5afb73494ebd", "methode": "POST", "route": "/inscriptions", "code": 201, "duree_ms": 22.9, "message": "POST /inscriptions -> 201"}
2026-09-15 19:33:28.931 {"horodatage": "2026-09-15T19:33:28.931+00:00", "niveau": "ERROR", "id_requete": "f5e09e6ed543", "methode": "POST", "route": "/inscriptions", "code": 500, "duree_ms": 0.0, "message": "POST /inscriptions -> 500"}
2026-09-15 19:33:27.187 {"horodatage": "2026-09-15T19:33:27.187+00:00", "niveau": "INFO", "id_requete": "c4e29db13e12", "methode": "POST", "route": "/inscriptions", "code": 201, "duree_ms": 26.1, "message": "POST /inscriptions -> 201"}
…Τι ζητά το ερώτημα: «Οι γραμμές του API που περιέχουν το κείμενο inscriptions.»
Μία μόνο καινοτομία: το φίλτρο γραμμής |= "…", που κρατά τις γραμμές που περιέχουν ακριβώς αυτό το κείμενο. Είναι το grep. Αντίθετα με ένα label, το Loki πρέπει εδώ να ανοίξει κάθε γραμμή του ντοσιέ {service="api"} για να κοιτάξει μέσα: πιο αργό, αλλά μπορείς να ψάξεις οτιδήποτε. Οι παραλλαγές: != (δεν περιέχει), |~ (κανονική έκφραση), !~. Μεικτό αποτέλεσμα: 201 και 500, όλα όσα αφορούν τις εγγραφές.
Απολογισμός G1 έως G4: δύο τρόποι φιλτραρίσματος, ανά label (γρήγορο, πριν ανοίξουν οι γραμμές) και ανά κείμενο (ευέλικτο, μετά). Το σωστό ερώτημα αρχίζει πάντα με το στενότερο δυνατό label.
{service="api"} | json2026-09-15 19:33:30.416 {"horodatage": "2026-09-15T19:33:30.416+00:00", "niveau": "INFO", "id_requete": "875939d9cdac", "methode": "GET", "route": "/cours", "code": 200, "duree_ms": 11.5, "message": "GET /cours -> 200"}
labels : code="200" conteneur="labo-api" duree_ms="11.5" horodatage="2026-09-15T19:33:30.416+00:00" id_requete="875939d9cdac"
message="GET /cours -> 200" methode="GET" niveau="INFO" route="/cours" service="api" …Τι ζητά το ερώτημα: «Οι γραμμές του API, και για καθεμία, μετέτρεψε τα πεδία του JSON σε labels.»
Μία μόνο καινοτομία: ο αναλυτής | json. Οι εμφανιζόμενες γραμμές είναι οι ίδιες με το G1, αλλά ξεδίπλωσε μία: έχει τώρα πολύ περισσότερα labels (route, methode, duree_ms, id_requete, message…), ένα ανά πεδίο του JSON. Αυτά τα labels υπολογίζονται τη στιγμή του ερωτήματος, δεν αποθηκεύονται: το Loki ευρετηριάζει πάντα μόνο τα service, conteneur, niveau, code. Θα δεις επίσης code_extracted και niveau_extracted: όταν ένα πεδίο του JSON έχει το ίδιο όνομα με ένα label που έχει ήδη τοποθετήσει το Alloy, το Loki προσθέτει κατάληξη στο αντίγραφο αντί να το αντικαταστήσει.
{service="api"} | json | duree_ms > 5002026-09-15 19:33:27.838 {"horodatage": "2026-09-15T19:33:27.838+00:00", "niveau": "INFO", "id_requete": "416f1ab0eb41", "methode": "GET", "route": "/lent", "code": 200, "duree_ms": 587.4, "message": "GET /lent -> 200"}
2026-09-15 19:33:16.335 {"horodatage": "2026-09-15T19:33:16.335+00:00", "niveau": "INFO", "id_requete": "9d1cb1767846", "methode": "GET", "route": "/lent", "code": 200, "duree_ms": 737.8, "message": "GET /lent -> 200"}
2026-09-15 19:33:15.375 {"horodatage": "2026-09-15T19:33:15.375+00:00", "niveau": "INFO", "id_requete": "cab8f698f89b", "methode": "GET", "route": "/lent", "code": 200, "duree_ms": 526.2, "message": "GET /lent -> 200"}
…Τι ζητά το ερώτημα: «Οι γραμμές του API των οποίων το πεδίο duree_ms, αφού ανοιχτεί το JSON, ξεπερνά το 500.»
Μία μόνο καινοτομία: το φίλτρο label | duree_ms > 500, που συγκρίνει ένα εξαγόμενο label με έναν αριθμό. Αυτό είναι δυνατό μόνο μετά το | json, αλλιώς το duree_ms δεν υπάρχει. Αποτέλεσμα: μόνο /lent, η επίτηδες αργή διαδρομή (300 έως 900 ms). Είναι η εκδοχή ημερολογίου του P10: ο Prometheus λέει «το p95 είναι στα 98 ms»· το Loki δείχνει τα μεμονωμένα αιτήματα που ξεπέρασαν ένα κατώφλι, με το αναγνωριστικό τους. Ισοδύναμο SQL: WHERE duree_ms > 500, με τη διαφορά ότι η στήλη δεν υπήρχε πριν από το ερώτημα.
{service="api"} |= "dc1c2ff189a6"2026-09-15 19:33:14.781 {"horodatage": "2026-09-15T19:33:14.781+00:00", "niveau": "ERROR", "id_requete": "dc1c2ff189a6", "methode": "GET", "route": "/cours/{id}", "code": 500, "duree_ms": 0.1, "message": "GET /cours/C0019 -> 500"}Μία μόνο γραμμή.
Τι ζητά το ερώτημα: «Η γραμμή του API που περιέχει το αναγνωριστικό dc1c2ff189a6.»
Τίποτα νέο: είναι το G4 με ένα άλλο κείμενο. Αυτό που αλλάζει είναι η χρήση: σε σένα, το dc1c2ff189a6 δεν υπάρχει· αντίγραψε ένα id_requete που είδες στη δική σου έξοδο του G2 και επικόλλησέ το στη θέση του. Είναι η κίνηση που θα κάνεις στην παραγωγή: ένας χρήστης σου δίνει το αναγνωριστικό που επιστρέφεται από την κεφαλίδα x-id-requete της απόκρισής του σε σφάλμα, και βρίσκεις με ένα ερώτημα την ακριβή γραμμή, με τη διαδρομή, τον κωδικό και τη διάρκεια. Μία μόνο γραμμή: το αναγνωριστικό είναι μοναδικό ανά αίτημα.
sum by (niveau) (count_over_time({service="api"}[1m]))Αυτή τη φορά, το Grafana εμφανίζει ένα γράφημα αντί για γραμμές: τρεις καμπύλες, {niveau="INFO"} γύρω στις 470 έως 500 γραμμές ανά λεπτό, {niveau="WARNING"} γύρω στις 40 έως 50, {niveau="ERROR"} μεταξύ 2 και 10, στο εργαστήριο του μαθήματος. Πέρασε το ποντίκι πάνω από το γράφημα για να διαβάσεις τις τιμές.
Τι ζητά το ερώτημα: «Μέτρησε τις γραμμές του API ανά διάστημα ενός λεπτού, και μετά άθροισε κρατώντας το label niveau.»
Μία μόνο καινοτομία, σε ένα κομμάτι που ήδη γνωρίζεις: το count_over_time(…[1m]) μετρά τις γραμμές ενός επιλογέα σε ένα εύρος, ακριβώς όπως το rate(…[1m]) υπολογίζει μια κλίση στο P5. Γύρω του, το sum by (niveau) είναι το sum by (route) του P6, λέξη προς λέξη. Η LogQL δανείστηκε αυτή τη γραμματική από την PromQL επίτηδες: αυτό που έμαθες από τη μία πλευρά χρησιμεύει από την άλλη. Σύγκρινε με το P7: ο Prometheus μετρά 0,09 απόκριση 500 ανά δευτερόλεπτο, δηλαδή 5 ανά λεπτό· το Loki μετρά 5 γραμμές ERROR ανά λεπτό. Δύο εργαλεία, δύο διαδρομές, ο ίδιος αριθμός.
Η ουσιώδης διαφορά μεταξύ Prometheus και Loki. Ο Prometheus αποθηκεύει αριθμούς που έχει ήδη μετρήσει το API (
http_requetes_total), το Loki αποθηκεύει τις γραμμές και μπορεί να τις ξαναμετρήσει κατά παραγγελία (count_over_time). Ο πρώτος είναι ελαφρύς και γρήγορος, και απαντά «πόσα»· ο δεύτερος είναι βαρύς αλλά κρατά τη λεπτομέρεια, και απαντά «ποια». Το εργαστήριο έχει και τα δύο γιατί κανένα δεν αντικαθιστά το άλλο.
Το μήνυμα που πρέπει να περάσει. Αυτό που κάνει και το
grep: αναζήτηση ενός κειμένου σε γραμμές (|=). Αυτό που κάνει μόνο το Loki: τακτοποιεί τις γραμμές δέκα containers ανά labels και διαβάζει μόνο το σωστό ντοσιέ, ανοίγει το JSON κατά παραγγελία για να φιλτράρει σε ένα αριθμητικό πεδίο (| json | duree_ms > 500), και μετατρέπει καταγραφές σε καμπύλη με τη γραμματική της PromQL (count_over_time).
Τρία ερωτήματα που συνδυάζουν όσα είδες, χωρίς νέα έννοια. Πληκτρολόγησέ τα, και μετά εξήγησε σε μία φράση τι δείχνει καθένα.
topk(5, increase(inscriptions_total[1h]))Τα πέντε μαθήματα που έλαβαν τις περισσότερες εγγραφές την τελευταία ώρα. Το increase είναι το rate πολλαπλασιασμένο με τη διάρκεια του παραθύρου· το topk(5, …) κρατά τις πέντε μεγαλύτερες σειρές. Στο εργαστήριο του μαθήματος, το C0001 έρχεται πρώτο με περίπου 177 εγγραφές: το charge ευνοεί μερικά «δημοφιλή» μαθήματα.
histogram_quantile(0.95, sum by (le, route) (rate(http_duree_requete_seconds_bucket[5m])))Το P10 με ένα label παραπάνω στο by: ένα p95 ανά διαδρομή. Θα δεις το /lent γύρω στα 0,7 s και τις άλλες διαδρομές κάτω από 0,03 s. Κοίτα τι αλλάζει σε σχέση με το συνολικό p95 του P10.
{service="api"} |= "inscriptions" | json | code = 201Τα G4, G5 και G6 αλυσιδωτά: μόνο οι επιτυχημένες εγγραφές. Έλεγξε ότι ο αριθμός γραμμών ανά λεπτό αντιστοιχεί στη γραμμή {code="201"} του P7, περίπου 0,87 ανά δευτερόλεπτο, δηλαδή καμιά πενηνταριά ανά λεπτό.
Όλες οι εντολές πληκτρολογούνται στο PowerShell, από τον φάκελο lab3. Το Docker Desktop πρέπει να είναι εκκινημένο (πράσινο εικονίδιο). Αν το PowerShell αρνείται να εκτελέσει το .\labo.ps1, πληκτρολόγησε μία φορά Set-ExecutionPolicy -Scope CurrentUser RemoteSigned και απάντησε O.
cd C:\Users\<toi>\Documents
git clone https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr.git lab3
cd lab3
lsΠρέπει να δεις docker-compose.yml, labo.ps1, labo.sh, README.md, και τους φακέλους alertmanager, alloy, api, charge, grafana, loki, modules, outils, prometheus, webhook. Αν έχεις ήδη κλωνοποιήσει το kit στο μάθημα 03, παράλειψε αυτό το βήμα και κάνε απλώς cd lab3.
.\labo.ps1 prerequis
== 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 9090 libre
✔ port 9093 libre
✔ port 3000 libre
✔ port 3100 libre
✔ port 12345 libre
✔ port 9100 libre
✔ port 8080 libre
✔ port 8000 libre
✔ port 8090 libre
Tout est prêt. Lancez : .\labo.ps1 demarrerΣημείο ελέγχου: η τελευταία γραμμή είναι Tout est prêt.. Οι εκδόσεις, η μνήμη και ο αριθμός επεξεργαστών είναι αυτοί του μηχανήματος του μαθήματος. Αν μια θύρα σημειώνεται ✘ … déjà occupé, το μάθημα 03 εξηγεί τι να κάνεις· για την 3000, το $env:GRAFANA_PORT = '3001' αρκεί.
.\labo.ps1 demarrerΤην πρώτη φορά, η λήψη των έξι δημόσιων εικόνων παίρνει ένα έως πέντε λεπτά ανάλογα με τη σύνδεσή σου. Τέλος της αναμενόμενης εξόδου:
== 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)Σημείο ελέγχου: δέκα prêt, μετά Le labo est prêt.. Στο μηχάνημα του μαθήματος, με τις εικόνες ήδη σε cache, η εντολή πήρε 44 δευτερόλεπτα. Το μάθημα 04 σχολιάζει αυτή την έξοδο γραμμή προς γραμμή.
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.Σημείο ελέγχου: δέκα (healthy), 8/8, 64 cours, 0 alerte(s) reçue(s), και η τελευταία γραμμή Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.. Ο αριθμός σειρών στη μνήμη ανεβαίνει τα πρώτα λεπτά (9038 αμέσως μετά την εκκίνηση, 12 000 έως 15 000 μετά από μία ώρα στο μηχάνημα του μαθήματος). Αν το alloy ή το grafana είναι ακόμη (health: starting), περίμενε τριάντα δευτερόλεπτα και ξαναεκτέλεσε.
Άνοιξε το http://localhost:9090, μενού Status, μετά Target health. Οκτώ μπλοκ, ένα ανά job, καθένα με 1 / 1 up και μια γραμμή:
api
1 / 1 up
Endpoint Labels Last scrape State
http://api:8000/metrics instance="api:8000" job="api" service="api" 6.014s ago UPΣημείο ελέγχου: οκτώ UP, κανένα DOWN. Η στήλη Last scrape δεν ξεπερνά ποτέ τα 15 δευτερόλεπτα: είναι το scrape_interval του prometheus.yml. Στη γραμμή εντολών, η ίδια πληροφορία:
(Invoke-RestMethod http://localhost:9090/api/v1/targets).data.activeTargets | Select-Object @{n='job';e={$_.labels.job}}, health, scrapeUrl | Sort-Object jobjob health scrapeUrl
--- ------ ---------
alertmanager up http://alertmanager:9093/metrics
alloy up http://alloy:12345/metrics
api up http://api:8000/metrics
cadvisor up http://cadvisor:8080/metrics
grafana up http://grafana:3000/metrics
loki up http://loki:3100/metrics
node-exporter up http://node-exporter:9100/metrics
prometheus up http://localhost:9090/metricsΠερίμενε το demarrer να έχει τουλάχιστον δύο λεπτά πίσω του, και μετά ακολούθησε τις ενότητες Prometheus, στην καρτέλα Graph και Grafana, στο Explore παραπάνω. Τα ερωτήματα είναι έτοιμα για αντιγραφή:
Get-Content modules\01-le-labo\requetes.txt
Get-Content modules\01-le-labo\requetes-logql.txtΣημείο ελέγχου: το P1 επιστρέφει 8 σειρές στο 1, το P12 επιστρέφει Empty query result, το G1 επιστρέφει γραμμές JSON, το G8 επιστρέφει τρεις καμπύλες.
Πριν σπάσεις, σημείωσε την ώρα (Get-Date -Format HH:mm:ss). Μετά:
.\labo.ps1 casser api
== Panne : arrêt de l'API ==
Container labo-api Stopping
Container labo-api Stopped
✔ API arrêtée. La charge continue de frapper dans le vide.
À observer : .\labo.ps1 etat · http://localhost:9090/targets (api → down)
http://localhost:9090/alerts (APIInjoignable : pending puis firing après 30 s)
http://localhost:9093 et http://localhost:8090 (l'alerte arrive ~10 s après firing)
Pour tout remettre en ordre : .\labo.ps1 reparerΤο σενάριο έκανε ένα docker compose stop api: το container σταμάτησε καθαρά, τα δεδομένα και η εικόνα του είναι άθικτα. Τώρα, παρατήρησε τη βλάβη από πέντε διαδρομές, με τη σειρά. Έχεις περίπου 70 δευτερόλεπτα πριν φτάσει η ειδοποίηση στο webhook: εκτέλεσε το etat αμέσως.
Διαδρομή 1, etat:
.\labo.ps1 etat
== Conteneurs ==
NAME SERVICE STATUS
labo-alertmanager alertmanager Up 22 minutes (healthy)
labo-alloy alloy Up 22 minutes (healthy)
labo-api api Exited (0) About a minute ago
labo-cadvisor cadvisor Up 22 minutes (healthy)
labo-charge charge Up 22 minutes (healthy)
labo-grafana grafana Up 22 minutes (healthy)
labo-loki loki Up 22 minutes (healthy)
labo-node-exporter node-exporter Up 22 minutes (healthy)
labo-prometheus prometheus Up 22 minutes (healthy)
labo-webhook webhook Up 22 minutes (healthy)
== Supervision ==
✔ Prometheus répond — cibles up : 7/8
✘ cible api (http://api:8000/metrics) : down — Get "http://api:8000/metrics": dial tcp: lookup api on 127.0.0.11:53: no such host
séries en mémoire : 15116
alertes : 2 active(s), 0 en attente (pending)
✘ APIInjoignable [critique] — L'API catalogue ne répond plus
✘ TauxErreursEleve [critique] — Plus de 5 % des requêtes de l'API échouent
✔ Alertmanager répond (http://localhost:9093)
✔ Grafana répond (http://localhost:3000)
✔ Loki répond (http://localhost:3100)
✘ API catalogue ne répond pas (http://localhost:8000)
✔ Webhook répond — 2 alerte(s) reçue(s) (http://localhost:8090)
Labo : 9/10 services, 7/8 cibles up, 2 alertes actives.Τι πρέπει να διαβάσεις, από πάνω προς τα κάτω: το labo-api είναι Exited (0) (κωδικός 0: εκούσιο σταμάτημα, όχι κατάρρευση)· ο Prometheus διαβάζει πλέον μόνο 7/8 στόχους και σου λέει γιατί (lookup api … no such host: το όνομα api δεν υπάρχει πλέον στο δίκτυο Docker αφού το container είναι σταματημένο)· η ειδοποίηση APIInjoignable είναι ενεργή· το API δεν απαντά στη θύρα 8000· το webhook έλαβε κάτι. Αυτή η έξοδος καταγράφηκε στο μηχάνημα του μαθήματος ένα λεπτό μετά το casser api, ενώ ένα casser erreurs είχε μόλις παιχτεί λίγα λεπτά πριν: γι' αυτό εμφανίζεται επίσης μια δεύτερη ειδοποίηση, TauxErreursEleve. Στο δικό σου εργαστήριο, θα έχεις μόνο την APIInjoignable, 1 alertes actives και 1 alerte(s) reçue(s). Αν εκτελέσεις το etat στα πρώτα 30 δευτερόλεπτα, η ειδοποίηση είναι ακόμη en attente (pending) και το webhook είναι ακόμη στο 0: ξαναεκτέλεσε ένα λεπτό αργότερα.
Διαδρομή 2, οι στόχοι. Επαναφόρτωσε το http://localhost:9090 → Status → Target health. Το μπλοκ api πέρασε σε 0 / 1 up, κατάσταση DOWN, και η στήλη Error φέρει το ίδιο μήνυμα με το etat: Get "http://api:8000/metrics": dial tcp: lookup api on 127.0.0.11:53: no such host. Οι επτά άλλοι μένουν UP. Ξαναπληκτρολόγησε up στο Query: η γραμμή up{instance="api:8000", job="api", service="api"} είναι στο 0, οι επτά άλλες στο 1. Μετά up == 0: μία μόνο γραμμή.
Διαδρομή 3, οι ειδοποιήσεις στον Prometheus. Μενού Alerts. Ο κανόνας APIInjoignable αλλάζει κατάσταση σε τρεις χρόνους, χρονομετρημένους στο μηχάνημα του μαθήματος:
t+0 s : APIInjoignable inactive (Prometheus n'a pas encore rescrappé l'API)
t+40 s : APIInjoignable pending (up{job="api"} == 0 est vrai, le compte à rebours « for: 30s » tourne)
t+70 s : APIInjoignable firing (vrai depuis 30 s : Prometheus envoie à Alertmanager)
t+70 s : APIInjoignable reçue par le webhook (firing)Γιατί 40 δευτερόλεπτα πριν από το pending: ο Prometheus διαβάζει το API κάθε 15 δευτερόλεπτα, άρα χρειάζονται έως 15 δευτερόλεπτα για να αποτύχει ένα scrape, και μετά αξιολογεί τους κανόνες κάθε 15 δευτερόλεπτα. Γιατί 30 ακόμη πριν από το firing: ο κανόνας λέει for: 30s. Σε μια άλλη καταγραφή, το pending έφτασε στα 31 s και το firing στα 61 s: η τάξη μεγέθους είναι η ίδια, η λεπτομέρεια εξαρτάται από τη στιγμή που έσπασες σε σχέση με τον κύκλο scrape. Ξαναπληκτρολόγησε ALERTS στο Query:
ALERTS{alertname="APIInjoignable", alertstate="pending", instance="api:8000", job="api", service="api", severite="critique"} 1και μετά, τριάντα δευτερόλεπτα αργότερα, alertstate="firing". Σημείωσε την ώρα του περάσματος σε firing: είναι η πρώτη από τις τρεις γραμμές του παραδοτέου σου.
Διαδρομή 4, Alertmanager. Άνοιξε το http://localhost:9093. Η σελίδα Alerts δείχνει μια ομάδα alertname="APIInjoignable" service="api" (είναι το group_by: [alertname, service] του alertmanager.yml) με την ειδοποίηση, τα labels της (instance="api:8000", job="api", labo="observabilite", severite="critique"), την περίληψή της L'API catalogue ne répond plus και την περιγραφή της. Το label labo="observabilite" δεν ήταν στον κανόνα: είναι το external_labels του prometheus.yml, που προστίθεται σε όλα όσα βγαίνουν από τον Prometheus. Στη γραμμή εντολών:
(Invoke-RestMethod http://localhost:9093/api/v2/alerts) | Select-Object @{n='alerte';e={$_.labels.alertname}}, @{n='etat';e={$_.status.state}}, startsAtalerte etat startsAt
------ ---- --------
APIInjoignable active 2026-09-15T19:41:11.496ZΔιαδρομή 5, το webhook. Άνοιξε το http://localhost:8090. Η σελίδα «Alertes reçues d'Alertmanager» (Ειδοποιήσεις που λήφθηκαν από τον Alertmanager) δεν είναι πλέον κενή: μια γραμμή APIInjoignable · critique · firing · api · L'API catalogue ne répond plus, και η κεφαλίδα μετρά 1 alerte(s) en mémoire · 1 notification(s) reçue(s). Η ακατέργαστη μορφή, http://localhost:8090/alertes.json, δείχνει τι έστειλε ο Alertmanager:
{"recu_a":"2026-09-15T19:41:26+00:00","etat":"firing","nom":"APIInjoignable","severite":"critique","service":"api","resume":"L'API catalogue ne répond plus","description":"Prometheus n'arrive plus à lire http://api:8000/metrics depuis 30 secondes (cible api:8000).","debut":"2026-09-15T19:41:11.496Z","fin":"0001-01-01T00:00:00Z","labels":{"alertname":"APIInjoignable","instance":"api:8000","job":"api","labo":"observabilite","service":"api","severite":"critique"}}Διάβασε το debut (19:41:11, η ώρα του firing στον Prometheus) και το recu_a (19:41:26): δεκαπέντε δευτερόλεπτα διαφορά, εκ των οποίων τα 10 δευτερόλεπτα του group_wait του Alertmanager. Το fin στο έτος 0001 σημαίνει «δεν έχει τελειώσει ακόμη». Σημείωσε το recu_a: δεύτερη γραμμή του παραδοτέου σου.
Τι βλέπει το charge, στο μεταξύ:
.\labo.ps1 journal chargelabo-charge | {"horodatage": "2026-09-15T19:40:36.600+00:00", "niveau": "WARNING", "message": "API injoignable : ConnectionError"}
labo-charge | {"horodatage": "2026-09-15T19:40:40.925+00:00", "niveau": "WARNING", "message": "API injoignable : ConnectionError"}
labo-charge | {"horodatage": "2026-09-15T19:40:45.393+00:00", "niveau": "WARNING", "message": "API injoignable : ConnectionError"}Και τι βλέπεις αν καλέσεις το API εσύ ο ίδιος:
Invoke-RestMethod http://localhost:8000/sante -TimeoutSec 5Invoke-RestMethod : Le délai de l'opération a expiré.(Χωρίς -TimeoutSec, το PowerShell περιμένει περισσότερο πριν παραιτηθεί· το curl.exe -sS http://localhost:8000/sante απαντά πιο γρήγορα: curl: (7) Failed to connect to localhost:8000 after 2237 ms: Could not connect to server.)
Η ουσιώδης διαφορά μεταξύ ενός 404 και καμίας απόκρισης. Στο βήμα A.8, το
/cours/C9999θα απαντήσει404: το API τρέχει και σου λέει ευγενικά ότι αυτό το μάθημα δεν υπάρχει· μετριέται στοhttp_requetes_total{code="404"}, γράφεται σε ένα logWARNING, και τοupμένει στο1. Εδώ,Le délai de l'opération a expiré(η λειτουργία έληξε): κανείς δεν απαντά, δεν υπάρχει ούτε κωδικός ούτε log από την πλευρά του API, και είναι τοupπου πέφτει στο0. Δύο καταστάσεις, δύο σήματα, δύο σημεία όπου πρέπει να ψάξεις.
.\labo.ps1 reparer
== Réparation ==
✔ API redémarrée
api .. prêt (6 s)
✔ taux d'erreurs remis à 0.01, lenteur à 0 ms
Les alertes passent en « resolved » dans les minutes qui suivent (voir http://localhost:8090).Το σενάριο έκανε docker compose start api, περίμενε το /sante να απαντήσει, και μετά κάλεσε το /admin/reparer (χρήσιμο για τα δύο άλλα σενάρια βλάβης). Έλεγξε ότι το API δουλεύει:
.\labo.ps1 journal apilabo-api | {"horodatage": "2026-09-15T19:41:48.720+00:00", "niveau": "INFO", "id_requete": "6a4228a46a56", "methode": "POST", "route": "/inscriptions", "code": 201, "duree_ms": 15.6, "message": "POST /inscriptions -> 201"}
labo-api | {"horodatage": "2026-09-15T19:41:48.914+00:00", "niveau": "WARNING", "id_requete": "b4ebe9fa32d8", "methode": "GET", "route": "inconnue", "code": 404, "duree_ms": 0.4, "message": "GET /inexistant -> 404"}
labo-api | {"horodatage": "2026-09-15T19:41:48.937+00:00", "niveau": "INFO", "id_requete": "732adf61ee17", "methode": "GET", "route": "/cours/{id}", "code": 200, "duree_ms": 25.7, "message": "GET /cours/C0043 -> 200"}Μετά κοίτα την ειδοποίηση να σβήνει, με την ίδια σειρά που άναψε. Χρονομετρημένο στο μηχάνημα του μαθήματος μετά το reparer:
t+15 s : APIInjoignable firing (Prometheus) · webhook : firing
t+40 s : APIInjoignable inactive (Prometheus) · webhook : firing
t+55 s : APIInjoignable inactive (Prometheus) · webhook : resolvedΣτο πρώτο επιτυχημένο scrape, το up{job="api"} επανέρχεται στο 1 και ο κανόνας ξαναπερνά σε inactive· ο Alertmanager στέλνει τότε μια γνωστοποίηση resolved στο webhook. Επαναφόρτωσε το http://localhost:8090: δύο γραμμές τώρα για την APIInjoignable, μία firing και μία resolved, και στο /alertes.json η δεύτερη έχει ένα πεδίο fin συμπληρωμένο:
{"recu_a":"2026-09-15T19:42:26+00:00","etat":"resolved","nom":"APIInjoignable",…,"debut":"2026-09-15T19:41:11.496Z","fin":"2026-09-15T19:41:56.496Z",…}fin μείον debut: η βλάβη διήρκεσε 45 δευτερόλεπτα στα μάτια του Prometheus. Σημείωσε το recu_a της γραμμής resolved: τρίτη γραμμή του παραδοτέου σου.
Το API τρέχει. Ζήτησέ του ένα μάθημα που δεν υπάρχει:
Invoke-RestMethod http://localhost:8000/cours/C9999Invoke-RestMethod : {"detail":"cours C9999 introuvable"}Είναι ένα σφάλμα HTTP 404: το API απάντησε. Για να δεις τον ίδιο τον κωδικό:
try { Invoke-WebRequest http://localhost:8000/cours/C9999 -UseBasicParsing } catch { $_.Exception.Response.StatusCode.value__ }404Ξαναπληκτρολόγησε το P4 στον Prometheus αντικαθιστώντας το 500 με 404: η σειρά route="/cours/{id}" αυξήθηκε κατά 1. Ξαναπληκτρολόγησε το G1 προσθέτοντας |= "C9999": το αίτημά σου είναι εκεί, επίπεδο WARNING, με το id_requete του. Τίποτα από όλα αυτά δεν υπάρχει για τη βλάβη του A.6: ένα σταματημένο API δεν μετρά τίποτα και δεν γράφει τίποτα.
.\labo.ps1 etat
== Conteneurs ==
NAME SERVICE STATUS
labo-alertmanager alertmanager Up 23 minutes (healthy)
labo-alloy alloy Up 23 minutes (healthy)
labo-api api Up 53 seconds (healthy)
labo-cadvisor cadvisor Up 23 minutes (healthy)
labo-charge charge Up 23 minutes (healthy)
labo-grafana grafana Up 23 minutes (healthy)
labo-loki loki Up 23 minutes (healthy)
labo-node-exporter node-exporter Up 23 minutes (healthy)
labo-prometheus prometheus Up 23 minutes (healthy)
labo-webhook webhook Up 23 minutes (healthy)
== Supervision ==
✔ Prometheus répond — cibles up : 8/8
séries en mémoire : 15395
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 — 2 alerte(s) reçue(s) (http://localhost:8090)
Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.Τι πρέπει να έχεις: το labo-api ξανά Up … (healthy) με μικρότερο χρόνο από τα άλλα (μόλις επανεκκίνησε), 8/8, 0 active(s), 64 cours, 2 alerte(s) reçue(s) (η firing και η resolved), και η τελευταία γραμμή Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.. Στο μηχάνημα του μαθήματος, αυτή η έξοδος έφερε ακόμη 1 alertes actives και 3 alerte(s) reçue(s) λόγω του υπολειμματικού TauxErreursEleve που αναφέρθηκε στο A.6· σε σένα, με τη μόνη βλάβη api, οι τιμές είναι οι παραπάνω. Αυτή η έξοδος και οι τρεις σημειωμένες ώρες σου είναι το παραδοτέο σου. Μπορείς να αφήσεις το εργαστήριο να τρέχει για τα εργαστήρια 06 και 07, ή να το σταματήσεις με .\labo.ps1 arreter: τα δεδομένα διατηρούνται και το demarrer συνεχίζει από εκεί που ήσουν.
Όλες οι εντολές πληκτρολογούνται σε ένα τερματικό bash, από τον φάκελο lab3. Σε macOS και Windows (WSL 2 ή Git Bash), το Docker Desktop πρέπει να είναι εκκινημένο· σε εγγενές Linux, το docker info πρέπει να απαντά χωρίς sudo (αλλιώς sudo usermod -aG docker $USER, και μετά άνοιξε μια νέα συνεδρία). Το σενάριο bash χρειάζεται το curl.
cd ~
git clone https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr.git lab3
cd lab3
lsΠρέπει να δεις docker-compose.yml, labo.sh, labo.ps1, README.md, και τους φακέλους alertmanager, alloy, api, charge, grafana, loki, modules, outils, prometheus, webhook. Σε Linux και macOS, κάνε το σενάριο εκτελέσιμο μία φορά: chmod +x labo.sh (χωρίς αυτό, το ./labo.sh απαντά bash: ./labo.sh: Permission denied). Αν έχεις ήδη κλωνοποιήσει το kit στο μάθημα 03, παράλειψε αυτό το βήμα και κάνε απλώς cd lab3.
./labo.sh prerequis
== Prérequis ==
✔ docker : Docker version 29.3.1, build c2be9cc
✔ le démon Docker répond
✔ docker compose : 5.1.1
✔ curl : présent
✔ mémoire disponible pour Docker : 31 Go
✔ processeurs : 20
✔ port 9090 libre
✔ port 9093 libre
✔ port 3000 libre
✔ port 3100 libre
✔ port 12345 libre
✔ port 9100 libre
✔ port 8080 libre
✔ port 8000 libre
✔ port 8090 libre
Tout est prêt. Lancez : ./labo.sh demarrerΣημείο ελέγχου: η τελευταία γραμμή είναι Tout est prêt.. Το σενάριο bash ελέγχει μια γραμμή παραπάνω από το PowerShell, curl : présent. Οι εκδόσεις και η μνήμη είναι αυτές του μηχανήματος του μαθήματος (Git Bash σε Windows). Αν μια θύρα είναι κατειλημμένη, το μάθημα 03 εξηγεί τι να κάνεις· για την 3000, GRAFANA_PORT=3001 ./labo.sh demarrer.
./labo.sh demarrerΤέλος της αναμενόμενης εξόδου, μόλις κατέβουν οι εικόνες:
== 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.sh etat (laissez tourner 2 minutes pour avoir des courbes)Σημείο ελέγχου: δέκα prêt, μετά Le labo est prêt..
etat./labo.sh etat
== Conteneurs ==
NAME SERVICE STATUS
labo-alertmanager alertmanager Up 11 minutes (healthy)
labo-alloy alloy Up 10 minutes (healthy)
labo-api api Up 4 minutes (healthy)
labo-cadvisor cadvisor Up 11 minutes (healthy)
labo-charge charge Up 10 minutes (healthy)
labo-grafana grafana Up 10 minutes (healthy)
labo-loki loki Up 11 minutes (healthy)
labo-node-exporter node-exporter Up 11 minutes (healthy)
labo-prometheus prometheus Up 11 minutes (healthy)
labo-webhook webhook Up 11 minutes (healthy)
== Supervision ==
✔ Prometheus répond — cibles up : 8/8
séries en mémoire : 10602
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.Σημείο ελέγχου: δέκα (healthy), 8/8, 64 cours, και η τελευταία γραμμή Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.. Το μπλοκ Conteneurs προέρχεται από το docker compose ps· μπορείς να το πληκτρολογήσεις εσύ ο ίδιος. Τρίτη διαδρομή, σε curl:
curl -s http://localhost:8000/sante
curl -s http://localhost:8090/sante{"etat":"ok","version":"1.0.0","cours":64}
{"etat":"ok","alertes_en_memoire":0,"notifications":0,"alertes":0}Στον περιηγητή, http://localhost:9090 → Status → Target health: οκτώ μπλοκ 1 / 1 up, κατάσταση UP, στήλη Last scrape πάντα κάτω από 15 δευτερόλεπτα. Σε curl, με python3 για να διαβάσεις το JSON (ή jq αν το έχεις):
curl -s http://localhost:9090/api/v1/targets | python3 -c 'import json,sys; [print(t["labels"]["job"].ljust(14), t["health"], t["scrapeUrl"]) for t in sorted(json.load(sys.stdin)["data"]["activeTargets"], key=lambda t: t["labels"]["job"])]'alertmanager up http://alertmanager:9093/metrics
alloy up http://alloy:12345/metrics
api up http://api:8000/metrics
cadvisor up http://cadvisor:8080/metrics
grafana up http://grafana:3000/metrics
loki up http://loki:3100/metrics
node-exporter up http://node-exporter:9100/metrics
prometheus up http://localhost:9090/metricsΠερίμενε δύο λεπτά μετά το demarrer, και μετά ακολούθησε τις ενότητες Prometheus, στην καρτέλα Graph και Grafana, στο Explore παραπάνω· γίνονται στον περιηγητή, με τον ίδιο τρόπο σε όλα τα συστήματα. Τα ερωτήματα είναι έτοιμα για αντιγραφή:
cat modules/01-le-labo/requetes.txt
cat modules/01-le-labo/requetes-logql.txtΤρίτη διαδρομή, ένα ερώτημα PromQL σε curl:
curl -s 'http://localhost:9090/api/v1/query?query=up' | python3 -m json.tool | head -n 20Αναγνωρίζεις στο JSON τις ίδιες σειρές με την καρτέλα Table: "metric": {"__name__": "up", "instance": "localhost:9090", "job": "prometheus"} και "value": [1789500809.696, "1"].
Σημείο ελέγχου: το P1 επιστρέφει 8 σειρές στο 1, το P12 επιστρέφει Empty query result, το G1 επιστρέφει γραμμές JSON, το G8 επιστρέφει τρεις καμπύλες.
Σημείωσε την ώρα (date +%T), μετά:
./labo.sh casser api
== Panne : arrêt de l'API ==
Container labo-api Stopping
Container labo-api Stopped
✔ API arrêtée. La charge continue de frapper dans le vide.
À observer : ./labo.sh etat · http://localhost:9090/targets (api → down)
http://localhost:9090/alerts (APIInjoignable : pending puis firing après 30 s)
http://localhost:9093 et http://localhost:8090 (l'alerte arrive ~10 s après firing)
Pour tout remettre en ordre : ./labo.sh reparerΤο σενάριο έκανε docker compose stop api. Έχεις περίπου 70 δευτερόλεπτα πριν φτάσει η ειδοποίηση στο webhook. Παρατήρησε από πέντε διαδρομές.
Διαδρομή 1, etat:
./labo.sh etat
== Conteneurs ==
NAME SERVICE STATUS
labo-alertmanager alertmanager Up 22 minutes (healthy)
labo-alloy alloy Up 22 minutes (healthy)
labo-api api Exited (0) About a minute ago
labo-cadvisor cadvisor Up 22 minutes (healthy)
labo-charge charge Up 22 minutes (healthy)
labo-grafana grafana Up 22 minutes (healthy)
labo-loki loki Up 22 minutes (healthy)
labo-node-exporter node-exporter Up 22 minutes (healthy)
labo-prometheus prometheus Up 22 minutes (healthy)
labo-webhook webhook Up 22 minutes (healthy)
== Supervision ==
✔ Prometheus répond — cibles up : 7/8
✘ cible api (http://api:8000/metrics) : down — Get "http://api:8000/metrics": dial tcp: lookup api on 127.0.0.11:53: no such host
séries en mémoire : 15116
alertes : 2 active(s), 0 en attente (pending)
✘ APIInjoignable [critique] — L'API catalogue ne répond plus
✘ TauxErreursEleve [critique] — Plus de 5 % des requêtes de l'API échouent
✔ Alertmanager répond (http://localhost:9093)
✔ Grafana répond (http://localhost:3000)
✔ Loki répond (http://localhost:3100)
✘ API catalogue ne répond pas (http://localhost:8000)
✔ Webhook répond — 2 alerte(s) reçue(s) (http://localhost:8090)
Labo : 9/10 services, 7/8 cibles up, 2 alertes actives.Τι πρέπει να διαβάσεις: το labo-api είναι Exited (0) (εκούσιο σταμάτημα)· ο Prometheus διαβάζει πλέον μόνο 7/8 στόχους και λέει γιατί (lookup api … no such host: το όνομα api εξαφανίστηκε από το δίκτυο Docker)· η APIInjoignable είναι ενεργή· το API δεν απαντά· το webhook έλαβε την ειδοποίηση. Αυτή η έξοδος καταγράφηκε ένα λεπτό μετά το casser api, σε ένα μηχάνημα όπου το casser erreurs είχε μόλις παιχτεί: εξ ου και η δεύτερη ειδοποίηση TauxErreursEleve. Σε σένα: 1 alertes actives, 1 alerte(s) reçue(s). Αν εκτελέσεις το etat στα πρώτα 30 δευτερόλεπτα, η ειδοποίηση είναι ακόμη pending και το webhook στο 0: ξαναεκτέλεσε ένα λεπτό αργότερα. Τρίτη διαδρομή, σε curl:
curl -s http://localhost:8000/santecurl: (7) Failed to connect to localhost:8000 after 2237 ms: Could not connect to serverΔιαδρομή 2, οι στόχοι. Επαναφόρτωσε το http://localhost:9090 → Status → Target health: το μπλοκ api είναι στο 0 / 1 up, κατάσταση DOWN, στήλη Error: Get "http://api:8000/metrics": dial tcp: lookup api on 127.0.0.11:53: no such host. Ξαναπληκτρολόγησε up στο Query: το up{instance="api:8000", job="api", service="api"} είναι στο 0. Μετά up == 0: μία μόνο γραμμή.
Διαδρομή 3, οι ειδοποιήσεις στον Prometheus. Μενού Alerts. Η APIInjoignable αλλάζει κατάσταση σε τρεις χρόνους, χρονομετρημένους στο μηχάνημα του μαθήματος:
t+0 s : APIInjoignable inactive (Prometheus n'a pas encore rescrappé l'API)
t+40 s : APIInjoignable pending (up{job="api"} == 0 est vrai, le compte à rebours « for: 30s » tourne)
t+70 s : APIInjoignable firing (vrai depuis 30 s : Prometheus envoie à Alertmanager)
t+70 s : APIInjoignable reçue par le webhook (firing)Γιατί 40 δευτερόλεπτα πριν από το pending: έως 15 δευτερόλεπτα για να αποτύχει ένα scrape (scrape_interval: 15s), και μετά έως 15 δευτερόλεπτα για την επόμενη αξιολόγηση των κανόνων (evaluation_interval: 15s). Γιατί 30 ακόμη: for: 30s στο alertes.yml. Ξαναπληκτρολόγησε ALERTS στο Query:
ALERTS{alertname="APIInjoignable", alertstate="pending", instance="api:8000", job="api", service="api", severite="critique"} 1και μετά alertstate="firing". Σημείωσε την ώρα του firing: πρώτη γραμμή του παραδοτέου σου. Σε curl, το ίδιο:
curl -s http://localhost:9090/api/v1/alerts | python3 -m json.toolΔιαδρομή 4, Alertmanager. Άνοιξε το http://localhost:9093: η σελίδα Alerts δείχνει μια ομάδα alertname="APIInjoignable" service="api" (το group_by του alertmanager.yml) με την ειδοποίηση, τα labels της (instance="api:8000", job="api", labo="observabilite", severite="critique"), την περίληψη L'API catalogue ne répond plus. Το label labo="observabilite" προέρχεται από τα external_labels του prometheus.yml. Σε curl:
curl -s http://localhost:9093/api/v2/alerts | python3 -c 'import json,sys; [print(a["labels"]["alertname"], a["status"]["state"], a["startsAt"]) for a in json.load(sys.stdin)]'APIInjoignable active 2026-09-15T19:41:11.496ZΔιαδρομή 5, το webhook. Άνοιξε το http://localhost:8090: μια γραμμή APIInjoignable · critique · firing · api · L'API catalogue ne répond plus, κεφαλίδα 1 alerte(s) en mémoire · 1 notification(s) reçue(s). Η ακατέργαστη μορφή:
curl -s http://localhost:8090/alertes.json | python3 -m json.tool[
{
"recu_a": "2026-09-15T19:41:26+00:00",
"etat": "firing",
"nom": "APIInjoignable",
"severite": "critique",
"service": "api",
"resume": "L'API catalogue ne répond plus",
"description": "Prometheus n'arrive plus à lire http://api:8000/metrics depuis 30 secondes (cible api:8000).",
"debut": "2026-09-15T19:41:11.496Z",
"fin": "0001-01-01T00:00:00Z",
"labels": {
"alertname": "APIInjoignable",
"instance": "api:8000",
"job": "api",
"labo": "observabilite",
"service": "api",
"severite": "critique"
}
}
]Το debut (19:41:11) είναι η ώρα του firing στον Prometheus· το recu_a (19:41:26) φτάνει δεκαπέντε δευτερόλεπτα αργότερα, εκ των οποίων τα 10 δευτερόλεπτα του group_wait. Το fin στο έτος 0001: δεν έχει τελειώσει ακόμη. Σημείωσε το recu_a: δεύτερη γραμμή του παραδοτέου σου.
Τι βλέπει το charge:
./labo.sh journal chargelabo-charge | {"horodatage": "2026-09-15T19:40:36.600+00:00", "niveau": "WARNING", "message": "API injoignable : ConnectionError"}
labo-charge | {"horodatage": "2026-09-15T19:40:40.925+00:00", "niveau": "WARNING", "message": "API injoignable : ConnectionError"}
labo-charge | {"horodatage": "2026-09-15T19:40:45.393+00:00", "niveau": "WARNING", "message": "API injoignable : ConnectionError"}Η ουσιώδης διαφορά μεταξύ ενός 404 και καμίας απόκρισης. Στο βήμα B.8, το
/cours/C9999θα απαντήσει404: το API τρέχει και σου λέει ότι αυτό το μάθημα δεν υπάρχει· μετριέται στοhttp_requetes_total{code="404"}, γράφεται σε ένα logWARNING, και τοupμένει στο1. Εδώ,curl: (7) Failed to connect: κανείς δεν απαντά, δεν υπάρχει ούτε κωδικός ούτε log από την πλευρά του API, και είναι τοupπου πέφτει στο0.
./labo.sh reparer
== Réparation ==
✔ API redémarrée
api .. prêt (6 s)
✔ taux d'erreurs remis à 0.01, lenteur à 0 ms
Les alertes passent en « resolved » dans les minutes qui suivent (voir http://localhost:8090).Το σενάριο έκανε docker compose start api, περίμενε το /sante, και μετά κάλεσε το /admin/reparer. Έλεγξε ότι το API δουλεύει:
./labo.sh journal apilabo-api | {"horodatage": "2026-09-15T19:41:48.720+00:00", "niveau": "INFO", "id_requete": "6a4228a46a56", "methode": "POST", "route": "/inscriptions", "code": 201, "duree_ms": 15.6, "message": "POST /inscriptions -> 201"}
labo-api | {"horodatage": "2026-09-15T19:41:48.914+00:00", "niveau": "WARNING", "id_requete": "b4ebe9fa32d8", "methode": "GET", "route": "inconnue", "code": 404, "duree_ms": 0.4, "message": "GET /inexistant -> 404"}
labo-api | {"horodatage": "2026-09-15T19:41:48.937+00:00", "niveau": "INFO", "id_requete": "732adf61ee17", "methode": "GET", "route": "/cours/{id}", "code": 200, "duree_ms": 25.7, "message": "GET /cours/C0043 -> 200"}Μετά η ειδοποίηση σβήνει, χρονομετρημένο στο μηχάνημα του μαθήματος μετά το reparer:
t+15 s : APIInjoignable firing (Prometheus) · webhook : firing
t+40 s : APIInjoignable inactive (Prometheus) · webhook : firing
t+55 s : APIInjoignable inactive (Prometheus) · webhook : resolvedΤρίτη διαδρομή, επιτήρηση σε βρόχο:
watch -n 5 'curl -s http://localhost:8090/alertes.json | python3 -c "import json,sys; [print(a[\"recu_a\"], a[\"nom\"], a[\"etat\"]) for a in json.load(sys.stdin)]"'2026-09-15T19:42:26+00:00 APIInjoignable resolved
2026-09-15T19:41:26+00:00 APIInjoignable firingΗ γραμμή resolved φέρει ένα πεδίο fin συμπληρωμένο (2026-09-15T19:41:56.496Z): 45 δευτερόλεπτα βλάβης στα μάτια του Prometheus. Σημείωσε το recu_a της: τρίτη γραμμή του παραδοτέου σου. Ctrl+C για να βγεις από το watch.
curl -s -i http://localhost:8000/cours/C9999HTTP/1.1 404 Not Found
date: Tue, 15 Sep 2026 20:50:01 GMT
server: uvicorn
content-length: 36
content-type: application/json
x-id-requete: bbc7be667f1d
{"detail":"cours C9999 introuvable"}Το API απάντησε: έναν κωδικό 404, ένα αναγνωριστικό x-id-requete, ένα σώμα JSON. Ξαναπληκτρολόγησε το P4 στον Prometheus με 404 στη θέση του 500: η σειρά route="/cours/{id}" αυξήθηκε κατά 1. Ξαναπληκτρολόγησε το G1 προσθέτοντας |= "bbc7be667f1d" (το δικό σου αναγνωριστικό, διαβασμένο στην κεφαλίδα): το αίτημά σου είναι εκεί, επίπεδο WARNING. Τίποτα από όλα αυτά δεν υπάρχει για τη βλάβη του B.6: ένα σταματημένο API δεν μετρά τίποτα και δεν γράφει τίποτα.
./labo.sh etat
== Conteneurs ==
NAME SERVICE STATUS
labo-alertmanager alertmanager Up 23 minutes (healthy)
labo-alloy alloy Up 23 minutes (healthy)
labo-api api Up 53 seconds (healthy)
labo-cadvisor cadvisor Up 23 minutes (healthy)
labo-charge charge Up 23 minutes (healthy)
labo-grafana grafana Up 23 minutes (healthy)
labo-loki loki Up 23 minutes (healthy)
labo-node-exporter node-exporter Up 23 minutes (healthy)
labo-prometheus prometheus Up 23 minutes (healthy)
labo-webhook webhook Up 23 minutes (healthy)
== Supervision ==
✔ Prometheus répond — cibles up : 8/8
séries en mémoire : 15395
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 — 2 alerte(s) reçue(s) (http://localhost:8090)
Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.Τι πρέπει να έχεις: το labo-api ξανά Up … (healthy), 8/8, 0 active(s), 64 cours, 2 alerte(s) reçue(s) (η firing και η resolved), και Labo : 10/10 services, 8/8 cibles up, 0 alertes actives.. Στο μηχάνημα του μαθήματος, αυτή η έξοδος έφερε ακόμη 1 alertes actives και 3 alerte(s) reçue(s) λόγω του υπολειμματικού TauxErreursEleve που αναφέρθηκε στο B.6· σε σένα, με τη μόνη βλάβη api, οι τιμές είναι οι παραπάνω. Αυτή η έξοδος και οι τρεις σημειωμένες ώρες σου είναι το παραδοτέο σου. Μπορείς να το αφήσεις να τρέχει για τα εργαστήρια 06 και 07, ή ./labo.sh arreter: τα δεδομένα διατηρούνται.
Το etat λέει 9/10 services ενώ δεν έσπασες τίποτα. Κοίτα ποιο container δεν είναι (healthy). Αν είναι το grafana ή το alloy αμέσως μετά το demarrer με (health: starting), περίμενε τριάντα δευτερόλεπτα. Αν είναι το labo-api σε Exited, κάποιος (ίσως εσύ, σε προηγούμενη δοκιμή) εκτέλεσε casser api: reparer. Αν ένα container είναι Restarting, διάβασε το ημερολόγιό του: .\labo.ps1 journal <service> ή ./labo.sh journal <service>.
cibles up : 7/8 και APIInjoignable ενεργή, αλλά το API απαντά στο http://localhost:8000. Ο Prometheus διαβάζει το API μέσα από το δίκτυο Docker (http://api:8000/metrics), εσύ μέσα από τη δημοσιευμένη θύρα (localhost:8000). Αν το API μόλις επανεκκίνησε, ο Prometheus μπορεί να έχει καθυστέρηση ενός scrape (15 s) και η ειδοποίηση μένει firing έως την επόμενη αξιολόγηση, και μετά λίγα δευτερόλεπτα ακόμη για να λάβει το webhook το resolved. Περίμενε ένα λεπτό και ξαναεκτέλεσε το etat.
Οι ειδοποιήσεις αργούν: το pending δεν περνά σε firing. Η APIInjoignable έχει for: 30s· χρειάζονται λοιπόν έως 15 s (scrape) + 15 s (αξιολόγηση) + 30 s (for) = 60 έως 70 s για το firing, και μετά 10 s group_wait για το webhook. Δεν είναι αργό, είναι ρυθμισμένο για να αποφεύγονται ψευδείς ειδοποιήσεις σε μια μεμονωμένη αποτυχία. Αν μετά από δύο λεπτά τίποτα δεν κινείται, έλεγξε ότι το labo-api είναι πράγματι Exited (docker compose ps).
Το webhook μένει στο 0 alerte(s) reçue(s) ενώ ο Alertmanager δείχνει την ειδοποίηση. Άνοιξε το http://localhost:9093 → Status: η ενότητα Config πρέπει να δείχνει receiver: webhook και url: http://webhook:8090/alertes. Μετά journal webhook: πρέπει να δεις μια γραμμή POST /alertes σε κάθε γνωστοποίηση. Αν το container labo-webhook δεν είναι healthy, docker compose restart webhook.
Empty query result στο P3, P5 ή G1, αμέσως μετά το demarrer. Ο Prometheus χρειάζεται τουλάχιστον ένα scrape (15 s) για το P3, δύο για το P5 (το rate θέλει δύο σημεία στο [1m]), και το Alloy χρειάζεται λίγα δευτερόλεπτα για να στείλει την πρώτη γραμμή στο Loki. Περίμενε δύο λεπτά μετά το Le labo est prêt.. Αν το {service="api"} μένει κενό μετά από πέντε λεπτά, έλεγξε το http://localhost:12345 (το Alloy πρέπει να είναι ready και τα συστατικά του Healthy) και journal alloy.
Το P10 επιστρέφει NaN. Το histogram_quantile επιστρέφει NaN όταν το παράθυρο [5m] δεν περιέχει ακόμη αρκετά σημεία. Περίμενε πέντε λεπτά μετά την εκκίνηση, ή αντικατάστησε το [5m] με [1m] για να δεις μια τιμή νωρίτερα (λιγότερο σταθερή).
Το G1 επιστρέφει μηδέν γραμμές ενώ το API τρέχει. Έλεγξε πρώτα την περίοδο (πάνω δεξιά, Last 1 hour) και το όνομα του label (service, με πεζά). Μετά το http://localhost:12345: το Alloy πρέπει να απαντά Alloy is ready. και το journal alloy δεν πρέπει να δείχνει επαναλαμβανόμενο σφάλμα. Ως έσχατη λύση, docker compose restart alloy.
parse error στον Prometheus. Τα τρία πιο συχνά, όλα ορατά σε αυτή τη σελίδα: unexpected identifier "api" in label matching, expected string (ξεχασμένα εισαγωγικά: {job=api})· unexpected character inside braces: '5' ({code=500} αντί για {code="500"})· expected type range vector in call to function "rate", got instant vector (ξεχασμένο παράθυρο [1m]).
parse error στο Loki. syntax error: unexpected IDENTIFIER: ξέχασες τα άγκιστρα (service="api" αντί για {service="api"}). unexpected $end, expecting } or ,: λείπει το άγκιστρο κλεισίματος. Μηδέν γραμμές χωρίς σφάλμα: όνομα ή πεζά/κεφαλαία του label ({service="API"}, {app="api"}).
Μόνο Windows — το Invoke-RestMethod εμφανίζει περίεργους χαρακτήρες (é) στους τίτλους των μαθημάτων. Είναι η εμφάνιση της κονσόλας, όχι το API. [Console]::OutputEncoding = [Text.Encoding]::UTF8 πριν από την εντολή, ή διάβασε στον περιηγητή.
Μόνο Windows — .\labo.ps1: «l'exécution de scripts est désactivée sur ce système» (η εκτέλεση σεναρίων είναι απενεργοποιημένη σε αυτό το σύστημα). Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, απάντησε O, ξαναεκτέλεσε.
Μόνο Windows — το demarrer αποτυγχάνει με port is already allocated στην 3000. Ένα άλλο πρόγραμμα ακούει ήδη (συχνά μια εφαρμογή Node). $env:GRAFANA_PORT = '3001' και μετά ξαναεκτέλεσε το demarrer· το Grafana είναι τότε στο http://localhost:3001 και το etat το εμφανίζει έτσι.
Μόνο εγγενές Linux — permission denied while trying to connect to the Docker daemon socket. sudo usermod -aG docker $USER, κλείσε τη συνεδρία, ξανάνοιξέ την, το docker info πρέπει να απαντά.
Εγγενές Linux και macOS — bash: ./labo.sh: Permission denied. Το αρχείο είναι αποθηκευμένο χωρίς το bit εκτέλεσης στο αποθετήριο: chmod +x labo.sh μία φορά, ή εκτέλεσε bash labo.sh prerequis. Σε Git Bash (Windows), το ερώτημα δεν τίθεται.
Θέλεις να ξεκινήσεις από το μηδέν. Το .\labo.ps1 reinitialiser ή ./labo.sh reinitialiser διαγράφει τα containers και τους τόμους: ο Prometheus, το Loki και το Grafana ξεκινούν κενά. Μετά demarrer. Να μην το κάνεις σε ένα εργαστήριο που μοιράζεσαι με άλλους.