استراتيجيات النشر والتسليم التدريجي

مشروع: خطة التسليم التدريجي

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

مشروع: خطة التسليم التدريجي

كل مفهوم في هذا الدرس — إصدارات الكناري، وأعلام الميزات، والتراجع، والتحليل الآلي، ونمط التوسع والتقليص — موجود لحل مشكلة واحدة: كيف تشحن تغييراً لخدمة بالغة الأهمية دون التسبب في انقطاع؟ يأخذك هذا الدرس الختامي خطوة بخطوة عبر تصميم نظام تسليم تدريجي متكامل وجاهز للإنتاج لخدمة عالية المخاطر: معالج مدفوعات يتعامل مع آلاف المعاملات في الدقيقة.

ستصمم النظام بالكامل من البداية إلى النهاية: مسار الكناري، وتسلسل الأعلام، وبوابات الترقية المقيّدة بـ SLO، واستراتيجية هجرة قاعدة البيانات، ودليل التراجع. هذه هي الطريقة التي تقترب بها فرق في Stripe وAmazon وGoogle من إصدارات مساراتهم الأكثر أهمية.

السيناريو: يستبدل فريقك محرك توجيه الدفع القديم بآخر يدعم تحويل العملات الديناميكي. تعالج الخدمة 3,000 معاملة في الثانية عند الذروة. تكلف الانقطاعة لمدة دقيقة واحدة 180,000 دولار من الإيرادات المفقودة. مهمتك هي تصميم خطة التسليم — ليس فقط المسار السعيد، بل كل حالة فشل محتملة والاستجابة الدقيقة لكل منها.

الخطوة 1 — رسم خريطة المخاطر

قبل كتابة ملف YAML واحد، احصِ ما يمكن أن يسوء. لكل خطر، ستعيّن إجراء تخفيف من مجموعة أدوات التسليم التدريجي:

  • منطق التوجيه الجديد يرفض بطاقات صالحة — التخفيف: كناري بنسبة 1%، بوابة آلية لمعدل الأخطاء، مفتاح إيقاف طارئ عبر علم.
  • تراجع في الأداء (الكمون) في المحرك الجديد — التخفيف: بوابة SLO لكمون p99؛ تراجع تلقائي إذا تجاوز p99 الـ 200ms لمدة 5 دقائق.
  • مخطط قاعدة البيانات غير متوافق بين الكود القديم والجديد — التخفيف: هجرة بنمط التوسع والتقليص مع فترة كتابة مزدوجة.
  • تحويل العملات الديناميكي يسبب أخطاء في التقريب — التخفيف: علم تحويل العملات يبدأ معطلاً؛ تجربة A/B مع بوابة دلالة إحصائية قبل الطرح الكامل.
  • واجهة برمجة أسعار صرف طرف ثالث معطلة — التخفيف: مفتاح إيقاف طارئ للعمليات يعطّل التحويل الديناميكي ويعود فوراً للأسعار الثابتة.

الخطوة 2 — تصميم تسلسل الأعلام

تحتاج إلى ثلاثة أعلام متمايزة لهذا الإصدار، لكل منها دورة حياة مختلفة ومالك مختلف:

  1. payments.new-router.enabledعلم إصدار. يتحكم في ما إذا كان حجم حركة المرور يمر عبر محرك التوجيه الجديد أصلاً. هذا هو مفتاح الإيقاف الرئيسي. القيمة الافتراضية: false. تاريخ الإزالة: 30 يوماً بعد الطرح بنسبة 100%.
  2. payments.dynamic-currency.enabledعلم تجربة. يتحكم في ما إذا كانت أسعار صرف ديناميكية تُعرض على المستخدم. يبدأ من 0%. مقيّد بالدلالة الإحصائية (p-value < 0.05، حد أدنى 10,000 تحويل لكل نسخة). تاريخ الإزالة: 14 يوماً بعد إعلان الفائز.
  3. payments.new-router.circuit-openعلم عمليات. عندما يكون true، يُحوّل فوراً 100% من حجم حركة المرور إلى المحرك القديم بصرف النظر عن أي علم آخر. هذا قاطع الدائرة الطارئ، ليس نفس التراجع — يعمل في ثوانٍ. طويل الأمد؛ يمتلكه فريق الاستجابة لدفعات المدفوعات بشكل دائم.
ترتيب تقييم الأعلام مهم. قيّم علم قاطع الدائرة أولاً في كل طلب، قبل أي علم آخر. إذا كانت قيمته true، تحويل فوري إلى المسار القديم — لا تقيّم الأعلام الأدنى. هذا يضمن أن مفتاح الإيقاف يعمل في أقل من 10ms حتى تحت الحمل الشديد.

الخطوة 3 — تصميم مسار الكناري

