السجلات على نطاق واسع: ELK وLoki

معمارية تسجيل الأحداث على نطاق واسع

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

معمارية تسجيل الأحداث على نطاق واسع

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

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

لماذا يوجد تسجيل الأحداث المركزي

بدون نظام تسجيل مركزي، يعني تصحيح أخطاء نظام موزع الدخول بـ SSH إلى عقد فردية والبحث بـ grep في ملفات محلية، وأمل أن يكون السجل ذي الصلة قد وصل إلى الجهاز الذي تنظر إليه. على نطاق واسع هذا مستحيل تشغيلياً: الـ pods مؤقتة (تُقتل بعد نفاد الذاكرة، ويُعيد الجدولي إنشاءها)، يمكن استبدال العقد بواسطة القياس التلقائي، والطلب الذي تحقق فيه ربما لمس عشرين خدمة عبر خمسة namespaces.

يحل تسجيل الأحداث المركزي هذا بتوجيه كل مخرجات السجل إلى backend واحد قابل للاستعلام. تطرح استعلاماً واحداً، تحصل على الصورة الكاملة. التحدي الهندسي هو القيام بذلك بشكل موثوق، دون أن يُصبح خط أنابيب التسجيل نفسه نقطة فشل، ودون انفلات التكلفة عن السيطرة.

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

المرحلة 1: الجمع

الجمع هو فعل التقاط مخرجات السجل الخام من أي مكان نشأت: stdout/stderr من حاوية، ملف على القرص تكتبه عملية خلفية، مقبس syslog، أو SDK تطبيق يكتب أحداثاً منظَّمة مباشرة. يعمل جامع الأحداث قريباً من المصدر — كـ DaemonSet في Kubernetes، كـ sidecar للحاوية، أو كمكتبة مُدمجة في التطبيق.

الجامعات ذات الجودة الإنتاجية (Fluent Bit، Promtail، OpenTelemetry Collector، Vector) تفعل أكثر من مجرد إعادة توجيه البايتات. تُنفّذ التحليل (تحويل النص غير المنظَّم إلى حقول منظَّمة)، والإثراء (إضافة بيانات Kubernetes الوصفية: اسم الـ pod، الـ namespace، العقدة، وسم صورة الحاوية)، والتصفية (حذف ضجيج فحوصات الصحة قبل وصولها إلى الشبكة)، والتخزين المؤقت (الاحتفاظ بالبيانات في الذاكرة أو على القرص حين يكون المصب مؤقتاً غير متاح).

طبقة الجمع هي المكان الذي يرتكب فيه كثير من المنظمات أول خطأ مكلف: اختيار وكيل ثقيل (Logstash كـ DaemonSet) يستهلك 500 ميجابايت من الذاكرة لكل عقدة، أو نشر بدون تخزين مؤقت بحيث يُسبب انقطاع النقل ضياع السجلات. Fluent Bit هو الخيار الأفضل الممارسة الحالي لـ Kubernetes: مكتوب بـ C، يعمل في أقل من 50 ميجابايت RSS، ولديه إثراء بيانات Kubernetes الوصفية مُدمج.

ممارسة احترافية: دائماً عيّن Mem_Buf_Limit وstorage.type filesystem في Fluent Bit. بدون مخزن مؤقت على الملف، يُضيع انقطاع المصب السجلات. معه، يُخزّن الوكيل على القرص ويُعيد التشغيل حين يتعافى النقل. Google وDatadog وAWS جميعها تُشغّل خطوط أنابيب جمع ذات تخزين مؤقت لهذا السبب بالذات.

المرحلة 2: النقل

طبقة النقل تُحرّك بيانات السجل من الجامعات إلى backend التخزين. على نطاق صغير هذا دفع HTTP/gRPC مباشر من كل وكيل. على نطاق إنتاجي — آلاف العقد، جيجابايتات في الثانية — وسيط رسائل مُخصص يفصل الجامع عن المخزن.

