Prometheus وGrafana

نموذج Prometheus

18 دقيقة الدرس 1 من 32

نموذج Prometheus

أمضيت دروساً سابقة في بناء أسس قابلية الملاحظة — التتبع الموزع مع Jaeger، والتسجيل المنظَّم مع Loki، ونموذج الركائز الثلاث. حان الآن وقت التعمق في نظام المقاييس الأكثر انتشاراً في عالم DevOps. Prometheus ليس مجرد قاعدة بيانات للمقاييس: إنه نموذج فكري متكامل لكيفية التفكير في القياس في الأنظمة الأصيلة للسحابة. فهم ذلك النموذج — بنيته القائمة على السحب، ومحرك تخزين السلاسل الزمنية، والمنظومة المحيطة به — هو الشرط المسبق لكل شيء آخر في هذا الدرس التعليمي.

على نطاق Google، تُشغّل الفرق أنظمة داخلية (Borgmon ثم Monarch) تتشارك فلسفة التصميم الجوهرية لـ Prometheus. مشروع Prometheus، الذي بدأ في SoundCloud عام 2012 وأُهدي إلى CNCF عام 2016، أتاح تلك الفلسفة للجميع. اليوم هو الـ backend الافتراضي للمقاييس في كتل Kubernetes لدى كل مزود سحابي رئيسي.

السحب القائم على الاستطلاع: الاختيار التصميمي الجوهري

معظم أنظمة المقاييس التي ربما صادفتها — StatsD وGraphite وكثير من وكلاء APM — تعمل بالدفع push-based: التطبيقات ترسل المقاييس إلى مجمِّع مركزي. Prometheus يعكس هذا. إنه يعمل بالسحب pull-based: Prometheus نفسه يتواصل عبر HTTP مع كل هدف ويسحب نقطة نهاية /metrics التي يعرضها الهدف. هذا ليس تفصيلاً في التنفيذ — إنه قرار معماري متعمد له تداعيات تشغيلية عميقة.

تقدم نقطة نهاية /metrics تنسيق عرض Prometheus: تنسيق نصي عادي يسطرياً يمكن لأي عميل HTTP قراءته دون أدوات خاصة. كل هدف مسؤول عن الحفاظ على رؤية حالية لعداداته وقياساته ومخططاته وملخصاته، وتقديمها عند الطلب. Prometheus يسحب هذه اللقطة بفاصل زمني قابل للإعداد — الـ scrape_interval — عادةً 15 أو 30 ثانية في الإنتاج.

الفكرة الأساسية: في نموذج السحب، Prometheus يعرف دائماً ما إذا كان الهدف قابلاً للوصول. إن فشل السحب، يمتلك Prometheus مقياساً لذلك: up == 0. في نموذج الدفع، غياب البيانات يكون غامضاً — هل الخدمة معطلة أم لا ترسل فحسب؟ هذا الفرق يجعل التنبيه على توافر الخدمة أبسط وأكثر موثوقية بكثير في بنية السحب.

لماذا السحب بدلاً من الدفع؟ أسباب هندسية متعددة تتقاطع:

  • الصحة كإشارة من الدرجة الأولى: فشل السحب يظهر فوراً كمقياس up مفقود أو صفري. لا تحتاج نظام فحص صحة منفصل.
  • الإعداد يعيش مع Prometheus لا مع الأهداف: تتحكم في تكرار السحب، والمهل الزمنية، وإعادة التسمية مركزياً. الأهداف تحتاج فقط تقديم نقطة نهاية — لا تحتاج معرفة أين يعيش Prometheus.
  • التصحيح المحلي: يمكنك curl http://my-service:8080/metrics في أي وقت وترى بالضبط ما يراه Prometheus. لا وكيل غير مرئي، لا تخزين مؤقت، لا قائمة إعادة محاولة لاستيعابها.
  • لا فقدان مقاييس من أقسام الشبكة نحو Prometheus: إن كان Prometheus غير قابل للوصول مؤقتاً، تتراكم الأهداف الحالة في الذاكرة. حين يستأنف السحب، السحب التالي يعكس الحالة الحالية. استمرارية العداد محفوظة لأن Prometheus يتتبع آخر قيمة محسوبة.
ممارسة احترافية: في النشرات كبيرة النطاق (آلاف الأهداف)، تُشغّل الفرق نسخ Prometheus متعددة في طوبولوجيا متحدة أو مشاردة. كل نسخة تسحب مجموعة فرعية من الأهداف. لا تفترض أبداً أن نسخة Prometheus واحدة ستتوسع لأجل غير مسمى — عند 10 ملايين سلسلة زمنية نشطة تقريباً يبدأ نسخة واحدة بـ 64 جيجابايت RAM في الصراع. صمّم للتوسع الأفقي من البداية.

تنسيق العرض ومكتبات العملاء

استجابة /metrics تبدو هكذا — ملف نصي عادي حيث كل سطر هو ملاحظة مقياس:

