إدارة الحوادث والمناوبة

مشروع: تنفيذ حادث محاكى

35 دقيقة الدرس 10 من 28

مشروع: تنفيذ حادث محاكى

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

كيفية استخدام هذا المشروع: نفّذه وحدك كتمرين طاولي باستخدام الأوامر أدناه على بيئة محلية أو بيئة اختبار، أو نفّذه مع زميلين أو ثلاثة — واحد بوصفه قائد حادث (IC)، وواحد بوصفه قائداً تقنياً، وواحد بوصفه قائد تواصل. السيناريو هو انقطاع واقعي لخدمة الدفع ناجم عن نشر معيب. كل أمر حقيقي وقابل للتشغيل.

السيناريو

يشغّل فريقك payments-api، وهي خدمة مستضافة على Kubernetes تتولى عملية الدفع لمنصة تجارة إلكترونية. في الساعة 14:32 UTC يصدر نشر — ترقية اعتيادية لمكتبة تجمّع اتصالات قاعدة البيانات من pgpool v2.3.1 إلى v2.4.0. في الساعة 14:38 UTC يرسل PagerDuty تنبيهاً حرجاً: ErrorBudgetBurnHigh. ارتفع معدل الأخطاء من 0.03% إلى 4.2% وما زال في ارتفاع. يواجه المستخدمون أخطاء HTTP 500 على نقطة نهاية الدفع. هذا حادث P1 وفق مقياس خطورتك — تأثير كبير على المستخدم، لا انقطاع كامل، لكنه يحرق ميزانية الأخطاء بسرعة. مهمتك هي قيادة الاستجابة.

Mock Incident Timeline: from deploy to resolved postmortem 14:32 Deploy pgpool v2.4.0 14:38 Alert fires ErrorBudgetBurnHigh 14:40 IC + Channel opened 14:47 Root cause identified 14:53 Rollback complete 14:59 Incident resolved Incident Metrics TTD: 6 min TTM: 21 min TTR: 27 min SLO breach window: 14:32 – 14:59 | Error budget consumed: ~4.8% Mock Incident Timeline payments-api P1 — deploy-induced connection pool misconfiguration
الجدول الزمني الكامل للحادث المحاكى من النشر حتى الحل. TTD دقيقتان و6 ثوانٍ، TTM 21 دقيقة، TTR 27 دقيقة — نتيجة جيدة مدفوعة بالكشف السريع ومسار واضح للتراجع.

الخطوة 1 — وصول التنبيه (T+0، الساعة 14:38 UTC)

يوقظك PagerDuty. التنبيه هو ErrorBudgetBurnHigh — خطورة حرجة، معدل حرق 14 ضعفاً. قبل أي شيء، اعترف بالتنبيه (يوقف التصعيد) وافتح فوراً قناة الحادث.

# الاعتراف بالتنبيه عبر PagerDuty CLI (يوقف التصعيد للمناوب التالي) pd incident acknowledge --id P4X9KLM # افتح قناة Slack مخصصة (أو استخدم API أداة الحادث) # في Slack اكتب: /incident declare (تكامل FireHydrant / Rootly) # يدوياً: /open #inc-2024-1211-payments-errors # انشر رسالة الافتتاح فوراً — لا تنتظر حتى تفهم المشكلة # القالب: # [INCIDENT OPENED] P1 — payments-api elevated errors # IC: @yourname | Tech lead: @jane | Comms: @bob # Symptoms: HTTP 500s on /checkout, error rate 4.2% (normal 0.03%) # First update in 10 minutes. Status page updated to Investigating. # تحديث صفحة الحالة (Statuspage.io CLI أو API) curl -s -X POST https://api.statuspage.io/v1/pages/PAGE_ID/incidents \ -H "Authorization: OAuth YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "incident": { "name": "Elevated checkout error rate", "status": "investigating", "body": "We are investigating elevated error rates on the checkout service. Impact: some users may experience failures at checkout. We will update in 10 minutes.", "impact_override": "minor", "component_ids": ["CHECKOUT_COMPONENT_ID"] } }'
طقوس الافتتاح مهمة: تحدد الدقيقتان الأوليان من الحادث نغمة كل ما يليه. رسالة افتتاح هادئة ومنظمة مع تعيين واضح للأدوار توقف فوضى "من يتولى هذا؟" قبل أن تبدأ. في Google وStripe، هذه الرسالة الافتتاحية في قالب دليل التشغيل حتى ينسخها IC في أقل من 60 ثانية.

