349
0

מסבירים את קוברנטיס – Scheduler (המתזמן)

349
זמן קריאה: 5 דקות

יצרנו קונטיינר שירוץ בקוברנטיס, לאחר זמן קצר הוא יתחיל לרוץ באחד השרתים בקלאסטר. במאמר הקרוב נדבר על איך קורה הבחירה של השרת הדרכים השונות לבחור מקום ועוד. 

אחד הדברים החשובים שפועלים מאחורי הקלעים הוא תהליך שבוחר באיזה שרת להריץ את הקונטיינר, לתהליך הזה קוראים תזמון והוא מתבצע ע”י ה 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. 

יונה דיסין
WRITEN BY

יונה דיסין

Devops Engineer @VMware
נהנה להתעדכן ולהשתמש בטכנולוגיות החמות והחדשות בשוק.

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *