ארכיטקטורת Kubernetes: מה קורה מתחת למכסה המנוע כשאתם מריצים kubectl apply
מאת צוות מדיה דיל · 03.08.2026 · DevOps · 7 דק'
צלילה ארכיטקטונית לרכיבי הליבה של Kubernetes - control plane, etcd, scheduler, kubelet ו-CNI - ולהחלטות עיצוב שמסבירות למה הענן מתנהג כמו שהוא מתנהג.
צוות פיתוח שהריץ Kubernetes cluster במשך שנה שאל שאלה שנשמעת פשוטה: 'למה לוקח 30 שניות מהרגע שאני מריץ kubectl apply ועד שה-pod באמת רץ?'. התשובה חושפת את כל שכבות הארכיטקטורה של Kubernetes - מהרגע שהבקשה מגיעה ל-API Server, דרך אימות, שמירה ב-etcd, קבלת החלטה של ה-scheduler, ועד ל-kubelet שמריץ בפועל את הקונטיינר בנוד. הבנה עמוקה של השרשרת הזו היא ההבדל בין מישהו שמריץ Kubernetes לבין מישהו שיודע לדבג אותו כשמשהו משתבש בפרודקשן - ומשהו תמיד משתבש.
Control Plane: המוח שמקבל את כל ההחלטות
ה-control plane הוא אוסף רכיבים שמנהלים את מצב ה-cluster אבל לא מריצים בעצמם workloads. kube-apiserver הוא נקודת הכניסה היחידה - כל בקשה, בין אם מ-kubectl, מ-controller פנימי או מרכיב אחר, עוברת דרכו, כולל אימות (authentication), הרשאה (authorization דרך RBAC) ו-admission control. חשוב להבין: ה-API Server לא מבצע שום דבר בעצמו מעבר לאימות ושמירת state - הוא לא זה שמריץ pods, הוא רק כותב את הכוונה ('אני רוצה 3 replicas של הדפלוימנט הזה') ל-etcd.
etcd הוא מסד הנתונים המבוזר (key-value store מבוסס Raft consensus) שמחזיק את כל מצב ה-cluster - כל אובייקט, כל קונפיגורציה, כל secret. זהו הרכיב הכי קריטי מבחינת עמידות: אם etcd אבד או קרס, ה-cluster איבד את הזיכרון שלו לגמרי, גם אם כל ה-pods עדיין רצים בפועל בנודים. גיבוי סדיר של etcd הוא לא אופציה - הוא תנאי הכרחי להרצת cluster בפרודקשן, וארגונים רבים לומדים את זה בדרך הקשה אחרי incident שבו etcd התמוטט בלי גיבוי עדכני.
kube-scheduler אחראי להחליט על איזה נוד ירוץ כל pod חדש, בהתבסס על משאבים זמינים, אילוצי affinity/anti-affinity, taints וtolerations, ומגבלות נוספות. חשוב להבין שהscheduler לא מריץ כלום בעצמו - הוא רק כותב חזרה ל-etcd (דרך ה-API Server) איזה נוד נבחר, וה-kubelet באותו נוד הוא שלוקח את זה משם ומריץ בפועל.
controller-manager מריץ אוסף controllers שכל אחד מהם עוקב אחרי סוג אובייקט מסוים ומנסה להביא את המצב בפועל למצב הרצוי - זה הלב של ה-reconciliation loop שמאפיין את Kubernetes: הצהרת state רצוי, וcontrollers שמתאמצים ללא הרף לגרום למציאות להתכנס אליו.
Reconciliation Loop: הרעיון שמסביר הכל
ההבדל המהותי בין Kubernetes לכלי orchestration ישנים יותר הוא המודל ה-declarative: במקום להגיד למערכת 'הרץ פקודה X', אתם מצהירים 'אני רוצה שהמצב יהיה כך', והמערכת רצה ברקע כל הזמן ומתאמת בין המצב הרצוי (desired state) למצב בפועל (actual state). כל controller עובד לפי אותה תבנית: קרא את המצב הרצוי מ-etcd, בדוק את המצב בפועל, ואם יש פער - בצע פעולה כדי לצמצם אותו. ה-ReplicaSet controller, לדוגמה, כל הזמן בודק: יש לי 3 replicas שהוגדרו, ורק 2 רצים בפועל? אז תיצור pod נוסף. יש 4 רצים? תמחק אחד.
המשמעות המעשית העמוקה: אין פעולת 'תיקון' ידנית אמיתית ב-Kubernetes - אם מחקתם pod ידנית מתוך deployment, הוא פשוט ייוצר מחדש, כי ה-desired state עדיין אומר 3 replicas. הדרך הנכונה לשנות משהו היא תמיד לשנות את המצב הרצוי (למשל לערוך את ה-YAML), לא להתערב ישירות במצב בפועל.
Kubelet ו-Container Runtime: איפה הקונטיינרים באמת רצים
בכל נוד רץ kubelet - הסוכן שמדבר עם ה-API Server, מקבל הוראות לגבי אילו pods צריכים לרוץ בנוד הזה, ומתאם עם ה-container runtime (containerd או CRI-O ברוב הקלאסטרים המודרניים, לאחר שDocker הוצא משימוש כ-runtime ישיר עם הפרישה מ-dockershim) כדי להריץ בפועל את הקונטיינרים. ה-kubelet גם אחראי על health checks - liveness ו-readiness probes - ומדווח בחזרה על מצב הבריאות של כל pod.
רכיב חשוב נוסף בכל נוד הוא kube-proxy, שאחראי על ניתוב תעבורת רשת בין Services ל-pods בפועל, בדרך כלל דרך iptables או IPVS. הבנת השכבה הזו קריטית לדיבוג בעיות רשת - כשsService 'לא מוצא' pods, הבעיה כמעט תמיד היא ב-label selectors שלא תואמים, לא בבעיית רשת אמיתית.
CNI: הרשת שאף אחד לא רואה עד שהיא נשברת
Kubernetes עצמו לא מממש רשת - הוא מגדיר ממשק (Container Network Interface, CNI) שפלאגינים כמו Calico, Cilium או Flannel ממלאים. הבחירה בין הפלאגינים האלה משפיעה עמוקות על ביצועים, יכולות אבטחה (Network Policies) ומורכבות תפעולית. Calico ו-Cilium תומכים ב-Network Policies מלאות שמאפשרות להגביל אילו pods יכולים לדבר עם אילו - יכולת קריטית לאבטחה שחסרה בפלאגינים בסיסיים יותר כמו Flannel הישן. Cilium, שבנוי על eBPF, מציע יכולות תצפית עמוקות ברמת ה-kernel וביצועים גבוהים יותר בסקייל גדול, אך דורש הבנה טכנית עמוקה יותר לתפעול נכון.
נקודה שמפתיעה מפתחים רבים: כברירת מחדל, בלי Network Policy מוגדרת, כל pod יכול לדבר עם כל pod אחר ב-cluster, ללא קשר ל-namespace. זו לא רק סוגיית אבטחה תיאורטית - זו נקודת תורפה ממשית שכל cluster בפרודקשן חייב לטפל בה במפורש.
ניהול משאבים: Requests, Limits ו-QoS Classes
כל container ב-pod יכול להגדיר requests (המשאב המובטח לו) ו-limits (התקרה שהוא לא יכול לחצות). ההבדל בין השניים קובע את ה-Quality of Service class של ה-pod: אם requests שווים ל-limits לכל המשאבים, ה-pod מקבל class Guaranteed - הכי מוגן מפני eviction בזמן לחץ משאבים. אם יש רק requests בלי limits, ה-class הוא Burstable. ופודים בלי requests כלל מקבלים BestEffort - הראשונים להיזרק כשהנוד נגמר לו הזיכרון.
טעות פרודקשן קלאסית: להריץ workloads קריטיים בלי הגדרת requests/limits בכלל, מתוך אמונה ש'זה יעבוד'. כשהנוד מגיע ללחץ זיכרון, ה-kernel OOM killer קוטל pods לפי סדר עדיפות שנקבע בדיוק לפי ה-QoS classes האלה - וללא הגדרה מפורשת, אין שליטה על מה ייהרג קודם.
Autoscaling בשלוש רמות
Kubernetes תומך בשלושה סוגי autoscaling שלעיתים קרובות מבלבלים ביניהם: Horizontal Pod Autoscaler (HPA) מוסיף או מוריד מספר replicas של pod לפי מדדים (CPU, memory, או מדדים מותאמים אישית); Vertical Pod Autoscaler (VPA) משנה את ה-requests/limits של pod קיים בהתאם לשימוש בפועל - אבל דורש הפעלה מחדש של ה-pod כדי להחיל שינוי, מה שהופך אותו לפחות שימושי לworkloads רגישים לזמינות; ו-Cluster Autoscaler מוסיף או מוריד נודים שלמים מה-cluster כשאין מספיק משאבים לתזמן pods חדשים או כשנודים לא מנוצלים. שילוב נכון של השלושה, במיוחד HPA עם Cluster Autoscaler, הוא מה שמאפשר ל-cluster להגיב אוטומטית לעומס משתנה בלי התערבות ידנית.
סוגי Workloads: מתי Deployment לא מספיק
רוב המדריכים המקדימים מלמדים רק על Deployment, אבל Kubernetes מציע כמה סוגי controllers שמתאימים לצרכים שונים מהותית. Deployment מתאים לworkloads stateless שבהם כל replica זהה ואין חשיבות לזהות ספציפית של pod - שירות web טיפוסי. StatefulSet נדרש כשלכל replica יש זהות יציבה (hostname עקבי, volume ייעודי שנשאר צמוד לו גם אחרי restart) - מסדי נתונים מבוזרים כמו Cassandra או Kafka brokers חייבים StatefulSet כי כל node בקלאסטר צריך זהות עקבית לאורך זמן. DaemonSet מבטיח שpod אחד בדיוק ירוץ על כל נוד ב-cluster (או תת-קבוצה מוגדרת) - שימושי לagents כמו log collectors או מוניטורינג שצריכים לרוץ בכל מקום. ו-Job ו-CronJob מיועדים למשימות עם התחלה וסוף ברורים, בניגוד ל-Deployments שרצים ברציפות.
בחירה לא נכונה בין הסוגים האלה היא מקור נפוץ לבעיות פרודקשן - למשל הרצת מסד נתונים כ-Deployment רגיל עם PersistentVolumeClaim משותף, מה שיוצר race conditions בין replicas שמנסים לגשת לאותו volume בו-זמנית. הכלל: לשאול תמיד האם ל-pod יש זהות וstate ייחודיים שצריכים להישמר, ולבחור את סוג ה-workload לפי התשובה, לא לפי הרגל.
הרחבת ה-API: CRDs והאקוסיסטם סביב Kubernetes
אחת הסיבות המרכזיות להצלחת Kubernetes היא היכולת להרחיב את ה-API עצמו דרך Custom Resource Definitions (CRDs). זה מה שמאפשר לכלים כמו cert-manager, Prometheus Operator או Istio להוסיף אובייקטים חדשים (למשל Certificate, ServiceMonitor) שמתנהגים בדיוק כמו אובייקטי Kubernetes מובנים - אפשר להריץ עליהם kubectl get, kubectl apply, ולכתוב עליהם controllers משלכם (Operators) שמיישמים לוגיקה עסקית מותאמת. ה-Operator Pattern הפך לדרך הסטנדרטית לניהול תוכנה מורכבת סטייטפולית בתוך Kubernetes - במקום להריץ סקריפטים ידניים לניהול מסד נתונים, כותבים Operator שמבין את מחזור החיים המלא שלו (גיבוי, שחזור, שדרוג גרסה) ומנהל אותו אוטומטית דרך אותו reconciliation loop שמנהל pods רגילים.
ההבנה שKubernetes הוא בעצם פלטפורמה כללית לניהול כל משאב declarative, לא רק קונטיינרים, היא מה שמסביר למה כל כך הרבה כלי תשתית מודרניים (מ-databases ועד מערכות ML) בנויים היום כ-Operators שרצים בתוך cluster קיים במקום ככלים עצמאיים.
טעויות נפוצות בפרודקשן
הטעות הכי נפוצה היא הפעלת cluster יחיד בלי multi-AZ redundancy ל-control plane - כשה-availability zone שבו יושב etcd נופל, כל ה-cluster משותק, גם אם ה-workloads עצמם היו יכולים להמשיך לרוץ. טעות שנייה היא הזנחת עדכוני גרסה - Kubernetes משחרר גרסה חדשה כל כמה חודשים, ופיגור של שנה או יותר הופך שדרוג עתידי לפרויקט מסוכן במקום עדכון שגרתי. טעות שלישית היא חוסר בהגדרת resource requests/limits כפי שתואר למעלה, שגורם ל-noisy neighbor problem - workload אחד גוזל את כל משאבי הנוד ופוגע בשכנים שלו.
מתי Kubernetes הוא הבחירה הנכונה
Kubernetes משתלם כשיש ריבוי services שצריכים ניהול עקבי של פריסה, סקיילינג ורשת, וכשהצוות מוכן להשקיע בלמידה תפעולית משמעותית. לצוות קטן עם שירות אחד או שניים, מורכבות Kubernetes לרוב עולה על התועלת - שירותים מנוהלים כמו Cloud Run או ECS Fargate נותנים חלק גדול מהיתרונות בלי הנטל התפעולי. במדיה דיל אנחנו רואים הרבה ארגונים שקופצים ל-Kubernetes מוקדם מדי 'כי זה הסטנדרט', ורק אחר כך מבינים שהם צריכים צוות ייעודי רק כדי לתחזק את התשתית - שיקול שכדאי לשקול בכנות לפני ההחלטה, לא אחריה.
תגיות: Kubernetes · control plane · etcd · kube-scheduler · kubelet · CNI · Autoscaling · K8s architecture