الوحدة 5 · التشغيل في الإنتاج
التحديثات المتدرّجة والتراجع وHelm
20 دقراءة 4 داملأ الفراغات
الدرس في لمحة
اضبط التحديث المتدرّج لـ notes-api حتى لا تقلّل الإصدارات السعة أبدًا، وراقب rollout سيئًا وتراجع عنه بـ kubectl، وغلّف الـ manifests في Helm chart بقيم خاصة بكل بيئة.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
الإصدار الذي علّمني احترام الـ rollouts خرج يوم جمعة في الرابعة عصرًا (أعرف). كان في الإصدار الجديد خطأ إملائي في اسم متغيّر بيئة، فلم يستطع الوصول إلى قاعدة البيانات. لم يكن في الـ Deployment أي readiness probe، وكانت إعدادات الـ rollout افتراضية، فاستبدل Kubernetes بكل سرور كل Pod يعمل بآخر يُرجع أخطاء 500. استغرق الأمر ست دقائق لنلاحظ، وأربعين ثانية لنتراجع. هذا الدرس عن جعل الجزء الأول مستحيلًا والجزء الثاني روتينيًا.
كيف يعمل التحديث المتدرّج
حين تغيّر أي شيء في قالب الـ Pod في Deployment، وعادةً الـ image، ينشئ الـ Deployment ReplicaSet جديدًا للقالب الجديد ويبدأ في نقل النسخ من القديم إلى الجديد. إعدادان يحدّدان الوتيرة:
spec:
revisionHistoryLimit: 10
minReadySeconds: 10
progressDeadlineSeconds: 300
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0maxSurge: كم Pod فوق العدد المرغوب يمكن أن يوجد أثناء التحديث. مع 4 نسخ وmaxSurge: 1، يمكن أن يوجد 5 Pods في وقت واحد.maxUnavailable: كم Pod تحت العدد المرغوب يُتسامح معه. عند0لا تنخفض السعة أبدًا.minReadySeconds: يجب أن يبقى الـ Pod الجديد جاهزًا هذه المدة قبل أن يُعدّ متاحًا. هذا يلتقط الإصدارات التي تجتاز الـ readiness ثم تنهار بعد عشر ثوانٍ.progressDeadlineSeconds: إذا لم يحرز الـ rollout أي تقدّم طوال هذه المدة، يُبلغ الـ Deployment بأنه فشل.
القيم الافتراضية هي 25% للزيادة و25% لعدم الإتاحة. أنا أضبط maxUnavailable: 0 على كل خدمة تواجه المستخدمين: لا يُزال Pod قديم إلا بعد أن يصبح Pod جديد جاهزًا. ومع الـ readiness probe من الدرس 5.1، لا يستطيع إصدار معطوب أخذ الحركة من إصدار يعمل. إنه يتوقّف مع Pod جديد واحد غير جاهز بينما تخدم الـ Pods القديمة.
الاستراتيجية الأخرى، Recreate، تحذف كل الـ Pods القديمة قبل تشغيل الجديدة. استخدمها فقط حين لا يستطيع الإصداران فعلًا العمل جنبًا إلى جنب، واقبل التوقّف.
انشر وراقب وتراجع
غيّر الـ image في k8s/deployment.yaml وطبّق، ثم راقب:
kubectl apply -f k8s/deployment.yaml
kubectl rollout status deployment/notes-api --timeout=5mالأمر rollout status ينتظر حتى يكتمل الـ rollout أو يفشل، ويخرج برمز غير صفري عند الفشل، فيستطيع الـ CI استخدامه كخطوة «هل نجح النشر؟». إذا فشل، أو صارت لوحات المتابعة حمراء بعد نجاحه:
kubectl rollout history deployment/notes-api
kubectl rollout undo deployment/notes-api
kubectl rollout undo deployment/notes-api --to-revision=7كل ReplicaSet قديم (حتى revisionHistoryLimit) هدف تراجع جاهز. الأمر undo يوسّع السابق من جديد باستخدام قواعد التحديث المتدرّج نفسها، فيكون التراجع آمنًا مثل التقدّم ويستغرق بضع ثوانٍ. ولتجعل history مقروءًا، سجّل سبب وجود كل revision: kubectl annotate deployment/notes-api kubernetes.io/change-cause="1.5.0: bulk export".
بعد الـ undo، يشغّل الـ cluster الإصدار 1.4.0 بينما يقول Git 1.5.0. اعكس الـ commit فورًا، وإلا فسيعيد الـ apply الروتيني التالي نشر الخلل.
التغليف بـ Helm
صار مجلد k8s/ الخاص بـ notes-api ستة ملفات، وبيئة staging تحتاج إلى عدد نسخ وموارد وtags مختلفة عن الإنتاج. Helm يغلّف الـ manifests في chart: قوالب فيها عناصر نائبة، مع ملف values.yaml للقيم الافتراضية تتجاوزه كل بيئة.
charts/notes-api/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
hpa.yaml
configmap.yaml containers:
- name: api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
{{- toYaml .Values.resources | nindent 12 }}image:
repository: ghcr.io/skylane/notes-api
tag: "1.5.1"
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { memory: 512Mi }الأوامر تحاكي ما فعلته بـ kubectl:
helm lint charts/notes-api
helm template notes-api charts/notes-api -f charts/notes-api/values-prod.yaml
helm upgrade --install notes-api charts/notes-api -n notes -f charts/notes-api/values-prod.yaml
helm history notes-api -n notes
helm rollback notes-api 4 -n notesالأمر helm template يولّد الـ YAML محليًا حتى تراجعه. وupgrade --install يثبّت في المرة الأولى ويرقّي بعدها، مسجّلًا release revision مرقّمة في كل مرة. وhelm rollback يعيد تطبيق المجموعة الكاملة من manifests revision سابقة، بما فيها الـ ConfigMap، وهذا أوسع من kubectl rollout undo. Helm 4، الصادر أواخر 2025، أعاد تسمية بعض الخيارات؛ فمثلًا --atomic، الذي يتراجع تلقائيًا حين تفشل الترقية، صار الآن --rollback-on-failure.
تستطيع الآن شحن إصدار ومراقبته وعكسه بثقة. الدرس الأخير يُدخل الحركة إلى الـ cluster كما يجب، ويجمع دليل التشخيص لتلك اللحظات التي يحدث فيها خلل رغم كل شيء.
أهم النقاط
- التحديث المتدرّج يوسّع ReplicaSet جديدًا ويقلّص القديم، بوتيرة يضبطها
maxSurgeوmaxUnavailable، ولا يتقدّم إلا بقدر ما تسمح به الـ readiness. - مع
maxUnavailableعلى 0، يتوقّف الإصدار الذي لا تصبح الـ Pods الخاصة به جاهزة أبدًا دون ضرر، بينما يستمر الإصدار القديم في الخدمة. - الأمر
kubectl rollout undoيعود إلى الـ ReplicaSet السابق خلال ثوانٍ، لكن حدّث Git أيضًا وإلا فسيعيد الـ apply التالي نشر الإصدار السيئ. - التراجع عن الكود لا يتراجع عن قاعدة البيانات، لذا يجب أن تعمل تغييرات المخطّط مع الإصدارين القديم والجديد معًا.
- Helm يغلّف الـ manifests كقوالب مع قيم؛ والأمر
helm upgrade --installينشر، وhelm rollbackيعود إلى release revision سابقة.