Karpenter للمحترف: نهاية Cluster Autoscaler وبداية Provisioning الذكي
المستوى: محترف — هذا المقال يفترض إنك بتشغّل EKS في إنتاج، عندك خبرة عملية بـ Cluster Autoscaler و Node Groups و Spot Instances، وبتفهم الفرق بين requests و limits في Kubernetes.
لو فاتورة EC2 الخاصة بالـ EKS بتاعك بتعدّي $14,200 شهرياً، و Cluster Autoscaler بياخد 4 دقائق علشان pod واحد pending يلاقي نود يقعد عليه، انت بتدفع ضريبتين في نفس الوقت: ضريبة الـ over-provisioning في الـ ASGs، وضريبة الـ scheduling latency. Karpenter v1.0 من AWS بيشيل الاتنين في إعداد واحد، وبيوفّر 38% من فاتورة EC2 على workload إنتاج فعلي بـ 142 microservice على EKS 1.30.
المشكلة باختصار
Cluster Autoscaler (CA) بيعتمد على Auto Scaling Groups (ASGs) في AWS. كل ASG ليها instance type واحد أو list محدودة، وعملية الـ scale-up بتمر بـ EC2 ASG ثم cloud-init ثم kubelet ثم cluster join. الزمن الكلي من قرار الـ scale لحد ما الـ pod يشتغل: 3 إلى 5 دقائق في أفضل الحالات.
وبالنسبة للتكلفة، انت بتختار instance type واحد لكل ASG، فحتى لو الـ workload محتاج 200m CPU و 256Mi RAM بس، الـ ASG هتجيب m5.xlarge كاملة بـ 4 vCPU و 16Gi RAM. الموقف ده بيخلّيك تشتغل بمنطق "اختار instance type متوسط ومسامح في الـ waste"، وده اللي بيخلّي الـ cluster utilization الفعلية بتقعد بين 38% و 45% في أغلب إنتاجات EKS.
ليه Karpenter مختلف: تخصيص بدون Node Groups
Karpenter بيلغي طبقة الـ ASG بالكامل. لما يلاقي pod في حالة Pending، بيقرا الـ resource requests و nodeSelector و taints و topology constraints، وبيحسب الـ instance type الأمثل من بين ~700 instance type متاحة في الـ region، ثم بيعمل CreateFleet API call لـ EC2 مباشرة.
تشبيه للمبتدئ علشان الفكرة تثبت قبل ما ندخل في التفاصيل: تخيّل انك مدير مطعم. Cluster Autoscaler يشبه انك جايب 5 طرابيز بـ 6 كراسي ثابتة عشان أي حد يدخل يلاقي مكان جاهز. لو دخل 2 أشخاص بس، 4 كراسي ضايعة كل الوقت. Karpenter يشبه انك بتجيب الطاولة والكراسي المناسبة بالظبط لحظة ما العميل يدخل من الباب. لو دخل 3 أشخاص بطفل، بتجيب طاولة بـ 3 كراسي زائد كرسي عالي. مفيش طاولة فاضية ومفيش انتظار.
على المستوى التقني: Karpenter Controller بيشتغل كـ Kubernetes Custom Controller. بيعمل watch على الـ PodScheduled condition، ولما يلاقي Pending pod، بيـ simulate الـ scheduling decision بنفسه، وبيختار الـ SKU اللي يحقق الـ requirements بأقل تكلفة لحظية. الـ flow بياخد 40-60 ثانية من Pending لـ Running، مقارنة بـ 180-300 ثانية في CA لأنه بيعدّي على طبقتين أقل.
الإعداد العملي: NodePool و EC2NodeClass
Karpenter v1.0 بيستخدم Custom Resources اتنين فقط: NodePool (سياسة الـ scheduling) و EC2NodeClass (تكوين الـ instance). إعداد production-ready لـ EKS 1.30 كالتالي:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
metadata:
labels:
managed-by: karpenter
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["m", "c", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
expireAfter: 720h
limits:
cpu: 1000
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2023
role: KarpenterNodeRole-prod-eks
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: prod-eks
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: prod-eks
الـ NodePool ده بيقول لـ Karpenter: "اختار من العائلات m/c/r، الجيل أكبر من 5، Spot أو On-Demand زي ما تشوف، أي architecture (للاستفادة من Graviton)." الـ consolidateAfter: 30s هو الـ killer feature: لو فيه nodes تحت 50% utilization لمدة 30 ثانية، Karpenter بيـ bin-pack الـ pods على nodes أقل ويـ terminate اللي بقت فاضية. ده هو الـ knob اللي بيوفّر أكتر من نص قيمة الـ migration.
الأرقام: قياس من إنتاج فعلي
القياس على EKS cluster بـ 142 microservice، متوسط 380 pod نشط، traffic 14K req/s peak، region eu-west-1. الفترة: 60 يوم قبل الـ migration و 60 يوم بعدها بنفس الـ workload.
- فاتورة EC2 الشهرية: $14,280 → $8,840 (توفير 38%، حوالي $5,440/شهر)
- متوسط Node Utilization (CPU): 41% → 73%
- Spot Coverage: 22% → 68% من الـ compute capacity
- زمن Pending pod → Running: 247s → 52s (تحسّن 4.7×)
- عدد الـ Node Groups المُدارة يدوياً: 11 → 0
- Instance Type Diversity: 3 types → 47 types (Karpenter بيختار من pool أوسع، فالـ Spot interruption rate ينخفض من 4.2% لـ 1.1%)
الافتراض المهم هنا: ده cluster فيه workloads متنوعة (CPU-bound و memory-bound)، والـ load بيتغير على مدار اليوم بنسبة ±60%. لو الـ cluster بتاعك steady-state على نفس النوع من الـ pods 24/7، التوفير المتوقع هيكون أقل، في حدود 10-15% فقط. الـ trade-off هنا واضح: Karpenter بيلمع في الـ workloads المتغيرة، وقيمته بتنخفض في الـ steady ones.
4 trade-offs خفية لازم تفهمها قبل ما تحرك
- الـ Disruption حقيقية ومش هتعجبك الأول: Karpenter بيـ consolidate كل 30 ثانية افتراضياً. ده معناه إن pods بتتنقل بين nodes كتير. لو عندك stateful workloads بدون PodDisruptionBudget صحيح، هتشوف restarts متكررة في أول أسبوع. الحل: ضع
karpenter.sh/do-not-disrupt: "true"على الـ pods الحساسة (DB clients، long-running jobs)، واضبطconsolidateAfter: 5mلو الـ workload حساس للحركة. - الـ IAM permissions أوسع من CA: Karpenter Controller محتاج
ec2:RunInstancesوec2:CreateFleetوiam:PassRole. لو الـ security team بتاعتك بتطبق least privilege strict، ده مفاوضة لازم تخوضها. الـ alternative الأذكى: استخدم EKS Pod Identity + scoped condition keys (ec2:InstanceType,ec2:RequestTag) للحد من الـ instance types اللي يقدر يطلبها. - الـ Observability بتتغير شكلها: أسماء الـ nodes في Karpenter بتبقى عشوائية وقصيرة العمر (متوسط حياة node حوالي 6-18 ساعة). لو dashboards الـ Grafana بتاعتك معتمدة على node labels من Node Groups (
eks.amazonaws.com/nodegroup)، هتحتاج تعيد كتابة الـ queries لتعتمد علىkarpenter.sh/nodepoolوkarpenter.sh/capacity-typeبدلها. خصّص يوم كامل لإعادة بناء الـ dashboards. - Spot Interruption Handling مش مجاني: Karpenter بيتلقى Spot interruption notices من SQS queue وبيـ gracefully drain الـ node في 120 ثانية. لكن لو الـ pods بتاعتك مش بتـ handle
SIGTERMصح، هتشوف 502 errors في الـ ALB في كل interruption. لازم تتأكد إن كل container بيـ respond للـSIGTERMويقفل الـ connections في أقل من 60 ثانية، وإن الـterminationGracePeriodSecondsعند 90 على الأقل.
متى Karpenter يبقى قرار غلط
Karpenter مش الحل المناسب لكل cluster. تجنّبه في الحالات دي بدون تردد:
- Cluster صغير (≤ 3 nodes): الـ operational overhead لـ Karpenter Controller أكبر من المكسب. CA كافي وأبسط.
- Workload ثابت 100% بدون موسمية: لو الـ pods نفسها كل يوم بنفس الأرقام، Reserved Instances أو Savings Plans مع CA يقدّموا نفس التوفير بدون التعقيد الإضافي.
- Compliance بيشترط instance types محددة: بعض الـ workloads (PCI-DSS, HIPAA, sovereign clouds) بيحتاجوا instance types معتمدة من قائمة محدودة. Karpenter بـ requirements صارمة بيقلّل التنوع لدرجة إن قيمته المضافة بتختفي.
- مفيش Spot tolerance أصلاً: لو الـ workload مش بيتحمل interruptions (legacy stateful DB، gaming session servers)، 80% من قيمة Karpenter بتروح. On-demand بس مع CA يكفي وأبسط في الإدارة.
- الـ team مش جاهز للـ observability changes: لو مفيش حد عنده وقت يعيد كتابة dashboards و alerts، الـ migration هيخلق مشاكل أكتر مما يحل.
الخطوة التالية
افتح الـ EKS cluster بتاعك واعمل الأمر ده علشان تشوف الـ wasted capacity الحالية قبل ما تقرر:
kubectl get nodes -o json | jq -r '
.items[] |
"\(.metadata.name): allocatable_cpu=\(.status.allocatable.cpu) allocatable_mem=\(.status.allocatable.memory)"
'
kubectl top nodes --no-headers | awk '{print $1, $3, $5}'قارن الـ allocatable بالـ actual usage من top. لو الفرق أكتر من 35% utilization gap على cluster فاتورته تتعدّى $3K/شهر، Karpenter هيوفّرلك من $400/شهر فما فوق. ابدأ بـ helm install karpenter oci://public.ecr.aws/karpenter/karpenter على namespace منفصل، حرّك namespace واحد non-critical الأول، وراقب لمدة أسبوع كامل قبل التحرّك الكامل. لا تنقل الـ DB workloads ولا الـ ingress controllers في الموجة الأولى.