{"id":"l-dk-5-2","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"اضبط requests وlimits للمعالج والذاكرة في notes-api انطلاقًا من القياسات، وتوقّع الخنق (throttling) والقتل بسبب نفاد الذاكرة وفئات QoS، ووسّع عدد النسخ تلقائيًا بـ HorizontalPodAutoscaler من autoscaling/v2.","html":"<p>في المرة الأولى التي واجه فيها notes-api حركة حقيقية على cluster مشترك، حدث أمران خلال ساعة. مهمّة دفعية (batch job) على الـ node نفسه استهلكت كل الذاكرة، فطُرد أحد Pods ‏notes-api. ثم رفعت قفزة في الحركة استهلاك المعالج إلى أقصاه، وصار زمن الاستجابة بالثواني، وبقي عدد الـ Pods ثلاثة بالضبط لأن أحدًا لم يخبر Kubernetes بأنه يستطيع إضافة المزيد. الإخفاقان جاءا من الفجوة نفسها: لم يكن Kubernetes يعرف ما يحتاج إليه notes-api.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ Requests والـ Limits</h2>\n<p>يستطيع كل container التصريح برقمين لكل مورد:</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\">          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>\n<span class=\"line\"><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>\n<span class=\"line\"><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>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            limits</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><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></code></pre></div>\n<ul>\n<li>الـ <strong>request</strong> (الطلب) هو ما يحجزه الـ scheduler. لا يوضع الـ Pod إلا على node لديه سعة غير محجوزة كافية لكل الـ requests الخاصة به. و<code dir=\"ltr\">250m</code> تعني 250 millicores، أي ربع نواة معالج.</li>\n<li>الـ <strong>limit</strong> (الحدّ) هو السقف الذي تفرضه النواة عبر الـ cgroups، الآلية التي تعرّفت إليها في الدرس 1.1.</li>\n</ul>\n<p>يتصرّف الموردان بشكل مختلف جدًا حين يبلغ الـ container حدّه:</p>\n<ul>\n<li><strong>المعالج قابل للضغط.</strong> فوق حدّ المعالج يُخنق الـ container: يحصل على وقت معالج أقل ويصبح أبطأ، لكنه يستمر في العمل.</li>\n<li><strong>الذاكرة ليست كذلك.</strong> فوق حدّ الذاكرة، ينهي الـ OOM killer في النواة العملية. يعرض <code dir=\"ltr\">kubectl describe pod</code> السبب <code dir=\"ltr\">OOMKilled</code> ورمز الخروج 137، ويُعاد تشغيل الـ container.</li>\n</ul>\n<p>إذا ضبطت limit دون request، ينسخ Kubernetes الـ limit إلى الـ request. أما الـ request الأكبر من الـ limit فيُرفض مباشرة.</p>\n<aside class=\"callout callout-why\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>لماذا لا يوجد حدّ للمعالج في notes-api</span></p>\n<p>هذا أمر خلافي، فإليك منطقي. حدّ المعالج يخنق التطبيق حتى حين تكون لدى الـ node أنوية خاملة، وهذا يظهر كقفزات في زمن الاستجابة لا يستطيع أحد تفسيرها. مع request معقول للمعالج، يضمن الـ scheduler أصلًا لـ notes-api حصّته عند التزاحم، لذا نترك حدّ المعالج ونضبط حدًّا للذاكرة، لأن نفاد الذاكرة يؤذي الـ node كله. بعض المؤسسات تشترط حدود المعالج كسياسة؛ وإن كانت مؤسستك منها، فاضبطها بسخاء وراقب مقاييس الخنق.</p>\n</aside>\n<p>في Node.js، أعطِ V8 سقفًا للـ heap تحت حدّ ذاكرة الـ container، مثلًا <code dir=\"ltr\">NODE_OPTIONS=--max-old-space-size=384</code> مع حدّ 512Mi. عندها يعمل جامع النفايات بجهد كلما امتلأت الذاكرة، بدلًا من أن تنهي النواة العملية دون إنذار.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">اختيار الأرقام</h2>\n<p>لا تخمّن. شغّل notes-api تحت حمل واقعي وقِس:</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\"> top</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> pods</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> app.kubernetes.io/name=notes-api</span></span></code></pre></div>\n<p>يحتاج <code dir=\"ltr\">kubectl top</code> إلى metrics-server، الذي تتضمّنه كثير من الـ clusters المُدارة أو تقدّمه كإضافة (add-on)؛ وفي kind تثبّته بنفسك. اضبط request المعالج قرب الاستهلاك المعتاد تحت الحمل العادي، واضبط request الذاكرة وحدّها مع هامش فوق الذروة التي لاحظتها. راجع الأرقام بعد الإصدارات الكبيرة. الـ requests المرتفعة جدًا تهدر المال على حجوزات خاملة؛ والمنخفضة جدًا تجعل الـ Pods تُحشر على nodes لا تستطيع حملها فعلًا.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">فئات QoS والطرد</h2>\n<p>من الـ requests والـ limits، يشتقّ Kubernetes <strong>فئة جودة الخدمة</strong> (Quality of Service class) لكل Pod:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>الفئة</th>\n<th>القاعدة</th>\n<th>تُطرد عند ضغط ذاكرة الـ node</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Guaranteed</td>\n<td>لكل container ‏requests للمعالج والذاكرة مساوية للـ limits</td>\n<td>أخيرًا</td>\n</tr>\n<tr>\n<td>Burstable</td>\n<td>request أو limit واحد على الأقل مضبوط، لكنها ليست Guaranteed</td>\n<td>بعد BestEffort، بدءًا بالـ Pods الأكثر تجاوزًا للـ requests الخاصة بها</td>\n</tr>\n<tr>\n<td>BestEffort</td>\n<td>لا requests ولا limits في أي مكان</td>\n<td>أولًا</td>\n</tr>\n</tbody>\n</table></div><p>‏notes-api في الأعلى من فئة Burstable. والمهمّة الدفعية التي سبّبت الطرد كانت BestEffort، فمع وجود الـ requests كانت ستُطرد قبل notes-api. تشغيل أحمال الإنتاج كـ BestEffort يعني أنك تتطوّع بها لتكون أول ما يُطرد.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">التوسّع الأفقي التلقائي للـ Pods</h2>\n<p>الـ HorizontalPodAutoscaler ‏(HPA) يعدّل عدد نسخ الـ Deployment بناءً على المقاييس. إنه حلقة تحكّم أخرى: كل 15 ثانية تقريبًا يقارن الاستهلاك الملحوظ بالهدف ويحسب عدد النسخ الذي سيعيد الاستهلاك إلى الهدف.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">k8s/hpa.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\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">autoscaling/v2</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">HorizontalPodAutoscaler</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  namespace</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<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\">  scaleTargetRef</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">apps/v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Deployment</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  minReplicas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  maxReplicas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  metrics</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\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Resource</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      resource</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">cpu</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        target</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\">Utilization</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          averageUtilization</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">70</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  behavior</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    scaleDown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      stabilizationWindowSeconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">300</span></span></code></pre></div>\n<p>القيمة <code dir=\"ltr\">averageUtilization: 70</code> تعني «أبقِ متوسط استهلاك المعالج عند 70% من <strong>request</strong> المعالج». مع request قدره 250m، هذا نحو 175m لكل Pod. عند ستة Pods بمتوسط 350m، يحسب الـ HPA ‏6 × 350 / 175 = 12 فيوسّع إلى الحدّ الأقصى. والكتلة <code dir=\"ltr\">behavior</code> تجعل التقليص ينتظر خمس دقائق من الحمل المنخفض باستمرار، حتى لا يُزيل هدوءٌ عابر سعةً ستحتاج إليها بعد دقيقة. والدقائق الخمس هي القيمة الافتراضية أصلًا؛ لكن كتابتها صراحةً تجعل القرار ظاهرًا وسهل الضبط.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-dk-5-2-t1\"><defs><marker id=\"arrow-l-dk-5-2-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-2-t1\">يجمع metrics-server استهلاك المعالج من الـ Pods. يقرؤه الـ HPA، ويقارنه بـ 70 بالمئة من الـ requests، ويحدّث عدد نسخ الـ Deployment، فيتغيّر عدد الـ Pods.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"80\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"95\" y=\"115\" text-anchor=\"middle\">metrics-server</text>\n  <rect class=\"d-box-primary\" x=\"240\" y=\"80\" width=\"200\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"340\" y=\"106\" text-anchor=\"middle\">HPA</text>\n  <text class=\"d-code\" x=\"340\" y=\"128\" text-anchor=\"middle\">الهدف 70% من الـ request</text>\n  <rect class=\"d-box-accent\" x=\"510\" y=\"80\" width=\"170\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"595\" y=\"106\" text-anchor=\"middle\">Deployment</text>\n  <text class=\"d-code\" x=\"595\" y=\"128\" text-anchor=\"middle\">replicas: 3 → 6</text>\n  <path class=\"d-arrow\" d=\"M170 110 L238 110\" marker-end=\"url(#arrow-l-dk-5-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M440 110 L508 110\" marker-end=\"url(#arrow-l-dk-5-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"300\" y=\"170\" width=\"250\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"425\" y=\"195\" text-anchor=\"middle\">Pods ‏notes-api</text>\n  <path class=\"d-arrow\" d=\"M595 140 L520 168\" marker-end=\"url(#arrow-l-dk-5-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M300 190 L95 142\" marker-end=\"url(#arrow-l-dk-5-2-1)\"/>\n  <text class=\"d-label-muted\" x=\"170\" y=\"185\" text-anchor=\"middle\">استهلاك المعالج</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"60\" text-anchor=\"middle\">كل ~15 ثانية</text>\n</svg><figcaption>الـ HPA حلقة تحكّم تعمل على عدد نسخ الـ Deployment</figcaption></figure>\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>حالما يدير HPA ‏notes-api، احذف <code dir=\"ltr\">replicas:</code> من <code dir=\"ltr\">k8s/deployment.yaml</code>. وإلا فكل <code dir=\"ltr\">kubectl apply</code> يعيد العدد إلى قيمة الملف، ربما 3 في منتصف ذروة حركة عند 9، ويضطر الـ HPA إلى التسلّق من جديد بينما ينتظر المستخدمون. حقل واحد، مالك واحد.</p>\n</aside>\n<p>راقبه وهو يعمل بـ <code dir=\"ltr\">kubectl get hpa notes-api --watch</code> أثناء تشغيل اختبار حمل. إذا أظهر عمود الأهداف <code dir=\"ltr\">&lt;unknown&gt;</code>، فإمّا أن الـ containers ليس لها request للمعالج، أو أن metrics-server لا يعمل.</p>\n<p>المعالج يناسب notes-api لأن عمله معالجة طلبات مرتبطة بالمعالج. أما الخدمات التي يظهر حملها في مكان آخر، مثل عمق الطابور أو عدد الطلبات في الثانية، فتستطيع التوسّع على مقاييس مخصّصة أو خارجية عبر قائمة <code dir=\"ltr\">metrics</code> نفسها، مع adapter مثبّت في الـ cluster.</p>\n<p>لدى notes-api الآن الموارد التي يحتاج إليها، وينمو مع الحمل. الدرس التالي يغيّر الـ image بأمان: التحديثات المتدرّجة، والتراجع، وتغليف كل شيء بـ Helm.</p>\n","toc":[{"id":"sec-1","text":"الـ Requests والـ Limits","level":2},{"id":"sec-2","text":"اختيار الأرقام","level":2},{"id":"sec-3","text":"فئات QoS والطرد","level":2},{"id":"sec-4","text":"التوسّع الأفقي التلقائي للـ Pods","level":2}],"takeaways":["الـ requests هي ما يحجزه الـ scheduler للـ container؛ والـ limits هي السقف المفروض وقت التشغيل.","تجاوز حدّ المعالج يخنق الـ container؛ وتجاوز حدّ الذاكرة يجعله يُقتل بـ OOM مع رمز الخروج 137.","فئة QoS تُشتقّ من الـ requests والـ limits: ‏Guaranteed (متساوية لكل container)، وBurstable (بعضها مضبوط)، وBestEffort (لا شيء)، والـ Pods من فئة BestEffort تُطرد أولًا.","هدف استخدام المعالج في الـ HPA نسبة مئوية من <em>request</em> المعالج، فلا يعمل على containers ليس لها request.","حين يدير HPA كائن Deployment، احذف <code dir=\"ltr\">replicas</code> من manifest الـ Deployment حتى لا يتصارع <code dir=\"ltr\">kubectl apply</code> مع الـ autoscaler."],"further":[{"title":"Resource Management for Pods and Containers","url":"https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"},{"title":"Pod Quality of Service Classes","url":"https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/"},{"title":"Horizontal Pod Autoscaling","url":"https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/"}],"quiz":[{"q":"<p>‏Pod من notes-api يُعاد تشغيله عدّة مرات في اليوم. ويعرض <code dir=\"ltr\">kubectl describe pod</code> السبب <code dir=\"ltr\">Reason: OOMKilled</code> و<code dir=\"ltr\">Exit Code: 137</code>. ماذا حدث؟</p>","options":[{"text":"كان حدّ المعالج منخفضًا جدًا، فقتلت النواة الـ container.","why":"تجاوز حدّ المعالج يؤدّي إلى الخنق (الإبطاء)، لا إلى القتل أبدًا."},{"text":"فشل الـ liveness probe فأعاد Kubernetes تشغيل الـ container.","why":"إعادة التشغيل بسبب الـ liveness تُظهر فشل probe في الأحداث، لا <code dir=\"ltr\">OOMKilled</code> كسبب."},{"text":"استهلك الـ container ذاكرة أكثر من حدّه، فأنهاه الـ OOM killer في النواة.","why":"صحيح. ارفع حدّ الذاكرة ليناسب الاستهلاك الحقيقي، أو ابحث عن التسرّب؛ وفي Node.js ضع أيضًا سقفًا للـ heap في V8 تحت الحدّ."},{"text":"نفدت مساحة القرص المخصّصة للسجلات على الـ node.","why":"ضغط القرص يؤدّي إلى الطرد (eviction) بسبب مختلف، لا إلى قتل بسبب نفاد الذاكرة."}],"answer":2},{"q":"<p>يستهدف HPA استخدامًا للمعالج بنسبة 70%، ويعرض <code dir=\"ltr\">kubectl get hpa</code> القيمة <code dir=\"ltr\">cpu: &lt;unknown&gt;/70%</code>. ما السبب الأرجح؟</p>","options":[{"text":"الـ HPA يستخدم <code dir=\"ltr\">autoscaling/v2</code>، الذي لا يدعم المعالج.","why":"‏autoscaling/v2 يدعم المعالج والذاكرة والمقاييس المخصّصة والخارجية."},{"text":"containers ‏notes-api ليس لها request للمعالج، فلا يمكن حساب نسبة الاستخدام (أو أن metrics-server غير مثبّت).","why":"صحيح. نسبة الاستخدام هي الاستهلاك مقسومًا على الـ request. لا request، لا نسبة. وتحقّق أيضًا من أن metrics-server يعمل."},{"text":"‏<code dir=\"ltr\">minReplicas</code> أعلى من عدد النسخ الحالي.","why":"هذا يجعل الـ HPA يوسّع حتى الحدّ الأدنى؛ ولا يجعل المقاييس مجهولة."},{"text":"في الـ Deployment نسخ أكثر من أن يديرها الـ HPA.","why":"لا يوجد حدّ كهذا؛ يستطيع الـ HPA إدارة أي عدد ضمن حدوده."}],"answer":1},{"q":"<p>يضبط notes-api القيم <code dir=\"ltr\">requests: {cpu: 250m, memory: 256Mi}</code> و<code dir=\"ltr\">limits: {memory: 512Mi}</code>. ما فئة QoS التي يحصل عليها الـ Pod؟</p>","options":[{"text":"Guaranteed","why":"فئة Guaranteed تحتاج إلى requests مساوية للـ limits في المعالج والذاكرة معًا، في كل container."},{"text":"BestEffort","why":"فئة BestEffort تعني عدم وجود أي requests أو limits. وهذا الـ Pod فيه requests."},{"text":"Critical","why":"هذه ليست فئة QoS. الفئات الثلاث هي Guaranteed وBurstable وBestEffort."},{"text":"Burstable","why":"صحيح. له requests، لكنها لا تساوي الـ limits في كل مورد، فيستطيع تجاوز الـ requests الخاصة به مؤقتًا."}],"answer":3},{"q":"<p>الـ manifest الخاص بـ Deployment ‏notes-api يقول <code dir=\"ltr\">replicas: 3</code>، ووسّعه HPA إلى 9 أثناء ذروة حركة. ماذا يحدث حين يشغّل الـ CI الأمر <code dir=\"ltr\">kubectl apply -f k8s/</code>؟</p>","options":[{"text":"يهبط الـ Deployment إلى 3 نسخ حتى يوسّعه الـ HPA من جديد، فتنخفض السعة في منتصف الذروة.","why":"صحيح. ‏apply يضبط الحقل من الملف. احذف <code dir=\"ltr\">replicas</code> من الـ manifest حالما يتولّاه HPA."},{"text":"لا شيء؛ الـ HPA يقفل حقل النسخ.","why":"الـ HPA يكتب في الحقل، لكنه لا يقفله. أي شيء آخر يكتب فيه يفوز حتى قرار الـ HPA التالي."},{"text":"يفشل apply بخطأ تعارض.","why":"مع الـ apply العادي من جهة العميل لا يوجد خطأ؛ إنه يكتب فوق القيمة بصمت، وهذا ما يجعل الأمر خطيرًا."},{"text":"يتغيّر <code dir=\"ltr\">minReplicas</code> في الـ HPA إلى 3.","why":"تطبيق Deployment لا يعدّل كائن الـ HPA أبدًا."}],"answer":0}],"exercise":{"kind":"spot-bug","language":"yaml","code":"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: notes-api\nspec:\n  replicas: 3\n  # …selector and template labels as before\n  template:\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io/skylane/notes-api:1.4.0\n          resources:\n            limits:\n              memory: 512Mi\n---\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: notes-api\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: notes-api\n  minReplicas: 3\n  maxReplicas: 12\n  metrics:\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: 70","bugLines":[6,14],"title":"الـ autoscaler الذي لا يوسّع أبدًا","prompt":"<p>يعرض <code dir=\"ltr\">kubectl get hpa notes-api</code> القيمة <code dir=\"ltr\">cpu: &lt;unknown&gt;/70%</code> مع أن metrics-server يعمل، وفي الأيام المزدحمة يهبط عدد النسخ إلى 3 في كل مرة ينشر فيها الـ CI. اختر السطرين <strong>الاثنين</strong> المسؤولين.</p>\n","hints":["استخدام المعالج يُقاس كنسبة مئوية من شيء ما. هل هذا الشيء معرَّف لهذا الـ container؟","أي حقل يكتب فيه كلٌّ من الـ HPA وكل <code dir=\"ltr\">kubectl apply</code>؟"],"explanation":"<p>السطر 14: الـ container يضبط حدًّا للذاكرة فقط، فلا يوجد request للمعالج، ولا يستطيع الـ HPA حساب الاستخدام كنسبة منه. أضف الـ requests:</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\">          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>\n<span class=\"line\"><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>\n<span class=\"line\"><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>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            limits</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><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></code></pre></div>\n<p>السطر 6: وجود <code dir=\"ltr\">replicas: 3</code> في الـ manifest يعني أن كل <code dir=\"ltr\">kubectl apply</code> يعيد ضبط العدد، متصارعًا مع الـ HPA. احذف الحقل حالما يدير HPA الـ Deployment؛ فالحدّ الأدنى صار في <code dir=\"ltr\">minReplicas</code>.</p>\n","checkLabels":{}},"words":767,"readMinutes":5}