وسيط النقل النموذجي لتسجيل الأحداث الكبير النطاق هو Apache Kafka. تُنشر الجامعات إلى موضوعات Kafka مُقسَّمة حسب الخدمة أو الـ namespace. يقرأ المستهلكون المصب (الفهارس، طبقة استيعاب التخزين) من تلك الموضوعات بوتيرتهم الخاصة. يوفر Kafka المتانة (أجزاء السجل على القرص، احتفاظ قابل للضبط)، وعزل الضغط الخلفي (مُفهرس بطيء لا يتسبب في تراكم الجامع ونفاد ذاكرته)، والإعادة (إذا كان للمُفهرس خطأ وفقد يوماً من البيانات، يمكنك الإعادة من Kafka).

بالنسبة للمنظمات الأصغر أو المتواجدة بالفعل على AWS، تُؤدي Kinesis Data Streams نفس الدور. بالنسبة لـ Grafana Loki، يمكن أن يكون النقل دفعاً مباشراً عبر Promtail أو Grafana Alloy (نطاق صغير) أو Kafka (نطاق كبير). يمكن لخطوط أنابيب OpenTelemetry Collector التوزيع على backends متعددة في وقت واحد — نمط قوي للمنظمات التي تهاجر بين أنظمة التخزين.

مصيدة إنتاجية: إذا لم يكن في طبقة النقل آلية ضغط خلفي، يتدرج انقطاع التخزين نحو المصدر. تملأ الجامعات مخازنها المؤقتة في الذاكرة وتبدأ في إسقاط السجلات. تعاني من الانقطاع، تفتح نظام التسجيل للتحقيق، وتجد لا سجلات للفترة المعنية. صمّم النقل ليمتص انقطاعات التخزين: Kafka أو قائمة انتظار ذات تخزين مؤقت على القرص من جانب المستهلك هو الحل القياسي.
Centralized Logging Pipeline: Collect, Transport, Store, Query App Container A App Container B System Daemon Ingress / LB Sources COLLECT Fluent Bit Promtail / Alloy OTel Collector parse · enrich filter · buffer Stage 1 TRANSPORT Kafka Kinesis Pub/Sub decouple · replay backpressure Stage 2 STORE Elasticsearch Grafana Loki ClickHouse index · compress retain · tier Stage 3 QUERY Kibana · Grafana Explore LogQL · KQL · Alerting Stage 4 — engineers, on-call, dashboards, alerts small scale direct push
خط الأنابيب الأربعي لتسجيل الأحداث المركزي. يُوضّح السهم المتقطع مساراً مباشراً للنطاق الصغير؛ على نطاق الإنتاج يفصل وسيط الرسائل الجمع عن التخزين لاستيعاب طفرات الحمل.

المرحلة 3: التخزين

طبقة التخزين تُفهرس وتحتفظ ببيانات السجل حتى يمكن الاستعلام عنها بكفاءة. الخياران الرئيسيان مفتوحا المصدر يعكسان فلسفتَي تصميم مختلفتين جوهرياً:

  • Elasticsearch (أو OpenSearch) — فهرس عكسي للنص الكامل لكل حقل. سريع للغاية لبحث الكلمات المفتاحية والتجميعات عبر الحقول الاعتباطية. تكلفة كتابة عالية: كل حقل يُحلَّل ويُفهرس عند الاستيعاب، مستهلكاً CPU وتخزيناً كبيرَين (عادةً 2–5 أضعاف حجم السجل الخام). الخيار الصحيح حين يحتاج المهندسون إلى استعلامات ad-hoc على مستوى الحقل عبر بيانات شبه منظَّمة.
  • Grafana Loki — تخزين مُفهرَس بالتسميات ومضغوط في مقاطع. فقط التسميات (البيانات الوصفية: app، namespace، level، region) تُفهرَس؛ محتوى السجل يُخزَّن في مقاطع مضغوطة ويُمسح عند الاستعلام عبر مرشح LogQL. أرخص 10–20 مرة من Elasticsearch لكل جيجابايت. الخيار الصحيح حين تكون سجلاتك منظَّمة وأنماط استعلامك تبدأ بالتسمية. مُصمَّم للعمل مع Prometheus وGrafana Tempo بشكل طبيعي.

خيار ثالث يكتسب زخماً في سياقات التحليلات عالية الحجم هو ClickHouse: قاعدة بيانات OLAP عمودية يمكنها استيعاب ملايين الأحداث في الثانية وتنفيذ استعلامات التجميع في ميلي ثوان بنسب ضغط عالية جداً. تستخدمها Cloudflare وByteDance لتحليلات السجلات على نطاق البيتابايت.

