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

التراجع والمضي قُدُمًا

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

التراجع والمضي قُدُمًا

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

لماذا التراجع ليس مجانيًا

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

  • الحزمة السابقة لا تزال موجودة وغير قابلة للتغيير. يجب أن تحتفظ سجلات الحاويات وحاويات S3 ومستودعات مخططات Helm بالإصدارات القديمة. إذا سمحت بتغيير الوسوم (مثلاً إعادة رفع :latest)، فلا يوجد حزمة للتراجع إليها.
  • مخطط قاعدة البيانات متوافق مع الكود السابق للأمام. إذا أجرى الإصدار الحالي ترحيلًا إضافيًا (عمود جديد، جدول جديد)، فالتراجع بالكود آمن — الكود القديم ببساطة يتجاهل العمود الجديد. أما إذا أجرى ترحيلًا تدميريًا (حذف عمود، إعادة تسميته، تغيير نوعه)، فالتراجع كارثي ما لم تُطبّق نمط التوسع-والتقليص مسبقًا.
  • الحالة الخارجية لم تتجاوز نقطة اللا عودة. إذا أرسل الكود الجديد رسائل بريد إلكتروني، أو فوتر عملاء، أو نشر أحداثًا على Kafka، فالتراجع بالخدمة لا يتراجع عن هذه الآثار الجانبية. خطّط للمعاملات التعويضية أو تقبّل الانحراف.
أخطر لحظة في الحوادث هي حين يُشغّل مهندس ترحيل ALTER TABLE تدميريًا ثم يحاول التراجع. إذا حُذف العمود في الإنتاج، لن تُفيد أوامر kubectl rollout undo. تغييرات قاعدة البيانات يجب أن تكون آخر سطر في خطة التراجع، لا الغريزة الأولى. طبّق دائمًا ترحيلات التوسع-والتقليص قبل نشر الكود الجديد.

ميكانيكا التراجع السريع حسب المنصة

لكل هدف نشر أداة تراجع خاصة به. معرفة جميعها — وزمن استجابة كل منها — أمر ضروري.

Kubernetes: kubectl rollout undo

يخزّن Kubernetes آخر عشرة مراجعات لـ ReplicaSet افتراضيًا (يتحكم فيها revisionHistoryLimit). التراجع يُعيد تفعيل ReplicaSet سابق بدلًا من إعادة سحب الصورة — مما يجعله سريعًا، يكتمل عادةً في أقل من 60 ثانية لـ Deployment صغيرة:

# فحص المراجعات المتاحة kubectl rollout history deployment/payments-api -n prod # التراجع إلى المراجعة السابقة فورًا kubectl rollout undo deployment/payments-api -n prod # التراجع إلى مراجعة محددة (مثلاً المراجعة 3) kubectl rollout undo deployment/payments-api -n prod --to-revision=3 # مراقبة تقدم التراجع kubectl rollout status deployment/payments-api -n prod --timeout=120s # التحقق من الصورة النشطة بعد التراجع kubectl get deployment payments-api -n prod \ -o jsonpath='{.spec.template.spec.containers[0].image}'
اضبط revisionHistoryLimit: 5 في مواصفة Deployment الخاصة بك صراحةً — الافتراضي 10 يُهدر مساحة etcd عند الحجم الكبير. خمس مراجعات تمنحك هامش تراجع كافيًا مع الاقتصاد في الموارد. في بيئة GitOps (ArgoCD, Flux)، التراجع يتم بإعادة عكس كومت Git؛ kubectl rollout undo احتفظ به لحالات الطوارئ فقط لأنه يُحدث انحرافًا بين Git والكلاستر.

Helm: التراجع عن إصدار مخطط

يحتفظ Helm بتاريخ الإصدارات في Kubernetes Secrets. التراجع عن إصدار Helm يستعيد لقطة values.yaml السابقة ويُعيد تطبيق القوالب القديمة:

# عرض تاريخ إصدارات Helm helm history payments-api -n prod # التراجع إلى المراجعة السابقة للإصدار helm rollback payments-api -n prod # التراجع إلى مراجعة إصدار محددة مع مهلة دقيقتين helm rollback payments-api 3 -n prod --timeout 2m0s --wait # التحقق من الإصدار المتراجع helm status payments-api -n prod

AWS ECS / App Runner: التراجع في تعريف المهام

# سرد مراجعات تعريف المهام الأخيرة aws ecs list-task-definitions \ --family-prefix payments-api \ --sort DESC \ --query 'taskDefinitionArns[0:5]' \ --output table # تحديث خدمة ECS لتشغيل تعريف المهمة السابق (مثلاً المراجعة 47) aws ecs update-service \ --cluster prod \ --service payments-api \ --task-definition payments-api:47 \ --force-new-deployment \ --region us-east-1 # انتظار استقرار الخدمة aws ecs wait services-stable \ --cluster prod \ --services payments-api \ --region us-east-1

إطار القرار: التراجع أم المضي قُدُمًا؟

Rollback vs Roll-Forward Decision Tree Production Incident triggered by new deploy Destructive DB migration ran in this deploy? YES Roll Forward (DB cannot go back) NO Root cause identified and fix is < 30 min away? YES Roll Forward (ship the fix fast) NO Previous artifact is immutable and DB-safe? YES Roll Back (fastest recovery) NO Escalate (incident bridge) Engage incident bridge — manual triage
شجرة القرار: التراجع أم المضي قُدُمًا بناءً على حالة قاعدة البيانات وجاهزية الإصلاح وسلامة الحزمة.

يتلخّص الإطار أعلاه في ثلاثة أسئلة تُطرح بالترتيب:

  1. هل تقدّمت قاعدة البيانات تدميريًا؟ إذا نعم، التراجع مستحيل — الكود القديم يتوقع مخططًا لم يعد موجودًا. تقدّم بإصلاح مُحدَّد.
  2. هل تعرف ما الذي انكسر، وهل يمكنك إصلاحه سريعًا؟ إذا نعم (فحص nil مفقود، متغير بيئة خاطئ، قيمة إعداد سيئة)، فالأسرع والأأمن في الغالب شحن الإصلاح لا تنسيق التراجع — خاصةً حين تجري حركة المرور عبر مسار الكود الجديد وبنية الكناري جاهزة.
  3. هل الحزمة السابقة سليمة وآمنة لقاعدة البيانات؟ إذا كانت الإجابتان نعم، تراجع فورًا. كل دقيقة من الخدمة المتدهورة تكلف مالًا حقيقيًا وثقةً حقيقية.
في Google، الموقف الافتراضي لمعظم الخدمات هو المضي قُدُمًا. الخدمات مصمّمة بحيث أي إصدار N+1 يمكن إصلاحه بإصدار N+2 بدلًا من العودة إلى N-1. هذه الفلسفة مفروضة هيكليًا: جميع تغييرات المخططات متوافقة للأمام بحكم السياسة، وكل إصدار فارق واحد من HEAD، وخط CI للإصلاح العاجل يُنجز في أقل من 10 دقائق. صمّم نظامك بحيث المضي قُدُمًا دائمًا خيار قابل للتطبيق.

تراجع GitOps — النمط الآمن للإنتاج

في بيئة GitOps (ArgoCD, Flux)، مستودع Git هو مصدر الحقيقة. التراجع الصحيح هو git revert، لا أمر kubectl، لأن تغييرات kubectl الأمرية تُحدث انحرافًا بين حالة الكلاستر والحالة المُعلنة في Git:

# تراجع GitOps — عكس الكومت المسيء git log --oneline -5 # a3f9c12 deploy: bump payments-api to v2.4.1 <-- النشر المعيب # 8e1b047 deploy: bump payments-api to v2.4.0 <-- آخر حالة مستقرة # ... # عكس الكومت المعيب git revert a3f9c12 --no-edit # الدفع — سيتزامن ArgoCD / Flux خلال فترة الاستطلاع (<=3 دقائق) git push origin main # للاسترداد الفوري، شغّل مزامنة يدوية في ArgoCD argocd app sync payments --prune --force # أو عبر API الخاص بـ ArgoCD: # argocd app rollback payments --revision <previous-revision-id>

أمر app rollback في ArgoCD يُعيد النشر من حالة Git سابقة مخزّنة مؤقتًا — هو منفذ طوارئ حين لم يتسرّب الكومت المعكوس بعد. في الحالة الطبيعية، git revert + push هو المسار المعتمد لأنه يُنتج تغييرًا قابلًا للتدقيق في تاريخ Git ولا يترك الكلاستر في حالة يعدّها ArgoCD "خارج المزامنة".

شبكات أمان النشر — الوقاية من الحاجة إلى التراجع

أفضل تراجع هو الذي لن تحتاجه أبدًا. ثلاث شبكات أمان تُقلّل تكرار التراجع بشكل جذري:

1. اختبارات الدخان الآلية في خط النشر

# GitHub Actions — بوابة اختبار الدخان بعد ترقية Helm - name: Deploy to staging run: | helm upgrade --install payments-api ./charts/payments \ --namespace staging \ --set image.tag=${{ github.sha }} \ --wait --timeout 3m - name: Run smoke tests run: | # انتظار حاوية صحية على الأقل kubectl rollout status deployment/payments-api -n staging --timeout=90s # فحص نقطة نهاية الصحة — فشل سريع إذا لم يكن 200 HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ https://payments-staging.internal/health) [ "$HTTP_STATUS" = "200" ] || { echo "Smoke test FAILED: HTTP $HTTP_STATUS"; exit 1; } # فحص نقطة نهاية تجارية حاسمة HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: Bearer $SMOKE_TEST_TOKEN" \ https://payments-staging.internal/api/v1/ping) [ "$HTTP_STATUS" = "200" ] || { echo "API smoke test FAILED"; exit 1; } - name: Promote to production (only if smoke tests pass) if: success() run: | helm upgrade --install payments-api ./charts/payments \ --namespace prod \ --set image.tag=${{ github.sha }} \ --wait --timeout 5m

2. تحويل حركة المرور التدريجي مع الإحجام التلقائي

يمكن ضبط Argo Rollouts (أو Flagger) للإحجام والتراجع تلقائيًا حين تُخرق عتبات معدل الأخطاء أو الاستجابة أثناء ترقية الكناري. مواصفة التدريج تُدمج شبكة الأمان مباشرةً:

# argo-rollout.yaml — تراجع تلقائي عند خرق SLO apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: payments-api namespace: prod spec: replicas: 20 revisionHistoryLimit: 3 strategy: canary: steps: - setWeight: 5 - pause: { duration: 5m } - setWeight: 25 - pause: { duration: 10m } - setWeight: 100 analysis: templates: - templateName: payments-success-rate startingStep: 1 args: - name: service-name value: payments-api-canary --- apiVersion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: name: payments-success-rate namespace: prod spec: args: - name: service-name metrics: - name: success-rate interval: 1m successCondition: result[0] >= 0.995 failureLimit: 2 # إحجام بعد إخفاقَين متتاليَين provider: prometheus: address: http://prometheus.monitoring:9090 query: | sum(rate(http_requests_total{service="{{args.service-name}}",status!~"5.."}[2m])) / sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))

حين تُسجّل AnalysisTemplate إخفاقَين متتاليَين (failureLimit: 2)، تضبط Argo Rollouts تلقائيًا weight: 0 للكناري، وتُعيد ReplicaSet المستقر إلى 100%، وتُعلم التدريج بأنه Degraded. لا حاجة لتدخل بشري — النظام يتراجع بنفسه.