# HELP http_requests_total Total HTTP requests handled # TYPE http_requests_total counter http_requests_total{method="GET",status="200",handler="/api/users"} 1827364 http_requests_total{method="GET",status="500",handler="/api/users"} 42 http_requests_total{method="POST",status="201",handler="/api/orders"} 93241 # HELP process_resident_memory_bytes Resident memory size in bytes # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes 1.34217728e+08 # HELP http_request_duration_seconds HTTP request latency # TYPE http_request_duration_seconds histogram http_request_duration_seconds_bucket{le="0.005"} 183921 http_request_duration_seconds_bucket{le="0.01"} 248034 http_request_duration_seconds_bucket{le="0.025"} 291047 http_request_duration_seconds_bucket{le="0.05"} 298201 http_request_duration_seconds_bucket{le="+Inf"} 298391 http_request_duration_seconds_sum 4872.3 http_request_duration_seconds_count 298391

مشروع Prometheus يُدير مكتبات عملاء رسمية لـ Go وJava/JVM وPython وRuby. مكتبات مدعومة من المجتمع موجودة لكل لغة تقريباً. أداة قياس خادم HTTP بـ Go مسألة استيراد prometheus/client_golang وتسجيل المقاييس — المكتبة تُعالج التجميع الآمن للخيوط وعرض HTTP. في بيئات Kubernetes، تُعرض مُصدِّر kube-state-metrics حالة الكتلة، ويُعرض node_exporter المقاييس على مستوى نظام التشغيل من كل عقدة — تسحبهما تماماً كنقاط نهاية التطبيقات.

قاعدة بيانات السلاسل الزمنية (TSDB)

يخزن Prometheus جميع العينات المسحوبة في TSDB المدمج — قاعدة بيانات سلاسل زمنية مخصصة محسّنة لأعباء عمل كثيفة الكتابة، إلحاقية فحسب، مع مجموعات تسميات عالية الكاردينالية. فهم نموذج تخزينها يساعدك على تشغيلها بصورة صحيحة وتجنب أشيع حالات الفشل في الإنتاج.

كل سلسلة زمنية تُحدَّد بمجموعة فريدة من اسم مقياس ومجموعة تسميات — أزواج مفتاح-قيمة توفر أبعاداً. السلسلة http_requests_total{method="GET", status="200", service="orders"} هي سلسلة زمنية مستقلة تماماً عن http_requests_total{method="POST", status="201", service="orders"}. كل مجموعة تسميات مميزة تنشئ سلسلة جديدة. هذه هي أعظم قوة Prometheus وأشيع مصيدة فيه في نفس الوقت.

تنظم TSDB البيانات في طبقتين:

  1. كتلة الرأس في الذاكرة: آخر ساعتين من البيانات تعيشان في سجل كتابة مسبقة (WAL) مضغوط ومعيّن للذاكرة. عمليات السحب تكتب هنا أولاً — I/O تسلسلي سريع للغاية. عند إعادة التشغيل، يُعيد WAL تشغيله لإعادة بناء الرأس.
  2. الكتل الثابتة: كل ساعتين، تُضغط كتلة الرأس وتُكتب إلى كتلة ثابتة على القرص. الكتل غير قابلة للتغيير. مُدمِّج في الخلفية يدمج الكتل الأصغر في كتل أكبر (تغطي حتى 31% من نافذة الاحتفاظ المُعدَّة) لتقليل تكاليف الاستعلام عبر نطاقات زمنية طويلة.

الاحتفاظ الافتراضي هو 15 يوماً من التخزين المحلي. للاحتفاظ طويل الأمد، يدعم Prometheus واجهة remote_write التي تبث العينات إلى backend خارجي — Thanos أو Cortex أو Mimir أو Victoria Metrics — والتي تتعامل مع الاحتفاظ لسنوات على نطاق واسع مع تخزين الكائنات.

مصيدة إنتاجية — انفجار الكاردينالية: السبب الأكثر شيوعاً لانهيارات Prometheus بسبب نفاد الذاكرة هو التسميات عالية الكاردينالية. إن قمت بتسمية المقاييس بمعرفات المستخدمين أو الطلبات أو أي سلسلة غير محدودة، ستنشئ ملايين السلاسل الزمنية المميزة. كل سلسلة تستهلك نحو 3–4 كيلوبايت من الذاكرة في كتلة الرأس. مليون سلسلة = ~3 جيجابايت RAM فقط للرأس. صمّم دائماً مجموعات التسميات بكاردينالية محدودة. Prometheus يعرض prometheus_tsdb_head_series — أنبّه حين يتجاوز 80% من ميزانية طاقتك.

منظومة Prometheus: مخطط البنية

لا يعمل Prometheus في عزلة. تتضمن البنية الإنتاجية مجموعة من المكونات محددة التعريف، لكل منها دور محدد. فهم ما يفعله كل مكون — وما لا يفعله — يمنع الأخطاء المعمارية التي يكون تصحيحها مكلفاً.

