أسس قابلية الرصد

الركائز الثلاث للمراقبة الشاملة

18 دقيقة الدرس 2 من 28

الركائز الثلاث للمراقبة الشاملة

الركائز الثلاث — المقاييس والسجلات والتتبع — هي الإشارات الأولية التي تتيح لك فهم ما يقوم به النظام الموزع في أي لحظة. جاء هذا الإطار من عمل كوري واتسون عام 2013 في Twitter، وانتشر بفضل كتابات سيندي سريدهاران حول المراقبة الشاملة. بحلول عام 2025، بنى كل فريق منصة رئيسي في Google وMeta وUber وStripe أدواته الإنتاجية حول هذه الأنواع الثلاثة بالضبط، مع مخزن مخصص لكل منها: Prometheus أو Thanos للمقاييس، وElasticsearch أو Loki للسجلات، وJaeger أو Tempo للتتبع.

لا تكفي ركيزة واحدة بمفردها. إنها متكاملة: المقاييس تخبرك أن هناك مشكلة، والسجلات تخبرك بما حدث، والتتبع يخبرك أين في سلسلة الاستدعاء نشأت المشكلة. إذا أغفلت أيًا منها، سيعمل مهندس الإنذارات في الظلام خلال الحوادث.

The Three Pillars of Observability Your System services, infra METRICS Numeric time-series cpu_usage, req_rate error_rate, latency_p99 Tool: Prometheus Viz: Grafana LOGS Discrete timestamped events structured JSON records request, error, audit Tool: Loki / ELK Viz: Grafana / Kibana TRACES Causal request spans trace_id, span_id parent → child spans Tool: Jaeger / Tempo Viz: Grafana / Jaeger UI Strengths Cheap at query time Alerting + trending Aggregation & math Cost: low per datapoint Strengths Full event context Human-readable detail Debuggability Cost: high at scale Strengths End-to-end latency Service dependency map Root cause localisation Cost: medium + sampling
الركائز الثلاث للمراقبة الشاملة — الإشارات التي يرسلها نظامك، والأدوات الأساسية لكل منها، وملف التكلفة والقوة.

الركيزة الأولى: المقاييس (Metrics)

المقاييس هي قياسات رقمية مُجمَّعة عبر الزمن. يتزايد العداد في كل مرة يُخدَّم فيها طلب HTTP؛ يتتبع المقياس الفوري استخدام الذاكرة الحالي؛ يُصنّف المدرج التكراري فترات الاستجابة ويتيح لك حساب أي نسب مئوية. تكلفة تخزينها منخفضة (عدد عشري واحد + وسوم + طابع زمني)، وتكلفة الاستعلام عنها منخفضة على نطاق واسع، وهي الأساس الصحيح للوحات المعلومات والتنبيهات.

نموذج بيانات Prometheus هو المعيار الصناعي. كل مقياس له اسم ومجموعة من الوسوم (أزواج مفتاح-قيمة) — http_requests_total{method="POST", status="500", service="checkout"}. الوسوم هي ما يجعل المقاييس قوية: يمكنك الجمع عبر جميع الخدمات، أو التصفية لنقطة نهاية واحدة، أو مقارنة معدلات الأخطاء حسب المنطقة. لكن الوسوم لها تكلفة: كل تركيبة فريدة من قيم الوسوم هي سلسلة زمنية منفصلة. وسم ذو قدرة استيعابية غير محدودة (مثل user_id أو request_id) سيُفجّر قاعدة بيانات Prometheus الزمنية.

# Prometheus scrape config — scrape your app's /metrics endpoint # prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'api-service' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: "true" - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] target_label: __metrics_path__ regex: (.+) # PromQL query: 99th-percentile latency over 5m per service histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service) ) # Error rate as a percentage 100 * ( sum(rate(http_requests_total{status=~"5.."}[1m])) by (service) / sum(rate(http_requests_total[1m])) by (service) )
الإشارات الذهبية الأربع (كتاب Google SRE): الكمون (Latency)، والحركة (Traffic/معدل الطلبات)، والأخطاء (Error rate)، والتشبع (Saturation/مدى امتلاء مورد). إذا لم تقس شيئاً آخر، فاقس هذه الأربع لكل خدمة. إنها الحد الأدنى من المقاييس الفعّالة التي تخبرك ما إذا كانت الخدمة تعمل بصحة جيدة.

الركيزة الثانية: السجلات (Logs)

السجلات هي سجلات منفصلة موقّتة زمنياً لأحداث فردية. يلتقط سطر السجل ما حدث في لحظة دقيقة بالضبط — استُلم طلب HTTP بهذه الترويسات، انتهت مهلة استعلام قاعدة البيانات، رُفض وصول مستخدم. تمنحك السجلات السياق الكامل الذي لا تستطيع المقاييس توفيره: الاستعلام الدقيق الذي فشل، هوية المستخدم بالضبط، والتتبع الكامل للمكدس.

