KEDA بالعربي: scale-to-zero للـ Workers ووفّر 30% من فاتورة Kubernetes
لو عندك worker بيقرأ من Kafka ومحتفظ بـ 3 replicas طول الوقت حتى الساعة 4 الفجر لما ميكونش فيه رسايل، أنت بتدفع مقابل CPU فاضي. KEDA بيحل ده بإنه ينزّل الـ replicas لـ 0 لما الـ queue تفضى، ويرجّع يفتح Pods جديدة في ثواني أول ما يجي event. النتيجة الواقعية: تقليل 30% من تكلفة الـ compute على workloads غير متصلة بالـ HTTP requests.
المشكلة باختصار
الـ Horizontal Pod Autoscaler (HPA) الافتراضي في Kubernetes بيعتمد على CPU وMemory كـ signal. الـ signal ده بيفشل في حالتين شائعتين:
- Queue Workers: الـ CPU ممكن يفضل 15% والـ Kafka lag يوصل لـ 80 ألف رسالة. المستهلك بطيء لأن عدده قليل، مش لأنه مختنق.
- Idle Services: الـ HPA الحد الأدنى عنده
minReplicas: 1، يعني ممنوع ينزل تحت pod واحد حتى لو الخدمة فاضية ساعات.
KEDA بيحل الاتنين معًا: بيقرأ الـ signal من مصدر الأحداث مباشرة (Kafka, SQS, RabbitMQ, Redis, Prometheus…) وبيدي صلاحية النزول لـ 0.
ابدأ بمثال مبسّط: الكاشير في السوبر ماركت
تخيّل إنك مدير سوبر ماركت. عندك 3 كاشيرز واقفين من 8 الصبح لـ 12 بالليل. في الساعة 3 الفجر كاشير واحد كافي جدًا. في يوم العيد ممكن تحتاج 8. الـ HPA بيقيس "إزاي الكاشير تعبان" (CPU)، لكن المقياس الحقيقي هو طول الطابور قدامه.
KEDA بيشوف الطابور نفسه (عدد الرسايل في Kafka، عدد الـ messages في SQS). لو الطابور فاضي: صرف كل الكاشيرز لبيوتهم. لو الطابور زاد: افتح كاشيرز جداد. الفرق ده هو اللي بيخلّيه يوفر فلوس فعلاً.
المفهوم العلمي: إزاي KEDA بيشتغل تحت الكابوت
KEDA مش بديل للـ HPA، ده طبقة فوقه. لمّا تعمل ScaledObject، KEDA بيعمل الآتي:
- الـ KEDA Operator بيعمل poll كل
pollingIntervalثانية (افتراضي 30) للمصدر الخارجي. - لو الـ metric اتجاوز الـ
lagThreshold، KEDA بيعمل Scale من 0 لـ 1. - بعد كده بيسلّم الدفة للـ HPA العادي اللي بيعمل scaling من 1 لـ
maxReplicaCountبناءً على نفس الـ metric المعروضة عبرexternal.metrics.k8s.io. - لو المصدر فضي لمدة
cooldownPeriod(افتراضي 300 ثانية)، KEDA بيرجع ينزّل من 1 لـ 0.
الافتراض: المستهلك عندك asynchronous ويقبل cold start بين 5 و 30 ثانية. لو الخدمة real-time بتستقبل HTTP requests، الكلام ده مش هيناسبك (هنرجعله في قسم "متى لا تستخدم").
إعداد عملي: ScaledObject لـ Kafka Consumer
بنفرض إن عندك deployment اسمه orders-worker بيستهلك من topic orders-events. نثبّت KEDA أولاً عن طريق Helm:
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace
kubectl get pods -n kedaبعدها نطبّق الـ ScaledObject ده:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: orders-worker-scaler
namespace: production
spec:
scaleTargetRef:
name: orders-worker
minReplicaCount: 0
maxReplicaCount: 20
pollingInterval: 15
cooldownPeriod: 120
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-broker-0:9092,kafka-broker-1:9092
consumerGroup: orders-worker-group
topic: orders-events
lagThreshold: "1000"
offsetResetPolicy: latestالأرقام هنا مدروسة: lagThreshold: 1000 يعني لكل 1000 رسالة متراكمة هيبقى فيه pod زيادة. لو الـ lag وصل 10 آلاف، هتلاقي 10 pods شغالة. لو نزل صفر لمدة دقيقتين، هيروح Pods كلها.
قياس الأثر: من 3 Pods دائمة لـ 0.8 متوسط
في workload حقيقي ينتج حوالي 2 مليون event/يوم موزّعة بشكل غير منتظم (peak بعد الظهر، خمول بالليل):
- قبل KEDA: 3 replicas × 0.5 vCPU × 24 ساعة = 36 vCPU-hour/يوم ≈ 108 دولار شهريًا.
- بعد KEDA: متوسط 0.8 replica × 0.5 vCPU × 24 ساعة ≈ 28.8 دولار شهريًا.
- توفير فعلي: ~73% على هذا الـ worker تحديدًا. المتوسط عبر 12 worker عندنا كان 30-35%.
الرقم ده متوافق مع ما نشرته Microsoft وKedify في case studies إنتاج (30% خفض compute على batch workloads).
الـ trade-offs اللي لازم تفهمها قبل ما تطبق
مافيش wins مجانية. الـ scale-to-zero بيكلفك تلات حاجات:
- Cold start latency: أول رسالة بعد فترة خمول ممكن تستنى 5-30 ثانية (polling + image pull + app warmup). لو ده غير مقبول، خلي
minReplicaCount: 1. - Polling overhead: كل
ScaledObjectبيعمل query للمصدر كل 15-30 ثانية. لو عندك 200 ScaledObject على نفس الـ Kafka cluster، هتضيف load ملحوظ. حل: ارفعpollingIntervalلـ 60 على الـ scalers القليلة الحساسية. - Flapping: لو الـ lag بيتأرجح فوق وتحت الـ threshold، الـ pods تتفتح وتتقفل كتير. الحل: زوّد
cooldownPeriodلـ 300 ثانية على الأقل، واستخدمHPA Stabilization Window.
متى لا تستخدم KEDA scale-to-zero
الأسلوب ده بيفشل في الحالات دي:
- HTTP APIs حساسة للـ latency (أقل من 200 ms P95): الـ cold start هيكسر الـ SLO.
- Stateful services بتاخد دقايق تعمل warmup للـ cache المحلي.
- خدمات بتستقبل traffic مستمر ومعدّله تحت عتبة الـ scale-down أصلًا (الـ HPA العادي كافي).
- بيئات بها quota على API calls للمصدر الخارجي (مثل AWS SQS LongPoll محدود).
الخطوة التالية
افتح أضعف worker عندك (اللي بياخد أقل CPU لأطول فترة) وطبّق عليه ScaledObject بـ minReplicaCount: 0 و cooldownPeriod: 300. راقبه 48 ساعة على Grafana: لو الـ lag الأقصى بعد scale-out مقبول (أقل من SLA القسم عندك)، عمّمه على باقي الـ workers. لو الـ cold start كسر SLA، خلي minReplicaCount: 1 واكتفي بالـ upper bound.