الخطوة 2 — الفرز وتحديد النطاق (T+2، الساعة 14:40)

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

# فحص معدل الأخطاء وزمن الاستجابة في Prometheus (أو Grafana عبر API) curl -sG 'http://prometheus.internal:9090/api/v1/query' \ --data-urlencode 'query=rate(http_requests_total{service="payments-api",status=~"5.."}[5m]) / rate(http_requests_total{service="payments-api"}[5m])' \ | jq '.data.result[].value[1]' # فحص أي pods تُطلق أخطاء (يعزل ما إذا كانت جميع النسخ معطوبة أم بعضها فقط) kubectl top pods -n prod -l app=payments-api kubectl get events -n prod --sort-by='.lastTimestamp' | tail -30 # الترابط مع تاريخ النشر — هل كان هناك نشر قرب الساعة 14:32؟ kubectl rollout history deployment/payments-api -n prod # فحص السجلات الحديثة لنمط الخطأ kubectl logs -n prod -l app=payments-api --since=15m | grep -E "ERROR|FATAL|panic" | tail -40 # النتيجة المتوقعة: ستجد شيئاً كـ: # ERROR pgpool: max_connections exceeded, pool exhausted, rejecting new connections # هذا يخبرك أن الإصدار الجديد من pgpool غيّر max_connections الافتراضية من 100 إلى 10

في T+7 (الساعة 14:47) حصلت على الإجابة: غيّرت مكتبة pgpool v2.4.0 قيمة افتراضية — انخفضت pool_max_conn من 100 إلى 10 في الإصدار الجديد. تحت الحمل الاعتيادي (70 طلب/ثانية) ينضب التجمع في ثوانٍ، مما يُسبب أخطاء اتصال قاعدة بيانات تظهر على شكل 500. كل نسخة متأثرة. المشكلة ناجمة عن النشر، مما يعني أن التراجع هو أسرع مسار للتخفيف.

الخطوة 3 — إعلان الخطورة وتعيين IC (T+2، الساعة 14:40)

بناءً على الفرز، أعلن الخطورة رسمياً في قناة الحادث. في هذا السيناريو: معدل أخطاء 4.2%، الدفع معطوب جزئياً، لا انقطاع كامل، يؤثر على ~35% من محاولات الدفع. هذا P1 وفق مصفوفة الخطورة الخاصة بك. عيّن الأدوار صراحةً — لا تفترض أن الناس يعرفون وظيفتهم.

انشر في قناة الحادث:

"تأكيد الخطورة: P1. IC: @yourname. قائد تقني: @jane (يملك التحقيق). قائد تواصل: @bob (يملك صفحة الحالة وتحديثات المعنيين). مرشح السبب الجذري: نشر pgpool v2.4.0 في 14:32، تراجع pool_max_conn. التحديث القادم: 14:50."

خطأ شائع — تخطي إعلان IC: في اللحظات عالية الضغط، يقفز الفرق مباشرة إلى تصحيح الأخطاء دون إعلان صريح عمن يتولى القيادة. في غضون دقائق يكون لديك ثلاثة مهندسين يشغّلون أوامر kubectl مختلفة، ويُعيد أحدهم النشر بينما يحاول آخر الاستنساخ، وقناة التواصل صامتة. إعلان IC استثمار يستغرق 15 ثانية يمنع 20 دقيقة من الفوضى.

