הגדרת מכונות וירטואליות עם מודעות ל-NUMA

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

במכונה וירטואלית שתומכת ב-NUMA, כל התקשורת נשארת מקומית לצומת ה-NUMA. הארכיטקטורה הזו מונעת העברות נתונים מרחוק שעלולות לפגוע בביצועים.

הגדרת צמתים לשימוש ב-NUMA

בקטעים הבאים מוסבר איך להגדיר את הרכיבים הקריטיים של Kubernetes כדי לכוונן את הצומת ולוודא שהוא יכול לתזמן קונטיינרים שמותאמים ל-NUMA. צומתי ה-NUMA האלה מותאמים לאופטימיזציה של ביצועי המעבד והזיכרון. פועלים לפי ההוראות לכל צומת שרוצים להריץ בו מכונות וירטואליות שתומכות ב-NUMA.

עדכון ההגדרות של kubelet

כחלק מהגדרת הצומת לתמיכה ב-NUMA node affinity, צריך לבצע את השינויים הבאים בהגדרת ה-kubelet:

  • הפעלת מנהל המעבד (CPU) באמצעות מדיניות static
  • הפעלת מנהל הזיכרון באמצעות מדיניות Static
  • הפעלת מנהל הטופולוגיה עם טופולוגיה של restricted

כדי להגדיר את kubelet בצומת העובד:

  1. מאתרים את הקובץ kubelet בצומת העובד ופותחים אותו לעריכה:

    edit /etc/default/kubelet
    

    אם הקובץ kubelet לא מופיע, יוצרים אותו באמצעות הפקודה הבאה:

    echo "KUBELET_EXTRA_ARGS=\"\"" >> /etc/default/kubelet
    

    הפקודה הזו יוצרת את הקובץ kubelet עם קטע KUBELET_EXTRA_ARGS="" ריק.

  2. כדי להפעיל את מנהל המעבד (CPU) עם מדיניות static, מוסיפים את הדגל --cpu-manager-policy=static לקטע KUBELET_EXTRA_ARGS="" בקובץ:

    KUBELET_EXTRA_ARGS="--cpu-manager-policy=static"
    
  3. כדי להפעיל את מנהל הזיכרון באמצעות מדיניות Static, מוסיפים את הדגל --memory-manager-policy=Static לקטע KUBELET_EXTRA_ARGS="" בקובץ:

    KUBELET_EXTRA_ARGS="--cpu-manager-policy=static --memory-manager-policy=Static"
    
  4. כדי להפעיל את Topology Manager עם מדיניות restricted, מוסיפים את הדגל --topology-manager-policy=restricted לקטע KUBELET_EXTRA_ARGS="" בקובץ:

    KUBELET_EXTRA_ARGS="--cpu-manager-policy=static --memory-manager-policy=Static --topology-manager-policy=restricted"
    
  5. בודקים את כמות הזיכרון הנוכחית ששמורה ל-Google Distributed Cloud:

    cat /var/lib/kubelet/kubeadm-flags.env
    

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

    KUBELET_KUBEADM_ARGS="--anonymous-auth=false --authentication-token-webhook=true --authorization-mode=Webhook --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --feature-gates=SeccompDefault=true --kube-reserved=cpu=100m,memory=3470Mi --max-pods=110 --node-ip=192.168.1.190 --node-labels=baremetal.cluster.gke.io/k8s-ip=192.168.1.190,baremetal.cluster.gke.io/namespace=cluster-user001,baremetal.cluster.gke.io/node-pool=node-pool-1,cloud.google.com/gke-nodepool=node-pool-1 --pod-infra-container-image=gcr.io/anthos-baremetal-release/pause-amd64:3.1-gke.5 --provider-id=baremetal://192.168.1.190 --read-only-port=10255 --rotate-server-certificates=true --seccomp-default=true"

    ההגדרה --kube-reserved=cpu=100m,memory=3470Mi מציינת ש-Google Distributed Cloud שמר 3,470 mebibytes של זיכרון בצומת.

  6. מגדירים את הדגל --reserved-memory בקטע KUBELET_EXTRA_ARGS בקובץ kubelet ל-100 mebibytes יותר מהזיכרון השמור הנוכחי, כדי להביא בחשבון את סף הפינוי. אם אין זיכרון שמור, אפשר לדלג על השלב הזה.

    לדוגמה, אם הזיכרון השמור הוא 3470Mi מהדוגמה בשלב הקודם, אתם שומרים זיכרון של 3570Mi בקובץ kubelet:

    KUBELET_EXTRA_ARGS="--cpu-manager-policy=static --memory-manager-policy=Static --topology-manager-policy=restricted --reserved-memory=0:memory=3570Mi"
    
  7. מסירים את קובצי תמונת המצב של המעבד (CPU) והזיכרון מהספרייה /var/lib:

    rm /var/lib/cpu_manager_state
    rm /var/lib/memory_manager_state
    
  8. מפעילים מחדש את kubelet:

    systemctl start kubelet
    

