أساسيات Kubernetes

ReplicaSets والـ Deployments

22 دقيقة الدرس 5 من 32

ReplicaSets والـ Deployments

تشغيل Pod واحد يصلح للتجريب، لكنه ليس مستوى الإنتاج. إذا تعطّلت العقدة (Node) التي تستضيف ذلك الـ Pod، لا يفعل Kubernetes شيئاً — اختفى الـ Pod واختفت معه خدمتك. الحل هو الحالة المطلوبة (Desired State): تُخبر Kubernetes بعدد النسخ التي تريدها وهي تجعل الواقع يطابق هذا الإعلان باستمرار. تُنفّذ ReplicaSets هذا الضمان؛ وتُغلّف Deployments تلك الـ ReplicaSets بآليات طرح آمن واسترداد. معاً، هكذا تعمل كل حمولة عمل عديمة الحالة — واجهات API وخوادم الويب وعمال المعالجة — على نطاق واسع في الإنتاج.

الحالة المطلوبة وحلقة التحكم

Kubernetes نظام يعمل بالمستوى (level-triggered). تكتب مواصفة تُعلن عما تريده (الحالة المطلوبة). يراقب متحكم (Controller) الكلاستر ويتصرف كلما انحرفت الحالة الفعلية عن المطلوبة. يفعل متحكم ReplicaSet شيئاً واحداً بالضبط: يُوفّق spec.replicas (المطلوب) مع عدد الـ Pods الجارية التي تطابق تسمياتها spec.selector (الفعلي). أقل من المطلوب → أنشئ Pods. أكثر من المطلوب → احذف Pods. تُخزَّن المواصفة في etcd؛ وتبقى حية عبر أعطال العقد وإعادات التشغيل وانقطاع الشبكة.

لماذا نادراً ما تكتب ReplicaSet مباشرة: تمنحك ReplicaSets نسخاً تُشفى ذاتياً لكن بلا مسار ترقية آمن. إذا غيّرت قالب الـ Pod داخل ReplicaSet، لن تُستبدَل الـ Pods الموجودة — فقط الـ Pods الجديدة المُنشأة بعد التغيير تستخدم القالب الجديد. تحل Deployments هذا بإدارة الـ ReplicaSets نيابةً عنك وتوفير آلية ترقية متحكَّم فيها.

تشريح Deployment

لمانيفست Deployment ثلاثة أقسام رئيسية: البيانات الوصفية (metadata)، المُحدِّد (selector)، وقالب الـ Pod. يُنشئ متحكم Deployment رسيماً (ReplicaSet) يطابق قالب الـ Pod المُحدَّد، ثم يضمن تشغيل العدد الصحيح من الـ Pods من ذلك القالب. إليك مانيفست واقعي من الإنتاج:

apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: production labels: app: api-server team: platform spec: replicas: 6 selector: matchLabels: app: api-server # يجب مطابقة تسميات قالب Pod -- غير قابل للتغيير بعد الإنشاء strategy: type: RollingUpdate rollingUpdate: maxSurge: 2 # 8 pods كحد أقصى أثناء الطرح (6 + 2) maxUnavailable: 0 # لا تنزل أبدا عن 6 pods جاهزة (صفر توقف) minReadySeconds: 10 # الـ pod يجب أن يكون جاهزاً 10 ث قبل احتسابه متاحاً progressDeadlineSeconds: 300 # اعتبر الطرح فاشلاً إن تجاوز 5 دقائق template: metadata: labels: app: api-server version: "2.4.1" spec: containers: - name: api image: myrepo/api-server:2.4.1 ports: - containerPort: 8080 resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "1000m" memory: "512Mi" readinessProbe: httpGet: path: /healthz/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 livenessProbe: httpGet: path: /healthz/live port: 8080 initialDelaySeconds: 15 periodSeconds: 10
مصيدة إنتاجية — ثبات المُحدِّد (selector): حقل spec.selector غير قابل للتغيير بعد إنشاء Deployment. إذا احتجت لتغيير التسميات، يجب حذف الـ Deployment وإعادة إنشائه. في الممارسة، تستخدم فرق الشركات الكبرى مفتاح تسمية ثابتاً مثل app: api-server في المُحدِّد وتضيف البيانات القابلة للتغيير مثل version فقط في تسميات قالب الـ Pod (لا في المُحدِّد). تغيير المُحدِّد سيرفض kubectl apply بخطأ تحقق.

التحديثات المتدحرجة: كيف يستبدل Kubernetes الـ Pods بأمان

