{"id":"l-dk-5-1","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"أضف إلى notes-api فحوص startup وreadiness وliveness تخبر Kubernetes بالحقيقة، وأوقف الـ Pods دون إسقاط الطلبات باستخدام توقّف preStop ومعالج SIGTERM.","html":"<p>لا يستطيع Kubernetes رؤية ما بداخل تطبيقك. إن تُرك وشأنه، يعدّ الـ container سليمًا ما دامت عمليته تعمل، حتى لو كانت تلك العملية عالقة في deadlock، أو ما زالت تسخّن، أو عاجزة عن الوصول إلى قاعدة البيانات. الـ probes (الفحوص) هي طريقة التطبيق لقول الحقيقة عن نفسه. لكن يجب صياغة هذه الحقيقة بعناية، لأن Kubernetes يتصرّف بناءً عليها تلقائيًا وعلى نطاق واسع.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">ثلاثة probes، وثلاث عواقب</h2>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>الـ Probe</th>\n<th>السؤال الذي يجيب عنه</th>\n<th>حين يفشل</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>startup</strong></td>\n<td>هل انتهى التطبيق من البدء؟</td>\n<td>واصل الانتظار حتى الحدّ، ثم أعِد التشغيل</td>\n</tr>\n<tr>\n<td><strong>readiness</strong></td>\n<td>هل يجب أن يستقبل هذا الـ Pod حركة الآن؟</td>\n<td>أزِل الـ Pod من endpoints الـ Service؛ دون إعادة تشغيل</td>\n</tr>\n<tr>\n<td><strong>liveness</strong></td>\n<td>هل العملية عالقة بلا أمل في التعافي؟</td>\n<td>أعِد تشغيل الـ container</td>\n</tr>\n</tbody>\n</table></div><p>ما دام startup probe مضبوطًا ولم ينجح بعد، لا يعمل الاثنان الآخران. وحالما ينجح، لا يعمل مرة أخرى أبدًا لذلك الـ container.</p>\n<p>لدى notes-api أصلًا المساران (endpoints) اللذان يحتاج إليهما هذا. ‏<code dir=\"ltr\">/healthz</code> يُرجع <code dir=\"ltr\">ok</code> إذا كانت حلقة الأحداث (event loop) تستطيع الإجابة عن طلب أصلًا. و<code dir=\"ltr\">/readyz</code> يفحص أيضًا مجمّع اتصالات قاعدة البيانات ويُرجع 503 إذا تعذّر الوصول إلى Postgres.</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:#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\">ghcr.io/skylane/notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          ports</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\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          startupProbe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            httpGet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/healthz</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            periodSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            failureThreshold</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">30</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">      # up to 60 s to start</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          readinessProbe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            httpGet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/readyz</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            periodSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            failureThreshold</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          livenessProbe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            httpGet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/healthz</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            periodSeconds</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\">            timeoutSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            failureThreshold</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span></span></code></pre></div>\n<p>أي حالة HTTP من 200 إلى 399 تُعدّ نجاحًا. وإلى جانب <code dir=\"ltr\">httpGet</code> توجد probes من نوع <code dir=\"ltr\">tcpSocket</code> و<code dir=\"ltr\">grpc</code> و<code dir=\"ltr\">exec</code>؛ فضّل HTTP لخدمات الويب، لأنه يختبر المسار الذي تسلكه الحركة الحقيقية. القيم الافتراضية، إن تركت الحقول، هي <code dir=\"ltr\">periodSeconds: 10</code> و<code dir=\"ltr\">timeoutSeconds: 1</code> و<code dir=\"ltr\">failureThreshold: 3</code>. مهلة الثانية الواحدة تفاجئ التطبيقات البطيئة، فاضبطها عن قصد.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ Readiness مفتاح للحركة</h2>\n<p>الـ readiness هو الـ probe الأهمّ في العمل اليومي. الـ Pod الذي يبدأ، أو المُثقل، أو المقطوع عن قاعدة بياناته يُبلغ بأنه غير جاهز ويخرج من endpoints الـ Service حتى يتعافى. وهو أيضًا <strong>بوّابة للـ rollouts</strong>: لا يُحسب الـ Pod الجديد متاحًا إلا حين يصبح جاهزًا، ولا يزيل التحديث المتدرّج الـ Pods القديمة إلا بقدر ما تصبح الجديدة متاحة. الإصدار الذي لا يصل إلى قاعدة البيانات لا يأخذ الحركة أبدًا من إصدار يصل إليها. لهذا فإن الـ Deployment الذي لا readiness probe فيه هو أول ما أنبّه عليه في كل مراجعة.</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>‏liveness probe يفحص الاعتماديات</span></p>\n<p>توجيه الـ liveness إلى <code dir=\"ltr\">/readyz</code> يبدو دقيقًا وشاملًا. ثم يُعاد تشغيل Postgres لمدة 20 ثانية، فتفشل كل containers ‏notes-api في الـ liveness في اللحظة نفسها، ويعيد Kubernetes تشغيلها كلها. وحين تعود، تعيد الاتصال كلها دفعة واحدة. صار عطل عابر في قاعدة البيانات مدّته 20 ثانية انقطاعًا مدّته خمس دقائق. الـ liveness يسأل سؤالًا ضيّقًا واحدًا: هل هذه العملية عالقة؟ وكل ما يخصّ الأنظمة الأخرى مكانه الـ readiness.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">البدء دون حلقات إعادة تشغيل</h2>\n<p>قبل وجود الـ startup probes، كانت التطبيقات بطيئة البدء تُحمى بـ <code dir=\"ltr\">initialDelaySeconds</code> على الـ liveness. هذا يؤخّر الفحص الأول في <em>كل</em> بدء بمقدار أسوأ زمن بدء، وينكسر مجددًا حين يصبح البدء أبطأ. أما الـ startup probe فيعطي التطبيق <code dir=\"ltr\">periodSeconds × failureThreshold</code> (هنا 2 × 30 = 60 ثانية) لينهض، مع فحص متكرّر حتى يُكتشف البدء السريع بسرعة، ثم يسلّم الأمر إلى الـ liveness بوتيرته العادية.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الإيقاف دون إسقاط الطلبات</h2>\n<p>حين يُحذف Pod، أثناء rollout أو تقليص للنسخ أو تفريغ لـ node، يبدأ شيئان <strong>في الوقت نفسه</strong>: يُزال الـ Pod من endpoints الـ Service، ويبدأ الـ kubelet في إنهاء الـ container. إزالة الـ endpoints يجب أن تنتشر إلى قواعد الشبكة في كل node وإلى أي موازن أحمال، وهذا يستغرق لحظة. إذا توقّف تطبيقك عن قبول الاتصالات لحظة وصول SIGTERM، فالطلبات الموجّهة إليه في تلك اللحظة تُرفض.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-dk-5-1-t1\"><defs><marker id=\"arrow-l-dk-5-1-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-1-t1\">عند حذف الـ Pod، تنتشر إزالة الـ endpoints خلال بضع ثوانٍ بينما يعمل توقّف preStop. ثم تُرسل SIGTERM فيفرّغ التطبيق الطلبات الجارية ويخرج، وكل ذلك خلال مهلة السماح البالغة 30 ثانية، وبعدها كانت ستُرسل SIGKILL.</title>\n  <path class=\"d-line\" d=\"M40 40 L40 210\"/>\n  <text class=\"d-label-strong\" x=\"46\" y=\"30\">حذف الـ Pod</text>\n  <rect class=\"d-box-warn\" x=\"40\" y=\"60\" width=\"200\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"140\" y=\"85\" text-anchor=\"middle\">إزالة الـ endpoints</text>\n  <rect class=\"d-box-accent\" x=\"40\" y=\"120\" width=\"160\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"120\" y=\"145\" text-anchor=\"middle\">preStop sleep 5s</text>\n  <path class=\"d-arrow\" d=\"M200 140 L236 140\" marker-end=\"url(#arrow-l-dk-5-1-1)\"/>\n  <rect class=\"d-box-success\" x=\"240\" y=\"120\" width=\"230\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"355\" y=\"145\" text-anchor=\"middle\">SIGTERM: تفريغ، ثم exit 0</text>\n  <path class=\"d-line d-dashed\" d=\"M40 190 L650 190\"/>\n  <path class=\"d-line\" d=\"M650 110 L650 200\"/>\n  <text class=\"d-label-muted\" x=\"345\" y=\"215\" text-anchor=\"middle\">terminationGracePeriodSeconds: 30</text>\n  <text class=\"d-label-strong\" x=\"650\" y=\"100\" text-anchor=\"middle\">SIGKILL</text>\n</svg><figcaption>إزالة الـ endpoints والإنهاء يبدآن معًا؛ وتوقّف preStop يغطّي الفجوة</figcaption></figure>\n<p>للحل نصفان. في مواصفات الـ Pod، توقّف قبل SIGTERM حتى يلحق التوجيه بالتغيير:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"1\"><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\">      terminationGracePeriodSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">30</span></span>\n<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\">          lifecycle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            preStop</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              sleep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                seconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span></span></code></pre></div>\n<p>الإجراء <code dir=\"ltr\">sleep</code> يعمل داخل الـ kubelet، فيعمل حتى في image من نوع distroless لا تحتوي على ملف <code dir=\"ltr\">sleep</code> التنفيذي. وفي التطبيق، أبقِ معالج SIGTERM من الدرس 1.4: توقّف عن قبول اتصالات جديدة، وأنهِ الطلبات الجارية، وأغلق مجمّع اتصالات قاعدة البيانات، ثم اخرج. يجب أن يتّسع توقّف preStop والتفريغ معًا داخل <code dir=\"ltr\">terminationGracePeriodSeconds</code>، وإلا فسيرسل الـ kubelet إشارة SIGKILL عند انتهاء المهلة.</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>اختبره ولا تكتفِ بالثقة</span></p>\n<p>شغّل مولّد حمل على الـ Service، وأطلِق <code dir=\"ltr\">kubectl rollout restart deployment/notes-api</code>. عُدّ الأخطاء. الوصول إلى صفر أخطاء ممكن، وحالما تراه ستلاحظ فورًا حين يكسره تغيير ما.</p>\n</aside>\n<p>يعرف Kubernetes الآن متى يكون notes-api سليمًا وكيف يوقفه بأمان. التالي: يحتاج إلى معرفة كم يستهلك notes-api من المعالج والذاكرة، وكيف يضيف Pods حين تنمو الحركة.</p>\n","toc":[{"id":"sec-1","text":"ثلاثة probes، وثلاث عواقب","level":2},{"id":"sec-2","text":"الـ Readiness مفتاح للحركة","level":2},{"id":"sec-3","text":"البدء دون حلقات إعادة تشغيل","level":2},{"id":"sec-4","text":"الإيقاف دون إسقاط الطلبات","level":2}],"takeaways":["فشل الـ readiness probe يزيل الـ Pod من endpoints الـ Service؛ وفشل الـ liveness probe يعيد تشغيل الـ container؛ والـ startup probe يؤجّل الاثنين حتى يكتمل بدء التطبيق.","الـ liveness يفحص فقط هل العملية نفسها عالقة؛ لا تجعله أبدًا يعتمد على قاعدة بيانات أو خدمة أخرى.","الـ readiness بوّابة للتحديثات المتدرّجة، فالإصدار الجديد العاجز عن الخدمة لا يحلّ أبدًا محلّ إصدار يعمل.","عند الحذف، تحدث إزالة الـ endpoints وإرسال SIGTERM في الوقت نفسه؛ وتوقّف <code dir=\"ltr\">preStop</code> قصير يتيح للحركة أن تنصرف عن الـ Pod قبل أن يبدأ التطبيق بالإيقاف.","يجب أن ينهي التطبيق العمل الجاري خلال <code dir=\"ltr\">terminationGracePeriodSeconds</code> ‏(30 افتراضيًا)، وإلا فسيُقتل."],"further":[{"title":"Liveness, Readiness, and Startup Probes","url":"https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/"},{"title":"Configure Liveness, Readiness and Startup Probes","url":"https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/"},{"title":"Pod Lifecycle (termination of Pods)","url":"https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination"}],"quiz":[{"q":"<p>يبدأ الـ readiness probe لـ notes-api بالفشل على أحد الـ Pods لأنه لا يصل إلى Postgres. ماذا يفعل Kubernetes؟</p>","options":[{"text":"يعيد تشغيل الـ container حتى ينجح الفحص.","why":"إعادات التشغيل تأتي من إخفاقات الـ liveness. الـ readiness يؤثّر في الحركة فقط."},{"text":"يحذف الـ Pod ويُجدوِل واحدًا جديدًا على node آخر.","why":"الـ probes لا تحذف الـ Pods أبدًا. يبقى الـ Pod في مكانه."},{"text":"لا شيء حتى يفشل الـ liveness probe أيضًا.","why":"الـ readiness يتصرّف وحده، باستقلال عن الـ liveness."},{"text":"يزيل الـ Pod من endpoints الـ Service، فلا يحصل على حركة جديدة حتى ينجح الفحص مجددًا.","why":"صحيح. يستمر الـ Pod في العمل ويعود إلى التناوب حالما يصبح جاهزًا، دون إعادة تشغيل."}],"answer":3},{"q":"<p>الـ liveness probe لديك يستدعي <code dir=\"ltr\">/readyz</code>، الذي يفحص قاعدة البيانات. يُعاد تشغيل Postgres لمدة 20 ثانية. ماذا يحدث؟</p>","options":[{"text":"تفشل كل containers ‏notes-api في فحص الـ liveness وتُعاد تشغيلها دفعة واحدة، فيتحوّل عطل عابر في قاعدة البيانات إلى انقطاع كامل.","why":"صحيح. يجب أن يسأل الـ liveness فقط «هل هذه العملية عالقة؟». فحوص الاعتماديات مكانها الـ readiness."},{"text":"تُعلَّم الـ Pods بأنها غير جاهزة وتتعافى حين يعود Postgres.","why":"هذا صحيح لو كان readiness probe. أما إخفاقات الـ liveness فتسبّب إعادات تشغيل."},{"text":"يكتشف Kubernetes إعادة تشغيل قاعدة البيانات ويوقف الـ probes مؤقتًا.","why":"لا يعرف Kubernetes شيئًا عن اعتماديات تطبيقك؛ إنه يشغّل الـ probe كما ضُبط."},{"text":"يُعاد تشغيل Pod واحد فقط، لأن إخفاقات الـ liveness محدودة المعدّل.","why":"لا يوجد حدّ معدّل كهذا على مستوى الـ cluster. الـ probe في كل Pod يفشل باستقلال، وكلها في الوقت نفسه."}],"answer":0},{"q":"<p>يستغرق notes-api حتى 60 ثانية ليبدأ على قاعدة بيانات كبيرة لأنه يسخّن cache. أي إعداد للـ probes يتجنّب حلقة إعادة التشغيل دون إبطاء اكتشاف الأعطال لاحقًا؟</p>","options":[{"text":"‏<code dir=\"ltr\">initialDelaySeconds: 60</code> على الـ liveness probe.","why":"يعمل، لكن كل بدء ينتظر الآن 60 ثانية قبل أن يعمل الـ liveness أصلًا، وينكسر مجددًا حين يستغرق البدء 65 ثانية."},{"text":"احذف الـ liveness probe.","why":"عندها لن يُعاد تشغيل العملية العالقة في deadlock أبدًا."},{"text":"startup probe مع <code dir=\"ltr\">periodSeconds: 5</code> و<code dir=\"ltr\">failureThreshold: 15</code>، فيحصل التطبيق على ما يصل إلى 75 ثانية للبدء قبل أن يتولّى الـ liveness.","why":"صحيح. يُؤجَّل الـ liveness والـ readiness حتى ينجح الـ startup probe مرة واحدة؛ وبعدها يعمل الـ liveness بوتيرته العادية."},{"text":"‏<code dir=\"ltr\">timeoutSeconds: 60</code> على الـ liveness probe.","why":"هذا يسمح لفحص واحد بالتعليق لمدة دقيقة، فيؤخّر اكتشاف حالات التعليق الحقيقية ولا يحلّ مشكلة البدء."}],"answer":2},{"q":"<p>خلال كل rollout تفشل بضعة طلبات إلى notes-api بـ <code dir=\"ltr\">connection refused</code>، مع أن التطبيق يعالج SIGTERM. ما الحل المعتاد؟</p>","options":[{"text":"ارفع <code dir=\"ltr\">terminationGracePeriodSeconds</code> إلى 300.","why":"التطبيق يتوقّف أصلًا خلال مهلة السماح. الأخطاء تحدث في بداية الإنهاء، لا في نهايته."},{"text":"أضف توقّف <code dir=\"ltr\">preStop</code> لبضع ثوانٍ حتى تنتشر إزالة الـ endpoints قبل أن يتوقّف التطبيق عن قبول الاتصالات.","why":"صحيح. ‏SIGTERM وإزالة الـ endpoints يحدثان بالتوازي؛ والتوقّف القصير يتيح للـ proxies أن تتوقّف عن توجيه الحركة إلى الـ Pod أولًا."},{"text":"اضبط <code dir=\"ltr\">replicas: 1</code> أثناء الـ rollouts.","why":"نسخ أقل تجعل أي اتصال ساقط أكثر إيلامًا، لا أقل."},{"text":"عطّل الـ readiness probe أثناء الـ rollouts.","why":"الـ readiness هو ما يُبقي الـ Pods غير الجاهزة خارج التناوب؛ وتعطيله يجعل الـ rollouts أسوأ."}],"answer":1}],"exercise":{"kind":"choice","multiple":false,"options":["startup","initial-delay","no-liveness","liveness-readyz"],"correct":["startup"],"title":"حلقة إعادة التشغيل مع قواعد البيانات الكبيرة","prompt":"<p>بعد أن كبرت قاعدة بيانات أحد العملاء، صار تسخين الـ cache عند بدء notes-api يستغرق حتى 90 ثانية. الـ liveness probe على <code dir=\"ltr\">/healthz</code> يستخدم القيم الافتراضية (<code dir=\"ltr\">periodSeconds: 10</code> و<code dir=\"ltr\">failureThreshold: 3</code>)، فتُقتل الـ Pods عند نحو 30 ثانية ولا تنتهي من البدء أبدًا. معالج <code dir=\"ltr\">/healthz</code> لا يُسجَّل إلا بعد اكتمال التسخين. أي تغيير هو الحل الأفضل؟</p>\n","hints":["تريد صبرًا سخيًّا أثناء البدء واكتشافًا سريعًا للتعليق بعده. أي probe يفصل بين هاتين المرحلتين؟","احسب: كم يجب أن يكون <code dir=\"ltr\">periodSeconds × failureThreshold</code>؟"],"explanation":"<p>الـ startup probe يؤجّل الـ liveness والـ readiness حتى ينجح مرة واحدة. مع <code dir=\"ltr\">periodSeconds: 5</code> و<code dir=\"ltr\">failureThreshold: 24</code> يحصل notes-api على ما يصل إلى 120 ثانية للتسخين، ويُكتشف أنه بدأ خلال 5 ثوانٍ من انتهائه، ومن بعدها يعمل الـ liveness بوتيرته العادية كل 10 ثوانٍ:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">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\">startupProbe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  httpGet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/healthz</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  periodSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  failureThreshold</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">24</span></span></code></pre></div>\n<p>أما <code dir=\"ltr\">initialDelaySeconds</code> فسيعمل اليوم، لكنه يؤخّر كل بدء بمقدار أسوأ حالة وينكسر مجددًا حين يصل التسخين إلى 95 ثانية.</p>\n","checkLabels":{},"optionText":{"startup":{"text":"أضف <code dir=\"ltr\">startupProbe</code> على <code dir=\"ltr\">/healthz</code> مع <code dir=\"ltr\">periodSeconds: 5</code> و<code dir=\"ltr\">failureThreshold: 24</code>.","why":"صحيح. ما يصل إلى 120 ثانية للبدء، مع فحص كل 5 ثوانٍ، ثم يتولّى الـ liveness دون تغيير."},"initial-delay":{"text":"اضبط <code dir=\"ltr\">initialDelaySeconds: 90</code> على الـ liveness probe.","why":"يؤخّر الـ liveness بـ 90 ثانية في كل بدء، حتى السريع منها، ويفشل مجددًا حالما يتجاوز التسخين 90 ثانية."},"no-liveness":{"text":"احذف الـ liveness probe؛ الـ readiness يكفي.","why":"الـ readiness لا يعيد تشغيل أي شيء أبدًا، فستبقى العملية العالقة في deadlock مكانها إلى الأبد، خارج التناوب لكن دون إصلاح."},"liveness-readyz":{"text":"وجّه الـ liveness probe إلى <code dir=\"ltr\">/readyz</code> بدلًا من ذلك.","why":"هذا لا يضيف وقتًا للبدء، ويربط إعادات التشغيل بصحة قاعدة البيانات، فيحوّل الأعطال العابرة في قاعدة البيانات إلى انقطاعات."}}},"words":683,"readMinutes":4}