مشروع التخرج: منصة إنتاج بمستوى الشركات الكبرى

طبقة المراقبة والرصد

18 دقيقة الدرس 6 من 30

طبقة المراقبة والرصد

عندما يُطلق إنذار في بيئة الإنتاج الساعة الثالثة صباحاً، يكمن الفارق بين حلٍّ يستغرق خمس دقائق وانقطاعٍ يمتد ساعتين في جودة منظومة المراقبة. المقاييس تُخبرك أن ثمة خللاً؛ والتتبعات تُحدد مكانه؛ والسجلات تكشف سببه. على نطاق الشركات الكبرى، يجب تصميم هذه الإشارات الثلاث بوصفها منصة متكاملة — لا أدوات مُلصقة بعضها — مع سياسات الاحتفاظ، وميزانيات البُعد، والتنبيهات المرتبطة بـ SLO، وذلك قبل أن تُطلق أي خدمة للإنتاج.

الركائز الثلاث على نطاق واسع

كل منظومة مراقبة مقيدة بثلاثة محاور: معدل الاستيعاب، وزمن الاستعلام، وتكلفة الاحتفاظ. الخيارات المعمارية التالية مدفوعة بهذه القيود، لا بتفضيلات بائعين بعينهم.

  • المقاييس — Prometheus + Thanos أو Mimir. تفشل نسخة Prometheus الأحادية عند نحو مليون سلسلة زمنية نشطة على الأجهزة المعتادة. ما بعد ذلك تحتاج إلى Thanos (نموذج Sidecar مع تخزين في Object Storage وطبقة استعلام عالمية) أو Grafana Mimir (خدمات مصغرة قابلة للتوسع أفقياً). في النطاقات المتوسطة-الكبيرة (10–50 ألف Pod)، يُعدّ Mimir مع S3 واحتفاظ 13 شهراً الخيار القياسي حالياً. فترة الاستطلاع 15 ثانية لمقاييس البنية التحتية، و30 ثانية لمقاييس التطبيقات؛ ولا تقل أبداً عن 10 ثوانٍ — فذلك يُفجّر البُعد دون أي تحسن حقيقي في الإشارة.
  • السجلات — OpenTelemetry Collector ← Loki أو OpenSearch. السجلات المنظمة بصيغة JSON فحسب. كل سطر سجل يُصدر trace_id وservice.name وenv وseverity. عند أكثر من 500 غيغابايت في اليوم، يُوفر مخزن Loki في S3 مع طبقة ساخنة لمدة 30 يوماً وطبقة باردة لمدة عام تكلفةً أقل بنحو 70٪ مقارنة بـ Elasticsearch. الأخذ بعينة السجلات مشروع عند تجاوز 10,000 طلب/ثانية لكل خدمة؛ عيّن DEBUG بنسبة 1٪، وINFO بنسبة 10٪، وWARN/ERROR بنسبة 100٪، مع الاحتفاظ دائماً بسياق التتبع.
  • التتبعات — OpenTelemetry SDK ← Tempo أو Jaeger. أخذ العينات من الذيل (Tail-based sampling) إلزامي على نطاق واسع. أخذ العينات من الرأس يُهدر تتبعات الطلبات البطيئة والخاطئة — وهي تحديداً ما تحتاجه. يحتفظ Tempo 2.x بـ 100٪ من تتبعات الأخطاء، و100٪ من تتبعات P99، ونسبة مُهيَّأة من حركة المرور الطبيعية.

مخطط تدفق الإشارات

Observability stack signal flow App Pods (OTel SDK) OTel Collector Traces Pipeline Metrics Pipeline Logs Pipeline Tail Sampling Processor Batch / Retry Tempo (Traces) Mimir (Metrics) Loki (Logs) S3 / GCS Long-term store Grafana Dashboards Alertmanager Prometheus scrape /metrics remote_write OTLP/gRPC gRPC remote_write HTTP
تدفق الإشارات: تُصدر الـ Pods تتبعات وسجلات بصيغة OTLP إلى OTel Collector؛ يستطلع Prometheus المقاييس ويكتبها عن بُعد إلى Mimir؛ تحتفظ الواجهات الخلفية الثلاث بالبيانات الباردة في Object Storage؛ ويوفر Grafana سطح الاستعلام والتنبيه الموحد.

تصميم SLO وميزانية الخطأ

