יצרנו קונטיינר שירוץ בקוברנטיס, לאחר זמן קצר הוא יתחיל לרוץ באחד השרתים בקלאסטר. במאמר הקרוב נדבר על איך קורה הבחירה של השרת הדרכים השונות לבחור מקום ועוד.
אחד הדברים החשובים שפועלים מאחורי הקלעים הוא תהליך שבוחר באיזה שרת להריץ את הקונטיינר, לתהליך הזה קוראים תזמון והוא מתבצע ע”י ה Scheduler או בעברית המתזמן.
מה המטרה שלו?
כשאנחנו מייצרים משאב כלשהו נניח pod, נרצה שקוברנטיס יריץ אותו על השרת הכי מתאים עבורו. ברמה הבסיסית ביותר המתזמן צריך לחפש שרת שיש לו מספיק משאבים להריץ את האפליקציה. ללא המנגנון הזה היינו עלולים להגיע למצבים של OOM בשרתים.
לפעמים נאמר ל קוברנטיס לבחור שרת ספציפי ולפעמים ניתן לו פרמטרים כלליים.
המתזמן ידע להקשיב לפרמטרים האלו ולעשות את הבחירה הנכונה עבור המשאב.
איך הוא עובד?
המתזמן מקבל מהמשתמש קונפיגורציה שמכילה הוראות ודרישות. הוא מתייחס אליהם ומקבל החלטה. אם לא ניתנו הוראות ספציפיות המתזמן יעבוד לםי סט של הוראות דיפולטיביות.
ישנם שני קטגוריות של סוגי קונפיגורציה שניתן להגדיר עבור המתזמן.
דרישות/אילוצים :
Requests – כמות המשאבים שאנחנו אומרים לקוברנטיס להקצות לטובת הקונטיינר שלנו.
Nodeselector – מצב שבו אנחנו אנחנו אומרים ל pod לרוץ בשרת או קבוצת שרתים מסוימים.
הדבר הזה מתאפשר על ידי שימוש בתיוג של השרת ואמירה ל pod לבחור בשרת עם התיוג הרלוונטי
Daemonset – מצב שבו אנחנו אומרים לקוברנטיס להריץ מופע של ה סרביס בכל שרת אפשרי
Taints – מצב שבו אנחנו אומרים לקוברנטיס שלא להריץ על שרת מסוים אלא אם כן יש לו תיוג מסוים מסוג toleration.
לדוגמא:
הקונטיינר הכחול לא יכול להיות מתוזמן על השרת עם הtaint האדום אך יכול להיות מתוזמן על הכחול או השרת ללא taint.
במצבים האלה אין למתזמן מרווח תמרון, והוא חייב לתזמן או שלא לתזמן במקומות שמתאימים לפי ההוראות.
דרישות גמישות /המלצות :
פיזור – כדי לדאוג שהסרביס יהיה זמין ובעל יתירות גבוהה קוברנטיס מנסה לפזר שכפולים של אותו סרביס על פני כמה שרתים שהוא יכול.
Affinity – מגדיר מול איזה רכיבים אחרים כדאי שה pod ירוץ
למשל כדאי שה pod ירוץ בשרת כלשהו או כדאי שה pod ירוץ באותו שרת עם pod מסוים אחר.
Anti affinity – מגדיר מול אלו רכיבים כדאי שה pod לא ירוץ
למשל לא כדאי שpod ירוץ עם pod מסוים אחר באותו שרת.
התכונות האלו הם בגדר המלצה למתזמן. אם הוא לא יצליח לעמוד בדרישות הוא יתזמן את הקונטיינרים גם במקום שנוגד את ההמלצה.
הבהרה: affinity יכול להיכתב בצורה שהוא יהיה דרישה.
נניח שיש קונטיינר צריך 2 cpu כדי לרוץ, ובנוסף חייב מערכת הפעלה ווינדוס, המתזמן יחפש מקום מתאים עבורו לרוץ.
כך זה יראה:
אז איך מדברים עם המתזמן בצורה הנכונה והמתאימה?
ניתן כמה דוגמאות למקרים ואיך לקנפג.
צורך לרוץ על כל השרתים שקוברנטיס מנהל:
בשביל לאסוף את הלוגים מכל השרתים שקוברנטיס מנהל יש צורך שירוץ אוסף לוגים על כל אחד מהשרתים.
נגדיר daemonset עבורו. תפקידו של ה daemonset הוא בדיוק לתזמן קונטיינר מהסוג הנבחר על כל השרתים.
לדוגמא:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
אילוצי משאבים: pod א’ צריך 4 cpu כדי לרוץ, נגדיר לו requests של 4 cpu.
resources:
requests:
cpu: 4
מערכת הפעלה: אם pod כלשהו צריך להריץ מערכת הפעלה ווינדוס.
נגדיר תג על השרת ווינדוס
kubectl label nodes <your-node-name> OS=Windows
ובנוסף Nodeselector שימקם את הקונטיינר על השרת הספציפי.
apiVersion: v1
kind: Pod
metadata:
name: testImage
spec:
containers:
- name: testImage
image: testImage
nodeSelector:
OS: Windows
אי יכולת לרוץ באותו שרת: למשל אם שני סרביסים צורכים נתיב זהה בשרת שהם רצים.
נגדיר.
Anti affinity
בקונטיינר הראשון נגדיר תיוג:
apiVersion: v1
kind: Pod
metadata:
name: pod1
labels:
type: a1
spec:
containers:
- name: test1
image: my-test-image
בקונטיינר השני נגדיר לא לתזמן אותם יחד
apiVersion: v1
kind: Pod
metadata:
name: pod2
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: type
operator: In
values:
- a1
topologyKey: kubernetes.io/hostname
containers:
- name: pod-antiaffinity
image: my-test-image2
תפקיד נוסף של המתזמן הוא לתת קדימות לקונטיינרים מסוימים. ההחלטה נעשית לפי הגדרת העדיפות של הקונטיינר (Priority).
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
globalDefault: false
description: "This priority class is high”
דוגמא אם יש תור אצל המתזמן שבו יש קונטיינר עם עדיפות של 7 ו קונטיינר עם עדיפות של 3, המתזמן קודם יחפש מקום עבור הקונטיינר בעל העדיפות הגבוהה ביותר.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
בנוסף אם נגדיר זכות קדימה (preemption) המתזמן יחפש מקום פנוי עבורו, אם כזה לא קיים הוא יחפש מקום שיכול להכיל אותו אם יפונה קונטיינר בעל עדיפות נמוכה יותר ויחזיר את הקונטיינר שהוא פינה לתור של המתזמן.
האלגוריתם הזה של המתזמן לא תמיד יגרום לניצול מיטבי של המשאבים.
למשל אם נוסיף שרת תחת ניהול הקלאסטר עד שלא יהיה בקשות חדשות לתזמן עוד קונטיינרים הוא יעמוד שומם.
דוגמא נוספת היא במצב שהגדרנו ששרת מסוים יקבל קונטיינרים רק עם תיוג מסוים (taint)
Pod=Blue
ואז pod1 קיבל תיוג שמתאים ותוזמן על השרת המדובר.
לאחר זמן קצר התיוג הנדרש השתנה ל pod=Green
הקונטיינר שתוזמן כבר לא עם תיוג נכון אבל עדיין ישאר על השרת המדובר.
בדיוק בשביל זה יש פרויקט קוד פתוח
https://github.com/kubernetes-sigs/descheduler
הפרויקט נותן מענה לכמה מצבים שבו המתזמן אינו פועל באופן אקטיבי לשיפור המצב הקלאסטר או תיקוף החוקים שהגדרנו לו.
לדוגמא ניתן להגדיר חוקיות של מה הטווח שבו שרת מבחינתינו הוא במצב ניצולת טובה כלומר מהו רף המינימום שבו לא ננסה בכח להעביר אל השרת עוד קונטיינרים.
הרף יכול להיות ב
- Cpu
- Memory
- Number of pods
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"LowNodeUtilization":
enabled: true
params:
nodeResourceUtilizationThresholds:
thresholds:
"cpu" : 20
"memory": 20
"pods": 20
targetThresholds:
"cpu" : 65
"memory": 60
"pods": 40
פוליסה נוספת שניתן להגדיר בשביל לטפל במצב שהקונטיינר מפר את חוק ה affinity (הדוגמא למעלה) פשוט יגרום לקוברנטיס להעיף את הקונטיינר ולתזמן אותו מחדש במקום שמותר לו להיות.
לסיכום
המתזמן הוא חלק אינטגרלי וחשוב בקוברנטיס. מטרתו היא לתזמן את הקונטיינרים במקומות הכי מתאימים עבורם לפי הדרישות של המשתמש. בנוסף הוא לא עובד בצורה מושלמת בכל מצב וניתן לטפל במצבים האלו באמצעות התקנת ה Descheduler.