مسار الكناري هو الإطار الذي ينقل payments.new-router.enabled من 0% إلى 100% بأمان. كل مرحلة لها معايير دخول صريحة (مقاييس SLO يجب أن تكون ناجحة) ومعايير خروج (مقاييس SLO إذا انتُهكت تُطلق تراجعاً تلقائياً).

Canary pipeline with SLO gates and rollback path Deploy 0% traffic Gate 1 Canary 1% 30 min bake Gate 2 Canary 10% 60 min bake Gate 3 Full 100% Stable smoke ok err < 0.1% p99 < 200ms err < 0.05% p99 < 150ms Auto-rollback: circuit-open flag = true SLO Gate Promote Auto-Rollback
مسار الكناري مع بوابات SLO للترقية والتراجع التلقائي إلى الإصدار المستقر عبر علم قاطع الدائرة.

رمّز هذا المسار كمانيفست Argo Rollouts حتى تكون التنسيقية محفوظة في نظام التحكم بالإصدارات وقابلة للتكرار:

# payment-router-rollout.yaml apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: payment-router namespace: payments spec: replicas: 50 selector: matchLabels: app: payment-router template: metadata: labels: app: payment-router spec: containers: - name: payment-router image: registry.internal/payment-router:2.4.0 resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "1" memory: "1Gi" strategy: canary: analysis: templates: - templateName: payment-router-slo startingStep: 1 args: - name: service-name value: payment-router canaryService: payment-router-canary stableService: payment-router-stable trafficRouting: istio: virtualService: name: payment-router-vsvc routes: - primary steps: - setWeight: 1 # كناري 1% - pause: {duration: 30m} - setWeight: 10 # كناري 10% - pause: {duration: 1h} - setWeight: 100 # الترقية الكاملة

الخطوة 4 — ربط التحليل الآلي

تُطبَّق بوابات SLO عبر AnalysisTemplate يستعلم من منظومة الرصد كل 5 دقائق. إذا انتهك أي مقياس حده، يتوقف Argo Rollouts ويُنبّه مهندس الاستجابة:

# payment-router-analysis.yaml apiVersion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: name: payment-router-slo namespace: payments spec: args: - name: service-name metrics: - name: error-rate interval: 5m failureLimit: 1 provider: prometheus: address: http://prometheus.monitoring:9090 query: | sum(rate(http_requests_total{ service="{{args.service-name}}", status=~"5..", version="canary" }[5m])) / sum(rate(http_requests_total{ service="{{args.service-name}}", version="canary" }[5m])) successCondition: result[0] < 0.001 # < 0.1% معدل خطأ - name: p99-latency-ms interval: 5m failureLimit: 2 provider: prometheus: address: http://prometheus.monitoring:9090 query: | histogram_quantile(0.99, sum(rate( http_request_duration_seconds_bucket{ service="{{args.service-name}}", version="canary" }[5m] )) by (le)) * 1000 successCondition: result[0] < 200 # p99 < 200 ms

الخطوة 5 — تصميم هجرة قاعدة البيانات

يتطلب المحرك الجديد عموداً جديداً fx_rate_snapshot على جدول payment_transactions. هذا جدول على المسار الحرج يحتوي على 50 مليون سجل. لا يمكنك تنفيذ ALTER TABLE محجِّب في الإنتاج. استخدم نمط التوسع والتقليص عبر ثلاث عمليات نشر:

  • التوسع (النشر رقم N): أضف العمود بصفة NULLABLE بدون قيمة افتراضية — صفر توقف على MySQL/Postgres مع ALGORITHM=INSTANT. الكود القديم يتجاهل العمود. الكود الجديد يكتب فيه. يعمل الكودان في وقت واحد خلال نافذة الكناري.
  • الهجرة (النشر رقم N): شغّل مهمة خلفية عبر عامل يتحكم فيه علم لملء العمود للسجلات الموجودة على دفعات من 1,000 سجل مع توقف 10ms بين الدفعات لتجنب ضغط الإدخال/الإخراج على الخادم الرئيسي.
  • التقليص (النشر رقم N+1، بعد الطرح الكامل): بعد التقاعد الكامل للكود القديم، أضف قيد NOT NULL واحذف منطق الكتابة المزدوجة. هذا النشر التنظيفي — لا تشحنه قبل مرور 72 ساعة من الطرح الكامل للسماح بالتراجع الطارئ دون كسر عقد العمود.
لا تُعيد تسمية عمود في عملية نشر واحدة. إعادة التسمية تعني: أضف عموداً جديداً + اكتب مزدوجاً + انقل البيانات + احذف العمود القديم، كل خطوة في نشر منفصل مع فترة نضج بين الخطوات. إعادة التسمية في هجرة واحدة تُقفل الجدول وتكسر الكود القديم الذي يقرأ اسم العمود القديم في نفس الوقت — وهو بالضبط ما يحدث خلال نافذة الكناري.

