مستوى المقال: محترف — يفترض إنك عارف Kubernetes، والفرق بين requests وlimits، وأساسيات Linux cgroups.
لو خدمتك بطيئة والـ p99 latency بيقفز، وفي نفس الوقت لوحة المراقبة بتقول إن استهلاك المعالج على العقدة أقل من 40%، غالبًا المشكلة مش في السيرفر. دي حصّة CFS بتخنق الحاوية. المقال ده هيوريك إزاي تتأكد بالأرقام، وإزاي تصلحها بدون ما تكسر حاجة تانية.
الـ CPU Throttling في Kubernetes: ليه خدمتك بطيئة والمعالِج نصّه فاضي
المشكلة باختصار
الـ CPU limit في Kubernetes مش سقف ناعم. هو سقف صارم بيتفرض كل 100 مللي ثانية. لو خدمتك خلّصت حصّتها قبل ما تنتهي النافذة، الكيرنل بيوقّف كل خيوطها لحد بداية النافذة اللي بعدها. الانتظار ده اسمه throttling. النتيجة: latency عالي مع استغلال معالج يبان منخفض. فخ كلاسيكي بيضيّع ساعات في التشخيص الغلط.
ليه بيحصل الـ throttling أصلاً
تخيّل صنبور ماء بيفتح دقيقة واحدة بس كل ساعة. لو ملّيت كل جراكنك في أول 10 دقايق، مش هتقدر تاخد نقطة تانية غير الساعة الجاية، حتى لو الخزان مليان. الصنبور هنا هو حصّتك من المعالج، والساعة هي نافذة الجدولة.
علميًا: Kubernetes بيترجم الـ CPU limit إلى إعدادات CFS bandwidth في الـ cgroup. النافذة الافتراضية cpu.cfs_period_us بتساوي 100000 ميكروثانية، يعني 100 مللي ثانية. والحصّة cpu.cfs_quota_us بتتحسب كالتالي:
quota = (CPU limit بالـ millicores / 1000) * 100000 ميكروثانية
limit = 500m -> quota = 50000us (50ms شغل لكل 100ms)
limit = 1000m -> quota = 100000us (100ms شغل لكل 100ms)
لو استهلكت الحصّة قبل نهاية النافذة، الحاوية بتتوقف حتى النافذة اللي بعدها. المهم إن ده سقف على القمة (peak) مش على المتوسط. تطبيق بيعمل burst قصير ممكن يتخنق حتى لو متوسط استهلاكه بعيد جدًا عن الحد.
الفخ الأكبر: التطبيق متعدد الخيوط
ده السبب اللي بيخلّي ناس كتير تتفاجأ. الحصّة بتتحسب عبر كل خيوط الحاوية مجتمعة، مش لكل خيط. لو عندك خدمة بأربع خيوط شغّالة وحدّها limit = 1، الأربع خيوط بيستهلكوا الـ 100ms المسموحة في 25ms wall-clock بس. بعدها الحاوية بتتخنق 75ms كاملة من كل نافذة.
سيناريو واقعي: خدمة Go على 8-core node بحدّ 500m. الـ runtime بيشوف 8 أنوية فبيفتح 8 خيوط للـ GC والجدولة. الحصّة 50ms بس بتتوزّع على 8 خيوط، فبتخلص في أقل من 7ms. النتيجة قفز الـ p99 من 20ms لأكثر من 200ms تحت الحمل، مع throttling حوالي 60% من النوافذ.
إزاي تكتشفه بالأرقام
متعتمدش على الإحساس. اقرأ العدّاد مباشرة. جوّا الحاوية على cgroup v2:
# cgroup v2
cat /sys/fs/cgroup/cpu.stat
# بتطلع سطور مهمة:
# nr_periods عدد النوافذ
# nr_throttled كام نافذة اتخنقت
# throttled_usec إجمالي زمن الخنق بالميكروثانية
# cgroup v1
cat /sys/fs/cgroup/cpu/cpu.statلو nr_throttled بيزيد بسرعة، ده تأكيد. على مستوى العنقود استخدم PromQL:
rate(container_cpu_cfs_throttled_periods_total{pod="my-pod"}[5m])
/
rate(container_cpu_cfs_periods_total{pod="my-pod"}[5m])القيمة دي هي نسبة النوافذ المخنوقة. فوق 0.25 يعني ربع وقتك ضايع في الانتظار، وده كافي لتفسير latency واضح.
الحل بترتيب الأولوية
- ثبّت عدد الخيوط على الحد. ده أعلى ROI وأقل خطر. في Go استخدم مكتبة
uber-go/automaxprocsعشانGOMAXPROCSيتساوى مع الحد بدل عدد أنوية العقدة. في الـ JVM اضبط-XX:ActiveProcessorCount. غالبًا ده لوحده بيوقّف الـ throttling. - ارفع الـ limit أو وسّعه للـ burst. لو الشغل فعلاً محتاج، زوّد الحد بحيث الحصّة تكفي الـ peak. القياس قبل وبعد إلزامي.
- شيل الـ CPU limit وسيب الـ request بس. الـ request بيضمن حصّة عادلة عبر
cpu.weightمن غير سقف صارم. ده رأي شائع بين مهندسي الإنتاج، بس له ثمن (تحت).
resources:
requests:
cpu: "500m" # الضمان العادل
# بدون limits: للـ CPU لتجنّب الخنق
limits:
memory: "512Mi" # سيب حد الذاكرة، ده مختلفملحوظة مهمة: كان في باج في الكيرنل قبل الإصدار 5.4 بيخنق أحمالًا حتى لو تحت الحد. الإصلاح (commit 512ac999) دخل في Linux 5.4. لو عقدك قديمة، الترقية لوحدها ممكن تحسّن الوضع.
الـ trade-offs
شيل الـ limit بيمنع الخنق ويحسّن latency بوضوح، لكن بتخسر حاجتين. أولًا: الحماية من الجار المزعج (noisy neighbor) بتضعف، وpod طمّاع ممكن يزاحم غيره وقت الذروة. ثانيًا: بتفقد فئة الجودة Guaranteed، لأنها بتتطلب limit = request. الافتراض هنا إن عقدك مش مكتظة وعندك عزل كافي على مستوى الـ node. لو بتشارك عقدة واحدة بين خدمات متعددة المستأجرين، الحسبة بتختلف.
متى لا تستخدم هذه الطريقة
ماتشيلش الـ limit في بيئة multi-tenant صارمة بتحاسب كل فريق على استهلاكه، أو لما يكون عندك التزام تعاقدي بعدم تجاوز سقف معيّن. برضه لو خدمتك single-threaded وخفيفة، الـ throttling أصلًا مش مشكلتك، فمتحرّكش حاجة. القاعدة: متصلّحش عرض، صلّح سبب مؤكد بالعدّاد.
الخطوة التالية
افتح Prometheus دلوقتي وشغّل استعلام النسبة على أكثر خدمة بتشتكي من latency. لو النسبة فوق 0.25، ابدأ بالخطوة الأولى: ثبّت عدد الخيوط على الحد وقيس الـ p99 قبل وبعد. لو اتحسّن، دي كانت مشكلتك.
المصادر
- Kubernetes Docs — Assign CPU Resources to Containers and Pods
- Kubernetes Docs — Resource Management for Pods and Containers
- Linux Kernel — CFS Bandwidth Control
- Indeed Engineering — Unthrottled: Fixing CPU Limits in the Cloud (شرح باج الكيرنل والإصلاح في 5.4)
- Datadog — Kubernetes CPU limits and requests: A deep dive
- Uber — automaxprocs (ضبط GOMAXPROCS تلقائيًا على الحد)