3. الحزم الثابتة وانضباط الوسوم

# ممارسة سيئة — الوسوم القابلة للتغيير تُسبّب إخفاقات التراجع الصامتة docker push myregistry.io/payments-api:latest # سيئ — latest قابل للتغيير # الممارسة الصحيحة — كل بناء موسوم بـ SHA الخاص بـ git IMAGE_TAG=$(git rev-parse --short HEAD) docker build -t myregistry.io/payments-api:${IMAGE_TAG} . docker push myregistry.io/payments-api:${IMAGE_TAG} # أيضًا رفع وسم الإصدار الدلالي (ثابت بعد قطع الإصدار) docker tag myregistry.io/payments-api:${IMAGE_TAG} myregistry.io/payments-api:v2.4.1 docker push myregistry.io/payments-api:v2.4.1 # في AWS ECR — تفعيل ثبات وسم الصور عبر CLI aws ecr put-image-tag-mutability \ --repository-name payments-api \ --image-tag-mutability IMMUTABLE \ --region us-east-1
احتفظ بآخر خمس صور منشورة في الإنتاج مُثبَّتة في السجل بسياسة دورة حياة prod-pinned تمنع الحذف. هذا يضمن أنه حتى لو نظّف نظام CI الصور القديمة، الإصدارات الخمسة الأخيرة للإنتاج دائمًا متاحة لتراجع طوارئ دون إعادة بناء. سياسات دورة حياة ECR وقواعد احتفاظ Harbor كلاهما يدعمان هذا النمط.

المضي قُدُمًا عمليًا — خط الإصلاح العاجل

المضي قُدُمًا يتطلب خط إصلاح عاجل أسرع من إصدارك المعياري. في كبرى شركات التقنية، خطوط الإصلاح العاجل اهتمام من الدرجة الأولى مع أدوات مخصصة:

  • تفرّع من SHA المنشور، لا من HEAD. قد يحتوي HEAD على تغييرات غير مُصدَرة. تحقق من الكومت الدقيق الذي يعمل في الإنتاج، طبّق الإصلاح المحدد الواحد، وارقِّه.
  • شغّل مجموعة اختبارات مصغّرة. اختبارات دخان + حالة الاختبار الفاشلة للخطأ. ليس مجموعة الاختبارات الكاملة التي تستغرق 45 دقيقة — تلك للإصدارات المعيارية. وظيفة CI للإصلاح العاجل يجب أن تكتمل في أقل من 10 دقائق.
  • تجاوز مرحلة التدريج لحوادث P0 الحقيقية — بموافقة صريحة. إذا كان معدل الأخطاء في الإنتاج 40% والإصلاح فحص nil من سطر واحد، الانتظار لدورة ترقية تدريجية كاملة يُكلّف مستخدمين حقيقيين. هيكل خطك للسماح بتجاوز مُقيَّد بموافقة مطلوبة من مدير الهندسة المناوب.
  • انشر عبر الاستراتيجية التدريجية المعيارية. حتى الإصلاح العاجل يجب أن يستخدم الكناري أو الأزرق-الأخضر — فقط بفترات خطوات مضغوطة (دقيقة عند 5%، دقيقتان عند 25%، ثم 100%).
وثّق دفاتر تشغيل التراجع والمضي قُدُمًا في أداة إدارة الحوادث (PagerDuty Runbooks، Confluence، Notion) وأرفق روابطها بدفاتر تشغيل التنبيهات. خلال الحادث، يعمل المهندسون تحت ضغط بقدرة إدراكية مُقلَّصة. دفتر التشغيل يُقلّل متوسط وقت الاسترداد (MTTR) بإلغاء الحاجة لتذكّر الأوامر الدقيقة من الذاكرة — يجب أن تكون قابلة للتنفيذ من دفتر التشغيل مباشرةً، جاهزة للنسخ واللصق.