מידע נוסף על הגדרות המדיניות האלה זמין במאמרי העזרה הבאים של Kubernetes:

הגדרת הצומת לשימוש ב-hugepages

אחרי שמפעילים את Memory Manager באמצעות המדיניות Static, אפשר להוסיף דפים ענקיים כדי לשפר עוד יותר את הביצועים של עומסי העבודה של מאגרים בצומתי NUMA. כמו שאפשר להבין מהשם, Hugepages מאפשרים לציין דפי זיכרון שגדולים יותר מהגודל הרגיל של 4 קיביבייט (KiB). ב-VM Runtime on GDC יש תמיכה בדפים גדולים בנפח 2MiB ו-1GiB. אפשר להגדיר דפים גדולים לצומת בזמן הריצה, או כשהמכונה של הצומת מבצעת אתחול. מומלץ להגדיר hugepages בכל צומת שבו רוצים להריץ מכונות וירטואליות עם תמיכה ב-NUMA.

  1. כדי להגדיר את מספר ה-hugepages בגודל מסוים בצומת NUMA בזמן הריצה, משתמשים בפקודה הבאה:

    echo HUGEPAGE_QTY > \
        /sys/devices/system/node/NUMA_NODE/hugepages/hugepages-HUGEPAGE_SIZEkB/nr_hugepages
    

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

    • ‫HUGEPAGE_QTY: מספר ה-hugepages שצריך להקצות בגודל שצוין.

    • ‫NUMA_NODE: צומת ה-NUMA, כמו node0, שאליו מקצים דפים גדולים.

    • ‫HUGEPAGE_SIZE: הגודל של דפי ה-hugepages ב-KiB, ‏2048 (2MiB) או 1048576 (1GiB).

הגדרת מכונה וירטואלית לשימוש בצומת NUMA

אחרי שמתאימים את צומתי האשכול ל-NUMA, אפשר ליצור מכונות וירטואליות שתומכות ב-NUMA. מכונות וירטואליות שתומכות ב-NUMA מתוזמנות בצומתי NUMA.

כדי ליצור מכונה וירטואלית שתואמת ל-NUMA:

  1. פועלים לפי ההוראות ליצירת VM ממניפסט.

    כדי להגדיר את המכונה הווירטואלית כך שתהיה מותאמת ל-NUMA, משתמשים בהגדרות הבאות של compute:

    • ‫spec.compute.guaranteed: מגדירים את guaranteed להיות true. בהגדרה הזו, ה-Pod של virt-launcher מוגדר כך שיוצב ב-Kubernetes Guaranteed Quality of Service (QoS) class.

    • spec.compute.advancedCompute:

      • ‫dedicatedCPUPlacement: מגדירים את dedicatedCPUPlacement להיות true. ההגדרה הזו מצמידה מעבדים וירטואליים למעבדים הפיזיים של הצומת.
      • ‫hugePageSize: מגדירים את hugePageSize לערך 2Mi או 1Gi כדי לציין את גודל ה-hugepages לשימוש ב-VM, ‏2 mebibyte או 1 gibibyte.
      • ‫numaGuestMappingPassthrough: צריך לכלול מבנה ריק ({}) להגדרה הזו. ההגדרה הזו קובעת את הזיקה ל-NUMA, כך שהמכונה הווירטואלית תתוזמן רק בצומתי NUMA.

    במניפסט הבא של VirtualMachine אפשר לראות איך נראית הגדרה של VM עם תמיכה ב-NUMA:

    apiVersion: vm.cluster.gke.io/v1
    kind: VirtualMachine
    metadata:
      name: vm1
    spec:
      compute:
        cpu:
          vcpus: 2
        guaranteed: true
        advancedCompute:
          dedicatedCPUPlacement: true
          hugePageSize: 2Mi
          numaGuestMappingPassthrough: {}
        memory:
          capacity: 256Mi
      interfaces:
      - name: eth0
        networkName: pod-network
        default: true
      disks:
      - virtualMachineDiskName: disk-from-gcs
        boot: true
        readOnly: true