الخطوة 4 — التخفيف: تنفيذ التراجع (T+9، الساعة 14:47)

تأكد السبب الجذري. يقول دليل التشغيل للـ"تراجعات الناجمة عن النشر": أعِد النشر أولاً، حقق لاحقاً. نفّذه.

# التراجع إلى مراجعة نشر Kubernetes السابقة kubectl rollout undo deployment/payments-api -n prod # مراقبة النشر في الوقت الحقيقي kubectl rollout status deployment/payments-api -n prod --timeout=180s # بينما يعمل النشر، راقب انخفاض معدل الأخطاء في نافذة أخرى watch -n 10 "curl -sG 'http://prometheus.internal:9090/api/v1/query' \ --data-urlencode 'query=rate(http_requests_total{service=\"payments-api\",status=~\"5..\"}[2m]) / rate(http_requests_total{service=\"payments-api\"}[2m])' \ | jq '.data.result[].value[1]'" # تحقق باختبار دخاني على نقطة نهاية الدفع for i in $(seq 1 5); do STATUS=$(curl -o /dev/null -sw "%{http_code}" -X POST https://api.example.com/checkout \ -H "Content-Type: application/json" \ -d '{"cart_id":"test-001","dry_run":true}') echo "$(date -u +%H:%M:%S) HTTP $STATUS" sleep 5 done # المخرج المتوقع بعد اكتمال التراجع: # 14:52:05 HTTP 200 # 14:52:10 HTTP 200 # 14:52:15 HTTP 200 # 14:52:20 HTTP 200 # 14:52:25 HTTP 200 # انشر في قناة الحادث: "نُشر التراجع. معدل الأخطاء ينخفض. # نؤكد الاستقرار قبل إعلان الحل. التحديث القادم في 5 دقائق."

بحلول الساعة 14:53 اكتمل النشر، وجميع pods تشغّل الصورة السابقة، وانخفض معدل الأخطاء إلى 0.04% — ضمن النطاق الاعتيادي. نجح التراجع. الآن تنتظر 5 دقائق لتأكيد الاستقرار قبل الإعلان عن الحل، بدلاً من التسرع.

الخطوة 5 — إعلان الحل (T+21، الساعة 14:59)

معدل الأخطاء اعتيادي منذ 6 دقائق. SLO مُستعاد. أعلن حل الحادث — صراحةً، بطابع زمني، في قناة الحادث وفي نظام التذاكر.

# الحل في PagerDuty pd incident resolve --id P4X9KLM # إضافة ملاحظة الحل مع الجدول الزمني الكامل pd incident note add --id P4X9KLM --content "RESOLVED 14:59 UTC. Root cause: pgpool v2.4.0 changed pool_max_conn default from 100 to 10, causing connection pool exhaustion under normal load. Mitigation: rolled back deployment to previous revision (pgpool v2.3.1). Error rate returned to baseline 14:53 UTC. Postmortem scheduled 2024-12-12 10:00 UTC. IC: @yourname." # تحديث صفحة الحالة إلى محلول curl -s -X PATCH https://api.statuspage.io/v1/pages/PAGE_ID/incidents/INCIDENT_ID \ -H "Authorization: OAuth YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "incident": { "status": "resolved", "body": "This incident has been resolved. Checkout is operating normally. We apologize for the disruption. A postmortem will be completed and shared within 48 hours." } }' # انشر في قناة الحادث: # [INCIDENT RESOLVED] 14:59 UTC # TTD: 6 دقائق | TTM: 21 دقيقة | TTR: 27 دقيقة # ميزانية الأخطاء المستهلكة: ~4.8% من الميزانية الشهرية # التحليل اللاحق: 2024-12-12 10:00 UTC، المالك @jane # أرشفة القناة خلال ساعة.

الخطوة 6 — كتابة التحليل اللاحق

