التعافي من الكوارث وتعدد المناطق

اختبار التعافي من الكوارث وأيام اللعبة

18 دقيقة الدرس 8 من 27

اختبار التعافي من الكوارث وأيام اللعبة

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

يغطي هذا الدرس الطيف الكامل للتحقق من التعافي: تدريبات الاستعادة (إثبات سلامة النسخ الاحتياطية وإمكانية استعادتها ضمن RPO المحدد)، وتمارين تجاوز الفشل (إثبات تنفيذ تسلسل الترقية الكامل ضمن RTO المحدد)، وأيام اللعبة المنظمة — ممارسة شركات التقنية الكبرى المتمثلة في حقن أعطال مضبوطة في بيئات مشابهة للإنتاج مع فريق مراقبة وفرضية واضحة ومراجعة ما بعد اللعبة.

هرم اختبار التعافي

تمامًا كما تعمل الاختبارات الوحدوية بتكلفة زهيدة وتكرار مرتفع بينما تعمل الاختبارات الشاملة ببطء، يمتلك اختبار التعافي هرمًا: تدريبات الاستعادة على مستوى المكونات في القاعدة (متكررة، آلية)، وتمارين تجاوز الفشل على مستوى التكامل في الوسط (شهرية، شبه آلية)، وأيام اللعبة الكاملة في القمة (ربع سنوية، بمشاركة قيادية يدوية).

DR Testing Pyramid Restore Drills آلية · يومية/أسبوعية · نطاق مكوّن واحد تتحقق من: سلامة النسخ الاحتياطية، وقت الاستعادة، امتثال RPO Failover Exercises شبه آلية · شهرية · نطاق المكدس تتحقق من: RTO، اكتمال الدليل التشغيلي Game Days يدوية · ربع سنوية سيناريو DR كامل متكرر نادر
هرم اختبار التعافي: تدريبات الاستعادة الآلية متعددة التكرار في القاعدة، وتمارين تجاوز الفشل الشهرية في الوسط، وأيام اللعبة الكاملة ربع السنوية في القمة.

تدريبات الاستعادة: إثبات أن النسخ الاحتياطية حقيقية

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

يُشغّل النص البرمجي التالي تدريب استعادة Postgres ليليًا في CI/CD. يستعيد أحدث لقطة pg_dump إلى نسخة RDS مؤقتة، ويُشغّل فحوصات سلامة لأعداد الصفوف، ويُسجّل الوقت المنقضي إلى نقطة نهاية المقاييس:

#!/usr/bin/env bash # dr-restore-drill.sh — تدريب استعادة Postgres الليلي # الاستخدام: يُستدعى من مهمة GitHub Actions المجدولة أو Jenkins cron set -euo pipefail TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_BUCKET="s3://my-dr-backups/postgres" DB_NAME="appdb" METRICS_URL="https://pushgateway.internal/metrics/job/dr_drill" echo "[drill] بدء تدريب الاستعادة في $TIMESTAMP" START=$(date +%s) # 1. البحث عن أحدث بيان نسخة احتياطية LATEST=$(aws s3 ls "$BACKUP_BUCKET/" \ | sort \ | tail -1 \ | awk '{print $4}') echo "[drill] جارٍ استعادة $LATEST" # 2. تنزيل النسخة الاحتياطية aws s3 cp "$BACKUP_BUCKET/$LATEST" /tmp/restore.dump.gz # 3. تشغيل حاوية Postgres مؤقتة docker run -d \ --name "dr-drill-pg-$TIMESTAMP" \ -e POSTGRES_PASSWORD=drillpass \ -e POSTGRES_DB=$DB_NAME \ -p 5433:5432 \ postgres:16 sleep 10 # 4. الاستعادة zcat /tmp/restore.dump.gz \ | docker exec -i "dr-drill-pg-$TIMESTAMP" \ psql -U postgres -d $DB_NAME # 5. فحوصات السلامة ROW_COUNT=$(docker exec "dr-drill-pg-$TIMESTAMP" \ psql -U postgres -d $DB_NAME -tAc "SELECT COUNT(*) FROM orders;") if [ "$ROW_COUNT" -lt 1000 ]; then echo "[drill] فشل: جدول orders يحتوي على $ROW_COUNT صف (متوقع >= 1000)" EXIT_CODE=1 else echo "[drill] نجاح: $ROW_COUNT صف في جدول orders" EXIT_CODE=0 fi # 6. قياس ودفع المقاييس END=$(date +%s) ELAPSED=$((END - START)) echo "[drill] وقت الاستعادة المنقضي: ${ELAPSED}ث" cat <<EOF | curl --data-binary @- "$METRICS_URL" # HELP dr_restore_duration_seconds وقت استعادة أحدث نسخة احتياطية لقاعدة البيانات # TYPE dr_restore_duration_seconds gauge dr_restore_duration_seconds{db="$DB_NAME"} $ELAPSED # HELP dr_restore_success 1 إذا نجح آخر تدريب، 0 إذا فشل # TYPE dr_restore_success gauge dr_restore_success{db="$DB_NAME"} $EXIT_CODE EOF # 7. التنظيف docker rm -f "dr-drill-pg-$TIMESTAMP" exit $EXIT_CODE
أدخل مقاييس تدريب الاستعادة في لوحات تحكم SLO. عامل dr_restore_duration_seconds كـ SLI مباشر لـ RPO. إذا كان RPO هو 30 دقيقة وكان وقت الاستعادة يتجه نحو 28 دقيقة، فأنت على بُعد أسبوعين من انتهاك RPO — وستراه في الرسم البياني قبل أن يُجبرك حادث حقيقي على مواجهته. أنشئ تنبيه Grafana على dr_restore_duration_seconds > (rpo_seconds * 0.8).

