Upload files to "q2-postmortem/q3-cicd"
هذا الالتزام موجود في:
91
q2-postmortem/q3-cicd/postmortem-report.md
Normal file
91
q2-postmortem/q3-cicd/postmortem-report.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# تقرير Postmortem — انقطاع 45 دقيقة بسبب OOMKilled متكرر
|
||||
|
||||
**الحالة:** تم الحل | **الخطورة:** SEV-2 | **مدة التأثير:** 45 دقيقة | **النطاق:** تطبيق واحد على ghaymah.systems
|
||||
|
||||
## 1. الملخص التنفيذي
|
||||
|
||||
تعطّل تطبيق الإنتاج لمدة 45 دقيقة بسبب دخول الحاوية في حلقة تعطّل متكررة (`CrashLoopBackOff`) ناتجة عن تجاوز حد الذاكرة (`OOMKilled`). كل إعادة تشغيل تلقائية للحاوية كانت تصل لنفس حد الذاكرة خلال ثوانٍ من التشغيل، فيُعاد قتلها، دون أن يتدخل أي نظام auto-scaling لتوزيع الحمل أو رفع الحد. تم الحل برفع حد الذاكرة المخصص للحاوية مؤقتاً وإصلاح تسريب الذاكرة (memory leak) في الكود لاحقاً.
|
||||
|
||||
## 2. التأثير
|
||||
|
||||
- التطبيق كان يعيد HTTP 502/503 لكل الطلبات الواردة خلال فترة الانقطاع.
|
||||
- عدد الطلبات المتأثرة: ~ (يُقدَّر بناءً على متوسط الحمل × 45 دقيقة).
|
||||
- لا فقدان بيانات (التطبيق stateless، وقاعدة البيانات المُدارة لم تتأثر).
|
||||
|
||||
## 3. الجدول الزمني (Timeline)
|
||||
|
||||
| الوقت (UTC) | الحدث |
|
||||
|---|---|
|
||||
| T+0:00 | ارتفاع تدريجي في استهلاك الذاكرة يبدأ بعد نشر إصدار جديد يحتوي memory leak (كائنات غير مُحرَّرة في كاش داخلي للطلبات). |
|
||||
| T+12:00 | استهلاك الذاكرة يتجاوز حد الحاوية المخصص (512Mi). Kernel OOM-killer يقتل العملية. |
|
||||
| T+12:05 | منصة غيمة تعيد تشغيل الحاوية تلقائياً (restart policy). |
|
||||
| T+12:05 – T+45:00 | الحاوية تصل لنفس حد الذاكرة خلال ~90 ثانية من كل إعادة تشغيل (بسبب سرعة تراكم التسريب تحت الحمل)، فتدخل في `CrashLoopBackOff`. لا توجد نسخة (replica) ثانية تستقبل الحمل أثناء ذلك → انقطاع كامل للخدمة. |
|
||||
| T+20:00 | تنبيه أول يصل عبر health-check الخارجي (فشل 3 فحوصات متتالية = تنبيه). |
|
||||
| T+25:00 | فريق SRE يبدأ التحقيق، يفحص `kubectl describe pod` / سجلات المنصة، يجد `Reason: OOMKilled`. |
|
||||
| T+35:00 | إجراء مؤقت: رفع حد الذاكرة من 512Mi إلى 1Gi عبر إعدادات الخطة (`g4.medium`) لإعطاء هامش أثناء التحقيق. |
|
||||
| T+40:00 | التطبيق يستقر، يبدأ باستقبال الطلبات بنجاح. |
|
||||
| T+45:00 | تأكيد الاستقرار عبر health-check لمدة 5 دقائق متتالية → إغلاق الحادثة. |
|
||||
| T+1 يوم | تحديد السبب الجذري بدقة: كائن cache من نوع dict يكبر بلا حد أقصى (no eviction policy) في مسار معالجة الطلبات. تم إصلاحه وإضافة حد أقصى (LRU cache بحجم محدود). |
|
||||
|
||||
## 4. السبب الجذري (Root Cause)
|
||||
|
||||
**السبب المباشر:** تسريب ذاكرة في كود التطبيق (كاش داخلي بلا حد أقصى وبلا آلية إخلاء/TTL) أدى لنمو استهلاك الذاكرة باستمرار حتى تجاوز حد الحاوية (512Mi)، فقتل نظام التشغيل العملية (OOM Kill).
|
||||
|
||||
**أسباب مساهمة (Contributing Factors):**
|
||||
1. لا توجد نسخة ثانية (replica) — عند فشل النسخة الوحيدة، انقطعت الخدمة بالكامل بدلاً من أن تستمر نسخة أخرى بخدمة الطلبات.
|
||||
2. لا توجد سياسة auto-scaling أو حتى Horizontal scaling أساسي لتوزيع الحمل عند ارتفاع استهلاك الموارد.
|
||||
3. غياب تنبيه مبكر على استهلاك الذاكرة (memory usage trend) — التنبيه الوحيد كان على توفر الخدمة (uptime)، وليس على مؤشرات تنذر مسبقاً.
|
||||
4. لم يُختبر الإصدار الجديد تحت حمل واقعي (load test) قبل النشر، فلم يظهر التسريب في بيئة staging.
|
||||
|
||||
## 5. سياسة Auto-Scaling المقترحة لمنصة غيمة (لمنع التكرار)
|
||||
|
||||
```yaml
|
||||
# مثال توضيحي لسياسة auto-scaling على منصة الحاويات في غيمة
|
||||
autoscaling:
|
||||
min_replicas: 2 # لا تقل عن نسختين دائماً — يمنع انقطاعاً كاملاً عند فشل نسخة واحدة
|
||||
max_replicas: 6
|
||||
metrics:
|
||||
- type: memory
|
||||
target_utilization_percent: 70 # التوسع عند وصول الذاكرة لـ 70% من الحد المخصص
|
||||
- type: cpu
|
||||
target_utilization_percent: 75
|
||||
scale_up:
|
||||
cooldown_seconds: 60
|
||||
scale_down:
|
||||
cooldown_seconds: 300 # هبوط أبطأ لتفادي "flapping"
|
||||
restart_policy:
|
||||
max_restarts_within: 300s
|
||||
max_restarts_count: 3
|
||||
action_after_limit: page_oncall # بدل الاستمرار في إعادة التشغيل بلا نهاية، أبلغ الفريق فوراً
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 250m
|
||||
limits:
|
||||
memory: 1Gi # هامش أعلى من الـ request لتفادي OOM عند spikes مؤقتة
|
||||
cpu: 500m
|
||||
```
|
||||
|
||||
**لماذا هذا يمنع التكرار:**
|
||||
- `min_replicas: 2` يضمن استمرار الخدمة حتى لو دخلت نسخة واحدة في OOMKilled.
|
||||
- الحد `limits.memory` أعلى من `requests.memory` يعطي هامش أمان قبل الوصول لحد القتل الصارم.
|
||||
- `max_restarts_count` يحوّل مشكلة "restart loop صامتة" إلى تنبيه فوري لمهندس النوبة (on-call) بدل انتظار اكتشافها يدوياً.
|
||||
- التوسع الأفقي عند 70% استهلاك ذاكرة يوزّع الحمل قبل الوصول لحد الخطر.
|
||||
|
||||
## 6. الكشف المبكر باستخدام أدوات مراقبة غيمة
|
||||
|
||||
1. **مراقبة الموارد (Resource Metrics):** تفعيل تنبيه على غيمة عند تجاوز استهلاك الذاكرة 80% من الحد المخصص لمدة أكثر من دقيقتين متتاليتين (وليس فقط عند الفشل الكامل).
|
||||
2. **تتبع اتجاه الاستهلاك (Trend Alerting):** تنبيه عند نمو خطي مستمر في استهلاك الذاكرة عبر آخر 15 دقيقة (مؤشر تسريب) حتى قبل الوصول للحد.
|
||||
3. **سجلات الحاوية (Container Events):** ربط تنبيه فوري بحدث `OOMKilled` أو `CrashLoopBackOff` من سجل أحداث المنصة مباشرة لقناة on-call (Slack/Telegram/Webhook)، بدل الاعتماد فقط على health-check خارجي.
|
||||
4. **اختبار الحمل قبل النشر (Pre-deploy Load Test):** تشغيل اختبار حمل تلقائي في CI/CD (راجع q3) على بيئة staging قبل الموافقة على نشر production، لرصد تسريبات الذاكرة قبل وصولها للمستخدمين.
|
||||
5. **Canary Deployment:** نشر الإصدار الجديد على نسخة واحدة فقط أولاً (10% من الحمل) لمدة 15-30 دقيقة، ومراقبة الذاكرة قبل الانتشار الكامل.
|
||||
|
||||
## 7. عناصر إجراءات المتابعة (Action Items)
|
||||
|
||||
| المهمة | الأولوية | المسؤول |
|
||||
|---|---|---|
|
||||
| إصلاح تسريب الذاكرة (LRU cache بحد أقصى) | عالية | فريق التطبيق |
|
||||
| رفع `min_replicas` إلى 2 لكل تطبيقات production | عالية | SRE |
|
||||
| إضافة تنبيه على اتجاه استهلاك الذاكرة (trend-based) | عالية | SRE |
|
||||
| إضافة اختبار حمل (load test) كخطوة إجبارية في CI/CD قبل production | متوسطة | DevOps |
|
||||
| توثيق حد أقصى لعدد إعادة التشغيل قبل تصعيد تلقائي لـ on-call | متوسطة | SRE |
|
||||
المرجع في مشكلة جديدة
حظر مستخدم