EduFlow

الوحدة 5 · التشغيل في الإنتاج

الـ Requests والـ Limits والتوسّع التلقائي

22 دقراءة 5 داكتشف الخطأ

الدرس في لمحة

اضبط requests وlimits للمعالج والذاكرة في notes-api انطلاقًا من القياسات، وتوقّع الخنق (throttling) والقتل بسبب نفاد الذاكرة وفئات QoS، ووسّع عدد النسخ تلقائيًا بـ HorizontalPodAutoscaler من autoscaling/v2.

  1. 1الـ Requests والـ Limits
  2. 2اختيار الأرقام
  3. 3فئات QoS والطرد
  4. 4التوسّع الأفقي التلقائي للـ Pods

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

في المرة الأولى التي واجه فيها notes-api حركة حقيقية على cluster مشترك، حدث أمران خلال ساعة. مهمّة دفعية (batch job) على الـ node نفسه استهلكت كل الذاكرة، فطُرد أحد Pods ‏notes-api. ثم رفعت قفزة في الحركة استهلاك المعالج إلى أقصاه، وصار زمن الاستجابة بالثواني، وبقي عدد الـ Pods ثلاثة بالضبط لأن أحدًا لم يخبر Kubernetes بأنه يستطيع إضافة المزيد. الإخفاقان جاءا من الفجوة نفسها: لم يكن Kubernetes يعرف ما يحتاج إليه notes-api.

الـ Requests والـ Limits

يستطيع كل container التصريح برقمين لكل مورد:

k8s/deployment.yaml
        - name: api
          image: ghcr.io/skylane/notes-api:1.4.0
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              memory: 512Mi
  • الـ request (الطلب) هو ما يحجزه الـ scheduler. لا يوضع الـ Pod إلا على node لديه سعة غير محجوزة كافية لكل الـ requests الخاصة به. و250m تعني 250 millicores، أي ربع نواة معالج.
  • الـ limit (الحدّ) هو السقف الذي تفرضه النواة عبر الـ cgroups، الآلية التي تعرّفت إليها في الدرس 1.1.

يتصرّف الموردان بشكل مختلف جدًا حين يبلغ الـ container حدّه:

  • المعالج قابل للضغط. فوق حدّ المعالج يُخنق الـ container: يحصل على وقت معالج أقل ويصبح أبطأ، لكنه يستمر في العمل.
  • الذاكرة ليست كذلك. فوق حدّ الذاكرة، ينهي الـ OOM killer في النواة العملية. يعرض kubectl describe pod السبب OOMKilled ورمز الخروج 137، ويُعاد تشغيل الـ container.

إذا ضبطت limit دون request، ينسخ Kubernetes الـ limit إلى الـ request. أما الـ request الأكبر من الـ limit فيُرفض مباشرة.

في Node.js، أعطِ V8 سقفًا للـ heap تحت حدّ ذاكرة الـ container، مثلًا NODE_OPTIONS=--max-old-space-size=384 مع حدّ 512Mi. عندها يعمل جامع النفايات بجهد كلما امتلأت الذاكرة، بدلًا من أن تنهي النواة العملية دون إنذار.

اختيار الأرقام

لا تخمّن. شغّل notes-api تحت حمل واقعي وقِس:

Terminal
kubectl top pods -l app.kubernetes.io/name=notes-api

يحتاج kubectl top إلى metrics-server، الذي تتضمّنه كثير من الـ clusters المُدارة أو تقدّمه كإضافة (add-on)؛ وفي kind تثبّته بنفسك. اضبط request المعالج قرب الاستهلاك المعتاد تحت الحمل العادي، واضبط request الذاكرة وحدّها مع هامش فوق الذروة التي لاحظتها. راجع الأرقام بعد الإصدارات الكبيرة. الـ requests المرتفعة جدًا تهدر المال على حجوزات خاملة؛ والمنخفضة جدًا تجعل الـ Pods تُحشر على nodes لا تستطيع حملها فعلًا.

فئات QoS والطرد

من الـ requests والـ limits، يشتقّ Kubernetes فئة جودة الخدمة (Quality of Service class) لكل Pod:

الفئة القاعدة تُطرد عند ضغط ذاكرة الـ node
Guaranteed لكل container ‏requests للمعالج والذاكرة مساوية للـ limits أخيرًا
Burstable request أو limit واحد على الأقل مضبوط، لكنها ليست Guaranteed بعد BestEffort، بدءًا بالـ Pods الأكثر تجاوزًا للـ requests الخاصة بها
BestEffort لا requests ولا limits في أي مكان أولًا

‏notes-api في الأعلى من فئة Burstable. والمهمّة الدفعية التي سبّبت الطرد كانت BestEffort، فمع وجود الـ requests كانت ستُطرد قبل notes-api. تشغيل أحمال الإنتاج كـ BestEffort يعني أنك تتطوّع بها لتكون أول ما يُطرد.