تمارين تجاوز الفشل: توقيت التسلسل الكامل

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

بالنسبة لمكدس قائم على Kubernetes مع Argo CD يدير حالة GitOps، يتضمن تمرين تجاوز الفشل عادةً التسلسل التالي. أتمته بنص برمجي للدليل التشغيلي يُسجّل الطوابع الزمنية في كل خطوة:

#!/usr/bin/env bash # failover-drill.sh — Kubernetes + ArgoCD + Route53 failover exercise # مُصمَّم للتشغيل في بيئة DR التدريبية، وليس الإنتاج. set -euo pipefail DR_REGION="us-west-2" PRIMARY_CLUSTER="prod-us-east-1" DR_CLUSTER="dr-us-west-2" HOSTED_ZONE_ID="Z1234567890" RECORD_NAME="api.myapp.internal" DR_LB_DNS="k8s-dr-lb-abc123.us-west-2.elb.amazonaws.com" START=$(date +%s) log() { echo "[$(date -u +%T)] $*"; } # الخطوة 1: عزل المجموعة الأساسية (محاكاة فشل المنطقة) log "الخطوة 1: عزل المجموعة الأساسية" kubectl --context $PRIMARY_CLUSTER cordon --all-namespaces \ --selector tier=backend 2>&1 || true # الخطوة 2: ترقية نسخة Postgres في منطقة DR إلى أساسية log "الخطوة 2: ترقية Postgres في $DR_REGION" aws rds promote-read-replica \ --db-instance-identifier appdb-dr \ --region $DR_REGION aws rds wait db-instance-available \ --db-instance-identifier appdb-dr \ --region $DR_REGION T2=$(date +%s); log "اكتملت ترقية قاعدة البيانات في $((T2 - START))ث" # الخطوة 3: تحديث تطبيق ArgoCD للإشارة إلى مجموعة DR log "الخطوة 3: مزامنة ArgoCD مع مجموعة DR" argocd app set myapp \ --dest-server "https://$(kubectl --context $DR_CLUSTER config view \ --minify -o jsonpath='{.clusters[0].cluster.server}')" \ --revision main argocd app sync myapp --timeout 300 T3=$(date +%s); log "اكتملت مزامنة ArgoCD في $((T3 - START))ث" # الخطوة 4: تحويل DNS log "الخطوة 4: تحويل Route 53" aws route53 change-resource-record-sets \ --hosted-zone-id $HOSTED_ZONE_ID \ --change-batch "{ \"Changes\": [{ \"Action\": \"UPSERT\", \"ResourceRecordSet\": { \"Name\": \"$RECORD_NAME\", \"Type\": \"CNAME\", \"TTL\": 30, \"ResourceRecords\": [{\"Value\": \"$DR_LB_DNS\"}] } }] }" T4=$(date +%s); log "اكتمل تحويل DNS في $((T4 - START))ث" # الخطوة 5: التحقق من الصحة log "الخطوة 5: فحص صحة نقطة نهاية DR" until curl -sf "https://$RECORD_NAME/healthz" | grep -q '"status":"ok"'; do sleep 5 done T5=$(date +%s) TOTAL=$((T5 - START)) log "اكتمل. إجمالي وقت تجاوز الفشل: ${TOTAL}ث" if [ $TOTAL -le 900 ]; then log "نجاح RTO: $TOTAL ث <= 900 ث المستهدفة" else log "فشل RTO: $TOTAL ث > 900 ث المستهدفة" exit 1 fi
سخّن قيم TTL لـ DNS قبل التدريب — وليس خلاله. إذا كانت قيمة TTL الحالية 300 ثانية (5 دقائق) وخفّضتها إلى 30 ثانية عند تجاوز الفشل، ستستمر المحلّلات في تقديم السجلات القديمة لمدة 5 دقائق كاملة. يجب خفض TTL قبل فترة TTL كاملة على الأقل من أي تمرين تجاوز فشل مخطط له. في تخطيط DR للإنتاج، يعني ذلك إبقاء سجلات DNS الحرجة عند TTL من 30 إلى 60 ثانية بشكل دائم لخدمات المستوى 0.