التدرج في التخزين: على نطاق واسع، تخزين كل شيء في فهرس ساخن مكلف للغاية. تُدرّج بنى التسجيل الإنتاجية البيانات: ساخن (7 أيام أخيرة، SSD سريع، سرعة استعلام كاملة) → دافئ (8–30 يوم، تخزين أرخص، أبطأ قليلاً) → بارد (31–365 يوم، تخزين كائنات مثل S3/GCS، استعلام عبر إعادة تحميل انتقائية). تُنفّذ سياسات ILM لـ Elasticsearch وbackend S3 لـ Loki مع الـ ruler هذا تلقائياً.

المرحلة 4: الاستعلام

طبقة الاستعلام هي كيفية تفاعل البشر والأنظمة الآلية مع السجلات المخزَّنة. تشمل واجهة المستخدم (Kibana لـ Elasticsearch، Grafana Explore لـ Loki)، ولغة الاستعلام (KQL/Lucene أو LogQL)، والتكامل مع التنبيه (قواعد Grafana Alerting المدعومة باستعلامات السجل).

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

  1. التحقيق التفاعلي في الحوادث — مهندس يُشغّل استعلامات ad-hoc، يريد نتائج في أقل من 3 ثوان. يتطلب فهرساً ساخناً، وواجهة أمامية قوية للاستعلام تُخزّن النتائج مؤقتاً وتُوازي الاستعلامات، ونطاقاً زمنياً افتراضياً جيداً (15 دقيقة أخيرة لا كل الوقت).
  2. التنبيه الآلي — محرك قواعد يُقيّم تعبير LogQL أو KQL كل 60 ثانية ويُطلق تنبيهاً عند تحقق شرط. يجب أن تكون هذه استعلامات حتمية منخفضة القيم الكاردينالية رخيصة التقييم بشكل متكرر. وجّهها عبر مسار استعلام مُخصَّص لتجنب التنافس مع الاستعلامات التفاعلية خلال حادث.
# ── إعداد Fluent Bit DaemonSet للإنتاج على Kubernetes ────────────────────── [SERVICE] Flush 5 Log_Level warn storage.type filesystem # تخزين مؤقت على القرص حين يكون المصب بطيئاً storage.path /var/log/flb-storage/ [INPUT] Name tail Path /var/log/containers/*.log multiline.parser cri # معالجة سطور CRI متعددة الأسطر Tag kube.* Refresh_Interval 10 Mem_Buf_Limit 50MB storage.type filesystem [FILTER] Name kubernetes Match kube.* Merge_Log On # تحليل JSON المتداخل إلى حقول Keep_Log Off K8S-Logging.Parser On K8S-Logging.Exclude On [FILTER] Name grep Match kube.* Exclude log (ELB-HealthChecker|kube-probe) # حذف الضجيج قبل النقل [OUTPUT] Name kafka Match * Brokers kafka-0.kafka:9092,kafka-1.kafka:9092,kafka-2.kafka:9092 Topics logs.production rdkafka.queue.buffering.max.ms 100 rdkafka.compression.codec lz4
# ── أمثلة استعلامات LogQL (Grafana Loki) ───────────────────────────────── # 1. بث جميع سجلات الأخطاء من خدمة checkout (15 دقيقة أخيرة): {app="checkout", namespace="production"} |= "level=error" # 2. تحليل سجلات JSON والتصفية حسب رمز حالة HTTP: {app="api-gateway"} | json | status_code >= 500 # 3. عدّ الأخطاء لكل pod عبر الزمن (لوحة Grafana): sum by (pod) ( rate({app="checkout"} | json | level="error" [5m]) ) # 4. بحث نصي كامل عن معرف trace محدد عبر جميع الخدمات: {namespace="production"} |= "trace_id=abc123def456" # 5. قاعدة تنبيه — أطلق إذا تجاوزت أخطاء checkout 5/دقيقة لمدة دقيقتين: sum(rate({app="checkout"} | json | level="error" [1m])) > 5

أوضاع فشل خط الأنابيب

