ورشة عمل أساسية 1 — PromQL: مقياس واحد وتصنيف واحد ودالة واحدة

تدريب موجَّه13 دقيقة
المدة
20 دقيقة
الوحدة
1/7
المتطلبات الأساسية
المختبر يعمل منذ دقيقتين على الأقل (etat يعرض 8/8 cibles up)، وعلامة التبويب Query في Prometheus مفتوحة على http://localhost:9090
ستبني
عشرة استعلامات PromQL مكتوبة يدوياً، من الأقصر (api_info) إلى أول تجميع حقيقي (sum by (code) (rate(…[1m])))، مع تغيير شيء واحد فقط في كل مرة
المخرج المطلوب
مخرج الخطوة 10 كما يُعرض في علامة التبويب Table، سطران، مع جملة تقول ما يقيسه كل منهما

كيف تقرأ هذه الصفحة. عشر خطوات، استعلام واحد في كل مرة. لكل منها : الاستعلام الذي يجب كتابته، والاستجابة الدقيقة للمختبر (علامة التبويب Table)، وما يجب النظر إليه فيها. اكتب كل استعلام بنفسك (لا نسخ ولصق) : بكتابة الأقواس المعقوفة وعلامات الاقتباس والأقواس المربعة تترسّخ القواعد النحوية. ستكون الأرقام مختلفة عندك ؛ الأشكال (عدد الأسطر، والتسميات، ورتبة المقدار) يجب أن تكون نفسها. كتل « لفهم أعمق » اختيارية. إذا لم يكن المختبر قد بدأ، عد إلى التمرين الموجه : يعطي القسم باختصار الأوامر، بما فيها الحقيبة (https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr). لا يُنشأ ولا يُعدَّل شيء في هذه الورشة : PromQL لا يفعل سوى القراءة.

الهدف

جعلك التمرين الموجه تكتب اثني عشر استعلاماً مكتوباً مسبقاً. رأيت النتائج، لكن إذا سُحبت منك الورقة، هل تعرف كتابة sum by (code) (rate(http_requetes_total{route="/cours"}[1m])) دون الخطأ في قوس ؟ هنا، تنطلق من أقصر استعلام ممكن، اسم مقياس، وتضيف قطعة واحدة فقط في كل خطوة : تسمية، أو عامل، أو دالة، أو نطاق زمني، أو تجميع. خطوتان هما فخّان متعمدان : ستستحضر رسالتي الخطأ اللتين يصادفهما كل مبتدئ، للتعرف عليهما في المرة القادمة. في النهاية، تعرف ما هو المقياس والتسمية والدالة لأنك جمّعت القطع الثلاث بنفسك.

المصطلحات في صورة واحدة

Prometheus هو دفتر قراءات. كل 15 ثانية، يمرّ أمام كل هدف، ويقرأ صفحة /metrics الخاصة به ويدوّن كل قيمة مع الوقت. المقياس هو اسم عمود في الدفتر (up وhttp_requetes_total). التسمية هي ملصق يوضع على السطر ليقول عمّ نتحدث (job="api" وcode="200") ؛ اسم المقياس نفسه بملصقات مختلفة هو سلاسل مختلفة. الدالة هي عملية على ما قرأناه : عدّ الأسطر، أو حساب ميل، أو الجمع.

PromQLقاعدة SQL الكلاسيكيةفي هذه الورشة
مقياسجدولup وapi_info وhttp_requetes_total
سلسلةسطر في الجدولup{instance="api:8000", job="api", service="api"}
تسميةعمودjob وinstance وroute وcode
المحدد {job="api"}WHERE job = 'api'الخطوة 3
=~WHERE job LIKE 'a%' (بتعبير نمطي)الخطوة 5
count(…) وsum(…)COUNT(*) وSUM(…)الخطوتان 6 و10
by (code)GROUP BY codeالخطوة 10
النطاق [1m]« أسطر الدقيقة الأخيرة »الخطوة 7
rate(…[1m])لا مكافئ بسيط : ميل في الثانيةالخطوة 8
متجه لحظيقيمة واحدة لكل سلسلة، الآنما يعيده up
متجه نطاقعدة قيم مؤرخة لكل سلسلةما يعيده up[1m]

أين تكتب، وكيف تقرأ استجابة