SLO بلا ميزانية خطأ ليس سوى رقم. الميزانية هي الرافعة التشغيلية: حين تكون صحية تُطلق الميزات؛ حين تُحرق تُجمّد خط الإصدارات وتُركّز الهندسة على الموثوقية. تتبع هيكلية التنبيه نموذج معدل الاحتراق من كتاب Google SRE:

  • تنبيه P0 (حرج فوري): معدل الاحتراق أكبر من 14.4× لمدة دقيقة واحدة. بهذا المعدل تُستنزف ميزانية الخطأ الكاملة لمدة 30 يوماً في غضون ساعتين. أيقظ المناوب فوراً.
  • تنبيه P1 (تذكرة): معدل الاحتراق أكبر من 6× لمدة 5 دقائق. الميزانية ستنتهي في 5 أيام. يُعالَج خلال ساعات العمل.
  • تحذير معدل الاحتراق: معدل الاحتراق أكبر من 1× لمدة ساعة. الميزانية تتقلص؛ أنشئ مهمة، لا تنبيه طارئ.
البُعد العالي هو القاتل الصامت. كل مزيج فريد من قيم التسميات في Prometheus يُنشئ سلسلة زمنية جديدة. تسمية ذات بُعد عالٍ — مثل user_id أو request_id أو trace_id — يمكنها أن تُحوّل Prometheus بـ 100,000 سلسلة إلى 50 مليون سلسلة بين ليلة وضحاها. فرض ميزانيات بُعد قيم التسميات باستخدام Mimir Cardinality API، ورفض المقاييس عند مستوى المجمّع. في Uber، تسببت مقياس واحد عالي البُعد من تغيير في SDK في إنفاق زائد بـ 250,000 دولار شهرياً قبل اكتشافه.

إعداد Prometheus + Alertmanager للإنتاج

# prometheus.yml — scrape + remote_write to Mimir global: scrape_interval: 15s evaluation_interval: 15s external_labels: cluster: prod-us-east-1 env: production rule_files: - /etc/prometheus/rules/*.yml remote_write: - url: http://mimir-distributor.monitoring.svc:9009/api/v1/push queue_config: max_samples_per_send: 10000 capacity: 100000 max_shards: 30 metadata_config: send: true scrape_configs: - job_name: kubernetes-pods 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] action: replace target_label: __metrics_path__ regex: (.+) - source_labels: [__meta_kubernetes_namespace] target_label: namespace - source_labels: [__meta_kubernetes_pod_name] target_label: pod
# rules/slo-payment-api.yml — multi-window burn-rate alerts groups: - name: payment-api-slo rules: # Availability SLO: 99.9% (error budget: 43.8 min/month) - record: job:http_errors:rate5m expr: | sum(rate(http_requests_total{job="payment-api",status=~"5.."}[5m])) / sum(rate(http_requests_total{job="payment-api"}[5m])) - alert: PaymentAPIErrorBudgetBurn_Critical expr: | ( job:http_errors:rate5m > (14.4 * 0.001) ) and ( sum(rate(http_requests_total{job="payment-api",status=~"5.."}[1h])) / sum(rate(http_requests_total{job="payment-api"}[1h])) > (14.4 * 0.001) ) for: 1m labels: severity: critical slo: payment-api-availability annotations: summary: "Payment API burning error budget at >14.4x rate" runbook_url: "https://runbooks.internal/payment-api-5xx" description: "Current burn rate {{ $value | humanizePercentage }}, 2h to exhaustion" - alert: PaymentAPIErrorBudgetBurn_High expr: | job:http_errors:rate5m > (6 * 0.001) for: 5m labels: severity: high slo: payment-api-availability annotations: summary: "Payment API burning error budget at >6x rate"

إعداد OTel Collector: أخذ عينات الذيل

# otel-collector-config.yaml — tail-based sampling pipeline receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: tail_sampling: decision_wait: 10s num_traces: 200000 expected_new_traces_per_sec: 5000 policies: - name: error-traces type: status_code status_code: {status_codes: [ERROR]} - name: slow-traces type: latency latency: {threshold_ms: 500} - name: baseline-sample type: probabilistic probabilistic: {sampling_percentage: 1} batch: send_batch_size: 10000 timeout: 5s send_batch_max_size: 20000 filter/drop-high-cardinality: metrics: datapoint: - 'attributes["user_id"] != ""' exporters: otlp/tempo: endpoint: http://tempo.monitoring.svc:4317 tls: insecure: true loki: endpoint: http://loki-gateway.monitoring.svc/loki/api/v1/push default_labels_enabled: exporter: false job: true service: pipelines: traces: receivers: [otlp] processors: [tail_sampling, batch] exporters: [otlp/tempo] logs: receivers: [otlp] processors: [batch] exporters: [loki]

مخطط هيكلية SLO والتنبيهات

SLO and alerting hierarchy SLI availability latency p99 error rate SLO avail ≥ 99.9 % p99 ≤ 200 ms error ≤ 0.1 % 30-day window Error Budget 43.8 min / month remaining % Burn Rate multiwindow calc 1m + 5m + 60m Alert Tiers P0: rate > 14.4× P1: rate > 6× Warn: rate > 1× measured defines drives
هيكلية SLO: تُقاس SLIs وتُحدد هدف SLO وميزانية الخطأ؛ تحسابات معدل الاحتراق عبر نوافذ زمنية متعددة تقود سياسة التصعيد ذات المستويات الثلاثة.

