הקצאה ושימוש בכונני SSD מקומיים מעורבים לאחסון בלוקים גולמיים ולאחסון זמני

במסמך הזה מוסבר איך להקצות ולחלק כונני SSD מקומיים ב-Google Kubernetes Engine ‏ (GKE) כדי לתמוך בעומסי עבודה שדורשים גם מערכת קבצים רגילה של אחסון זמני וגם אחסון בלוקים לא מעובדים ולא מעוצבים באותו צומת.

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

כשיוצרים אשכול או מאגר צמתים עם קיבולת מעורבת של כונני SSD מקומיים, GKE מחלק כל כונן SSD מקומי שמחובר פיזית באופן הבא:

  • אחסון זמני: קיבולת האחסון הזמני המבוקשת מחולקת באופן שווה בין כונני ה-SSD המקומיים, ומשולבת כדי לספק את אחסון הקבצים הזמני הרגיל של הצומת. כשקיבולת זמנית מחולקת בין כמה כונני SSD מקומיים, האחסון משולב במערך RAID-0. אפשר לגשת לאחסון הארעי באמצעות אמצעי אחסון סטנדרטיים של emptyDir.
  • אחסון בלוקים גולמי: שאר הקיבולת הפיזית בכל דיסק מוצגת לצומת כמכשירי התקן בלוקים נפרדים ולא מעוצבים.

המסמך הזה מיועד למנהלים ולמפעילים של פלטפורמות ול-DevOps שרוצים לבצע אופטימיזציה של ניצול החומרה וביצועי האחסון ב-GKE לעומסי עבודה מיוחדים.

לתשומת ליבכם

בנוסף למגבלות של Compute Engine Local SSD, כדאי לעיין בשיקולים הבאים שספציפיים ל-GKE לפני שמגדירים מצבי Local SSD משולבים:

  • תאימות ל-Autopilot: אשכולות GKE Autopilot לא תומכים באחסון משולב של בלוקים גולמיים ואחסון זמני. במאמר כונני SSD מקומיים באשכולות Autopilot מוסבר איך משתמשים בכונני SSD מקומיים לאחסון זמני רגיל ב-Autopilot.
  • ‫ComputeClasses בהתאמה אישית: אי אפשר להשתמש בהגדרות משולבות של כונני SSD מקומיים ב-ComputeClasses בהתאמה אישית.
  • מערכת הפעלה: מצב משולב של Local SSD נתמך רק בתמונות של צומתי Linux (מערכת הפעלה שמותאמת לקונטיינרים ו-Ubuntu). אין תמיכה במאגרי צמתים של Windows Server.
  • אירועי תחזוקה: אנחנו לא ממליצים להשתמש ב-SSD מקומי כאחסון בלוקים גולמי למכונות וירטואליות מסוג Z3 או Z4D שעברו אופטימיזציה לאחסון. יכול להיות שהנתונים במכונות הווירטואליות האלה לא יישמרו באירועי תחזוקה. מידע נוסף זמין במאמר הקצאה ושימוש באחסון בלוקים גולמיים שמגובה ב-SSD מקומי.
  • קלט/פלט משותף: מכיוון שגם מערך RAID-0 הזמני וגם מחיצות הבלוקים הגולמיים המטורגטים מסתמכים על אותם כונני SSD פיזיים, פעולות אינטנסיביות במחיצה לוגית אחת עשויות להשפיע על התפוקה של המחיצה השנייה. למידע על מגבלות קצב העברת הנתונים המקסימלי, ראו ביצועי SSD מקומי.
  • תמחור: כשמשתמשים במצבים משולבים כדי לחלק את הקיבולת של ה-SSD המקומי, משלמים על הקיבולת הפיזית הכוללת של ה-SSD המקומי שמחובר לצומת, בלי קשר לכמות הקיבולת שמוקצית לאחסון זמני בהשוואה לאחסון בלוקים גולמי. מידע נוסף זמין במאמר תמחור של כונני SSD מקומיים.
  • הגדרה ידנית: כדי להשתמש באחסון בלוקים גולמיים שמגובה ב-SSD מקומי, צריך להגדיר את המכשירים באופן ידני כי GKE לא מעצב או טוען את המחיצות של הבלוקים הגולמיים. אתם גם אחראים לנהל את הקיבולת של הצמתים ולטפל בשיבושים פוטנציאליים בעומסי עבודה שממוקמים באותו מקום. שימוש באובייקט PersistentVolume מקומי מספק תזמון Pod משולב. אם עומסי העבודה לא דורשים שהנתונים יישמרו לאורך מחזור החיים של ה-Pod, או אם אין להם צרכים מיוחדים של ביצועים, השתמשו במקום זאת בהגדרה של כונן SSD מקומי זמני לחלוטין. ההגדרה הזמנית המלאה מתאימה לרוב תרחישי השימוש.
  • החלפת זיכרון של צומת: אתם יכולים לשלב קיבולת מעורבת של כונני SSD מקומיים עם החלפת זיכרון של צומת באמצעות פרופיל דיסק האתחול או פרופיל כונן ה-SSD המקומי הזמני. הפרופיל הייעודי של Local SSD לא נתמך בקיבולת משולבת של Local SSD. מידע נוסף זמין במאמר החלפת זיכרון של צומת עם קיבולת מעורבת של כונני SSD מקומיים.

לפני שמתחילים

לפני שמתחילים, חשוב לוודא שביצעתם את המשימות הבאות:

  • מפעילים את Google Kubernetes Engine API.
  • הפעלת Google Kubernetes Engine API
  • כדי להשתמש ב-Google Cloud CLI למשימה הזו, מתקינים ואז מאתחלים את ה-CLI של gcloud. אם כבר התקנתם את ה-CLI של gcloud, הריצו את הפקודה gcloud components update כדי לקבל את הגרסה העדכנית. יכול להיות שבגרסאות קודמות של ה-CLI של gcloud אי אפשר להריץ את הפקודות שבמסמך הזה.

כדי להשתמש בתכונה הזו, צומתי GKE צריכים לפעול בגרסה 1.37.0-gke.3570000 ואילך.

התפקידים הנדרשים

כדי לקבל את ההרשאות שנדרשות ליצירת אשכולות ומאגרי צמתים עם קיבולת משולבת של כונני Local SSD, צריך לבקש מהאדמין להקצות לכם את התפקיד אדמין של Kubernetes Engine (roles/container.admin) ב-IAM בפרויקט. כדי לקרוא הסבר על מתן תפקידים, ראו איך מנהלים את הגישה ברמת הפרויקט, התיקייה והארגון.

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

יצירת אשכול או מאגר צמתים עם אחסון משולב ב-Local SSD

אפשר להקצות קיבולת זמנית ולשריין מחיצות של בלוקים גולמיים באמצעות המאפיין capacity של הדגל --ephemeral-storage-local-ssd.

יוצרים את האשכול או את מאגר הצמתים על ידי ציון שדה המשנה capacity באמצעות הדגל --ephemeral-storage-local-ssd.

אם סוג המכונה מאפשר להגדיר מספר של כונני SSD מקומיים, צריך לציין גם את שדה המשנה count. אם לסוג המכונה יש מספר קבוע של כונני SSD מקומיים, למשל סוגי -lssd, אפשר להשמיט את שדה המשנה count כי GKE מחשב באופן אוטומטי את מספר הדיסקים על סמך צורת ה-VM.

יצירת אשכול עם אחסון משולב ב-SSD מקומי

כדי ליצור אשכול חדש, משתמשים בפקודה gcloud container clusters create. ההגדרות האלה חלות רק על מאגר הצמתים שמוגדר כברירת מחדל. אם במאגרי הצמתים הבאים צריך אחסון מעורב ב-SSD מקומי, צריך לציין את הדגל --ephemeral-storage-local-ssd כשיוצרים את מאגר הצמתים. מספר הדיסקים המקסימלי שאפשר לצרף משתנה בהתאם לסוג המכונה ולאזור.

אם גרסת האשכול שלכם היא לפני 1.37.0-gke.3570000, הוסיפו את אחד מהדגלים הבאים לפקודות הבאות ליצירת אשכול:

  • הדגל --cluster-version=<var>VERSION</var> עם <var>VERSION</var> מוגדר לערך 1.37.0-gke.3570000 ואילך, או
  • הדגל --release-channel=rapid.

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

gcloud container clusters create CLUSTER_NAME \
    --machine-type=MACHINE_TYPE \
    --ephemeral-storage-local-ssd count=NUMBER_OF_DISKS,capacity=CAPACITY

לסוגי מכונות עם מספר קבוע של כונני SSD מקומיים, מריצים את הפקודה הבאה:

gcloud container clusters create CLUSTER_NAME \
    --machine-type=MACHINE_TYPE \
    --ephemeral-storage-local-ssd capacity=CAPACITY

מחליפים את מה שכתוב בשדות הבאים:

  • ‫CLUSTER_NAME: השם של האשכול שרוצים ליצור.
  • ‫MACHINE_TYPE: סוג המכונה שבה משתמשים. לרשימה של סוגי מכונות נתמכים, ראו תמיכה בסדרת מכונות. אי אפשר להשתמש בכונני SSD מקומיים עם סוג המכונה שמוגדר כברירת מחדל (e2-medium), כי סוגי מכונות עם ליבה משותפת (shared-core) לא תומכים בהם.
  • ‫NUMBER_OF_DISKS: בסוגי מכונות עם מספר דיסקים מקומיים מסוג SSD שאפשר להגדיר, מספר הדיסקים המקומיים מסוג SSD שצריך להקצות בכל צומת.
  • ‫CAPACITY: הקיבולת הכוללת של כונן ה-SSD המקומי שצריך לפרמט לאחסון זמני. הערך הזה חייב להיות גדול מ-0 וקטן מהקיבולת הפיזית הכוללת של ה-SSD המקומי (מספר הדיסקים × הגודל של כל דיסק), למשל 200GB. שימו לב לנקודות הבאות:

    • אי אפשר לשנות את הערך אחרי שיוצרים את האשכול.
    • ‫GKE שומר חלק מהקיבולת הזו לשימוש המערכת, כך שהאחסון הארעי שניתן להקצאה קטן מ-CAPACITY. מידע נוסף זמין במאמר הקצאת אחסון מקומי זמני.

יצירת מאגר צמתים עם אחסון משולב ב-SSD מקומי

כדי להוסיף מאגר צמתים לאשכול קיים, משתמשים בפקודה gcloud container node-pools create.

אם הגרסה של מאגר הצמתים שלכם היא לפני 1.37.0-gke.3570000, אתם צריכים:

  • מוסיפים את הדגל --node-version=<var>VERSION</var> לפקודות הבאות ליצירת מאגר צמתים, ומגדירים את <var>VERSION</var> לגרסה 1.37.0-gke.3570000 ואילך.
  • מוודאים שמישור הבקרה של האשכול הוא מגרסה 1.37.0-gke.3570000 ואילך.

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

gcloud container node-pools create POOL_NAME \
    --cluster=CLUSTER_NAME \
    --machine-type=MACHINE_TYPE \
    --ephemeral-storage-local-ssd count=NUMBER_OF_DISKS,capacity=CAPACITY

לסוגי מכונות עם מספר קבוע של כונני SSD מקומיים, מריצים את הפקודה הבאה:

gcloud container node-pools create POOL_NAME \
    --cluster=CLUSTER_NAME \
    --machine-type=MACHINE_TYPE \
    --ephemeral-storage-local-ssd capacity=CAPACITY

מחליפים את מה שכתוב בשדות הבאים:

  • ‫POOL_NAME: השם של מאגר הצמתים החדש שרוצים ליצור.
  • ‫CLUSTER_NAME: שם האשכול.
  • ‫MACHINE_TYPE: סוג המכונה שבה משתמשים. לרשימה של סוגי מכונות נתמכים, ראו תמיכה בסדרת מכונות. אי אפשר להשתמש בכונני SSD מקומיים עם סוג המכונה שמוגדר כברירת מחדל (e2-medium), כי סוגי מכונות עם ליבה משותפת (shared-core) לא תומכים בהם.
  • ‫NUMBER_OF_DISKS: בסוגי מכונות עם מספר דיסקים מקומיים מסוג SSD שאפשר להגדיר, מספר הדיסקים המקומיים מסוג SSD שצריך להקצות בכל צומת.
  • ‫CAPACITY: הקיבולת הכוללת של כונן ה-SSD המקומי שצריך לפרמט לאחסון זמני. הערך הזה חייב להיות גדול מ-0 וקטן מהקיבולת הפיזית הכוללת של ה-SSD המקומי (מספר הדיסקים × הגודל של כל דיסק), למשל 200GB. שימו לב לנקודות הבאות:

    • אי אפשר לשנות את הערך אחרי שיוצרים את מאגר הצמתים.
    • ‫GKE שומר חלק מהקיבולת הזו לשימוש המערכת, כך שהאחסון הארעי שניתן להקצאה קטן מ-CAPACITY. מידע נוסף זמין במאמר הקצאת אחסון מקומי זמני.

אימות תוויות הצמתים

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

kubectl describe node NODE_NAME

הפלט צריך לכלול את שתי תוויות הצומת הבאות:

  • cloud.google.com/gke-ephemeral-storage-local-ssd: "true"
  • cloud.google.com/gke-local-nvme-ssd-partition: "true"

גישה לקיבולת הארעית

אתם יכולים לגשת לאחסון הארעי ששמרתם דרך emptyDir volumes בדיוק כמו שהייתם ניגשים לכל נפח אחסון ארעי אחר ב-GKE.

גישה לקיבולת הבלוקים הגולמית

כדי לגשת לקיבולת הבלוקים הגולמית, יוצרים אובייקט StorageClass, מקצים אובייקט PersistentVolume מקומי ותובעים אותו באמצעות אובייקט PersistentVolumeClaim.

יצירת StorageClass

מכיוון שאתם מקצים את אובייקטי PersistentVolume ללא מקצה סטטי של הקהילה, מומלץ ליצור אובייקט StorageClass עם השדה volumeBindingMode: WaitForFirstConsumer. כך הקישור מתעכב עד לתזמון ה-Pod, ו-Kubernetes בוחר כונן SSD מקומי מצומת מתאים שיכול להריץ את ה-Pod.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: "local-storage"
provisioner: "kubernetes.io/no-provisioner"
volumeBindingMode: "WaitForFirstConsumer"

יצירת PersistentVolume

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

כדי ליצור את האובייקט PersistentVolume, מבצעים את השלבים הבאים כדי למצוא את נתיב הקישור הסמלי למחיצת ה-SSD המקומי, לקבוע את הקיבולת המדויקת שנותרה ולהגדיר את המניפסט.

איך בודקים את קיבולת המחיצה

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

kubectl debug node/NODE_NAME -it --image=ubuntu -- \
    sh -c 'ls -l /host/dev/disk/by-id/ | grep partition; lsblk -o NAME,SIZE,TYPE'

מחליפים את NODE_NAME בשם הצומת שאליו מחובר ה-SSD המקומי.

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

לדוגמה, אם מחברים שני כונני SSD מקומיים לצומת ומקצים 200GB לאחסון זמני (capacity=200GB), הפלט דומה לזה:

google-local-ssd-partition0 -> /dev/nvme0n1p2
google-local-ssd-partition1 -> /dev/nvme0n2p2
NAME          SIZE TYPE
...
nvme0n1       375G disk
|-nvme0n1p1   100G part
| `-md0     199.9G raid0
`-nvme0n1p2   275G part
nvme0n2       375G disk
|-nvme0n2p1   100G part
| `-md0     199.9G raid0
`-nvme0n2p2   275G part

שימו לב למיפוי בין הקישור הסימבולי google-local-ssd-partition לבין שם המכשיר הבסיסי, וגם לגודל של המחיצה הזו. משתמשים בגודל בתור PARTITION_SIZE ובנתיב הקישור הסימבולי בתור LOCAL_SSD_SYMLINK_PATH במניפסט של PersistentVolume.

לדוגמה, בפלט שלמעלה, הקישור הסימבולי /dev/disk/by-id/google-local-ssd-partition0 מפנה למחיצה nvme0n1p2, שהגודל שלה הוא 275G.

הפקודה kubectl debug משאירה Pod זמני שפועל במרחב השמות שמוגדר כברירת מחדל. כדי למנוע ממנו לצרוך משאבים, מוחקים את ה-Pod הזה באמצעות הפקודה הבאה:

kubectl delete pod DEBUG_POD_NAME

מחליפים את DEBUG_POD_NAME בשם של ה-Pod שנוצר על ידי הפקודה kubectl debug, שמוצג כשמריצים את הפקודה.

הגדרת המניפסט של PersistentVolume

מגדירים את המניפסט של אמצעי האחסון עם גודל המחיצה שמצאתם. השדה nodeAffinity הוא חובה לנפחי אחסון מקומיים. כך אפשר לוודא ש-Kubernetes מתזמן Pods שתובעים את הנפח הזה לצומת הספציפי שבו ה-SSD המקומי מחובר פיזית. הדוגמה הבאה מציגה מניפסט למחיצה של Local SSD עם בלוק גולמי:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: "example-local-pv"
spec:
  capacity:
    storage: PARTITION_SIZE
  accessModes:
  - "ReadWriteOnce"
  volumeMode: "Block"
  persistentVolumeReclaimPolicy: "Retain"
  storageClassName: "local-storage"
  local:
    path: "LOCAL_SSD_SYMLINK_PATH"
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: "kubernetes.io/hostname"
          operator: "In"
          values:
          - "NODE_NAME"

מחליפים את מה שכתוב בשדות הבאים:

  • ‫PARTITION_SIZE: הקיבולת הגולמית בפועל של הבלוקים שנותרה, שמצאתם בשלב הראשון. משתמשים בסיומת Gi (לדוגמה, 275Gi) כדי שהערך יתאים בדיוק לקיבולת של המחיצה.
  • ‫NODE_NAME: השם של הצומת שאליו מחובר ה-SSD המקומי.
  • ‫LOCAL_SSD_SYMLINK_PATH: הנתיב של הקישור הסמלי למחיצה הלא מעוצבת של ה-SSD המקומי שמצאתם בשלב הראשון. לכל כונן SSD מקומי שמחובר למאגר הצמתים, מערכת ההפעלה המארחת יוצרת קישור סימבולי כדי לגשת לקיבולת הבלוקים הגולמיים שנותרה. הנתיבים מבוססים על תבנית עם אינדקס שמתחיל ב-0. לדוגמה, אם מחברים שלושה כונני SSD מקומיים, הנתיבים הגולמיים של הבלוקים למחיצות הם:
    • /dev/disk/by-id/google-local-ssd-partition0
    • /dev/disk/by-id/google-local-ssd-partition1
    • /dev/disk/by-id/google-local-ssd-partition2

יצירת PersistentVolumeClaim

יוצרים אובייקט PersistentVolumeClaim כדי לתבוע את אמצעי האחסון. המפרט התואם נראה כך:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: "example-local-pvc"
spec:
  accessModes:
  - "ReadWriteOnce"
  volumeMode: "Block"
  storageClassName: "local-storage"
  resources:
    requests:
      storage: PARTITION_SIZE

שימוש בנפח ב-Pod

אפשר להשתמש באובייקט PersistentVolume ב-Pod על ידי הפניה לאובייקט PersistentVolumeClaim במפרט volumeDevices. הדוגמה הבאה היא מניפסט של Pod שמשתמש בבקשה example-local-pvc:

apiVersion: v1
kind: Pod
metadata:
  name: "example-pod"
spec:
  containers:
  - name: "app"
    image: "ubuntu:22.04"
    command: ["/bin/sh", "-c"]
    args: ["tail -f /dev/null"]
    volumeDevices:
    - name: "raw"
      devicePath: "/dev/xvda"
  volumes:
  - name: "raw"
    persistentVolumeClaim:
      claimName: "example-local-pvc"

בגלל השימוש במקצה kubernetes.io/no-provisioner, ‏GKE לא מנקה באופן אוטומטי את נפחי האחסון המקומיים. אם מוחקים את האובייקט PersistentVolume, צריך למחוק ידנית את הנתונים מהדיסק הבסיסי כדי שיהיה אפשר להשתמש בו שוב בבטחה.

פתרון בעיות

הוראות לפתרון בעיות מפורטות במאמר פתרון בעיות באחסון ב-GKE.

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