حُلّ الحادث، لكن العمل لم ينتهِ. جدوِل التحليل اللاحق خلال 24 ساعة بينما الذاكرة حية. أدناه وثيقة التحليل اللاحق المكتملة لهذا الحادث — لاحظ البنية: الجدول الزمني أولاً (حقائق موضوعية)، ثم العوامل المساهمة (لا "أسباب جذرية" بصيغة المفرد)، ثم بنود الإجراءات مع أصحاب محددين وتواريخ.

# التحليل اللاحق: payments-api P1 — 2024-12-11 # المدة: 14:32 – 14:59 UTC (27 دقيقة تأثير) # الخطورة: P1 | TTD: 6 دقائق | TTM: 21 دقيقة | TTR: 27 دقيقة # ميزانية الأخطاء المستهلكة: ~4.8% من ميزانية ديسمبر الشهرية ## التأثير - معدل أخطاء دفع 4.2% (الاعتيادي: 0.03%) خلال 14:38 – 14:53 UTC - فشل تقديري ~35% من محاولات الدفع خلال ذروة النافذة - ~2,400 معاملة فاشلة؛ لا فقدان بيانات، لا التقاط مدفوعات على الدفعات الفاشلة - 0 انقطاع كامل للخدمة (60% من الطلبات نجحت عبر منطق إعادة المحاولة) ## الجدول الزمني (UTC) 14:32 — نشر payments-api v1.47.0 (ترقية تبعية pgpool v2.4.0، اعتيادية) 14:33 — اكتمال النشر؛ pods الجديدة تشغّل pgpool v2.4.0 14:34 — بدء أولى أخطاء نضوب التجمع (لم يُكتشف بعد) 14:38 — إطلاق تنبيه ErrorBudgetBurnHigh (معدل حرق 14x)؛ PagerDuty يتصل بالمناوب 14:40 — اعتراف المناوب، فتح #inc-2024-1211-payments؛ إعلان IC 14:40 — تحديث صفحة الحالة إلى "قيد التحقيق" 14:47 — تحديد السبب الجذري: pool_max_conn الافتراضية في pgpool v2.4.0 تغيرت 100→10 14:47 — بدء التراجع: kubectl rollout undo deployment/payments-api -n prod 14:53 — اكتمال التراجع؛ معدل الأخطاء ينخفض إلى 0.04% 14:59 — تأكيد نافذة استقرار 6 دقائق؛ إعلان حل الحادث ## العوامل المساهمة (لا "لوم") 1. سجل تغييرات pgpool v2.4.0 أشار إلى تغيير pool_max_conn الافتراضية في نقطة ثانوية. لم يقرأ أحد في الفريق سجل التغييرات الكامل قبل الدمج. 2. اختبارات التكامل لا تُمارس حدود تجمع الاتصالات — التراجع لم يكن قابلاً للكشف في CI. 3. حدث النشر في 14:32 UTC (ساعة الذروة). التوقيت ضخّم التأثير. 4. لم يكن لدينا نشر canary مؤتمت؛ ذهب التغيير فوراً إلى 100% من الحركة. ## ما سار بشكل جيد - أطلق التنبيه خلال 6 دقائق من بداية التأثير (هدف TTD: أقل من 10 دقائق) ✓ - إعلان IC خلال دقيقتين؛ تعيين الأدوار فوراً ✓ - تحديد السبب الجذري في أقل من 10 دقائق ✓ - تنفيذ التراجع من دليل التشغيل دون تردد ✓ - قائد التواصل نشر تحديث صفحة الحالة خلال 3 دقائق من فتح الحادث ✓ ## بنود الإجراءات | # | الإجراء | المالك | تاريخ الاستحقاق | |---|---------|-------|----------------| | 1 | تعيين pool_max_conn=100 صراحةً في إعدادات payments-api | @jane | 2024-12-12 | | 2 | إضافة اختبار تكامل لنضوب تجمع الاتصالات في CI | @alex | 2024-12-18 | | 3 | تطبيق نشر canary (10% حركة) لجميع نشرات payments-api | @devops | 2024-12-25 | | 4 | إضافة قائمة مراجعة سجل التغييرات لقالب PR ترقية التبعية | @yourname | 2024-12-13 | | 5 | تحويل نشرات ذروة الحركة إلى خارج الذروة | @yourname | 2024-12-13 |
التحليل اللاحق هو الوثيقة الأكثر استثماراً تنتجها: بند الإجراء 3 (نشرات canary) سيمنع فئة كاملة من الحوادث المستقبلية. بند الإجراء 2 سيكتشف هذا التراجع المحدد إذا تكرر. التحليلات اللاحقة الجيدة تنتج تحسينات تتراكم — كل حادث يجب أن يجعل النظام أكثر أماناً بشكل قابل للقياس مما كان عليه قبله.