فهم مواضع فشل خط الأنابيب تحت الحمل لا يقل أهمية عن فهم تشغيله الطبيعي. أكثر أوضاع الفشل الإنتاجية شيوعاً:

  • نفاد ذاكرة الجامع: طوفان سجلات (عاصفة استثناءات، تسجيل تصحيح مطوّل متروك في الإنتاج) يُطغي المخزن المؤقت في ذاكرة الجامع. بدون Mem_Buf_Limit والتخزين المؤقت على الملف، يُقتل pod الجامع بسبب OOM وتُفقد السجلات. الإصلاح: مخزن مؤقت على الملف + حد مضبوط أدنى من طلب الذاكرة للجامع.
  • تأخر مستهلك Kafka: طبقة استيعاب التخزين تتأخر عن منتج Kafka (الجامعات). يتراكم التأخر. إذا كان احتفاظ Kafka قصيراً جداً (مثلاً 24 ساعة)، تُحذف الأجزاء غير المُستهلَكة قبل أن يعالجها المُفهرس. الإصلاح: ضبط احتفاظ Kafka على 48–72 ساعة على الأقل لموضوعات التسجيل؛ تنبيه على تأخر مجموعة المستهلكين.
  • النقاط الساخنة في شرائح Elasticsearch: كل سجلات نافذة زمنية تستقر في نفس شريحة Elasticsearch لأن نمط الفهرس قائم على الزمن وعدد الشرائح منخفض جداً. تلك الشريحة تصبح عنق زجاجة للكتابة. الإصلاح: تحديد حجم الشرائح بـ 30–50 جيجابايت كحد أقصى؛ استخدام سياسات rollover في ILM لإنشاء شرائح جديدة بالوتيرة المناسبة.
  • عاصفة استعلامات خلال حادث: حين يقع انقطاع كبير، يُشغّل عشرة مهندسين في آن واحد استعلامات واسعة. يمكن أن يُثقل هذا طبقة الاستعلام ويجعل نظام التسجيل غير متاح تماماً حين يُحتاج إليه. الإصلاح: واجهة أمامية للاستعلام مع تخزين مؤقت للنتائج وحدود معدل لكل مستخدم؛ دائماً قيّد الاستعلامات بتسمية قبل فتح النطاق الزمني.
ممارسة احترافية: عامل خط أنابيب التسجيل كنظام حرج لا كـ sidecar بجهد أفضل. شغّله بنفس صرامة SLO التي تُطبّقها على خدمات منتجك. كحد أدنى: DaemonSet للجامع مع PodDisruptionBudget، وKafka بمعامل نسخ متماثل 3، وbackend تخزين بطوبولوجيا عالية التوفر، وتنبيه على "لم تُستقبل سجلات من namespace X في آخر 5 دقائق" — حتى تعرف حين يكون خط الأنابيب نفسه معطوباً قبل أن يكتشفه مهندس المناوبة خلال حادث.

اختيار بنيتك

البنية الصحيحة تعتمد على نطاقك وأنماط استعلامك، لكن الافتراضيات الصناعية لعام 2025 تبدو كالآتي:

  • Kubernetes-native، حساس للتكلفة: Grafana Alloy (جامع) → دفع مباشر أو Kafka → Grafana Loki (تخزين) → Grafana (استعلام). أدنى تكلفة تشغيلية؛ تكامل طبيعي مع Prometheus وTempo للملاحظة المترابطة.
  • أحمال عمل مختلطة، بحث ad-hoc غني: Fluent Bit (جامع) → Kafka → Elasticsearch/OpenSearch (تخزين) → Kibana (استعلام). أغلى لكن أكثر مرونة لمحتوى السجل غير المنظَّم.
  • تحليلات عالية الحجم، فريق هندسة بيانات: OTel Collector → Kafka → ClickHouse (تخزين) → Grafana أو أدوات SQL مخصَّصة. أفضل ضغط وأداء تجميع على نطاق البيتابايت.

تبني الدروس المتبقية في هذا الدرس التعليمي كل مكوّن من هذه البنية بعمق — بدءاً بمعايير التسجيل المنظَّم التي تجعل كل هذا قابلاً للاستعلام أصلاً.