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

النشر المتدحرج

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

النشر المتدحرج

يستبدل النشر المتدحرج (Rolling Deployment) نسخ الإصدار القديم بالإصدار الجديد بشكل تدريجي — دفعة واحدة في كل مرة — بحيث لا يكون التطبيق غير متاح بالكامل في أي وقت. إنه استراتيجية النشر الافتراضية في Kubernetes وAmazon ECS ومعظم منصات الحوسبة المُدارة، لأنه يحقق نقطة توازن عملية: صفر توقف، أقل قدر من الضرر للإصدار السيئ، ولا حاجة لبنية تحتية مضاعفة (على عكس النشر الأزرق-الأخضر).

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

الآليات: Surge وUnavailable وحجم الدفعة

يتحكم معاملان في عملية التدحرج بأكملها. في Kubernetes يوجدان في قسم spec.strategy.rollingUpdate في مورد Deployment:

  • maxUnavailable — الحد الأقصى لعدد الـ pods (أو نسبة من replicas) التي يمكن أن تكون أقل من الجاهزة في آنٍ واحد أثناء الطرح. ضبطها على 0 يعني: لا تقتل pod قديماً حتى يُؤكَّد أن الجديد سليم. هذا يحمي الطاقة الاستيعابية على حساب اشتراط وجود pod الـ"surge" الإضافي.
  • maxSurge — الحد الأقصى لعدد الـ pods فوق عدد النسخ المطلوب التي يمكن أن توجد في آنٍ واحد. ضبطها على 1 يعني السماح لـ Kubernetes بتشغيل replicas + 1 pods مؤقتاً. وهكذا يُنشئ بأمان الـ pod الجديدة قبل إنهاء القديمة.

يتبادل الإعدادان خطر السعة مقابل السرعة. في أحد الطرفين، maxUnavailable: 25%, maxSurge: 0 يقتل ربع الـ pods أولاً ثم يملأها — سريع لكن بسعة منخفضة مؤقتاً. في الطرف الآخر، maxUnavailable: 0, maxSurge: 1 يُضيف دائماً قبل الإزالة — صفر خسارة في السعة لكن يستخدم عقداً إضافية مؤقتاً. تختار معظم أنظمة الإنتاج نقطة وسطى بناءً على هامش الموارد المتاح ومتطلبات الـ SLO.

الفكرة الأساسية: لا يمكن أن يكون maxUnavailable + maxSurge كلاهما صفراً — هذا يجعل التقدم مستحيلاً. يتحقق Kubernetes من ذلك ويرفض مثل هذا الإعداد. الإعدادات الافتراضية (maxSurge: 25%, maxUnavailable: 25%) معقولة للخدمات عديمة الحالة لكنها تحتاج ضبطاً لأي شيء بمتطلبات توافرية صارمة.

استنزاف الاتصالات: لماذا يهم

حين يُعلّم Kubernetes pod للإنهاء، يُزيله فوراً من كل شرائح نقطة نهاية Service — تتوقف الاتصالات الجديدة عن التوجيه إليه. لكن الطلبات الجارية المتصلة بذلك الـ pod تُعالَج بالفعل. بدون الاستنزاف، تُوقَف تلك الطلبات بشكل مفاجئ في منتصفها.

الحل هو استنزاف الاتصالات، يتحقق عبر آليتين متعاونتين:

  1. terminationGracePeriodSeconds على الـ pod — كم من الوقت ينتظر Kubernetes بعد إرسال SIGTERM قبل الإنهاء القسري بـ SIGKILL. الافتراضي 30 ثانية. يجب أن يستمع تطبيقك لـ SIGTERM ويبدأ إيقاف تشغيل رشيق: يتوقف عن قبول اتصالات جديدة، يُنهي الطلبات النشطة، ثم يخرج بشكل نظيف.
  2. خطاف دورة الحياة preStop — نوم قصير (عادةً 5–15 ثانية) يُحقن قبل وصول SIGTERM للحاوية. تُعالج هذه النافذة تأخر الانتشار بين إزالة نقطة النهاية من شرائح Service وتحديث جميع عقد موازن الحمل الأعلى (kube-proxy، Envoy sidecars، cloud LB) لجداول اتصالاتها. بدون هذا النوم، لا تزال الطلبات الجارية في طبقة موازن الحمل تصل إلى الـ pod في الفجوة القصيرة بعد إزالة نقطة نهايته لكن قبل توقفه عن الاستماع.
