הסבר על שירותי Kubernetes

בדף הזה מתוארים הסוגים השונים של Services ב-Kubernetes, ומוסבר איך Google Kubernetes Engine ‏ (GKE) משתמש ב-Services כדי לקבץ נקודות קצה של Pod.

במאמר הזה תלמדו איך ליצור שירותים באמצעות דוגמאות של YAML. כל סוג שירות משתמש בכתובת ה-IP הקבועה של השירות כדי לצמצם את המורכבות של משימות ספציפיות של רישות ותקשורת. לדוגמה, תלמדו איך להשתמש בכמה יציאות ונקודות קצה, ואיך להגדיר אפשרויות של מחסנית יחידה או כפולה שתומכות ב-IPv4 וב-IPv6.

במאמר חשיפת אפליקציות באמצעות שירותים מוסבר איך ליצור שירות.

הדף הזה מיועד לאופרטורים ולמפתחים שמקצים ומגדירים משאבי ענן ופורסים אפליקציות ושירותים. מידע נוסף על תפקידים נפוצים ומשימות לדוגמה שמוזכרים בתוכן זמין במאמר תפקידים נפוצים של משתמשי GKE ומשימות. Google Cloud

מהו שירות Kubernetes?

הרעיון מאחורי שירות הוא לקבץ קבוצה של נקודות קצה של Pod למשאב יחיד. אפשר להגדיר דרכים שונות לגשת לקבוצה. כברירת מחדל, מקבלים כתובת IP יציבה של אשכול, שבעזרתה לקוחות בתוך האשכול יכולים ליצור קשר עם Pods בשירות. לקוח שולח בקשה לכתובת ה-IP הקבועה, והבקשה מנותבת לאחד מה-Pods בשירות.

שירות מזהה את ה-Pods החברים שלו באמצעות בורר. כדי ש-Pod יהיה חבר בשירות, ל-Pod צריכות להיות כל התוויות שצוינו בסלקטור. תווית היא צמד שרירותי של מפתח/ערך שמצורף לאובייקט.

במניפסט השירות הבא יש בורר שמציין שתי תוויות. בשדה selector מצוין שכל Pod שיש לו גם את התווית app: metrics וגם את התווית department:engineering הוא חבר בשירות הזה.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  # Use labels to select the member Pods of the Service.
  selector:
    app: metrics
    department: engineering
  ports:
  ...

למה כדאי להשתמש בשירות Kubernetes?

באשכול Kubernetes, לכל Pod יש כתובת IP פנימית. אבל הפודים בפריסת Deployment מגיעים והולכים, וכתובות ה-IP שלהם משתנות. לכן אין טעם להשתמש בכתובות ה-IP של ה-Pod באופן ישיר. כשמשתמשים בשירות, מקבלים כתובת IP יציבה שתקפה למשך מחזור החיים של השירות, גם אם כתובות ה-IP של חברי ה-Pod משתנות.

שירות מספק גם איזון עומסים. הלקוחות מתקשרים עם כתובת IP יציבה אחת, והבקשות שלהם מאוזנות בין ה-Pods ששייכים לשירות.

סוגים של שירותי Kubernetes

יש חמישה סוגים של שירותים:

  • ClusterIP (ברירת מחדל): לקוחות פנימיים שולחים בקשות לכתובת IP פנימית יציבה.

  • NodePort: לקוחות שולחים בקשות לכתובת ה-IP של צומת באחד או יותר מערכי nodePort שצוינו על ידי השירות.

  • LoadBalancer: לקוחות שולחים בקשות לכתובת ה-IP של מאזן עומסים ברשת.

  • ExternalName: לקוחות פנימיים משתמשים בשם ה-DNS של שירות ככינוי לשם DNS חיצוני.

  • Headless: אפשר להשתמש בשירות headless כשרוצים לקבץ Pod, אבל לא צריך כתובת IP יציבה.

הסוג NodePort הוא הרחבה של הסוג ClusterIP. לכן, לשירות מסוג NodePort יש כתובת IP של אשכול.

הסוג LoadBalancer הוא הרחבה של הסוג NodePort. לכן לשירות מסוג LoadBalancer יש כתובת IP של אשכול וערך אחד או יותר של nodePort.