الخطوة 6 — دليل التراجع

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

  • المستوى 1 — تلقائي (0 إلى 60 ثانية): يكتشف Argo Rollouts انتهاك SLO عبر AnalysisTemplate. يضع الطرح في حالة Paused وينبّه مهندس الاستجابة. يشغّل المهندس kubectl argo rollouts abort payment-router لتحويل 100% من حجم حركة المرور فوراً إلى البودات المستقرة.
  • المستوى 2 — قاطع الدائرة (0 إلى 10 ثوانٍ): إذا كان التراجع الآلي بطيئاً أو غير متاح، يقلب مهندس الاستجابة payments.new-router.circuit-open إلى true في لوحة الأعلام. يعود حجم حركة المرور إلى المسار القديم خلال دورة استطلاع واحدة. هذا أسرع من طرح Kubernetes لأنه لا يتطلب إعادة تشغيل البودات.
  • المستوى 3 — تراجع كامل (30 إلى 120 دقيقة): للأعطال الكارثية حيث البرنامج الثنائي الجديد نفسه تالف أو الحاوية لا تستطيع الإقلاع، أعِد رقم SHA في Git وشغّل بناء CI جديداً. هذا المسار نادر وبطيء — يجب أن يتعامل المستويان الأولان مع 99.9% من الحوادث.
# أوامر دليل التراجع (انسخها مباشرة في وثيقة الحادثة) # --- المستوى 1: إلغاء طرح Argo (تحويل حجم حركة المرور للبودات المستقرة) --- kubectl argo rollouts abort payment-router -n payments kubectl argo rollouts status payment-router -n payments # تحقق من الاستقرار # --- المستوى 2: تفعيل قاطع الدائرة عبر CLI (مثال LaunchDarkly) --- ld flag update \ --project payments-prod \ --flag payments.new-router.circuit-open \ --value true \ --environment production # --- تحقق: راقب انخفاض معدل الأخطاء في الوقت الفعلي --- watch -n 5 'kubectl exec -n monitoring deploy/prometheus -- \ promtool query instant http://localhost:9090 \ "sum(rate(http_requests_total{service=\"payment-router\",status=~\"5..\"}[1m]))"' # --- المستوى 3: تراجع كامل عبر git + إعادة نشر --- git revert HEAD --no-edit git push origin main # CI ينشغل تلقائياً وينشر الصورة المُرجَّعة

الخطوة 7 — ربط كل شيء معاً

خطة التسليم التدريجي ليست مجرد تهيئة تقنية — إنها بروتوكول تواصل لمنظمتك بأكملها. قبل دمج أول PR لهذا الإصدار، يجب أن توجد المخرجات التالية ومرتبطة من وصف طلب السحب:

  • وثيقة سطح المخاطر — الجدول من الخطوة 1، مراجَع من قِبَل قائد الفريق وفريق الاستجابة.
  • قيد سجل الأعلام — الأعلام الثلاثة مسجّلة في لوحة إدارة الأعلام مع المالك وتاريخ الإزالة والوصف.
  • YAML الطرح في Git — مانيفست Argo Rollouts محفوظ في نظام التحكم بالإصدارات بجوار كود الخدمة، لا يُدار بشكل عشوائي.
  • AnalysisTemplate في Git — عتبات SLO مُراجَعة كـ-كود، لا يضبطها مهندس واحد في واجهة مستخدم منتصف الليل.
  • دليل التراجع مرتبط من PagerDuty — عندما يُوقَظ مهندس الاستجابة الساعة 2 صباحاً، يكون الدليل هو أول رابط في تنبيه PagerDuty.
  • تذاكر التنظيف المجدوَلة — ثلاث تذاكر Jira مُنشأة في اليوم الأول: إزالة علم الإصدار، تطبيق قيد NOT NULL، حذف كود التوجيه القديم. لكل منها تاريخ استحقاق ومالك.
المقياس الحقيقي لخطة التسليم التدريجي هو مدى رتابة عملية النشر. إذا كان دليل النشر يقول "راقب لوحة المعلومات لمدة 30 دقيقة"، فأنت لم تؤتمت بما يكفي. الهدف هو أن يتمكن المهندس الذي يشحن التغيير من الابتعاد بعد الضغط على دمج — يتولى المسار الترقية والبوابات والتراجع تلقائياً. يتدخل البشر فقط عند حدوث شيء غير متوقع حقاً يتطلب حكماً بشرياً.

هذا هو المعيار الذي تضع به منظمات الهندسة من المستوى الأول نفسها. يستغرق بناؤه وقتاً واستثماراً، لكن المردود يُقاس بالحوادث التي لا تحدث أبداً — النجاحات الخفية التي تُعرّف هندسة الموثوقية العالمية المستوى.