الانتقال من السجلات غير المنظمة (بأسلوب printf) إلى سجلات JSON المنظمة هو أحد أعلى التحسينات إنتاجية يمكن لفريق تحقيقها. السجلات المنظمة يمكن فهرستها وتصفيتها وتجميعها بواسطة الآلات. سطر السجل غير المنظم هو أداة تصحيح؛ أما سجل JSON المنظم فهو بيانات.

# Application emitting structured JSON logs (Node.js / pino example) # Every log line is a valid JSON object — queryable by any field {"level":"info","time":1718000000000,"pid":42,"hostname":"api-7d4b9c-xkq2p", "service":"checkout","trace_id":"3fa85f64-5717-4562","span_id":"a1b2c3d4", "msg":"payment processed","user_id":"usr_8821","amount_cents":4999,"currency":"USD", "duration_ms":143} {"level":"error","time":1718000001234,"pid":42,"hostname":"api-7d4b9c-xkq2p", "service":"checkout","trace_id":"3fa85f64-5717-4562","span_id":"a1b2c3d4", "msg":"stripe charge failed","error":"card_declined","stripe_code":"do_not_honor", "user_id":"usr_8821","stack":"Error: card_declined\n at ChargeService.charge ..."} # Loki LogQL — find all error logs for a service in the last 15m and extract fields {service="checkout"} |= "error" | json | duration_ms > 500 # Count errors per minute by error type sum by (error) ( rate({service="checkout"} | json | __error__="" [1m]) )
حجم السجلات هو أكبر محرك لتكاليف المراقبة. يمكن لـ microservice مشغول أن ينتج مئات الآلاف من أسطر السجل في الثانية. بسعر 0.50-1.50 دولار لكل جيجابايت مُستوعَب (تسعير Datadog وSplunk)، يمكن لخدمة واحدة ثرثارة أن تكلف آلاف الدولارات شهرياً. الممارسة الإنتاجية: سجّل عند مستوى INFO للعمليات العادية، وERROR فقط عند الأعطال القابلة للتنفيذ. استخدم أخذ عينات السجلات الديناميكي — سجّل 100% من الأخطاء و1% من الطلبات الناجحة. لا تسجّل أبداً هيئات الطلب/الاستجابة الكاملة عند مستوى INFO. أرفق trace_id بكل سطر سجل حتى تتمكن من الربط مع التتبعات.

الركيزة الثالثة: التتبع الموزع (Distributed Traces)

التتبع الموزع هو قصة طلب واحد كما يتدفق عبر كل خدمة تعالجه. كل وحدة عمل داخل خدمة هي span (مقطع) — لها وقت بداية ومدة ومجموعة وسوم (بيانات وصفية بأزواج مفتاح-قيمة) ومرجع لـ span الأصلي. مجموعة جميع الـ spans لطلب واحد، مرتبطة بـ trace_id مشترك، تشكّل التتبع.

التتبعات تجيب على أسئلة لا تستطيع المقاييس ولا السجلات حلّها: "أي خدمة في شبكتنا المكوّنة من 40 خدمة مسؤولة عن الكمون الذيلي البالغ ثانيتين الذي يراه عملاؤنا عند الدفع؟" قد تخبرك المقاييس أن p99 الدفع بطيء. قد تظهر السجلات طلبات بطيئة فردية. التتبعات وحدها تكشف أن الاختناق هو مقطع محدد inventory-service → postgres بطيء باستمرار للطلبات التي تتضمن أكثر من 10 منتجات.

# OpenTelemetry auto-instrumentation for a Node.js service # npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node # tracing.js — loaded before your app (node --require ./tracing.js server.js) const { NodeSDK } = require('@opentelemetry/sdk-node'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4317', // Collector in the same K8s namespace }), instrumentations: [getNodeAutoInstrumentations()], serviceName: 'checkout-service', }); sdk.start(); # OpenTelemetry Collector config — receives traces and forwards to Tempo # otel-collector-config.yaml receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 1s send_batch_size: 1024 tail_sampling: # sample only interesting traces decision_wait: 10s policies: - name: errors-policy type: status_code status_code: {status_codes: [ERROR]} - name: slow-traces type: latency latency: {threshold_ms: 500} - name: probabilistic-baseline type: probabilistic probabilistic: {sampling_percentage: 5} exporters: otlp: endpoint: "tempo:4317" tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [batch, tail_sampling] exporters: [otlp]
أخذ العينات الذيلي هو المعيار الإنتاجي. أخذ العينات الرأسي (اتخاذ القرار عند نقطة الدخول، مثلاً "تتبع 5% من جميع الطلبات") بسيط لكنه يسقط بالضبط التتبعات المثيرة للاهتمام — فالأخطاء والحالات الشاذة البطيئة تُسقَط أيضاً. أخذ العينات الذيلي يُخزّن المقاطع مؤقتاً لفترة قصيرة (10-30 ثانية) ويتخذ قرار الاحتفاظ/الحذف بعد رؤية التتبع كاملاً. احتفظ بـ 100% من تتبعات الأخطاء والتتبعات فوق حد الكمون، وأخذ عينات احتمالية للبقية. هكذا يعمل Google Dapper وUber Jaeger ومعالج tail_sampling في OTel Collector.

كيف تتكامل الركائز الثلاث

تأتي القوة الحقيقية من استخدام الثلاثة معاً في سير عمل موحّد. إليك سيناريو إنذار تمثيلي على نطاق شركة إنتاجية:

  1. يُطلق الإنذار (مقاييس): يُطلق تنبيه Prometheus — checkout_error_rate > 2% لمدة 3 دقائق في منطقة US-EAST.
  2. تضييق نطاق التأثير (مقاييس): يفتح مهندس الإنذارات Grafana. معدل الخطأ مرتفع فقط على pod/checkout-v2-*، وليس checkout-v1-*. نشر حديث هو المشتبه به. تُظهر لوحة المعلومات أيضاً ارتفاعاً في كمون p99.
  3. فهم ما حدث (سجلات): تصفية Loki لـ service="checkout" level="error" في النافذة الزمنية المتأثرة. تُظهر السجلات "error": "upstream timeout", "upstream": "inventory-service" — خدمة الدفع تنتظر inventory وتنتهي مهلتها.
  4. إيجاد السبب الجذري (تتبع): فتح Tempo، والتصفية لـ service=checkout status=error. يُظهر التتبع أن مقطع inventory.GetStock يستهلك 1800 ميلي ثانية من إجمالي 2000 ميلي ثانية للطلب. التعمق في هذا المقطع — يُجري 47 استعلام قاعدة بيانات متسلسل (خطأ N+1 مُدخَل في النشر الجديد).
  5. الربط (التكامل بين الركائز): يتيح trace_id المضمّن في كل سطر سجل القفز مباشرةً من سجل السبب إلى التتبع الدقيق في Tempo. يدعم Grafana Explore هذا الربط بشكل أصلي عندما تشترك السجلات والتتبعات في حقل trace_id نفسه.

هذا سير العمل — التنبيه عبر المقاييس، والتحقيق بالسجلات، والتحديد بالتتبعات — هو حلقة المراقبة الشاملة القياسية. كل ركيزة تؤدي وظيفة واحدة بشكل جيد ثم تُحيل إلى التالية. محاولة القيام بهذا بالسجلات وحدها مكلفة وبطيئة على نطاق واسع. محاولة القيام به بالمقاييس وحدها تتركك عاجزاً عن فهم العلاقة السببية.

OpenTelemetry (OTel) هو الآن طبقة القياس المعيارية. إنه مشروع متخرج من CNCF يوفر SDKs محايدة البائع لإصدار المقاييس والسجلات والتتبعات من أي لغة. OTel Collector هو ملف ثنائي مستقل يستقبل الإشارات ويعالجها (دفعات، تصفية، أخذ عينات) ويُصدّرها لأي backend. اعتماد OTel يعني أنك تستطيع التبديل من Jaeger إلى Tempo، أو من Prometheus إلى Mimir، دون إعادة قياس تطبيقاتك.

التكلفة والقدرة الاستيعابية: المقايضة الهندسية

كل ركيزة لها ملف تكلفة مختلف يحدد كمية البيانات التي تستطيع الاحتفاظ بها عند أي دقة:

  • المقاييس: تكلفة منخفضة جداً لكل نقطة بيانات. قراءة عداد من 1,000 pod كل 15 ثانية هي ملايين نقاط البيانات يومياً لكنها لا تزال رخيصة في Prometheus. المحرك الرئيسي للتكلفة هو قدرة الاستيعاب للوسوم — كثرة تركيبات الوسوم الفريدة وسيُصيب Prometheus بنفاد الذاكرة. احتفظ بقيم الوسوم محدودة واستخدم قواعد التسجيل لتجميع الاستعلامات المكلفة مسبقاً.
  • السجلات: تكلفة تخزين عالية على نطاق واسع. يساعد ضغط الاستيعاب (Loki يخزن كتلاً مضغوطة)، لكن الحجم يتناسب أساساً مع معدل الطلبات × أسطر السجل لكل طلب. أخذ العينات وطبقات الاحتفاظ (ساخن/دافئ/بارد) ومستويات السجل هي الرافعات الأساسية.
  • التتبعات: تكلفة متوسطة يتحكم فيها معدل أخذ العينات. التقاط 100% من التتبعات ممكن فقط عند معدلات طلب منخفضة. عند >1000 طلب في الثانية، استخدم أخذ عينات ذيلي يستهدف 5-10% بشكل عام مع احتفاظ 100% بالأخطاء والحالات البطيئة. يخزن Tempo بيانات التتبع مضغوطة وهو أرخص بكثير من backend Elasticsearch الخاص بـ Jaeger على نطاق واسع.

الدرس التالي يتعمق في المقاييس المحددة التي تهم فعلاً — الإشارات المُثبَت أنها تتنبأ بالأعطال المرئية للمستخدم، وكيفية تعريف إشاراتك الذهبية لكل طبقة خدمة، وأنماط PromQL التي تكشفها في أقل من 60 ثانية خلال الحوادث.