{"id":"l-dk-5-3","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"اضبط التحديث المتدرّج لـ notes-api حتى لا تقلّل الإصدارات السعة أبدًا، وراقب rollout سيئًا وتراجع عنه بـ kubectl، وغلّف الـ manifests في Helm chart بقيم خاصة بكل بيئة.","html":"<p>الإصدار الذي علّمني احترام الـ rollouts خرج يوم جمعة في الرابعة عصرًا (أعرف). كان في الإصدار الجديد خطأ إملائي في اسم متغيّر بيئة، فلم يستطع الوصول إلى قاعدة البيانات. لم يكن في الـ Deployment أي readiness probe، وكانت إعدادات الـ rollout افتراضية، فاستبدل Kubernetes بكل سرور كل Pod يعمل بآخر يُرجع أخطاء 500. استغرق الأمر ست دقائق لنلاحظ، وأربعين ثانية لنتراجع. هذا الدرس عن جعل الجزء الأول مستحيلًا والجزء الثاني روتينيًا.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">كيف يعمل التحديث المتدرّج</h2>\n<p>حين تغيّر أي شيء في قالب الـ Pod في Deployment، وعادةً الـ image، ينشئ الـ Deployment <strong>ReplicaSet جديدًا</strong> للقالب الجديد ويبدأ في نقل النسخ من القديم إلى الجديد. إعدادان يحدّدان الوتيرة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">k8s/deployment.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  revisionHistoryLimit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  minReadySeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  progressDeadlineSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">300</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  strategy</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">RollingUpdate</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    rollingUpdate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      maxSurge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      maxUnavailable</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span></span></code></pre></div>\n<ul>\n<li><strong><code dir=\"ltr\">maxSurge</code></strong>: كم Pod فوق العدد المرغوب يمكن أن يوجد أثناء التحديث. مع 4 نسخ و<code dir=\"ltr\">maxSurge: 1</code>، يمكن أن يوجد 5 Pods في وقت واحد.</li>\n<li><strong><code dir=\"ltr\">maxUnavailable</code></strong>: كم Pod تحت العدد المرغوب يُتسامح معه. عند <code dir=\"ltr\">0</code> لا تنخفض السعة أبدًا.</li>\n<li><strong><code dir=\"ltr\">minReadySeconds</code></strong>: يجب أن يبقى الـ Pod الجديد جاهزًا هذه المدة قبل أن يُعدّ متاحًا. هذا يلتقط الإصدارات التي تجتاز الـ readiness ثم تنهار بعد عشر ثوانٍ.</li>\n<li><strong><code dir=\"ltr\">progressDeadlineSeconds</code></strong>: إذا لم يحرز الـ rollout أي تقدّم طوال هذه المدة، يُبلغ الـ Deployment بأنه فشل.</li>\n</ul>\n<p>القيم الافتراضية هي 25% للزيادة و25% لعدم الإتاحة. أنا أضبط <code dir=\"ltr\">maxUnavailable: 0</code> على كل خدمة تواجه المستخدمين: لا يُزال Pod قديم إلا بعد أن يصبح Pod جديد جاهزًا. ومع الـ readiness probe من الدرس 5.1، لا يستطيع إصدار معطوب أخذ الحركة من إصدار يعمل. إنه يتوقّف مع Pod جديد واحد غير جاهز بينما تخدم الـ Pods القديمة.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 240\" role=\"img\" aria-labelledby=\"l-dk-5-3-t1\"><defs><marker id=\"arrow-l-dk-5-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-5-3-t1\">أربع خطوات من تحديث متدرّج لـ 4 نسخ. كل خطوة تضيف Pod واحدًا من الإصدار الجديد، وتنتظر جاهزيته، ثم تزيل Pod واحدًا من الإصدار القديم، فيوجد دائمًا 4 Pods جاهزة على الأقل.</title>\n  <text class=\"d-label-strong\" x=\"20\" y=\"40\">البداية</text>\n  <rect class=\"d-box\" x=\"120\" y=\"22\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"190\" y=\"22\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"260\" y=\"22\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"330\" y=\"22\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <text class=\"d-label-strong\" x=\"20\" y=\"95\">زيادة</text>\n  <rect class=\"d-box\" x=\"120\" y=\"77\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"190\" y=\"77\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"260\" y=\"77\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"330\" y=\"77\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box-success\" x=\"400\" y=\"77\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <text class=\"d-label-muted\" x=\"480\" y=\"96\">‏Pod جديد جاهز</text>\n  <text class=\"d-label-strong\" x=\"20\" y=\"150\">تبديل</text>\n  <rect class=\"d-box\" x=\"190\" y=\"132\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"260\" y=\"132\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box\" x=\"330\" y=\"132\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box-success\" x=\"400\" y=\"132\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <text class=\"d-label-muted\" x=\"480\" y=\"151\">إزالة Pod قديم</text>\n  <text class=\"d-label-strong\" x=\"20\" y=\"205\">انتهى</text>\n  <rect class=\"d-box-success\" x=\"120\" y=\"187\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box-success\" x=\"190\" y=\"187\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box-success\" x=\"260\" y=\"187\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <rect class=\"d-box-success\" x=\"330\" y=\"187\" width=\"60\" height=\"28\" rx=\"6\"/>\n  <text class=\"d-label-muted\" x=\"480\" y=\"206\">الـ ReplicaSet القديم عند 0</text>\n  <text class=\"d-label-muted\" x=\"20\" y=\"234\">رمادي: 1.4.0 · أخضر: 1.5.0 · كرّر حتى يُستبدل الكل</text>\n</svg><figcaption>تحديث متدرّج مع maxSurge 1 وmaxUnavailable 0</figcaption></figure>\n<p>الاستراتيجية الأخرى، <code dir=\"ltr\">Recreate</code>، تحذف كل الـ Pods القديمة قبل تشغيل الجديدة. استخدمها فقط حين لا يستطيع الإصداران فعلًا العمل جنبًا إلى جنب، واقبل التوقّف.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">انشر وراقب وتراجع</h2>\n<p>غيّر الـ image في <code dir=\"ltr\">k8s/deployment.yaml</code> وطبّق، ثم راقب:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/deployment.yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> status</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --timeout=5m</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">rollout status</code> ينتظر حتى يكتمل الـ rollout أو يفشل، ويخرج برمز غير صفري عند الفشل، فيستطيع الـ CI استخدامه كخطوة «هل نجح النشر؟». إذا فشل، أو صارت لوحات المتابعة حمراء بعد نجاحه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> history</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> undo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> undo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --to-revision=7</span></span></code></pre></div>\n<p>كل ReplicaSet قديم (حتى <code dir=\"ltr\">revisionHistoryLimit</code>) هدف تراجع جاهز. الأمر <code dir=\"ltr\">undo</code> يوسّع السابق من جديد باستخدام قواعد التحديث المتدرّج نفسها، فيكون التراجع آمنًا مثل التقدّم ويستغرق بضع ثوانٍ. ولتجعل <code dir=\"ltr\">history</code> مقروءًا، سجّل سبب وجود كل revision: ‏<code dir=\"ltr\">kubectl annotate deployment/notes-api kubernetes.io/change-cause=&quot;1.5.0: bulk export&quot;</code>.</p>\n<p>بعد الـ undo، يشغّل الـ cluster الإصدار 1.4.0 بينما يقول Git ‏1.5.0. اعكس الـ commit فورًا، وإلا فسيعيد الـ apply الروتيني التالي نشر الخلل.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>افتراض أن التراجع يلغي كل شيء</span></p>\n<p>الأمر <code dir=\"ltr\">rollout undo</code> يبدّل الـ Pods. ولا يمسّ قاعدة بياناتك. إذا أعاد 1.5.0 تسمية عمود، فسيعود 1.4.0 إلى مخطّط لا يفهمه، ويصبح التراجع انقطاعًا ثانيًا. نفّذ تغييرات المخطّط على خطوات توسعة ثم تقليص (expand and contract): أضف العمود الجديد، وانشر كودًا يكتب في الاثنين، واملأ البيانات القديمة، وحوّل القراءات، ولا تحذف العمود القديم إلا بعد إصدار. عندها يستطيع أي إصدارين متجاورين العمل على قاعدة البيانات نفسها، وهذا ما تتطلّبه التحديثات المتدرّجة أصلًا.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">التغليف بـ Helm</h2>\n<p>صار مجلد <code dir=\"ltr\">k8s/</code> الخاص بـ notes-api ستة ملفات، وبيئة staging تحتاج إلى عدد نسخ وموارد وtags مختلفة عن الإنتاج. <strong>Helm</strong> يغلّف الـ manifests في <strong>chart</strong>: قوالب فيها عناصر نائبة، مع ملف <code dir=\"ltr\">values.yaml</code> للقيم الافتراضية تتجاوزه كل بيئة.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>charts/notes-api/\n  Chart.yaml\n  values.yaml\n  templates/\n    deployment.yaml\n    service.yaml\n    hpa.yaml\n    configmap.yaml</code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">charts/notes-api/templates/deployment.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"{{ .Values.image.repository }}:{{ .Values.image.tag }}\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          resources</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            {{- </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">toYaml .Values.resources | nindent 12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }}</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">charts/notes-api/values-prod.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  repository</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  tag</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"1.5.1\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">resources</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  requests</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">cpu</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">250m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#116329;--shiki-dark:#7EE787\">memory</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">256Mi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  limits</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">memory</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">512Mi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span></code></pre></div>\n<p>الأوامر تحاكي ما فعلته بـ kubectl:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">helm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lint</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> charts/notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">helm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> template</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> charts/notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> charts/notes-api/values-prod.yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">helm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> upgrade</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --install</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> charts/notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> charts/notes-api/values-prod.yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">helm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> history</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">helm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollback</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 4</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">helm template</code> يولّد الـ YAML محليًا حتى تراجعه. و<code dir=\"ltr\">upgrade --install</code> يثبّت في المرة الأولى ويرقّي بعدها، مسجّلًا <strong>release revision</strong> مرقّمة في كل مرة. و<code dir=\"ltr\">helm rollback</code> يعيد تطبيق المجموعة الكاملة من manifests ‏revision سابقة، بما فيها الـ ConfigMap، وهذا أوسع من <code dir=\"ltr\">kubectl rollout undo</code>. ‏Helm 4، الصادر أواخر 2025، أعاد تسمية بعض الخيارات؛ فمثلًا <code dir=\"ltr\">--atomic</code>، الذي يتراجع تلقائيًا حين تفشل الترقية، صار الآن <code dir=\"ltr\">--rollback-on-failure</code>.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>ابدأ بـ manifests عادية</span></p>\n<p>يستحق Helm مكانه حين تكون لديك عدّة بيئات أو تريد استهلاك charts ينشرها الآخرون، مثل منظومة مقاييس. أما لخدمة واحدة في بيئة واحدة، فـ YAML عادي مع <code dir=\"ltr\">kubectl diff</code> أسهل في القراءة والتشخيص. القوالب التي لا تستطيع توليدها في ذهنك تُبطئ كل حادث.</p>\n</aside>\n<p>تستطيع الآن شحن إصدار ومراقبته وعكسه بثقة. الدرس الأخير يُدخل الحركة إلى الـ cluster كما يجب، ويجمع دليل التشخيص لتلك اللحظات التي يحدث فيها خلل رغم كل شيء.</p>\n","toc":[{"id":"sec-1","text":"كيف يعمل التحديث المتدرّج","level":2},{"id":"sec-2","text":"انشر وراقب وتراجع","level":2},{"id":"sec-3","text":"التغليف بـ Helm","level":2}],"takeaways":["التحديث المتدرّج يوسّع ReplicaSet جديدًا ويقلّص القديم، بوتيرة يضبطها <code dir=\"ltr\">maxSurge</code> و<code dir=\"ltr\">maxUnavailable</code>، ولا يتقدّم إلا بقدر ما تسمح به الـ readiness.","مع <code dir=\"ltr\">maxUnavailable</code> على 0، يتوقّف الإصدار الذي لا تصبح الـ Pods الخاصة به جاهزة أبدًا دون ضرر، بينما يستمر الإصدار القديم في الخدمة.","الأمر <code dir=\"ltr\">kubectl rollout undo</code> يعود إلى الـ ReplicaSet السابق خلال ثوانٍ، لكن حدّث Git أيضًا وإلا فسيعيد الـ apply التالي نشر الإصدار السيئ.","التراجع عن الكود لا يتراجع عن قاعدة البيانات، لذا يجب أن تعمل تغييرات المخطّط مع الإصدارين القديم والجديد معًا.","‏Helm يغلّف الـ manifests كقوالب مع قيم؛ والأمر <code dir=\"ltr\">helm upgrade --install</code> ينشر، و<code dir=\"ltr\">helm rollback</code> يعود إلى release revision سابقة."],"further":[{"title":"Deployments (rolling update and rollback)","url":"https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-back-a-deployment"},{"title":"Performing a Rolling Update","url":"https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/"},{"title":"kubectl rollout","url":"https://kubernetes.io/docs/reference/kubectl/generated/kubectl_rollout/"}],"quiz":[{"q":"<p>يعمل notes-api بـ 4 نسخ مع <code dir=\"ltr\">maxSurge 1</code> و<code dir=\"ltr\">maxUnavailable 0</code>. ‏Pods الإصدار 1.5.0 تنهار عند البدء. ماذا يلاحظ المستخدمون؟</p>","options":[{"text":"لا شيء؛ يتوقّف الـ rollout مع Pod جديد واحد فاشل بينما تستمر الـ Pods القديمة الأربعة في الخدمة.","why":"صحيح. حين لا يُسمح بأي نقص في الإتاحة، لا يُزال Pod قديم إلا بعد أن يصبح Pod جديد جاهزًا، وهذا لا يحدث أبدًا."},{"text":"انقطاع كامل، لأن الـ ReplicaSet القديم يُحذف أولًا.","why":"هذا وصف لاستراتيجية <code dir=\"ltr\">Recreate</code>. التحديثات المتدرّجة تُبقي الـ ReplicaSet القديم حتى يصبح الجديد جاهزًا."},{"text":"يفشل نحو ربع الطلبات حتى يتدخّل أحد.","why":"قد يحدث هذا مع <code dir=\"ltr\">maxUnavailable</code> أكبر من صفر ودون readiness probe. أما هنا فالـ Pod الفاشل لا يستقبل أي حركة."},{"text":"يتراجع Kubernetes تلقائيًا إلى 1.4.0 بعد انتهاء المهلة.","why":"يعلّم Kubernetes الـ rollout بأنه فاشل بعد <code dir=\"ltr\">progressDeadlineSeconds</code>، لكنه لا يتراجع من تلقاء نفسه."}],"answer":0},{"q":"<p>شغّلت <code dir=\"ltr\">kubectl rollout undo deployment/notes-api</code> أثناء حادث فتعافت الخدمة. ما الذي يجب أن يحدث بعد ذلك؟</p>","options":[{"text":"لا شيء؛ التراجع دائم.","why":"الـ cluster أُصلح، لكن الـ manifest في Git ما زال يسمّي الـ image السيئة، والـ deploy التالي يعيدها."},{"text":"احذف الـ ReplicaSet القديم حتى لا يُستخدم مجددًا.","why":"الـ ReplicaSets القديمة هي ما يجعل التراجع ممكنًا. احتفظ بها."},{"text":"أعِد تشغيل كل الـ Pods لمسح الحالة المخزّنة.","why":"التراجع استبدل الـ Pods أصلًا."},{"text":"غيّر الـ image في Git (أو اعكس الـ commit) حتى لا يعيد <code dir=\"ltr\">kubectl apply</code> التالي نشر الإصدار المعطوب.","why":"صحيح. يجب أن يتّفق Git والـ cluster. أصلح مصدر الحقيقة قبل أن يشغّل أحد الـ deploy التالي."}],"answer":3},{"q":"<p>الإصدار 1.5.0 يعيد تسمية العمود <code dir=\"ltr\">body</code> في قاعدة البيانات إلى <code dir=\"ltr\">content</code> عبر migration. في 1.5.0 خلل فتتراجع إلى 1.4.0. ماذا يحدث؟</p>","options":[{"text":"التراجع يعكس الـ migration تلقائيًا أيضًا.","why":"لا يعرف Kubernetes شيئًا عن قاعدة بياناتك. الـ Pods وحدها تتغيّر."},{"text":"يبدأ 1.4.0 بالفشل، لأنه ما زال يستعلم عن <code dir=\"ltr\">body</code>، الذي لم يعد موجودًا.","why":"صحيح. لهذا يجب أن تكون تغييرات المخطّط على نمط التوسعة ثم التقليص (أضف الجديد، ورحّل، واحذف القديم لاحقًا)، حتى يعمل الإصداران المتجاوران كلاهما."},{"text":"يعيد Postgres تسمية العمود إلى اسمه القديم حين تصل الاستعلامات القديمة.","why":"قواعد البيانات لا تتراجع عن تغييرات المخطّط استجابة للاستعلامات."},{"text":"لا شيء ينكسر، لأن image الإصدار 1.4.0 تتضمّن مخطّطها الخاص.","why":"الـ images تحمل الكود، لا حالة قاعدة البيانات. المخطّط يعيش في Postgres."}],"answer":1},{"q":"<p>تريد رؤية YAML ‏Kubernetes الدقيق الذي سينتجه Helm chart للإنتاج قبل تثبيته. أي أمر تستخدم؟</p>","options":[{"text":"<code dir=\"ltr\">helm history notes-api</code>","why":"هذا يسرد الإصدارات السابقة لـ chart مثبّت، لا الـ manifests المولّدة."},{"text":"<code dir=\"ltr\">helm rollback notes-api 0</code>","why":"هذا يغيّر الـ cluster؛ ولا يعاين أي شيء."},{"text":"<code dir=\"ltr\">helm template notes-api ./charts/notes-api -f values-prod.yaml</code>","why":"صحيح. يولّد القوالب بقيمك محليًا ويطبع الـ YAML، فتستطيع قراءته أو مقارنته."},{"text":"<code dir=\"ltr\">kubectl get all -n notes</code>","why":"هذا يعرض ما يعمل أصلًا، لا ما سينشئه الـ chart."}],"answer":2}],"exercise":{"kind":"fill","language":"bash","template":"# Wait for the 1.5.0 rollout to finish (fails after 5 minutes)\nkubectl rollout {{0}} deployment/notes-api --timeout=5m\n\n# It's failing in production: list revisions, then go back one\nkubectl rollout {{1}} deployment/notes-api\nkubectl rollout {{2}} deployment/notes-api\n\n# On the Helm-managed staging cluster, return to release revision 4\nhelm {{3}} notes-api 4 -n notes","blanks":[{"answers":["status"]},{"answers":["history"]},{"answers":["undo"]},{"answers":["rollback"]}],"title":"راقبه، ثم استرجعه","prompt":"<p>الإصدار 1.5.0 قيد النشر (rollout). املأ الفراغات لانتظار الـ rollout، وسرد revisions الـ Deployment، والتراجع إلى السابقة، وإعادة release ‏staging الذي يديره Helm إلى الـ revision رقم 4.</p>\n","hints":["الأوامر الفرعية الثلاثة لـ <code dir=\"ltr\">kubectl rollout</code> كلمة واحدة لكل منها: واحد ينتظر، وواحد يسرد، وواحد يعكس.","أمر Helm للعودة إلى release سابق يحمل اسم المفهوم نفسه."],"explanation":"<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> status</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --timeout=5m</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> history</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> undo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">helm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollback</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 4</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">status</code> ينتظر حتى ينتهي الـ rollout ويخرج برمز غير صفري إن فشل، وهذا يجعله بوّابة جيدة في الـ CI. و<code dir=\"ltr\">undo</code> يوسّع الـ ReplicaSet السابق من جديد بقواعد التحديث المتدرّج نفسها. و<code dir=\"ltr\">helm rollback</code> يعيد تطبيق كل الـ manifests من release revision رقم 4. بعد ذلك، اعكس التغيير في Git حتى لا يعيد الـ deploy التالي الإصدار 1.5.0.</p>\n","checkLabels":{}},"words":667,"readMinutes":4}