افتح http://localhost:9090. أنت على صفحة Query. يقبل حقل الإدخال استعلاماً ؛ Execute (أو Enter) يرسله. تظهر النتيجة تحت الحقل، في علامة التبويب Table. ابقَ على Table طوال الورشة : هناك ترى التسميات مكتوبة بوضوح. ترسم علامة التبويب Graph الشيء نفسه في الزمن ؛ وتفكك علامة التبويب Explain الاستعلام.

لسطر النتيجة دائماً الشكل نفسه : اسم المقياس، ثم بين الأقواس المعقوفة التسميات مرتبة أبجدياً، ثم القيمة على اليمين :

text
up{instance="api:8000", job="api", service="api"}    1

عندما يُذيب الاستعلام الاسم (دالة، أو تجميع)، تبقى الأقواس المعقوفة، فارغة أحياناً : {} 8. تحت علامات التبويب، يخبرك Result series: N بعدد الأسطر لديك. تُكتب النتيجة الفارغة Empty query result ؛ ويعرض الاستعلام المكتوب بشكل خاطئ مربعاً أحمر Error executing query متبوعاً بالرسالة.

الخطوة 1 — قراءة مقياس

promql
api_info

ما يطلبه الاستعلام : آخر قيمة للمقياس api_info، لجميع سلاسله.

text
api_info{instance="api:8000", job="api", service="api", version="1.0.0"}    1

ما يجب النظر إليه : سطر واحد فقط، Result series: 1. القيمة 1 ولن تتغير أبداً : api_info مقياس معلومات، كل ما لديه ليقوله موجود في تسميته version="1.0.0". ثلاث تسميات أخرى لم يكتبها API : instance وjob وservice أضافها Prometheus لحظة القراءة. على الصفحة http://localhost:8000/metrics، يُكتب السطر نفسه api_info{version="1.0.0"} 1.0.

لفهم أعمق
  • لماذا نبدأ بـ api_info وليس بـ up ؟ لأن له سلسلة واحدة. ترى الشكل الكامل لسطر نتيجة (اسم، تسميات، قيمة) دون أن تشتتك سبعة أسطر أخرى.
  • اسم المقياس يحتوي على أحرف وأرقام و_ و:. لا شرطة، ولا مسافة، ولا نقطة. api-info سيُقرأ كـ api ناقص info.
  • القيمة دائماً عدد عشري. 1 هنا ؛ لا يخزن Prometheus نصاً، لهذا السبب الإصدار في تسمية.

الخطوة 2 — قراءة مقياس متعدد السلاسل

promql
up

ما يطلبه الاستعلام : آخر قيمة لـ up، لجميع سلاسله.