أيام اللعبة: الفوضى المنظمة تحت المراقبة

يوم اللعبة هو تجربة فشل محدودة ومُراقَبة وقائمة على الفرضيات على مستوى النظام. يختلف عن تمرين تجاوز الفشل في ثلاثة أوجه: (1) يُحقن السيناريو في بيئة مشابهة للإنتاج، وغالبًا دون أن يعرف جميع المشاركين نمط الفشل المحدد مسبقًا؛ (2) يُوثّق فريق مراقبة (مهندسون ليسوا في فريق الاستجابة المباشر) الجدول الزمني والقرارات والثغرات؛ (3) توجد فرضية رسمية ومعيار واضح للنجاح والفشل يُقيَّم بعد التمرين.

يتبع يوم اللعبة المُدار جيدًا هذا الهيكل:

  1. إحاطة ما قبل اللعبة (30 دقيقة): نشر السيناريو (أو نسخة مُعقَّمة منه)، والتأكيد على أن نطاق التأثير محصور في بيئة الاختبار، وإحاطة فريق المراقبة، وتعيين مُسجّل وقت، والاتفاق على حالة الإلغاء.
  2. حقن الفشل (يتفاوت): حقن الفشل باستخدام أدوات الفوضى (chaos-mesh، AWS Fault Injection Simulator، أو إجراء يدوي). فريق الحقن لا يُساعد فريق الاستجابة.
  3. نافذة الاستجابة: يستجيب فريق المناوبة بالضبط كما هو الحال خلال حادث حقيقي — غرفة حرب على Slack، ودلائل تشغيل، ومسارات تصعيد. يُسجّل فريق المراقبة كل إجراء مع طابع زمني.
  4. التوقف والقياس: عند الشرط المتفق عليه، يوقف فريق الحقن التجربة. قياس RTO و RPO الفعليين من جدول المراقبة الزمني.
  5. مراجعة ما بعد اللعبة (60-90 دقيقة في نفس اليوم): العمل على ملاحظات فريق المراقبة كمجموعة. تحديد: ما الذي نجح، وما الذي كان بطيئًا، وما الذي كان مفقودًا، وما الذي فاجأ الجميع. تقديم بنود إجراءات مع أصحاب ومواعيد استحقاق قبل انتهاء الجلسة.