عندما تغيّر أي شيء في spec.template — وسم صورة جديد، أو متغير بيئة، أو حد موارد — يُنشئ Kubernetes ReplicaSet جديدة لقالب الـ Pod الجديد ويُنسّق التسليم بين الـ ReplicaSet القديمة والجديدة. الرافعتان الرئيسيتان هما maxSurge وmaxUnavailable.

Rolling Update -- old and new ReplicaSets during a rollout Rolling Update: maxSurge=2, maxUnavailable=0 (6 replicas) Before Step 1 Step 2 After Old RS New RS Ready Total 6 old pods 6 6 6 old 2 new (surge) 6 8 4 old 4 new (ready) 6 8 6 new pods 6 6
تحديث متدحرج بـ maxSurge=2 وmaxUnavailable=0: لا تنزل الـ Pods الجاهزة عن 6 أبداً؛ وبحد أقصى 8 Pods خلال عملية الانتقال.

مسبار الجاهزية (readiness probe) هو البوّاب. يعتبر Kubernetes الـ Pod "متاحاً" فقط بعد نجاح مسبار الجاهزية وبقاء الـ Pod في حالة Ready لمدة minReadySeconds على الأقل. هذا يعني أن نشراً معيباً يتعطل عند الإقلاع سيتوقف — لن تصبح الـ ReplicaSet الجديدة أبداً متاحة، وmaxUnavailable: 0 يمنع إنهاء الـ Pods القديمة، ولديك وقت للملاحظة والتراجع قبل فقدان أي حركة مرور.

تنفيذ الطرح ومراقبته

# أطلق طرحاً بتحديث الصورة kubectl set image deployment/api-server api=myrepo/api-server:2.5.0 -n production # أو عدّل المانيفست وأعد تطبيقه kubectl apply -f deployment.yaml # راقب تقدم الطرح في الوقت الحقيقي kubectl rollout status deployment/api-server -n production # Waiting for deployment "api-server" rollout to finish: 2 out of 6 new replicas have been updated... # استعرض تاريخ الطرح kubectl rollout history deployment/api-server -n production # REVISION CHANGE-CAUSE # 1 <none> # 2 Upgrade to 2.5.0 -- JIRA-1234 # اعرض تفاصيل مراجعة محددة kubectl rollout history deployment/api-server --revision=2 -n production # أضف تعليقاً توثيقياً (أفضل ممارسة للمساءلة) kubectl annotate deployment/api-server \ kubernetes.io/change-cause="Upgrade to 2.5.0 -- JIRA-1234" \ -n production

الاسترداد: التراجع عن نشر خاطئ

يحتفظ Kubernetes بعدد قابل للضبط من الـ ReplicaSets القديمة بعد اكتمال الطرح — يتحكم فيه spec.revisionHistoryLimit (الافتراضي 10). يستعيد التراجع قالب الـ Pod من الـ ReplicaSet السابقة ويعيد تشغيلها، مكرراً عملية التحديث المتدحرج بشكل معاكس. في Google والشركات المشابهة، التراجع إجراء تشغيلي اعتيادي لا أزمة — النظام مصمم ليجعله سريعاً وآمناً.

# تراجع فوري إلى المراجعة السابقة kubectl rollout undo deployment/api-server -n production # التراجع إلى رقم مراجعة محدد kubectl rollout undo deployment/api-server --to-revision=1 -n production # راقب عملية التراجع kubectl rollout status deployment/api-server -n production # أوقف الطرح في منتصف الطريق (لفحص حركة المرور الجزئية) kubectl rollout pause deployment/api-server -n production # استأنف طرحاً موقوفاً kubectl rollout resume deployment/api-server -n production # اعرض كل الـ ReplicaSets لـ Deployment (القديمة محتفظ بها للتراجع) kubectl get replicaset -n production -l app=api-server # NAME DESIRED CURRENT READY AGE # api-server-7d9f4b6c9d 6 6 6 5m <-- ReplicaSet النشطة # api-server-5f8a3b2e1a 0 0 0 2d <-- محتفظ بها للتراجع
ممارسة إنتاجية — اضبط revisionHistoryLimit بوعي: الافتراضي 10 ReplicaSets قديمة يستهلك تخزين etcd ويُعقّد مخرجات kubectl get replicaset. خطوط نشر عالية التردد (عدة مرات يومياً) يجب أن تضبط spec.revisionHistoryLimit: 3. احتفظ بما يكفي من التاريخ ليغطي نافذة التراجع المعتادة — إذا كنت تُطلق كل ساعة ومتوسط وقت الإصلاح 30 دقيقة، فمراجعتان كافيتان.