text
up{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"}    1

ما يجب النظر إليه : Result series: 8، الاسم نفسه على كل سطر، وما يتغير من سطر إلى آخر : قيم التسميتين job وinstance. هذه هي السلسلة : اسم بالإضافة إلى مجموعة تسميات. ثماني مجموعات مختلفة، ثماني سلاسل. لاحظ أن السطر الثالث وحده يحمل service="api" : أُضيفت هذه التسمية يدوياً في prometheus.yml، للمهمة api فقط. لا يوجد up على أي صفحة /metrics : يصنعه Prometheus بنفسه، 1 إذا نجحت القراءة، و0 خلاف ذلك.

الخطوة 3 — اختيار سلسلة بتسمية

promql
up{job="api"}

ما يطلبه الاستعلام : سلاسل up التي تساوي تسميتها job بالضبط api.

text
up{instance="api:8000", job="api", service="api"}    1

ما يجب النظر إليه : سطر واحد فقط، الثالث من الخطوة 2. الأقواس المعقوفة بعد الاسم هي مرشح : نسميها محدداً. job هو اسم التسمية، و"api" قيمتها، بين علامتي اقتباس مزدوجتين، و= المساواة الدقيقة. إنه WHERE job = 'api' في SQL، حرفياً. يمكنك وضع عدة شروط مفصولة بفواصل ؛ يجب أن تكون كلها صحيحة.

الخطوة 4 — الفخ : بلا علامات اقتباس، ثم بحالة الأحرف الخاطئة

استعلامان خاطئان، عمداً. أولاً، انسَ علامات الاقتباس :

promql
up{job=api}
text
Error executing query
invalid parameter "query": 1:8: parse error: unexpected identifier "api" in label matching, expected string

ما يجب النظر إليه : parse error، لم يبحث Prometheus حتى : الاستعلام مكتوب بشكل خاطئ. 1:8 هو الموضع (السطر 1، الحرف 8، مباشرة بعد up{job=). expected string : كان يتوقع سلسلة نصية بين علامتي اقتباس. قيمة التسمية هي دائماً سلسلة نصية، حتى عندما تشبه رقماً : {code=500} يعطي عائلة الخطأ نفسها، و{code="500"} هو الشكل الصحيح.

ثم، ضع علامات الاقتباس لكن غيّر حالة الأحرف :

promql
up{job="API"}
text
Empty query result

ما يجب النظر إليه : لا خطأ، ولا سطر. هذا هو الفخ الأخبث : الاستعلام صحيح، إنه ببساطة يطلب سلسلة غير موجودة. قيم التسميات حساسة لحالة الأحرف وللإملاء ("api " بمسافة لا يعمل أيضاً). عندما تحصل على Empty query result دون سبب، أعد كتابة الخطوة 2 وأعد قراءة القيم الدقيقة.

لفهم أعمق : قراءة رسالة خطأ PromQL

لرسالة خطأ Prometheus ثلاثة أجزاء : parse error (الاستعلام غير سليم البنية) أو bad_data (الاستعلام سليم البنية لكن يستحيل تنفيذه)، وموضع سطر:عمود، وجملة تقول ما كان يتوقعه. اذهب دائماً إلى الموضع المشار إليه : الخطأ هناك أو قبله مباشرة. تغطي رسائل هذه الورشة الثلاث الغالبية العظمى من الحالات : expected string (علامات الاقتباس)، وexpected "(" (الأقواس حول by)، وexpected type range vector (الأقواس المربعة، الخطوة 9).

الخطوة 5 — اختيار عدة سلاسل بنمط

promql
up{job=~"a.*"}

ما يطلبه الاستعلام : سلاسل up التي تطابق تسميتها job التعبير النمطي a.* : حرف a متبوع بأي شيء.

text
up{instance="alloy:12345", job="alloy"}    1
up{instance="api:8000", job="api", service="api"}    1
up{instance="alertmanager:9093", job="alertmanager"}    1

ما يجب النظر إليه : ثلاثة أسطر، المهام الثلاث التي تبدأ بـ a. جديد واحد فقط مقارنة بالخطوة 3 : =~ بدلاً من =. يجب أن يطابق التعبير النمطي القيمة بأكملها : "a" وحده لن يعيد شيئاً، يلزم "a.*". عوامل الاختيار الأربعة : = (يساوي)، و!= (مختلف : up{job!="api"} يعيد السبعة الأخرى)، و=~ (يطابق)، و!~ (لا يطابق). =~ هو ما استخدمه التمرين الموجه في {code=~"5.."} لالتقاط جميع 5xx.

الخطوة 6 — تطبيق دالة

promql
count(up)

ما يطلبه الاستعلام : عدد السلاسل التي يعيدها up.

text
{}    8

ما يجب النظر إليه : سطر واحد فقط، واختفى الاسم : {} فارغة، ثم 8. هذه أول دالة في الورشة، والنتيجة لم تعد up، بل رقماً محسوباً من up. count تجميع : يأخذ عدة سلاسل ويصنع منها واحدة. أبناء عمومته : sum (مجموع القيم : sum(up) يعطي أيضاً 8 ما دام كل شيء عند 1، و7 بمجرد سقوط هدف)، وmin، وmax، وavg. لوحة معلومات الحقيبة والأمر etat يعدّان الأهداف بهذه الطريقة بالضبط.

الخطوة 7 — طلب نطاق زمني

promql
http_requetes_total{route="/cours", code="200"}[1m]

ما يطلبه الاستعلام : جميع قيم هذه السلسلة المقروءة خلال الدقيقة الأخيرة، وليس الأخيرة فقط.

text
http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"}
    2439 @1789505608.199
    2499 @1789505623.199
    2550 @1789505638.196
    2609 @1789505653.197

ما يجب النظر إليه : سلسلة واحدة، لكن أربع قيم، كل منها متبوعة بـ @ وتاريخ بالثواني. خمس عشرة ثانية بين كل اثنتين : إنه scrape_interval. يرتفع العداد من 2439 إلى 2609 : 170 طلباً 200 على /cours في 45 ثانية. جديد واحد فقط : الأقواس المربعة [1m] بعد المحدد. تحوّل المتجه اللحظي (قيمة واحدة لكل سلسلة) إلى متجه نطاق (قائمة قيم مؤرخة لكل سلسلة). انقر على علامة التبويب Graph : ترفض هذا الاستعلام (Error executing query ثم invalid expression type "range vector" for range query, must be Scalar or instant Vector). لا نرسم نطاقاً خاماً، بل نعطيه لدالة. هذه هي الخطوة 8. عد إلى Table.

لفهم أعمق : لماذا أربع قيم وليس خمساً ؟

تحتوي الدقيقة على أربع فترات من 15 ثانية، إذن أربع أو خمس قراءات حسب اللحظة التي تشغّل فيها الاستعلام بالنسبة إلى دورة الكشط. إذا أعدت كتابة الاستعلام عدة مرات، فسترى أحياناً خمسة أسطر. التواريخ @1789505608.199 هي ثوانٍ منذ 1 يناير 1970 (وقت Unix) ؛ تحوّلها علامة التبويب Graph إلى ساعات مقروءة.

الخطوة 8 — تطبيق دالة على النطاق

promql
rate(http_requetes_total{route="/cours", code="200"}[1m])

ما يطلبه الاستعلام : السرعة التي ازداد بها هذا العداد، بالوحدات في الثانية، محسوبة على نطاق الدقيقة الأخيرة.

text
{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"}    3.7779456864749545

ما يجب النظر إليه : قيمة واحدة مجدداً، واختفى الاسم http_requetes_total من الأقواس المعقوفة : لم يعد عداداً، بل سرعة. 3,78 طلباً في الثانية. تحقق مع الخطوة 7 : 170 طلباً في 45 ثانية تعطي 3,78. جديد واحد فقط : الدالة rate()، التي تأخذ متجه نطاق وتعيد متجهاً لحظياً. إنها أهم دالة في PromQL : العداد الخام لا يُقرأ أبداً، أما ميله فنعم.

الخطوة 9 — الفخ : rate بلا نطاق

promql
rate(http_requetes_total{route="/cours", code="200"})
text
Error executing query
invalid parameter "query": 1:6: parse error: expected type range vector in call to function "rate", got instant vector

ما يجب النظر إليه : expected type range vector … got instant vector. أعطيت rate متجهاً لحظياً (قيمة واحدة)، وكانت تريد نطاقاً (عدة قيم مؤرخة) : بدون نقطتين، لا ميل. الحركة الصحيحة هي الخطوة 8، مع [1m]. ستقرأ هذه الرسالة كثيراً ؛ إنها تعني دائماً « ينقص […] ».

بديل لا يسبب خطأً لكنه لا يعيد شيئاً :

promql
rate(http_requetes_total{route="/cours", code="200"}[10s])
text
Empty query result

ما يجب النظر إليه : نطاق من 10 ثوانٍ لا يحتوي في أحسن الأحوال إلا على قراءة واحدة (تفصل بينها 15 ثانية)، ويحتاج rate إلى اثنتين على الأقل. قاعدة عملية : يجب أن يساوي النطاق ضعفي scrape_interval على الأقل، إذن [30s] كحد أدنى هنا ؛ [1m] أو [5m] في الحياة الواقعية.

الخطوة 10 — التجميع

promql
sum by (code) (rate(http_requetes_total{route="/cours"}[1m]))

ما يطلبه الاستعلام : سرعة جميع سلاسل /cours (كل الرموز)، مجموعة مع الاحتفاظ بالتسمية code فقط.

text
{code="200"}    3.7779456864749545
{code="500"}    0

ما يجب النظر إليه : سطران، ولم تبقَ سوى تسمية واحدة في الأقواس المعقوفة : code. ذابت كل الأخرى (instance وjob وmethode وroute وservice) في المجموع. جديد واحد فقط : sum by (code) (…)، تجميع الخطوة 6 مع عبارة by. الأقواس حول code إلزامية (sum by code (…) يعطي parse error: … expected "("). يستحق السطر {code="500"} 0 نظرة : يساوي صفراً لأن لا 500 وقع على /cours خلال الدقيقة الأخيرة (ينتج API حوالي واحد كل اثنتي عشرة ثانية، لجميع المسارات مجتمعة). الصفر ليس غياباً : السلسلة موجودة، ميلها فقط معدوم. إذا شغّلت .\labo.ps1 casser erreurs (أو ./labo.sh casser erreurs) وأعدت كتابة هذا الاستعلام بعد دقيقة، يرتفع السطر الثاني ؛ وreparer يعيده إلى الانخفاض.

هذه النتيجة هي مخرجك المطلوب : السطران، وجملة لكل منهما (« يقدّم /cours 3,78 استجابة 200 في الثانية » ؛ « لا استجابة 500 على /cours في الدقيقة الأخيرة »).

التحقق النهائي

أعد الاستعلامات العشرة من الذاكرة، بالترتيب، وضع علامة :

  • api_info يعيد سلسلة واحدة، القيمة 1، مع version="1.0.0" في التسميات.
  • up يعيد 8 سلاسل، كلها عند 1 (وإلا، فقد سقط هدف : سيخبرك etat أيّه).
  • up{job="api"} يعيد سلسلة واحدة.
  • up{job=api} يعيد parse error … expected string ؛ up{job="API"} يعيد Empty query result.
  • up{job=~"a.*"} يعيد 3 سلاسل : alloy وapi وalertmanager.
  • count(up) يعيد {} 8.
  • http_requetes_total{route="/cours", code="200"}[1m] يعيد سلسلة واحدة مع 4 أو 5 قيم مؤرخة @….
  • rate(…[1m]) يعيد قيمة واحدة، بين 3 و4 طلبات في الثانية على مختبر الدورة.
  • rate(…) بلا أقواس مربعة يعيد expected type range vector … got instant vector.
  • sum by (code) (rate(http_requetes_total{route="/cours"}[1m])) يعيد سلسلتين، {code="200"} و{code="500"}.

لا شيء للتنظيف : لم تنشئ شيئاً. لا يزال etat يعرض 8/8 cibles up والعدد نفسه من السلاسل في الذاكرة، بفارق بضع عشرات (يواصل Prometheus الجمع).

إذا حدثت مشكلة

عرض الحالات التي تحدث فيها مشكلة

Empty query result على api_info أو http_requetes_total. لم يُقرأ API بعد، أو هو متوقف. etat : إذا كان labo-api في حالة Exited، نفّذ reparer ؛ إذا كان كل شيء healthy، انتظر 15 ثانية (كشط واحد) وأعد التشغيل.

يعيد up 7 سلاسل بدلاً من 8، كلها عند 1. اختفت مهمة من الإعداد، وليس هدفاً سقط (سيكون عند 0). اذهب إلى Status → Target health وقارن مع المهام الثماني في الخطوة 2. إذا عدّلت prometheus/prometheus.yml، أعد الملف الأصلي (git checkout prometheus/prometheus.yml) ثم docker compose restart prometheus.

تعيد الخطوة 7 Empty query result. يجب أن توجد القيم الأربع للنطاق : مباشرة بعد demarrer، يجب الانتظار دقيقة. إذا كان API قد أعيد تشغيله للتو (reparer)، الأمر نفسه.

تعيد الخطوة 8 قيمة سالبة أو ضخمة. مستحيل من حيث المبدأ : يتعامل rate مع عمليات تصفير العداد. إذا رأيت ذلك، تحقق من أنك لم تكتب rate على مقياس لحظي (requetes_en_cours) : لا يسبب ذلك خطأً لكنه بلا معنى.

تبقى علامة التبويب Graph فارغة. لاستعلام نطاق (الخطوة 7)، هذا طبيعي : يعرض Graph invalid expression type "range vector". للأخرى، وسّع الفترة (الزر -/+ فوق الرسم البياني) : مباشرة بعد بدء التشغيل، لا توجد سوى بضع دقائق من البيانات.

parse error لا تتعرف عليه. اذهب إلى الموضع سطر:عمود في الرسالة. عدّ أقواسك : sum by (code) (rate(x[1m])) فيه ثلاثة أزواج. تحقق من كل علامة اقتباس : تأتي اثنتين اثنتين، مستقيمة (")، وليست طباعية أبداً (“ ”) ؛ النسخ واللصق من معالج نصوص يستبدلها أحياناً.