أكثر مخرجات يوم اللعبة قيمةً ليس حكم النجاح أو الفشل — بل هي المفاجآت. تُفيد الفرق التي تُشغّل أيام اللعبة باستمرار أن أكثر النتائج أثرًا ليست "استغرق تجاوز الفشل 18 دقيقة بدلًا من 10" بل "لم نكن نعلم أن الخدمة X تملك تبعية صارمة على مدير الأسرار في المنطقة الأساسية". هذه التبعيات الخفية غير مرئية في مخططات المعمارية وتظهر فقط في ظروف الفشل الفعلي.

k6 لاختبار الحمل على منطقة DR أثناء التمرين

تمرين تجاوز الفشل الذي يتحقق فقط من نقاط نهاية الصحة يُثبت التوافر لكن ليس السعة. قد تخدم منطقة DR حركة المرور بشكل صحيح لكنها تنهار تحت حمل الإنتاج إذا كانت أقل حجمًا. كجزء من كل تدريب لتجاوز الفشل، شغّل اختبار حمل لمدة 5 دقائق ضد نقطة نهاية DR فور اجتيازها فحص الصحة.

// dr-smoke-load.js — نص k6 للتحقق من حمل منطقة DR // الاستخدام: k6 run --out influxdb=http://influx:8086/k6 dr-smoke-load.js import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate } from 'k6/metrics'; const errorRate = new Rate('errors'); export const options = { stages: [ { duration: '1m', target: 200 }, // زيادة تدريجية إلى 200 مستخدم افتراضي { duration: '3m', target: 200 }, // الإبقاء عند 200 (10% من ذروة حركة الإنتاج) { duration: '1m', target: 0 }, // انخفاض تدريجي ], thresholds: { http_req_duration: ['p(95)<500'], // النسبة المئوية 95 يجب أن تكون < 500 ملي ثانية errors: ['rate<0.01'], // معدل الأخطاء يجب أن يكون < 1% }, }; const DR_BASE = __ENV.DR_BASE_URL || 'https://dr-api.myapp.internal'; export default function () { const res = http.get(`${DR_BASE}/api/v1/products`, { headers: { Authorization: `Bearer ${__ENV.TEST_TOKEN}` }, }); check(res, { 'status 200': (r) => r.status === 200, 'latency ok': (r) => r.timings.duration < 500, }); errorRate.add(res.status !== 200); sleep(0.5); }

تتبع النتائج وإغلاق الحلقة

يوم اللعبة الذي يُولّد نتائج دون بنود إجراءات متتبَّعة هو مجرد احتفال وليس حلقة تحسين. في Google وAmazon، يُنتج كل تمرين DR خطة إجراء تصحيحية (CAP) — ملف Jira/Linear مع تذاكر فرعية لكل ثغرة تم اكتشافها. تُراجَع هذه الخطة في مراجعة DR الفصلية التالية. تُسجَّل قياسات RTO و RPO من كل تمرين كنقاط بيانات سلسلة زمنية؛ والانحدار — زيادة RTO المقاس بشكل ربعي — يُطلق تحقيقًا فوريًا.

أتمت حزمة الأدلة لأيام اللعبة. هيّئ نصوص تدريب DR الخاصة بك لإصدار سجل JSON منظم في نهاية كل تشغيل. استوعب هذا في منصة الرصد والمراقبة حتى تتمكن من رسم اتجاهات RTO/RPO عبر التمارين. عند تقديمها للقيادة، فإن إظهار مخطط لخمسة أيام لعبة متتالية حيث تحسّن RTO من 18 دقيقة إلى 7 دقائق أكثر إقناعًا بكثير من تقرير سردي.

اختبار DR هو الانضباط الذي يحول الدروس السابقة في هذا الدرس التعليمي — النسخ المتماثل، وآليات تجاوز الفشل، والتعافي المدفوع بـ GitOps — من مخططات معمارية إلى قدرات مُثبَتة تشغيليًا. بدونه، كل ادعاء بـ RTO/RPO هو وعد. بوجوده، تلك الادعاءات هي قياسات. شغّل تدريبات الاستعادة يوميًا، وتمارين تجاوز الفشل شهريًا، وأيام اللعبة ربع سنويًا. عامل كل تمرين كاستثمار: التكلفة هي بضع ساعات هندسية من الاضطراب المضبوط؛ والعائد هو القضاء على المفاجأة الكارثية التي سيوفرها كارثة حقيقية.