مصيدة إنتاجية: تخطي نوم preStop هو السبب الأكثر شيوعاً لأخطاء 502/504 أثناء النشر المتدحرج. تحديث شريحة نقطة النهاية وتدفق موازن الحمل ليسا فوريين — توجد نافذة انتشار من 1–15 ثانية تبعاً لحجم الكلاسترز ونوع الـ LB. المهندسون الذين يتخطون هذا يرون طرحاً نظيفاً في الاختبار (كلاسترز صغيرة، انتشار سريع) وانفجاراً من الأخطاء في الإنتاج (كلاسترز كبيرة، انتشار بطيء). أضف النوم دائماً.
# ملف Deployment على مستوى الإنتاج # يُظهر: استراتيجية rolling، surge/unavailable، دورة حياة الاستنزاف apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: production spec: replicas: 10 strategy: type: RollingUpdate rollingUpdate: maxSurge: 2 # بحد أقصى 12 pod تعمل في آنٍ واحد (10 + 2) maxUnavailable: 0 # لا تنخفض عن 10 pods سليمة؛ صفر خسارة في السعة selector: matchLabels: app: api-server template: metadata: labels: app: api-server spec: terminationGracePeriodSeconds: 60 # يجب أن يتجاوز نوم preStop + أطول طلب containers: - name: api image: registry.example.com/api-server:v2.4.1 ports: - containerPort: 8080 readinessProbe: httpGet: path: /healthz/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 3 # يجب أن يفشل الـ pod 3 فحوصات متتالية ليُسحب livenessProbe: httpGet: path: /healthz/live port: 8080 initialDelaySeconds: 15 periodSeconds: 10 lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 10"] # نافذة 10 ثوانٍ لتدفق الـ LB قبل وصول SIGTERM للعملية

عملية التدحرج: مخطط خطوة بخطوة

يتتبع المخطط أدناه نشراً من 4 نسخ يتدحرج من v1 إلى v2 مع maxSurge: 1, maxUnavailable: 0. كل عمود يمثل دورة مصالحة واحدة في Kubernetes.

Rolling deployment wave: v1 to v2, maxSurge 1, maxUnavailable 0 Start Step 1 Step 2 Step 3 Step 4 v1 READY v1 READY v1 READY v1 READY v1 READY v1 READY v1 READY v1 READY v2 Starting (surge +1) Surge pod created Readiness probe runs v1 Draining preStop sleep v1 READY v1 READY v1 READY v2 READY v2 healthy; v1[0] removed from Service v1 READY v1 READY v1 Draining preStop sleep v2 READY v2 Starting (surge +1) Repeats per batch v2 READY v2 READY v2 READY v2 READY Rollout complete 4/4 replicas on v2 v1 serving v1 draining (preStop) v2 healthy
موجة التدحرج مع maxSurge=1، maxUnavailable=0: السعة لا تنخفض عن 4 نسخ أبداً؛ كل pod قديم يستنزف قبل الإنهاء.

فحوصات الجاهزية: بوابة الأمان

لن يُزيل متحكم التدحرج pod قديماً حتى يجتاز الجديد فحص الجاهزية. هذه هي الآلية التي تجعل maxUnavailable: 0 ذات معنى. إذا كان فحص جاهزيتك خاطئاً — يمر دائماً، يفحص المسار الخاطئ، أو يستخدم initialDelaySeconds قصيراً جداً — فسيعتبر المتحكم pod يُقلع "جاهزاً" ويبدأ في سحب السعة قبل أن يستطيع الخدمة فعلياً معالجة الحركة.

في شركات التقنية الكبرى، تفحص فحوصات الجاهزية نقطة نهاية صحة عميقة تتحقق من اتصالية قاعدة البيانات، وقابلية الوصول للكاش، وحالة التبعيات الحيوية — لا مجرد "هل منفذ HTTP مفتوح". الـ pod الذي يُجيب على HTTP لكن لا يستطيع الوصول إلى قاعدة بياناته ليس جاهزاً لخدمة الحركة ويجب ألا يستقبلها.

ممارسة احترافية: افصل فحوصات liveness عن readiness. يُجيب فحص liveness على: "هل هذا الـ pod في حالة تعليق ويحتاج إعادة تشغيل؟" — يجب أن يكون رخيصاً ومتسامحاً. يُجيب فحص readiness على: "هل هذا الـ pod قادر على خدمة حركة مرور حقيقية الآن؟" — يجب أن يكون شاملاً. الخلط بينهما خطأ تهيئة شائع يُسبب إعادات تشغيل متتالية أثناء انقطاعات التبعية.

مراقبة الطرح والتراجع

لا تُطلق نشراً متدحرجاً وتبتعد. استخدم kubectl rollout status لمتابعة الموجة مباشرة. اضبط موعداً نهائياً بـ spec.progressDeadlineSeconds — إذا لم يكتمل الطرح خلال تلك النافذة، يُعلّم Kubernetes النشر كمتوقف (يظهر كشرط DeadlineExceeded)، وهو ما يجب أن يُعامله نظام الـ CD كفشل ويُطلق التراجع.

# متابعة الطرح في الوقت الفعلي kubectl rollout status deployment/api-server -n production --timeout=300s # إيقاف مؤقت في منتصف التدحرج (بوابة كاناري يدوية — أوقف الموجة، راقب المقاييس) kubectl rollout pause deployment/api-server -n production # استئناف بعد التحقق من معدلات الأخطاء / الكمون kubectl rollout resume deployment/api-server -n production # تراجع فوري إلى ReplicaSet السابق (لا حاجة لإعادة نشر) kubectl rollout undo deployment/api-server -n production # التراجع إلى مراجعة محددة kubectl rollout history deployment/api-server -n production kubectl rollout undo deployment/api-server -n production --to-revision=3 # رؤية علامات الحالة الحالية (تحقق من DeadlineExceeded) kubectl get deployment api-server -n production -o jsonpath='{.status.conditions}'

