كيف تقرأ هذه الصفحة. كل قسم مطويّ تحت عنوانه : انقر على « عرض … » لفتحه، وأغلقه عندما تنتهي لإبقاء الصفحة مقروءة. ترتيب القراءة : الهدف، ثم باختصار (الأوامر التي ستكتبها)، ثم مجموعة البيانات (تُقرأ قبل أي استعلام)، ثم استعلامات Prometheus (من P1 إلى P12) وGrafana Explore (من G1 إلى G8)، مرتبة من الأبسط (
upو{service="api"}) إلى الأكثر تعبيراً، مع شرح بعد كل واحد. الخطوات المفصّلة، مع المخرج المتوقع لكل أمر والعطل الذي يجب استحضاره، في الملحق : الملحق A لـ Windows (PowerShell)، الملحق B لـ Linux وmacOS وWSL 2 وGit Bash. افتح ملحقاً واحداً فقط، ملحق نظامك. الملحق C، المشترك، يجمع الحالات التي تحدث فيها مشكلة. جميع مخرجات هذه الصفحة التُقطت على مختبر الدورة ؛ القيم التي تعتمد على اللحظة (العدادات والمدد والطوابع الزمنية) ستكون مختلفة عندك، لكن الأشكال ستكون متطابقة.
تنضم إلى الفريق الذي يشغّل كتالوج دورات منصة عبر الإنترنت. تمدّ لك رئيستك حقيبة المختبر : « غداً صباحاً، أريد حزمة قابلية المراقبة تعمل على جهازك، وAPI داخلها، والدليل على أنك تعرف قراءة عطل دون الاتصال بي. » ستبدأ إذن تشغيل الخدمات العشر، وتثبت أن Prometheus يقرأ أهدافه الثمانية فعلاً وأن Loki يستقبل سجلات API، وتكتب اثني عشر استعلام PromQL وثمانية استعلامات LogQL لتتعلم قراءة ما يقيسه المختبر، ثم توقف API عمداً. ستشاهد العطل ينتشر : يراه etat في ثانيتين، ويضع Prometheus الهدف في DOWN، وينتقل التنبيه APIInjoignable من pending إلى firing، ويرسله Alertmanager إلى webhook. ثم تصلح وتشاهد التنبيه ينطفئ. التعرف على « هذه الخدمة متوقفة » في عشر ثوانٍ، ومعرفة أين تبحث عنها، هو ما يوفر ساعات من البحث في المكان الخطأ.
الخطوات التسع في هذا المخطط مفصّلة، مع المخرج المتوقع لكل أمر، في الملحق A (Windows) أو الملحق B (Linux وmacOS) في أسفل الصفحة.
حقيبة المختبر : https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr
تستنسخ الحقيبة في مجلد lab3، وتتحقق من أن Docker جاهز، وتبدأ تشغيل الخدمات العشر، وتفتح صفحات الويب، وتكتب الاستعلامات، ثم تكسر وتصلح. في النهاية، يجب أن يعرض 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 في عناوين 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 في عناوين Grafana.
قبل كتابة استعلام واحد، انظر إلى ما يراقبه المختبر. كل شيء يدور حول API لكتالوج دورات : خدمة ويب صغيرة مكتوبة بلغة Python (FastAPI) تقدّم 64 دورة وتسجّل التسجيلات. خدمة ثانية، charge، تلعب دور المستخدمين : تستدعي API باستمرار، بطلبات ناجحة، وبضعة 404 متعمدة، ومرة من كل مئة، خطأ 500 يصنعه API بنفسه. كل ما ستقرؤه في Prometheus وفي Loki يأتي من هاتين الخدمتين. بقية الحقيبة، بوضوح :
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 مللي ثانية) لتغذية المدرج التكراري للكمون |
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 من سطر السجل المكتوب لهذا الطلب. سيفيدك في الاستعلام 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 | يرتفع وينخفض : طلب واحد قيد المعالجة في لحظة القراءة. يُقرأ كما هو (P11) |
| histogram (مدرج تكراري) | http_duree_requete_seconds | سلال تراكمية : 3248 طلباً على /cours استغرقت أقل من 50 مللي ثانية (le="0.05")، و3279 في المجموع (+Inf = _count). _sum / _count = المدة المتوسطة (هنا 26 مللي ثانية). يستخرج P10 منها p95 |
| info (مقياس لحظي عند 1) | api_info | القيمة دائماً 1 ؛ المعلومة في التسمية version="1.0.0" |
لا يوجد summary في هذا الـ API : ينصح Prometheus بعدم استخدامه لصالح المدرج التكراري، الذي يُجمَّع بين المثيلات.
السلسلة هي اسم مقياس بالإضافة إلى مجموعة أزواج label="valeur". مصدران :
| التسمية | يضعها | القيم على المختبر |
|---|---|---|
methode | API | GET وPOST |
route | API | /sante و/cours و/cours/{id} و/inscriptions و/lent و/admin/etat وinconnue (أي عنوان غير موجود) |
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 للمهمة api | api |
لاحظ المسار /cours/{id} : يوحّد API العنوان قبل العدّ. /cours/C0001 و/cours/C0043 يقعان في السلسلة نفسها. بدون ذلك، ستكون هناك 64 سلسلة لكل رمز بدلاً من واحدة، و64 ضعفاً من الأسطر في /metrics.
يكتب API سطر سجل لكل طلب يعالجه، بصيغة JSON. يقرأ Alloy مخرج كل حاوية 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 | القيم نفسها التي في تسميات http_requetes_total : هذا هو الجسر بين المقاييس والسجلات |
duree_ms | 0.1 | مدة المعالجة، بالمللي ثانية |
message | GET /cours/C0019 -> 500 | الجملة المقروءة، مع العنوان الحقيقي هذه المرة (C0019، وليس {id}) |
لا يقرأ Loki الـ JSON سطراً بسطر عند الاستعلام، إلا إذا طلبت منه ذلك (| json، الاستعلام G5). ما يفهرسه هو تسميات يضعها Alloy عند الوصول :
| تسمية Loki | القيم | يضعها |
|---|---|---|
service | api وcharge وwebhook وprometheus وalertmanager وgrafana وloki وalloy وnode-exporter وcadvisor | Alloy، وفقاً لاسم خدمة Compose |
conteneur | labo-api وlabo-charge… | Alloy، وفقاً لاسم الحاوية |
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 الخاص بك، لا API الدورة. الأشكال (عدد السلاسل، والتسميات، ورتبة المقدار) يجب أن تكون نفسها. يستخدم Prometheus وLoki فكرة الانطلاق نفسها، مجموعة تسميات بين أقواس معقوفة، وهذا مقصود : ما تتعلمه في 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 هو من يصنعه عند كل كشط : 1 إذا استجابت صفحة /metrics، و0 خلاف ذلك. ثماني سلاسل لأن prometheus.yml يعلن ثماني مهام. يُقرأ كل سطر هكذا : اسم المقياس، ثم بين الأقواس المعقوفة التسميات التي وضعها Prometheus (job وinstance على الكل، وservice إضافة على API)، ثم القيمة. المكافئ في SQL : SELECT * FROM up. ما يفعله Prometheus ولا يفعله SQL : أنتج هذا الجدول بنفسه بالذهاب للطرق على ثمانية أبواب.
للمختبر عشر حاويات لكن Prometheus لا يقرأ سوى ثمانٍ : charge وwebhook لا يعرضان صفحة /metrics في هذا الإصدار من الحقيبة، لذلك ليسا هدفين. يعدّ etat الاثنين على حدة : 10/10 services (الحاويات) و8/8 cibles up (عمليات الكشط). إذا أعاد up يوماً 7 سلاسل بدلاً من 8، فليس ذلك لأن هدفاً سقط (سيكون بقيمة 0) : بل لأن مهمة اختفت من الإعداد. للحقيبة تنبيه لذلك، CibleAbsente.
up{job="api"}up{instance="api:8000", job="api", service="api"} 1ما يطلبه الاستعلام : « قيمة up، فقط للسلاسل التي تساوي تسميتها job القيمة api. »
جديد واحد فقط : المحدد {job="api"}. تصفّي الأقواس المعقوفة حسب التسميات، مثل 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 ثلاث تسميات (instance وjob وservice) وتختلف القيمة ببضع وحدات : قُرئت الصفحة في لحظة أخرى. سلسلة لكل تركيبة (methode وroute وcode) : هذا ما يُسمى العدد الكلي (cardinalité) للمقياس، هنا 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 التي تساوي تسميتها 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 : لم تحسب شيئاً بعد. قرأت قيماً لحظية وتعلمت تصفيتها حسب التسمية.
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] تنعّم أكثر (منحنى أهدأ، وردّ فعل أبطأ). تستخدم الحقيبة [5m] في تنبيهاتها و[1m] هنا حتى ترى شيئاً يتحرك. قاعدة عملية : يجب أن تحتوي النافذة على كشطين على الأقل، إذن هنا [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 واجمعها مع الاحتفاظ بالتسمية route فقط. »
جديد واحد فقط : التجميع sum by (route) (…). يُذيب جميع التسميات الأخرى (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 بتسمية أخرى. هذا ما يفعله اللوح « Réponses par code » (الاستجابات حسب الرمز) في لوحة المعلومات « API catalogue — signaux dorés » في Grafana. خمسة رموز، خمسة أسطر ؛ الـ 500 عند 0,09 في الثانية، أي أكثر بقليل من خطأ واحد كل اثنتي عشرة ثانية.
sum(rate(http_requetes_total[1m])){} 8.956550727858652ما يطلبه الاستعلام : « اجمع كل السرعات، دون الاحتفاظ بأي تسمية. »
جديد واحد فقط : sum(…) بدون by. النتيجة : سلسلة واحدة، بمجموعة تسميات فارغة ({})، والقيمة 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 السلاسل التي لها التسميات نفسها، وهنا للطرفين مجموعة فارغة {}، إذن تتطابق. النتيجة : 0,0099، أي 1 % ؛ إنها القيمة المضبوطة في /admin/etat (taux_erreurs: 0.01). الإشارة الذهبية الثانية، الأخطاء. تنطلق قاعدة التنبيه TauxErreursEleve في الحقيبة عندما يتجاوز هذا التعبير نفسه، محسوباً على 5 دقائق، القيمة 0,05.
histogram_quantile(0.95, sum by (le) (rate(http_duree_requete_seconds_bucket[5m]))){} 0.09797705555555555ما يطلبه الاستعلام : « انطلاقاً من سلال المدرج التكراري للمدة، لجميع المسارات مجتمعة، تحت أي قيمة تقع 95 % من طلبات الدقائق الخمس الأخيرة ؟ »
جديد واحد فقط : histogram_quantile(0.95, …). تريد كمدخل السلال _bucket مجموعة حسب le (لهذا السبب sum by (le) إلزامي : بدونه، تحسب كمّيّاً لكل مسار ولا تعود للنتيجة المعنى الذي كنت تتوقعه). النتيجة : 0,098 ثانية، إذن 95 % من الطلبات تُخدم في أقل من 98 مللي ثانية. هذه هي الإشارة الذهبية الثالثة، الكمون، والمقياس الذي يراقبه التنبيه LatenceP95Elevee (العتبة : 0,5 ثانية). انظر إلى توزيع سلال /cours في مجموعة البيانات : 3248 طلباً من 3279 تحت 50 مللي ثانية، لكن /lent (من 300 إلى 900 مللي ثانية) يسحب 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ما يطلبه الاستعلام : « آخر قيمة للمقياس اللحظي requetes_en_cours. »
لا جديد في الصياغة، إنه P1. الجديد هو النوع : يُقرأ المقياس اللحظي كما هو، دون rate(). صفر أو واحد، حسب اللحظة : يعالج API كل طلب في بضع مللي ثوانٍ، ومن النادر التقاط واحد قيد المعالجة. هذه هي الإشارة الذهبية الرابعة، التشبع : لو ارتفعت هذه القيمة إلى 50، لكان API مثقلاً. rate(requetes_en_cours[1m]) لا يسبب خطأً، لكنه يعيد رقماً لا معنى له ؛ لا يحميك Prometheus من هذا الالتباس.
ALERTSEmpty query resultما يطلبه الاستعلام : « التنبيهات الحالية pending أو firing. »
لا جديد : اسم مقياس، مثل P1. ALERTS، مثل up، يصنعه Prometheus : سلسلة لكل تنبيه نشط، مع التسميتين 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 هو خزانة دفاتر يوميات، مصنّف لكل تركيبة تسميات. لا يقرأ محتوى الأسطر لترتيبها، بل فقط ملصق المصنّف. يبدأ استعلام 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"}
…ما يطلبه الاستعلام : « جميع أسطر السجل التي تساوي تسميتها service القيمة api. »
لا جديد مقارنة بـ P2 : محدد بين أقواس معقوفة. الفرق هو النتيجة : أسطر نصية، لا أرقام. يعرض Grafana الوقت (محوَّلاً إلى منطقتك الزمنية) ثم السطر الخام ؛ انقر على سطر لعرض تسمياته : 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 ,).
التسمية سلسلة نصية دقيقة. {service="API"} بالأحرف الكبيرة لا يعيد أي سطر، دون خطأ : المصنّف غير موجود. الأمر نفسه مع {app="api"} : التسمية تُسمى service في هذا المختبر، وليس app. عندما يعيد استعلام LogQL صفر سطر، تحقق أولاً من اسم التسمية وحالة أحرفها ؛ في Explore، يعرض لك الوضع Builder التسميات وقيمها الموجودة، وهذه أضمن طريقة لاكتشافها.
{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 التي تساوي تسميتها niveau القيمة ERROR. »
جديد واحد فقط : تسميتان في المحدد، مفصولتان بفاصلة، وهذا يعني «و». التسمية 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 التي تساوي تسميتها code القيمة 500. »
لا جديد : G2 بتسمية أخرى. الأسطر نفسها كما في G2، لأن في هذا الـ API كل 500 هو ERROR والعكس صحيح. هذه نسخة السجل من P4 : حيث يقول لك Prometheus « 32 خطأً على /cours »، يريك Loki أيّها، مع العنوان الحقيقي (/cours/C0019) ومعرّف الطلب. لاحظ أن code هنا سلسلة نصية ("500") لأنها تسمية ؛ في 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. خلافاً للتسمية، يجب على Loki هنا فتح كل سطر في المصنّف {service="api"} للنظر داخله : أبطأ، لكن يمكنك البحث عن أي شيء. البدائل : != (لا يحتوي)، و|~ (تعبير نمطي)، و!~. النتيجة مختلطة : 201 و500، كل ما يتعلق بالتسجيلات.
حصيلة من G1 إلى G4 : طريقتان للتصفية، حسب التسمية (سريعة، قبل فتح الأسطر) وحسب النص (مرنة، بعده). يبدأ الاستعلام الجيد دائماً بأضيق تسمية ممكنة.
{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 إلى تسميات. »
جديد واحد فقط : المحلل | json. الأسطر المعروضة هي نفسها كما في G1، لكن افتح واحداً منها : له الآن تسميات أكثر بكثير (route وmethode وduree_ms وid_requete وmessage…)، واحدة لكل حقل في الـ JSON. هذه التسميات تُحسب لحظة الاستعلام، ولا تُخزَّن : لا يزال Loki لا يفهرس سوى service وconteneur وniveau وcode. سترى أيضاً code_extracted وniveau_extracted : عندما يحمل حقل في الـ JSON الاسم نفسه لتسمية وضعها 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. »
جديد واحد فقط : مرشح التسمية | duree_ms > 500، الذي يقارن تسمية مستخرجة برقم. هذا ممكن فقط بعد | json، وإلا فإن duree_ms غير موجود. النتيجة : /lent فقط، المسار البطيء عمداً (من 300 إلى 900 مللي ثانية). هذه نسخة السجل من P10 : يقول Prometheus « p95 عند 98 مللي ثانية » ؛ ويعرض 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 لكل شريحة دقيقة واحدة، ثم اجمع مع الاحتفاظ بالتسمية 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 : ترتيب أسطر عشر حاويات حسب التسميات وقراءة المصنّف الصحيح فقط، وفتح الـ 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 بتسمية إضافية في by : p95 لكل مسار. سترى /lent حول 0,7 ثانية والمسارات الأخرى تحت 0,03 ثانية. انظر إلى ما يغيّره ذلك مقارنة بـ p95 الإجمالي في P10.
{service="api"} |= "inscriptions" | json | code = 201G4 و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. إذا كنت قد استنسخت الحقيبة مسبقاً في الدرس 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.. على جهاز الدورة، والصور في الذاكرة المؤقتة مسبقاً، استغرق الأمر 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. ثماني كتل، واحدة لكل مهمة، كل منها بـ 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 ثماني سلاسل بقيمة 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 : أُوقفت الحاوية بشكل نظيف، وبياناتها وصورتها سليمة. الآن، راقب العطل عبر خمسة مسارات، بالترتيب. لديك حوالي 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 لأن الحاوية متوقفة) ؛ التنبيه APIInjoignable نشط ؛ لا يستجيب API على المنفذ 8000 ؛ استلم webhook شيئاً. التُقط هذا المخرج على جهاز الدورة بعد دقيقة من casser api، بينما كان casser erreurs قد شُغّل قبل بضع دقائق : لهذا السبب يظهر أيضاً تنبيه ثانٍ، TauxErreursEleve. على مختبرك، لن يكون لديك سوى APIInjoignable و1 alertes actives و1 alerte(s) reçue(s). إذا شغّلت etat خلال الثواني الثلاثين الأولى، فسيكون التنبيه لا يزال 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 ثانية حتى يفشل كشط، ثم يقيّم القواعد كل 15 ثانية. لماذا 30 ثانية إضافية قبل firing : تقول القاعدة for: 30s. في التقاط آخر، وصل pending عند 31 ثانية وfiring عند 61 ثانية : رتبة المقدار هي نفسها، والتفاصيل تعتمد على اللحظة التي كسرت فيها بالنسبة إلى دورة الكشط. أعد كتابة 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) مع التنبيه، وتسمياته (instance="api:8000" وjob="api" وlabo="observabilite" وseverite="critique")، وملخصه L'API catalogue ne répond plus ووصفه. التسمية 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) : فارق خمس عشرة ثانية، منها ثوانٍ group_wait العشر الخاصة بـ Alertmanager. fin في العام 0001 تعني « لم ينتهِ بعد ». دوّن recu_a : السطر الثاني في مخرجك المطلوب.
ما يراه الحمل، في هذه الأثناء :
.\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"}، ويُكتب في سجلWARNING، ويبقىupعند1. هنا،Le délai de l'opération a expiré(انتهت مهلة العملية) : لا أحد يجيب، لا رمز ولا سجل من جهة 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عند أول كشط ناجح، يعود 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). إذا كنت قد استنسخت الحقيبة مسبقاً في الدرس 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 ثماني سلاسل بقيمة 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 خلال الثواني الثلاثين الأولى، فسيكون التنبيه لا يزال 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_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) مع التنبيه، وتسمياته (instance="api:8000" وjob="api" وlabo="observabilite" وseverite="critique")، والملخص L'API catalogue ne répond plus. تأتي التسمية 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) بعد خمس عشرة ثانية، منها ثوانٍ group_wait العشر. fin في العام 0001 : لم ينتهِ بعد. دوّن recu_a : السطر الثاني في مخرجك المطلوب.
ما يراه الحمل :
./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"}، ويُكتب في سجلWARNING، ويبقىupعند1. هنا،curl: (7) Failed to connect: لا أحد يجيب، لا رمز ولا سجل من جهة 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 بينما لم تكسر شيئاً. انظر أي حاوية ليست (healthy). إذا كانت grafana أو alloy مباشرة بعد demarrer مع (health: starting)، انتظر ثلاثين ثانية. إذا كانت labo-api في حالة Exited، فقد شغّل أحدهم (ربما أنت، في محاولة سابقة) casser api : reparer. إذا كانت حاوية في حالة 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 متأخراً بكشط واحد (15 ثانية) ويبقى التنبيه firing حتى التقييم التالي، ثم بضع ثوانٍ إضافية حتى يستلم webhook الـ resolved. انتظر دقيقة وأعد تشغيل etat.
التنبيهات تتأخر : pending لا ينتقل إلى firing. لـ APIInjoignable القيمة for: 30s ؛ إذن يلزم حتى 15 ثانية (كشط) + 15 ثانية (تقييم) + 30 ثانية (for) = من 60 إلى 70 ثانية لـ firing، ثم 10 ثوانٍ من 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 مع كل إشعار. إذا لم تكن الحاوية labo-webhook في حالة healthy، نفّذ docker compose restart webhook.
Empty query result على P3 أو P5 أو G1، مباشرة بعد demarrer. يحتاج Prometheus إلى كشط واحد على الأقل (15 ثانية) لـ 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) ومن اسم التسمية (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 , : قوس إغلاق مفقود. صفر سطر دون خطأ : اسم التسمية أو حالة أحرفها ({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. الملف محفوظ دون بت التنفيذ في المستودع : chmod +x labo.sh مرة واحدة، أو شغّل bash labo.sh prerequis. تحت Git Bash (Windows)، لا يُطرح هذا السؤال.
تريد البدء من الصفر. .\labo.ps1 reinitialiser أو ./labo.sh reinitialiser يحذف الحاويات ومجلدات التخزين : يبدأ Prometheus وLoki وGrafana فارغين. ثم demarrer. لا تفعل ذلك على مختبر مشترك مع أشخاص آخرين.