تقييم: ما الذي جعل هذه الاستجابة فعّالة

عُد عبر الحادث المحاكى وقيّم نفسك وفق ممارسات هذا الدرس التعليمي:

  • الكشف (الدروس 1-3): تنبيه معدل حرق SLO أطلق في 6 دقائق. المراقبة الاصطناعية لم تكن مطلوبة هنا لأن التنبيه كان سريعاً بما يكفي — لكنها كانت ستكشف الفجوة بين 14:34 و14:38 لو كانت مُعدَّة.
  • انضباط المناوبة (الدرس 2): اعتراف فوري بالتنبيه، لا تصعيد، إعلان IC خلال دقيقتين.
  • قيادة الحادث (الدرس 4): IC واحد، أدوار معينة، لا تصحيح أخطاء عشوائي، دورة تحديث 10 دقائق مُحترَمة.
  • دليل التشغيل (الدرس 5): "التراجع أولاً" في دليل التشغيل. الفريق لم يناقش؛ نفّذ.
  • التواصل (الدرس 6): تحديث صفحة الحالة خلال 3 دقائق، قناة المعنيين حصلت على تحديثات منظمة، لا صمت.
  • التحليل اللاحق (الدروس 7-8): لاوم، الجدول الزمني أولاً، عوامل مساهمة لا كبش فداء واحد، خمسة بنود إجراءات محددة مع أصحاب وتواريخ.
نفّذ هذا التمرين بانتظام: تُجري Google وPagerDuty وStripe أيام لعب ربع سنوية — حوادث محاكاة مجدولة يحقن فيها الفريق عمداً عطلاً ويُشغّل إجراء الاستجابة الكاملة. الهدف هو إبقاء العملية حادة حتى لا تكون المرة الأولى التي يواجه فيها أي شخص وضع فشل جديد هي أيضاً المرة الأولى التي يُشغّل فيها إجراء الحادث. الفرق التي تتدرب هكذا تتفوق باستمرار على الفرق التي تستجيب للحوادث الحقيقية فقط.

قائمة التحقق من الاستعداد للحوادث

قبل أن تبدأ المناوبة الحقيقية، تحقق من كل بند:

  • تنبيهات معدل حرق SLO مُعدَّة لكل خدمة موجهة للمستخدم (ليس مجرد حدود CPU/ذاكرة خام)
  • سياسة تصعيد PagerDuty (أو المكافئ) مُختبَرة من النهاية إلى النهاية في بيئة الاختبار
  • إنشاء قناة الحادث مؤتمت (أمر واحد أو اختصار Slack واحد)
  • حساب صفحة الحالة مشترك مع الفريق؛ إجراء التحديث موثّق
  • إجراء التراجع في دليل التشغيل ومُختبَر في بيئة الاختبار خلال آخر 30 يوماً
  • قالب التحليل اللاحق في الويكي؛ مالك العملية معيّن
  • يوم لعب واحد على الأقل نُفّذ في آخر ربع سنة

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