استراتيجيات Deployment ما وراء RollingUpdate

يدعم Kubernetes أصلاً استراتيجيتين تُضبطان عبر spec.strategy.type:

  • RollingUpdate (الافتراضي) — يستبدل الـ Pods القديمة بالجديدة تدريجياً. توقف صفري عند الضبط الصحيح (maxUnavailable: 0). يعمل كود قديم وجديد في آن واحد أثناء الانتقال — يجب أن تعالج الـ API هذا: هجرات مخطط متوافقة مع الخلف، ولا تغييرات كسر ضمن نافذة الطرح.
  • Recreate — يُنهي جميع الـ Pods القديمة قبل إنشاء الجديدة. يسبب توقفاً قصيراً. استخدمه فقط لحمولات العمل التي لا تستطيع تشغيل نسختين في آن واحد (كأدوات قاعدة البيانات الكاتب الوحيد، أو التطبيقات القديمة ذات أقفال الملفات الحصرية).

الاستراتيجيات الأكثر تطوراً — canary (توجيه نسبة صغيرة من الحركة للإصدار الجديد) وblue/green (الحفاظ على بيئتين كاملتين وتبديل محدد الـ Service) — تُبنى فوق Deployments باستخدام Deployments متعددة بمحددات تسميات مختلفة، أو متحكمات Ingress لتقسيم الحركة، أو شبكة خدمات مثل Istio. هذه تُغطى في درس الشبكات.

# استراتيجية Recreate -- لأدوات ذات نسخة واحدة لا تتحمل تشغيل نسختين apiVersion: apps/v1 kind: Deployment metadata: name: db-migrator spec: replicas: 1 strategy: type: Recreate # جميع الـ pods القديمة تُنهى قبل بدء الجديدة selector: matchLabels: app: db-migrator template: metadata: labels: app: db-migrator spec: containers: - name: migrator image: myrepo/db-migrator:3.0.0

تحجيم Deployments

التحجيم شبه فوري — يُحدّث Kubernetes spec.replicas في etcd ويُنشئ أو يحذف متحكم ReplicaSet الـ Pods للتطابق. التحجيم اليدوي مفيد للاستجابة للحوادث؛ يُؤتمت Horizontal Pod Autoscaler (HPA) هذا بناءً على المعالج أو الذاكرة أو مقاييس مخصصة ويُغطى في درس لاحق.

# تحجيم يدوي kubectl scale deployment/api-server --replicas=12 -n production # تحجيم إلى صفر (يحذف جميع الـ pods -- لتعطيل خدمة مؤقتاً) kubectl scale deployment/api-server --replicas=0 -n production # فحص حالة النسخ الحالية kubectl get deployment api-server -n production # NAME READY UP-TO-DATE AVAILABLE AGE # api-server 12/12 12 12 3d # describe يُعطيك الأحداث -- ضروري لتشخيص الطرحات المتوقفة kubectl describe deployment api-server -n production
مصيدة إنتاجية — Recreate على خدمات المستخدم: تضبط بعض الفرق عن طريق الخطأ strategy: Recreate على Deployments عالية الحركة لأنه أبسط في التفكير. أثناء النشر، يُنهي Kubernetes جميع الـ Pods قبل بدء الجديدة — ستحصل على انقطاع كامل يساوي وقت إقلاع الحاوية (في الغالب 15-60 ثانية لتطبيقات JVM أو Python). استخدم دائماً RollingUpdate مع maxUnavailable: 0 للخدمات التي يواجهها المستخدم، واستثمر في أوقات إقلاع سريعة حتى يكون التأخير الأولي لمسبار الجاهزية أقل من 5 ثوانٍ.

مسبار الجاهزية ليس اختيارياً

التحديث المتدحرج بلا مسبار جاهزية خطير. بدونه، يُضيف Kubernetes الـ Pod الجديدة إلى قائمة نقاط نهاية الـ Service فور انطلاقها — قبل أن يُنهي تطبيقك التهيئة أو تشغيل الهجرات أو تدفئة الذاكرة المؤقتة. أول طلب مستخدم حقيقي يصل إلى Pod غير جاهزة ويفشل. اضبط دائماً readinessProbe على كل حاوية في كل Deployment. يجب أن يختبر المسبار الجاهزية التجارية الفعلية (اتصال قاعدة البيانات، تدفئة الذاكرة المؤقتة، إمكانية الوصول لخدمة تابعة)، ليس فقط ما إذا كانت العملية حية.