الوحدة 5 · التشغيل في الإنتاج
الـ Requests والـ Limits والتوسّع التلقائي
22 دقراءة 5 داكتشف الخطأ
الدرس في لمحة
اضبط requests وlimits للمعالج والذاكرة في notes-api انطلاقًا من القياسات، وتوقّع الخنق (throttling) والقتل بسبب نفاد الذاكرة وفئات QoS، ووسّع عدد النسخ تلقائيًا بـ HorizontalPodAutoscaler من autoscaling/v2.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
في المرة الأولى التي واجه فيها notes-api حركة حقيقية على cluster مشترك، حدث أمران خلال ساعة. مهمّة دفعية (batch job) على الـ node نفسه استهلكت كل الذاكرة، فطُرد أحد Pods notes-api. ثم رفعت قفزة في الحركة استهلاك المعالج إلى أقصاه، وصار زمن الاستجابة بالثواني، وبقي عدد الـ Pods ثلاثة بالضبط لأن أحدًا لم يخبر Kubernetes بأنه يستطيع إضافة المزيد. الإخفاقان جاءا من الفجوة نفسها: لم يكن Kubernetes يعرف ما يحتاج إليه notes-api.
الـ Requests والـ Limits
يستطيع كل container التصريح برقمين لكل مورد:
- 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 تحت حمل واقعي وقِس:
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 ثانية تقريبًا يقارن الاستهلاك الملحوظ بالهدف ويحسب عدد النسخ الذي سيعيد الاستهلاك إلى الهدف.
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 تجعل التقليص ينتظر خمس دقائق من الحمل المنخفض باستمرار، حتى لا يُزيل هدوءٌ عابر سعةً ستحتاج إليها بعد دقيقة. والدقائق الخمس هي القيمة الافتراضية أصلًا؛ لكن كتابتها صراحةً تجعل القرار ظاهرًا وسهل الضبط.
راقبه وهو يعمل بـ 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.