Prometheus Ecosystem Architecture App: /metrics node_exporter kube-state-metrics Blackbox Exporter TARGETS Service Discovery (K8s / Consul / DNS) Prometheus Scrape Engine Rules Engine (recording + alerting) TSDB (local blocks + WAL) HTTP API / PromQL remote_write Alertmanager route / silence / inhibit PagerDuty / Slack / OpsGenie Grafana dashboards / explore Thanos / Mimir long-term storage alert PromQL remote_write PULL pull (scrape) push / notify
منظومة Prometheus: يسحب Prometheus المقاييس من الأهداف المكتشفة عبر Service Discovery، ويُقيّم القواعد، ويُطلق التنبيهات إلى Alertmanager، ويعرض PromQL لـ Grafana. الكتابة عن بعد ترسل العينات إلى التخزين طويل الأمد.

اكتشاف الخدمة: كيف يعرف Prometheus ماذا يسحب

في البيئات الثابتة يمكنك إدراج أهداف السحب يدوياً. في Kubernetes، حيث تُولد الـ pods وتموت باستمرار، الإعداد الثابت مستحيل. Prometheus يمتلك تكاملات اكتشاف خدمة مدمجة لـ Kubernetes وConsul وAWS EC2 وGCE وAzure وDNS-SD. في كتلة Kubernetes، الإعداد النموذجي يستخدم آلية kubernetes_sd_configs مع قواعد إعادة التسمية لتصفية الأهداف المكتشفة وتحويلها.

# prometheus.yml — إعداد بنمط إنتاجي لـ Kubernetes global: scrape_interval: 15s evaluation_interval: 15s external_labels: cluster: prod-us-east-1 region: us-east-1 rule_files: - /etc/prometheus/rules/*.yml alerting: alertmanagers: - static_configs: - targets: ["alertmanager:9093"] scrape_configs: # سحب Prometheus نفسه - job_name: prometheus static_configs: - targets: ["localhost:9090"] # Kubernetes pods المُعلَّمة للسحب - job_name: kubernetes-pods kubernetes_sd_configs: - role: pod relabel_configs: # سحب الـ pods ذات التعليق prometheus.io/scrape: "true" فقط - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: "true" # استخدام المنفذ من التعليق prometheus.io/port إن وُجد - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port] action: replace target_label: __address__ regex: (.+) replacement: ${1} # حمل اسم namespace واسم pod كتسميات - source_labels: [__meta_kubernetes_namespace] target_label: namespace - source_labels: [__meta_kubernetes_pod_name] target_label: pod remote_write: - url: "http://thanos-receive:10908/api/v1/receive" queue_config: max_samples_per_send: 10000 max_shards: 30 min_backoff: 1s max_backoff: 30s

كتلة external_labels حيوية في إعدادات متعددة الكتل. عند الكتابة عن بعد إلى مستودع مركزي كـ Thanos، تلتصق هذه التسميات بكل سلسلة حتى تستطيع التمييز بين cluster="prod-us-east-1" وcluster="prod-eu-west-1" في الاستعلامات العالمية.

ما Prometheus ليس عليه

فهم القيود المتعمدة لـ Prometheus يمنع الأخطاء المعمارية. Prometheus مصمم للمقاييس الرقمية للسلاسل الزمنية ذات الكاردينالية المحدودة. إنه غير مصمم صراحةً لـ:

  • تخزين السجلات: استخدم Loki أو Elasticsearch أو Splunk للسجلات. لا تشفّر محتوى السجل في تسميات المقاييس.
  • تتبع الأحداث أو الفواتير: Prometheus قد يفقد ما يصل إلى فاصل سحب واحد (15–30 ثانية) من البيانات عند الانهيار. لا يوفر ضمانات متانة للأحداث الفردية. استخدم Kafka أو مخزن تحويلي للأعداد الحيوية للفوترة.
  • الاحتفاظ طويل الأمد جاهزاً: تخزين محلي افتراضي 15 يوماً. لسنوات من التاريخ، اكتب عن بعد إلى Thanos أو Mimir من اليوم الأول.
  • الأبعاد عالية الكاردينالية: لا معرفات مستخدمين أو طلبات أو جلسات كتسميات. هذه تنتمي إلى التتبعات (Jaeger/Tempo) أو السجلات.
النموذج الذهني الصحيح: Prometheus يجيب على "كيف يتصرف نظامي في المجمل الآن وعلى مدى الأسابيع القليلة الماضية؟" التتبعات تجيب على "ماذا حدث خلال هذا الطلب المحدد؟" السجلات تجيب على "ماذا قالت العملية في هذه اللحظة بالضبط؟" ابنِ الثلاثة معاً؛ دع كلاً منها يؤدي دوره.

في الدرس التالي ستتعمق في أنواع المقاييس الأربعة — العداد والقياس والمخطط والملخص — وتنسيق العرض. مع نموذج السحب وبنية TSDB راسخين في ذهنك، ستنقر تلك التفاصيل في مكانها فوراً.