202 أسطر
16 KiB
Markdown
202 أسطر
16 KiB
Markdown
# تقرير تحليل حادثة (Postmortem)
|
|
## انقطاع خدمة بسبب OOMKilled متكرر — منصة غيمة
|
|
|
|
| الحقل | القيمة |
|
|
|---|---|
|
|
| **مدة الانقطاع** | 45 دقيقة |
|
|
| **الأثر** | توقف كامل لتوفر التطبيق (Full outage) |
|
|
| **السبب المباشر** | إعادة تشغيل متكررة للحاوية بسبب `OOMKilled` |
|
|
| **الخطورة** | High (SEV-2) |
|
|
| **الحالة** | تم الحل |
|
|
|
|
---
|
|
|
|
## 1. الملخص التنفيذي (Summary)
|
|
|
|
تعطّل التطبيق المستضاف على منصة غيمة لمدة 45 دقيقة بسبب دخول الحاوية (Container) في حلقة تعطّل متكررة (**CrashLoopBackOff**) ناتجة عن تجاوز استخدام الذاكرة للحد الأقصى المخصص لها (**Memory Limit**)، مما تسبب في قتل النظام للحاوية بواسطة Linux OOM Killer في كل مرة كانت تُعاد فيها المحاولة.
|
|
|
|
السبب الجذري يعود إلى عدم وجود حدود ذاكرة (Resource Limits) مضبوطة بشكل صحيح، مصحوبًا بزيادة تدريجية في استخدام الذاكرة (Memory Leak أو ضغط طلبات مرتفع) لم يتم رصدها لعدم وجود تنبيهات مبكرة (Alerting) على استهلاك الذاكرة.
|
|
|
|
---
|
|
|
|
## 2. الخط الزمني للحادثة (Timeline)
|
|
|
|
> ملاحظة: التوقيتات نسبية لتوضيح تسلسل الحدث (T = صفر عند أول إعادة تشغيل غير طبيعية).
|
|
|
|
| الوقت | الحدث |
|
|
|---|---|
|
|
| **T - 25 دقيقة** | بدء ارتفاع تدريجي وغير ملحوظ في استخدام الذاكرة (Memory Usage Trend) نتيجة زيادة تدريجية في الطلبات/تسريب ذاكرة داخل التطبيق |
|
|
| **T - 2 دقيقة** | استهلاك الذاكرة يقترب من 95%+ من الحد الأقصى المخصص للحاوية |
|
|
| **T = 0** | Kernel OOM Killer يقتل عملية التطبيق داخل الحاوية → الحالة تتحول إلى `OOMKilled` |
|
|
| **T + 1 دقيقة** | منصة غيمة تعيد تشغيل الحاوية تلقائيًا (Restart Policy) |
|
|
| **T + 3 دقيقة** | الحاوية الجديدة تصل لنفس مستوى استهلاك الذاكرة بسرعة (لأن سبب المشكلة لم يُحل) وتتكرر `OOMKilled` |
|
|
| **T + 3 إلى T + 40 دقيقة** | حلقة متكررة من (تشغيل → استهلاك ذاكرة مرتفع → OOMKilled → إعادة تشغيل) — التطبيق غير متاح للمستخدمين طوال هذه الفترة |
|
|
| **T + 40 دقيقة** | فريق التشغيل يلاحظ التنبيهات/الشكاوى ويبدأ التحقيق |
|
|
| **T + 42 دقيقة** | تم رفع حد الذاكرة (Memory Limit) للحاوية مؤقتًا كإجراء تخفيف سريع |
|
|
| **T + 45 دقيقة** | الخدمة تستقر وتعود متاحة بشكل طبيعي |
|
|
|
|
---
|
|
|
|
## 3. السبب الجذري (Root Cause Analysis)
|
|
|
|
### السبب المباشر (Immediate Cause)
|
|
تجاوز استخدام الذاكرة داخل الحاوية للحد الأقصى (`memory limit`) المحدد لها، مما دفع نظام Linux (cgroups) لاستدعاء **OOM Killer** لإنهاء العملية.
|
|
|
|
### السبب الجذري (Root Cause)
|
|
باستخدام تحليل "الأسباب الخمسة" (5 Whys):
|
|
|
|
1. **لماذا تعطّل التطبيق؟** → لأن الحاوية تلقّت `OOMKilled` بشكل متكرر.
|
|
2. **لماذا حدث OOMKilled؟** → لأن استخدام الذاكرة تجاوز الحد الأقصى المخصص للحاوية.
|
|
3. **لماذا تجاوز الاستخدام الحد الأقصى؟** → لأن الذاكرة كانت في ازدياد تدريجي مستمر (تسريب ذاكرة أو تراكم بيانات في الكاش/الجلسات دون تحرير).
|
|
4. **لماذا لم يُكتشف الازدياد قبل حدوث الانقطاع؟** → لعدم وجود تنبيه (Alert) مفعّل على نسبة استخدام الذاكرة قبل الوصول للحد الأقصى.
|
|
5. **لماذا لم تتعامل المنصة مع الأمر تلقائيًا؟** → لعدم وجود سياسة Auto-scaling أو Horizontal Pod Autoscaler مبنية على استهلاك الذاكرة، ولعدم وجود نسخ متعددة (Replicas) توزّع الحمل.
|
|
|
|
### السبب الجذري النهائي
|
|
**غياب طبقة حماية استباقية (Resource Limits غير مضبوطة بدقة + عدم وجود Auto-scaling + عدم وجود مراقبة/تنبيه مبكر على الذاكرة)**، مما حوّل مشكلة أداء تدريجية إلى انقطاع كامل ومتكرر بدلاً من معالجتها قبل الوصول لنقطة الانهيار.
|
|
|
|
---
|
|
|
|
## 4. الأثر (Impact)
|
|
|
|
- توقف كامل للخدمة لمدة 45 دقيقة
|
|
- فقدان طلبات المستخدمين أثناء كل محاولة إعادة تشغيل فاشلة
|
|
- استهلاك غير ضروري لموارد إعادة التشغيل المتكرر (CPU/Network) دون أي فائدة فعلية
|
|
|
|
---
|
|
|
|
## 5. التوصيات (Recommendations)
|
|
|
|
| # | التوصية | الأولوية |
|
|
|---|---|---|
|
|
| 1 | ضبط `requests` و `limits` للذاكرة بدقة بناءً على قياس فعلي لاستخدام التطبيق تحت الحمل (Load Testing) بدل قيم تقديرية | عالية |
|
|
| 2 | إضافة تنبيهات (Alerts) عند تجاوز استخدام الذاكرة 70% و 85% من الحد الأقصى، قبل الوصول لـ OOM | عالية |
|
|
| 3 | تفعيل Horizontal Pod Autoscaling (HPA) بناءً على الذاكرة/الـ CPU لتوزيع الحمل على عدة نسخ بدل الاعتماد على نسخة واحدة | عالية |
|
|
| 4 | إضافة `PodDisruptionBudget` وسياسة `restart backoff` مدروسة لمنع دخول النظام في حلقة تعطّل سريعة تستهلك الموارد | متوسطة |
|
|
| 5 | فحص كود التطبيق لتحديد مصدر تسريب الذاكرة المحتمل (اتصالات DB غير مغلقة، كاش بلا حد أقصى للحجم، تراكم Sessions) | عالية |
|
|
| 6 | إضافة Dashboard مخصص لمتابعة اتجاه استخدام الذاكرة (Memory Trend) بمرور الوقت لا القيمة اللحظية فقط | متوسطة |
|
|
| 7 | تفعيل Readiness/Liveness Probes بشكل صحيح لمنع توجيه حركة المرور لحاوية في حالة غير مستقرة | متوسطة |
|
|
|
|
---
|
|
|
|
## 6. سياسة Auto-scaling المقترحة لمنصة غيمة
|
|
|
|
الهدف: منع تكرار الحادثة عن طريق التوسّع الأفقي قبل الوصول لنقطة الانهيار، بدل الاعتماد فقط على إعادة التشغيل بعد الفشل.
|
|
|
|
### أ. حدود الموارد (Resource Requests & Limits)
|
|
```yaml
|
|
resources:
|
|
requests:
|
|
memory: "256Mi" # الحد الأدنى المضمون للحاوية
|
|
cpu: "250m"
|
|
limits:
|
|
memory: "512Mi" # حد أعلى بهامش أمان معقول، لا حد ضيق جدًا
|
|
cpu: "500m"
|
|
```
|
|
> القيم توضيحية — يجب ضبطها فعليًا بناءً على قياس الاستخدام الحقيقي للتطبيق (p95/p99 لاستهلاك الذاكرة تحت الحمل).
|
|
|
|
### ب. سياسة Horizontal Auto-scaling
|
|
- **المقياس (Metric):** استخدام الذاكرة (Memory Utilization) بالإضافة إلى CPU
|
|
- **حد التوسّع (Scale-out threshold):** عند تجاوز 70% من الحد الأقصى للذاكرة على مستوى النسخ الحالية
|
|
- **الحد الأدنى للنسخ (Min Replicas):** 2 (لضمان توفر دائم حتى مع سقوط نسخة واحدة)
|
|
- **الحد الأعلى للنسخ (Max Replicas):** يحدد حسب سقف الموارد المتاح على الحساب
|
|
- **فترة التبريد (Cooldown/Stabilization Window):** 2-3 دقائق قبل التوسّع لتجنب استجابة مبالغ فيها لارتفاع مؤقت، ومهلة أطول قبل التقليص (Scale-in) لتجنب التقلب (Flapping)
|
|
|
|
مثال توضيحي بمنطق HPA:
|
|
```yaml
|
|
apiVersion: autoscaling/v2
|
|
kind: HorizontalPodAutoscaler
|
|
metadata:
|
|
name: app-hpa
|
|
spec:
|
|
scaleTargetRef:
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
name: my-app
|
|
minReplicas: 2
|
|
maxReplicas: 6
|
|
metrics:
|
|
- type: Resource
|
|
resource:
|
|
name: memory
|
|
target:
|
|
type: Utilization
|
|
averageUtilization: 70
|
|
- type: Resource
|
|
resource:
|
|
name: cpu
|
|
target:
|
|
type: Utilization
|
|
averageUtilization: 65
|
|
behavior:
|
|
scaleUp:
|
|
stabilizationWindowSeconds: 60
|
|
scaleDown:
|
|
stabilizationWindowSeconds: 300
|
|
```
|
|
|
|
### ج. سياسة إعادة التشغيل (Restart Policy)
|
|
- استخدام `Exponential Backoff` بين محاولات إعادة التشغيل (بدل إعادة تشغيل فورية متكررة) لتقليل الضغط على الموارد وقت الفشل المتكرر
|
|
- تحديد حد أقصى لعدد محاولات إعادة التشغيل خلال فترة زمنية معينة، وإرسال تنبيه فوري (Critical Alert) عند تجاوزه بدل الاستمرار في المحاولة بصمت
|
|
|
|
### د. توزيع الحمل (Load Distribution)
|
|
- التأكد من وجود Load Balancer/Service أمام النسخ المتعددة لتوزيع الطلبات بالتساوي، بحيث لا تتحمل نسخة واحدة ضغطًا يدفعها أسرع نحو حد الذاكرة
|
|
|
|
---
|
|
|
|
## 7. الخلاصة
|
|
|
|
الحادثة نتجت عن غياب طبقتين أساسيتين من الحماية: **مراقبة استباقية** تكشف الاتجاه قبل الانهيار، و**توسّع تلقائي** يوزّع الحمل بدل الاعتماد على نسخة واحدة تُعاد إعادة تشغيلها بلا فائدة. تطبيق التوصيات أعلاه (ضبط الموارد + Auto-scaling + تنبيهات مبكرة على الذاكرة) يمنع تكرار هذا النوع من الانقطاع مستقبلًا ويحوّل الاستجابة من "رد فعل بعد وقوع الحادثة" إلى "إجراء استباقي قبل حدوثها".
|
|
|
|
---
|
|
|
|
## 8. الكشف المبكر عن هذه المشكلة باستخدام أدوات مراقبة غيمة
|
|
|
|
الهدف: رصد الاتجاه (Trend) قبل الوصول للحد الأقصى، لا الانتظار حتى وقوع OOMKilled.
|
|
|
|
### أ. مقاييس أساسية يجب مراقبتها (Metrics)
|
|
| المقياس | الفائدة |
|
|
|---|---|
|
|
| **Memory Usage % of Limit** لكل حاوية | يوضح الاقتراب من حد OOM قبل حدوثه |
|
|
| **Memory Usage Trend (over time)** | يكشف الازدياد التدريجي المستمر الذي يشير لتسريب ذاكرة، بعكس القيمة اللحظية |
|
|
| **Container Restart Count** | ارتفاع مفاجئ في عدد إعادة التشغيل = مؤشر قوي على مشكلة متكررة |
|
|
| **OOMKilled Event Count** | يجب رصده كحدث مباشر عند حدوثه، لا انتظار ملاحظة المستخدم |
|
|
| **Request Rate / Latency** | لربط ارتفاع الذاكرة بزيادة حمل حقيقي وليس تسريبًا فقط |
|
|
|
|
### ب. التنبيهات المبكرة (Alerting) المقترحة
|
|
1. **تنبيه تحذيري (Warning):** عند وصول استخدام الذاكرة لـ 70% من الحد الأقصى لمدة مستمرة (مثلًا 5 دقائق متواصلة) — إشارة على وجود اتجاه صاعد يحتاج مراجعة
|
|
2. **تنبيه حرج (Critical):** عند وصول الاستخدام لـ 90% — إجراء فوري مطلوب قبل وقوع OOM
|
|
3. **تنبيه فوري عند وقوع أي `OOMKilled` event** — بحيث يصل الفريق للحادثة في ثوانٍ لا بعد 40 دقيقة كما حدث فعليًا
|
|
4. **تنبيه عند تكرار Restart أكثر من N مرة خلال فترة زمنية قصيرة** — يكشف حلقة CrashLoopBackOff مبكرًا
|
|
|
|
### ج. لوحات المراقبة (Dashboards)
|
|
- لوحة تعرض اتجاه استخدام الذاكرة/CPU لكل خدمة عبر الزمن (وليس فقط القيمة الحالية) لتسهيل ملاحظة الازدياد التدريجي
|
|
- لوحة تجمع بين عدد النسخ الحالية (Replica Count)، حالة الصحة (Health Status)، وعدد مرات إعادة التشغيل في مكان واحد لرؤية شاملة سريعة عند أي حادثة
|
|
|
|
### د. المراجعة الدورية (Proactive Review)
|
|
- مراجعة أسبوعية لاتجاهات استخدام الموارد لكل خدمة لضبط الحدود (Limits) بناءً على بيانات حقيقية بدل قيم تقديرية ثابتة، خصوصًا للتطبيقات التي تشهد نموًا مستمرًا في الاستخدام
|
|
|
|
### هـ. تطبيق ما سبق فعليًا باستخدام الأدوات المتاحة في متجر غيمة (Marketplace)
|
|
|
|
منصة غيمة توفّر عدة خدمات جاهزة (One-click services) يمكن دمجها مباشرة لبناء طبقة المراقبة والتنبيه المذكورة أعلاه من غير الحاجة لبنائها من الصفر:
|
|
|
|
| الأداة | الفئة | دورها في منع تكرار هذه الحادثة |
|
|
|---|---|---|
|
|
| **Grafana** | Monitoring | لوحة تعرض اتجاه استخدام الذاكرة/CPU لكل حاوية عبر الزمن (Trend)، وليس فقط القيمة اللحظية — هي نفسها اللوحة المذكورة في البند (ج) أعلاه. تُبنى عليها قواعد تنبيه (Alert Rules) عند تجاوز 70%/90% من حد الذاكرة |
|
|
| **Uptime Kuma** | Automation | بديل جاهز لسكريبت `monitor.py` اللي بنيناه يدويًا: يفحص `/health` كل فترة زمنية محددة (مثلاً كل 30 ثانية)، يحتفظ بسجل Uptime%، ويبعت تنبيه فوري (عبر Email/Telegram/Discord/Webhook) لحظة أي فشل — بدل ما ياخد الفريق 40 دقيقة زي ما حصل في الحادثة الأصلية |
|
|
| **n8n** | Automation | لربط التنبيهات ببعضها وأتمتة الاستجابة: مثلاً استقبال Webhook من Grafana أو Uptime Kuma عند تجاوز حد الذاكرة، وتنفيذ إجراء تلقائي (إرسال رسالة لفريق التشغيل، تسجيل الحادثة، أو حتى استدعاء API لزيادة عدد النسخ إذا كانت المنصة تدعم ذلك) بدل التدخل اليدوي |
|
|
| **PgBouncer** *(إن كان التطبيق يعتمد على PostgreSQL)* | Database | لو كان سبب تسريب الذاكرة فعليًا هو تراكم اتصالات قاعدة بيانات غير مغلقة (Connection Leak)، فإن استخدام Connection Pooler مثل PgBouncer يمنع هذا النوع تحديدًا من النمو غير المنضبط في استهلاك الموارد |
|
|
| **Valkey** *(إن وُجد كاش داخل التطبيق)* | Database | نقل الكاش من داخل ذاكرة التطبيق (In-process cache بلا حد أقصى) إلى مخزن خارجي محدود الحجم مثل Valkey يمنع أحد أشهر أسباب الزيادة التدريجية في استهلاك الذاكرة (Unbounded in-memory cache) |
|
|
|
|
**التصميم المقترح للمعمارية (باستخدام أدوات غيمة الجاهزة):**
|
|
```
|
|
التطبيق (الحاوية) → /health, /metrics
|
|
│
|
|
├──► Grafana → لوحات اتجاه الذاكرة/CPU + Alert Rules (70%/90%)
|
|
│
|
|
├──► Uptime Kuma → فحص /health كل 30 ثانية + تنبيه فوري عند الفشل
|
|
│
|
|
└──► n8n → يستقبل تنبيهات Grafana/Uptime Kuma وينفذ الاستجابة التلقائية
|
|
```
|
|
|
|
بهذا الترتيب، كانت حادثة الـ 45 دقيقة ستُكتشف خلال أول دقيقة إلى دقيقتين من دخول استخدام الذاكرة نطاق الخطر (عبر Grafana)، وسيصل تنبيه الفشل الفعلي للفريق خلال ثوانٍ من أول `OOMKilled` (عبر Uptime Kuma)، بدل اكتشافها يدويًا بعد 40 دقيقة كما حدث فعليًا.
|