שירותים מסוג ClusterIP

כשיוצרים שירות מסוג ClusterIP, ‏ Kubernetes יוצר כתובת IP יציבה שאפשר לגשת אליה מצמתים באשכול.

זוהי מניפסט של שירות מסוג ClusterIP:

apiVersion: v1
kind: Service
metadata:
  name: my-cip-service
spec:
  selector:
    app: metrics
    department: sales
  # Create a ClusterIP Service. Kubernetes dynamically allocates an IP address
  # to the Service.
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

אפשר ליצור את השירות באמצעות kubectl apply -f [MANIFEST_FILE]. אחרי שיוצרים את השירות, אפשר להשתמש ב-kubectl get service כדי לראות את כתובת ה-IP הקבועה:

NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
my-cip-service   ClusterIP   10.11.247.213   none          80/TCP

לקוחות באשכול קוראים לשירות באמצעות כתובת ה-IP של האשכול ויציאת ה-TCP שצוינה בשדה port של מניפסט השירות. הבקשה מועברת לאחד מהפודים החברים ביציאת ה-TCP שצוינה בשדה targetPort. בדוגמה הקודמת, לקוח קורא לשירות בכתובת 10.11.247.213 ביציאת TCP 80. הבקשה מועברת לאחד מה-Pods של החברים ביציאת TCP 8080. ל-Pod של החבר חייב להיות מאגר שמקשיב ב-TCP port 8080. אם אין מאגר שמקשיב ליציאה 8080, הלקוחות יראו הודעה כמו 'החיבור נכשל' או 'אי אפשר לגשת לאתר הזה'.

שירות מסוג NodePort

כשיוצרים שירות מסוג NodePort, ‏ Kubernetes נותן ערך של nodePort. אחרי זה אפשר לגשת לשירות באמצעות כתובת ה-IP של כל צומת יחד עם הערך nodePort.

זוהי דוגמה למניפסט של שירות מסוג NodePort:

apiVersion: v1
kind: Service
metadata:
  name: my-np-service
spec:
  selector:
    app: products
    department: sales
  # Kubernetes allocates a nodePort between 30000 and 32767 for external traffic
  # that reaches the node IP address. Additionally, Kubernetes dynamically
  # allocates a clusterIP IP address for internal traffic.
  type: NodePort
  ports:
  - protocol: TCP
    port: 80 # Route internal traffic that reaches the clusterIP IP address on this port.
    targetPort: 8080

אחרי שיוצרים את השירות, אפשר להשתמש ב-kubectl get service -o yaml כדי לראות את המפרט שלו ואת הערך nodePort.

spec:
  clusterIP: 10.11.254.114
  externalTrafficPolicy: Cluster
  ports:
  - nodePort: 32675
    port: 80
    protocol: TCP
    targetPort: 8080

לקוחות חיצוניים קוראים לשירות באמצעות כתובת ה-IP החיצונית של צומת, יחד עם יציאת ה-TCP שצוינה על ידי nodePort. הבקשה מועברת לאחד מ-Pods החברים ביציאת ה-TCP שצוינה בשדה targetPort.

לדוגמה, נניח שכתובת ה-IP החיצונית של אחד מצמתי האשכול היא 203.0.113.2. בדוגמה הקודמת, הלקוח החיצוני קורא לשירות בכתובת 203.0.113.2 ביציאת TCP‏ 32675. הבקשה מועברת לאחד מ-Pods החברים ביציאת TCP‏ 8080. ל-Pod של החבר חייב להיות מאגר שמאזין ליציאת TCP‏ 8080.

סוג השירות NodePort הוא הרחבה של סוג השירות ClusterIP. לכן, ללקוחות פנימיים יש שתי דרכים להתקשר לשירות:

  • משתמשים ב-clusterIP וב-port.
  • משתמשים בכתובת ה-IP של הצומת וב-nodePort.

בהגדרות מסוימות של אשכולות, מאזן העומסים של אפליקציות (ALB) החיצוני משתמש בשירות מסוג NodePort.

מאזן עומסים חיצוני של אפליקציות (ALB) הוא שרת proxy, והוא שונה באופן מהותי ממאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי שמתואר בנושא הזה בקטע שירות מסוג LoadBalancer.

שירותים מסוג LoadBalancer

מידע נוסף על שירותים מסוג LoadBalancer זמין במאמר מושגים שקשורים לשירות LoadBalancer.

שירות מסוג ExternalName

שירות מסוג ExternalName מספק כינוי פנימי לשם DNS חיצוני. לקוחות פנימיים שולחים בקשות באמצעות שם ה-DNS הפנימי, והבקשות מופנות לשם החיצוני.

זוהי דוגמה למניפסט של שירות מסוג ExternalName:

apiVersion: v1
kind: Service
metadata:
  name: my-xn-service
spec:
  type: ExternalName
  externalName: example.com

כשיוצרים שירות, Kubernetes יוצר שם DNS שבעזרתו לקוחות פנימיים יכולים לקרוא לשירות. בדוגמה הקודמת, שם ה-DNS הוא my-xn-service.default.svc.cluster.local. כשלקוח פנימי שולח בקשה אל my-xn-service.default.svc.cluster.local, הבקשה מנותבת מחדש אל example.com.

סוג השירות ExternalName שונה באופן מהותי מסוגי השירותים האחרים. למעשה, שירות מסוג ExternalName לא מתאים להגדרה של שירות שמופיעה בתחילת הנושא הזה. שירות מסוג ExternalName לא משויך לקבוצת Pods, ואין לו כתובת IP יציבה. במקום זאת, שירות מסוג ExternalName הוא מיפוי משם DNS פנימי לשם DNS חיצוני.

Headless Service

שירות ללא ראש הוא סוג של שירות Kubernetes שלא מקצה כתובת IP של אשכול. במקום זאת, שירות ללא ראש משתמש ב-DNS כדי לחשוף את כתובות ה-IP של ה-Pods שמשויכים לשירות. כך תוכלו להתחבר ישירות ל-Pods, במקום דרך שרת Proxy.

שירותים ללא ממשק משתמש שימושיים במגוון תרחישים, כולל:

  • זיהוי שירותים: אפשר להשתמש בשירות ללא ראש כדי להטמיע זיהוי שירותים. כדי להטמיע את זה, יוצרים Service עם שם וסלקטור. רשומת ה-DNS של השירות ללא ראש מכילה את כל כתובות ה-IP של ה-Pods שמאחורי השירות שתואמות לבורר. לקוחות יכולים להשתמש ברשומות ה-DNS האלה כדי למצוא את כתובות ה-IP של ה-Pods שמשויכים לשירות.

  • גישה ישירה ל-Pod: לקוחות יכולים להתחבר ישירות ל-Pods שמשויכים לשירות headless, וזה יכול להיות שימושי לשירותים שדורשים גישה ישירה ל-Pods הבסיסיים, כמו מאזני עומסים ושרתי DNS.

  • גמישות: אפשר להשתמש בשירותים ללא ממשק משתמש כדי ליצור מגוון טופולוגיות שונות, כמו מאזני עומסים, שרתי DNS ומסדי נתונים מבוזרים.

אם יש לכם דרישות רשת מיוחדות לעומסי העבודה שלא ניתן לפתור באמצעות שירותים ללא ראש עם סלקטורים, אפשר גם להשתמש בשירותים ללא ראש ללא סלקטורים. שירותים ללא ראש הם כלי שימושי לגישה לשירותים שלא נמצאים באשכול Kubernetes עצמו, כי מישור הבקרה לא יוצר אובייקטים של EndpointSlice. אפשר לקרוא מידע נוסף במאמר בנושא שירות ללא סלקטורים.

הדוגמה הבאה היא מניפסט של שירות Headless:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  # Prevent Kubernetes from allocating an IP address to the Service.
  clusterIP: None
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    targetPort: 80

אחרי שיוצרים שירות ללא ראש, אפשר להריץ שאילתה ב-DNS כדי למצוא את כתובות ה-IP של ה-Pods שמשויכים לשירות. לדוגמה, הפקודה הבאה מציגה את כתובות ה-IP של ה-Pods שמשויכים לשירות nginx:

dig +short nginx.default.svc.cluster.local

דוגמה נוספת לשימוש בהרחבת שאילתות ב-Kubernetes:

dig +short +search nginx

אפשר ליצור שירות ללא ראש באמצעות פקודה אחת, וקל לעדכן ולשנות את הגודל של שירותים ללא ראש.

kubectl create service clusterip my-svc --clusterip="None" --dry-run=client -o yaml > [file.yaml]

Service abstraction

שירות הוא הפשטה במובן זה שהוא לא תהליך שמקשיב לממשק רשת כלשהו. חלק מההפשטה מיושם בכללי iptables של צמתי האשכול. בהתאם לסוג השירות, חלקים אחרים של ההפשטה מיושמים על ידי מאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי או על ידי מאזן עומסים חיצוני של אפליקציות (ALB).

יציאות שירות שרירותיות

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

הנה שירות, מסוג LoadBalancer, שהערך port שלו הוא 50000:

apiVersion: v1
kind: Service
metadata:
  name: my-ap-service
spec:
  # Set your own IP address for internal traffic instead of letting Kubernetes
  # select one for you.
  clusterIP: 10.11.241.93
  externalTrafficPolicy: Cluster
  ports:
  - nodePort: 30641 # Set a port for external traffic that reaches the node IP address.
    port: 50000 # Set a port for internal traffic that reaches the clusterIP IP address.
    protocol: TCP
    targetPort: 8080
  selector:
    app: parts
    department: engineering
  sessionAffinity: None
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 203.0.113.200

לקוח שולח קריאה לשירות בכתובת 203.0.113.200 ביציאת TCP מספר 50000. הבקשה מועברת לאחד מ-Pods של חברים ביציאת TCP‏ 8080.

יציאות מרובות

השדה ports של Service הוא מערך של אובייקטים מסוג ServicePort. לאובייקט ServicePort יש את השדות הבאים:

  • name
  • protocol
  • port
  • targetPort
  • nodePort

אם יש לכם יותר מ-ServicePort אחד, לכל אחד מהם צריך להיות שם ייחודי.

הנה שירות, מהסוג LoadBalancer, שיש לו שני אובייקטים מסוג ServicePort:

apiVersion: v1
kind: Service
metadata:
  name: my-tp-service
spec:
  clusterIP: 10.11.242.196
  externalTrafficPolicy: Cluster
  ports:
  # Forward traffic that reaches port 31233 on the node IP address, or port
  # 60000 on the clusterIP IP address, to port 50000 on a member Pod.
  - name: my-first-service-port
    nodePort: 31233
    port: 60000
    protocol: TCP
    targetPort: 50000
  # Forward traffic that reaches port 31081 on the node IP address, or port
  # 60001 on the clusterIP IP address, to port 8080 on a member Pod.
  - name: my-second-service-port
    nodePort: 31081
    port: 60001
    protocol: TCP
    targetPort: 8080
  selector:
    app: tests
    department: engineering
  sessionAffinity: None
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 203.0.113.201

בדוגמה הקודמת, אם לקוח קורא לשירות בכתובת 203.0.113.201 ביציאת TCP מספר 60000, הבקשה מועברת אל Pod של חבר ביציאת TCP מספר 50000. אבל אם לקוח קורא לשירות ב-203.0.113.201 ביציאת TCP‏ 60001, הבקשה מועברת ל-Pod של חבר ביציאת TCP‏ 8080.

לכל פוד של חבר צריך להיות מאגר נתונים (container) שמקשיב ביציאת TCP‏ 50000 ומאגר נתונים שמקשיב ביציאת TCP‏ 8080. יכול להיות שמדובר במאגר יחיד עם שני שרשורים, או בשני מאגרים שפועלים באותו Pod.

נקודות קצה של שירותים

כשיוצרים שירות, Kubernetes יוצר אובייקט Endpoints עם אותו שם כמו השירות. מערכת Kubernetes משתמשת באובייקט Endpoints כדי לעקוב אחרי קבוצות ה-Pod ששייכות לשירות.

שירותים עם ערימה יחידה ושירותים עם ערימה כפולה

אפשר ליצור שירות IPv6 מסוג ClusterIP או NodePort.

לכל אחד מסוגי השירותים האלה, אפשר להגדיר את השדות ipFamilies ו-ipFamilyPolicy כ-IPv4,‏ IPv6 או שירות dual-stack.

המאמרים הבאים