توجيه Alertmanager واستراتيجية الإشعارات

التنبيهات الخام الموجَّهة مباشرة إلى Slack أو PagerDuty دون تجميع تُفضي إلى إجهاد التنبيهات في غضون أسابيع. النمط الصحيح: تجميع حسب SLO والكتلة، وكبت التنبيهات الأدنى خطورة عند إطلاق تنبيه حرج على الخدمة ذاتها، وإزالة التكرار ضمن نافذة خمس دقائق.

# alertmanager.yml global: resolve_timeout: 5m pagerduty_url: https://events.pagerduty.com/v2/enqueue route: group_by: [alertname, cluster, slo] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: slack-warnings routes: - match: severity: critical receiver: pagerduty-critical continue: false - match: severity: high receiver: slack-oncall group_interval: 2m repeat_interval: 1h inhibit_rules: - source_match: severity: critical target_match: severity: high equal: [cluster, slo] receivers: - name: pagerduty-critical pagerduty_configs: - routing_key: <PD_INTEGRATION_KEY> description: '{{ .GroupLabels.alertname }} on {{ .GroupLabels.cluster }}' details: runbook: '{{ (index .Alerts 0).Annotations.runbook_url }}' firing: '{{ .Alerts.Firing | len }}' - name: slack-oncall slack_configs: - api_url: <SLACK_WEBHOOK_URL> channel: '#oncall-alerts' title: '[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}' text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}' - name: slack-warnings slack_configs: - api_url: <SLACK_WEBHOOK_URL> channel: '#platform-noise' send_resolved: true
التنبيه المرتكز على Runbook. كل تنبيه إنتاجي يجب أن يمتلك تعليقاً توضيحياً runbook_url يشير إلى Runbook حي ومُصان قبل أن يُطلق في الإنتاج. التنبيهات بدون Runbooks تُعطَّل. هذه الممارسة الأعلى تأثيراً في الموثوقية: المهندس المُستيقَظ الساعة 3 صباحاً يحتاج الأوامر التشخيصية الثلاث الأولى، وأنماط الفشل المتوقعة، وإجراءات التراجع — لا الكود المصدري. قنّن هذا كفحص في CI لمستودع قواعد التنبيه.
Grafana OnCall مقابل PagerDuty: إذا شغّلت Grafana OnCall داخل الكتلة، فإن عطلاً كاملاً للكتلة سيُصمت منظومة المناوبة الخاصة بك. وجّه دائماً تنبيهات P0 الحرجة عبر قناة خارج النطاق — PagerDuty أو OpsGenie أو Alertmanager منطقة منفصلة. لا تسمح أبداً لنطاق تأثير واحد بإسقاط النظام الفاشل ومسار إشعار الحوادث معاً. هذا النمط بالضبط تسبب في تعطل منصة تجارة إلكترونية كبرى لأربع ساعات خلال Black Friday 2023.

الاحتفاظ والتكلفة والنظافة التشغيلية

تعمل بنية تحتية للمراقبة عادةً بنسبة 8–15٪ من إجمالي الإنفاق السحابي للشركات الراسخة. الحفاظ على هذا الرقم مستداماً يستلزم هندسة تكلفة متعمدة:

  • المقاييس: احتفاظ 13 شهراً في Mimir. دقة كاملة لمدة 7 أيام؛ تخفيض كل 5 دقائق لمدة 30 يوماً؛ تخفيض كل ساعة ما بعد ذلك. التخفيض يُقلل التخزين 40 مرة.
  • السجلات: طبقة ساخنة 30 يوماً في Loki؛ طبقة باردة عام في S3 Glacier مع SLA استعادة 24 ساعة. فرض سياسات احتفاظ خاصة بالـ namespace.
  • التتبعات: Tempo مع S3، احتفاظ 14 يوماً للتتبعات الكاملة. تتبعات الأخطاء: 90 يوماً. تتبعات البطء: 30 يوماً. خط القاعدة الطبيعي: 7 أيام.
  • لوحات البيانات: توحيد على لوحات RED (معدل، خطأ، مدة) لكل خدمة. اللوحات المتضخمة بـ 40 لوحة دون تراتبية واضحة تُبطئ الاستجابة للحوادث.

عند اكتمال هذه المنظومة، تكون كل خدمة في المنصة مُجهَّزة بالأدوات، وكل SLO مرتبطاً بتنبيه معدل احتراق موصول بـ PagerDuty، ويستطيع فريق المنصة الإجابة على الأسئلة الثلاثة للحوادث — ماذا انكسر، وأين، ولماذا — ضمن هدف MTTD لا يتجاوز خمس دقائق.