النشر المتدحرج في ECS

يستخدم Amazon ECS مصطلحات مختلفة للمفاهيم ذاتها. minimumHealthyPercent يُقابل عكس maxUnavailable، وmaximumPercent يحدد سقف الـ surge. لخدمة ECS بـ 10 مهام، ضبط minimumHealthyPercent: 90, maximumPercent: 110 يُعادل maxUnavailable: 1, maxSurge: 1 في Kubernetes. يُهيَّئ استنزاف اتصالات موازن الحمل على مجموعة الهدف (deregistration_delay.timeout_seconds) — اضبطه ليُطابق أو يتجاوز مدة أطول طلب متوقع.

# AWS CLI: تحديث خدمة ECS مع إعداد rolling aws ecs update-service \ --cluster production \ --service api-server \ --task-definition api-server:42 \ --deployment-configuration '{ "deploymentCircuitBreaker": { "enable": true, "rollback": true }, "minimumHealthyPercent": 90, "maximumPercent": 110 }' \ --health-check-grace-period-seconds 30 \ --region us-east-1 # deploymentCircuitBreaker مع rollback: true هو ما يُعادل progressDeadlineSeconds في ECS # إذا فشل عدد كبير جداً من المهام في البدء بصحة، يتراجع ECS تلقائياً # إلى تعريف المهمة السابق. # انتظر حتى يستقر النشر aws ecs wait services-stable \ --cluster production \ --services api-server \ --region us-east-1

أنماط الفشل في الإنتاج وكيفية اكتشافها

تفشل عمليات النشر المتدحرجة بأنماط يمكن التنبؤ بها. معرفتها مسبقاً يسمح لك بالأدوات الرصدية قبل النشر، لا بعد الحادثة:

  • تباين الإصدارات (Version Skew): أثناء التدحرج، يعملان v1 وv2 في آنٍ واحد. إذا كتب v2 عموداً في قاعدة البيانات لا يعرفه v1، أو غيّر شكل استجابة API يتوقعه مستدعٍ من v1، فلديك خطأ تباين. الدرس 8 (Expand-Contract) يُعالج هذا تحديداً — لا تنشر تغييرات المخطط وتغييرات التطبيق في نفس التدحرج.
  • فحص الجاهزية البطيء: إذا احتاج تطبيقك 90 ثانية للإحماء لكن initialDelaySeconds هو 10، سيعمل الفحص قبل أن يكون التطبيق جاهزاً، ويفشل، وسيُعيد الـ pod تشغيله في دورة CrashLoopBackOff. يتوقف الطرح. اضبط initialDelaySeconds بسخاء واستخدم startupProbe للتطبيقات ذات أوقات التشغيل المتغيرة.
  • شح الموارد أثناء الـ Surge: مع maxSurge: 2 على نشر بـ 10 نسخ تحتاج مؤقتاً لعقد لـ 12 pod. إذا كانت كلاستر موارده بالفعل عند 95%، ستكون pods الـ surge في حالة Pending وسيتوقف الطرح. يساعد Cluster Autoscaler لكن له كمون خاص به (1–3 دقائق لتوفير عقدة جديدة). حجّم كلاسترك بهامش 20–30% على الأقل للنشر المتدحرج.
  • تعارض PodDisruptionBudget (PDB): PDB بـ minAvailable: 100% (أو maxUnavailable: 0) سيتعارض مع Deployment بـ maxUnavailable يساوي 0 أيضاً — لا يستطيع متحكم الإخلاء إرضاء كلا القيدين وسيتوقف الطرح. نسّق بين إعدادات PDB وNशر التدحرج.
مصيدة إنتاجية — تباين الإصدارات هو القاتل الصامت: على عكس فحص الجاهزية السيئ (الذي يفشل بصوت عالٍ)، يفشل تباين الإصدارات بصمت. الطلبات الموجّهة لـ pods من v1 تنجح؛ تلك الموجّهة لـ pods من v2 تفشل. معدل الأخطاء الإجمالي يرتفع بنسبة تتناسب مع نسبة الحركة التي تضرب pods v2. أثناء تدحرج بنسبة 25%، ترى 25% من الطلبات تفشل — لكن حد التنبيه قد يكون 1%، وبحلول وقت إطلاقه قد يكون التدحرج وصل 50%. تحقق دائماً من توافق التغييرات أمام/خلف للـ API والمخطط قبل بدء نشر متدحرج.

عمليات النشر المتدحرجة هي استراتيجية الحصان العملي للإصدارات الروتينية. لا تصلح للتغييرات الجذرية — استخدم feature flags (الدرس 5) أو blue-green (الدرس 3) حين لا يمكن للإصدارين القديم والجديد التعايش بأمان. لكل شيء آخر، نشر متدحرج جيد الضبط مع استنزاف مناسب، وفحوصات جاهزية دقيقة، وقاطع دائري هو الخيار الافتراضي الصحيح.