التوسّع الأفقي التلقائي للـ Pods

الـ HorizontalPodAutoscaler ‏(HPA) يعدّل عدد نسخ الـ Deployment بناءً على المقاييس. إنه حلقة تحكّم أخرى: كل 15 ثانية تقريبًا يقارن الاستهلاك الملحوظ بالهدف ويحسب عدد النسخ الذي سيعيد الاستهلاك إلى الهدف.

k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: notes-api
  namespace: notes
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: notes-api
  minReplicas: 3
  maxReplicas: 12
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300

القيمة averageUtilization: 70 تعني «أبقِ متوسط استهلاك المعالج عند 70% من request المعالج». مع request قدره 250m، هذا نحو 175m لكل Pod. عند ستة Pods بمتوسط 350m، يحسب الـ HPA ‏6 × 350 / 175 = 12 فيوسّع إلى الحدّ الأقصى. والكتلة behavior تجعل التقليص ينتظر خمس دقائق من الحمل المنخفض باستمرار، حتى لا يُزيل هدوءٌ عابر سعةً ستحتاج إليها بعد دقيقة. والدقائق الخمس هي القيمة الافتراضية أصلًا؛ لكن كتابتها صراحةً تجعل القرار ظاهرًا وسهل الضبط.

يجمع metrics-server استهلاك المعالج من الـ Pods. يقرؤه الـ HPA، ويقارنه بـ 70 بالمئة من الـ requests، ويحدّث عدد نسخ الـ Deployment، فيتغيّر عدد الـ Pods. metrics-server HPA الهدف 70% من الـ request Deployment replicas: 3 → 6 Pods ‏notes-api استهلاك المعالج كل ~15 ثانية
الـ HPA حلقة تحكّم تعمل على عدد نسخ الـ Deployment

راقبه وهو يعمل بـ kubectl get hpa notes-api --watch أثناء تشغيل اختبار حمل. إذا أظهر عمود الأهداف <unknown>، فإمّا أن الـ containers ليس لها request للمعالج، أو أن metrics-server لا يعمل.

المعالج يناسب notes-api لأن عمله معالجة طلبات مرتبطة بالمعالج. أما الخدمات التي يظهر حملها في مكان آخر، مثل عمق الطابور أو عدد الطلبات في الثانية، فتستطيع التوسّع على مقاييس مخصّصة أو خارجية عبر قائمة metrics نفسها، مع adapter مثبّت في الـ cluster.

لدى notes-api الآن الموارد التي يحتاج إليها، وينمو مع الحمل. الدرس التالي يغيّر الـ image بأمان: التحديثات المتدرّجة، والتراجع، وتغليف كل شيء بـ Helm.

أهم النقاط

  • الـ requests هي ما يحجزه الـ scheduler للـ container؛ والـ limits هي السقف المفروض وقت التشغيل.
  • تجاوز حدّ المعالج يخنق الـ container؛ وتجاوز حدّ الذاكرة يجعله يُقتل بـ OOM مع رمز الخروج 137.
  • فئة QoS تُشتقّ من الـ requests والـ limits: ‏Guaranteed (متساوية لكل container)، وBurstable (بعضها مضبوط)، وBestEffort (لا شيء)، والـ Pods من فئة BestEffort تُطرد أولًا.
  • هدف استخدام المعالج في الـ HPA نسبة مئوية من request المعالج، فلا يعمل على containers ليس لها request.
  • حين يدير HPA كائن Deployment، احذف replicas من manifest الـ Deployment حتى لا يتصارع kubectl apply مع الـ autoscaler.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

‏Pod من notes-api يُعاد تشغيله عدّة مرات في اليوم. ويعرض kubectl describe pod السبب Reason: OOMKilled وExit Code: 137. ماذا حدث؟

السؤال 2 من 4

يستهدف HPA استخدامًا للمعالج بنسبة 70%، ويعرض kubectl get hpa القيمة cpu: <unknown>/70%. ما السبب الأرجح؟

السؤال 3 من 4

يضبط notes-api القيم requests: {cpu: 250m, memory: 256Mi} وlimits: {memory: 512Mi}. ما فئة QoS التي يحصل عليها الـ Pod؟

السؤال 4 من 4

الـ manifest الخاص بـ Deployment ‏notes-api يقول replicas: 3، ووسّعه HPA إلى 9 أثناء ذروة حركة. ماذا يحدث حين يشغّل الـ CI الأمر kubectl apply -f k8s/؟

تدريب

اكتشف الخطأ

الـ autoscaler الذي لا يوسّع أبدًا

يعرض kubectl get hpa notes-api القيمة cpu: <unknown>/70% مع أن metrics-server يعمل، وفي الأيام المزدحمة يهبط عدد النسخ إلى 3 في كل مرة ينشر فيها الـ CI. اختر السطرين الاثنين المسؤولين.

حدّد السطر أو الأسطر التي تظنها خاطئة، ثم تحقّق.