Batchsystem mit Kueue bereitstellen

In dieser Anleitung erfahren Sie, wie Sie verfügbare Ressourcen optimieren, indem Sie Jobs mit Kueue in Google Kubernetes Engine (GKE) planen. In dieser Anleitung erfahren Sie, wie Sie mit Kueue Batchjobs effektiv verwalten und planen, die Ressourcennutzung verbessern und die Arbeitslastverwaltung vereinfachen. Sie richten einen freigegebenen Cluster für zwei Mandantenteams ein, wobei jedes Team einen eigenen Namespace hat und Jobs erstellt, die globale Ressourcen gemeinsam nutzen. Sie konfigurieren Kueue auch so, dass die Jobs basierend auf den von Ihnen definierten Ressourcenkontingenten geplant werden.

Diese Anleitung richtet sich an Cloud-Architekten und Plattformentwickler, die ein Batchsystem mit GKE implementieren möchten. Weitere Informationen zu gängigen Rollen und Beispielaufgaben, auf die in Google Cloud-Inhalten verwiesen wird, finden Sie unter Häufig verwendete GKE-Nutzerrollen und ‑Aufgaben.

Machen Sie sich vor dem Lesen dieser Seite mit den folgenden Themen vertraut:

Hintergrund

Jobs sind Anwendungen, die bis zum Abschluss ausgeführt werden, z. B. für maschinelles Lernen, Rendering, Simulation, Analyse, CI/CD und ähnliche Arbeitslasten.

Kueue ist ein cloudnativer Job-Scheduler, der mit dem Kubernetes-Standard-Scheduler, dem Job-Controller und dem Cluster Autoscaler zusammenarbeitet, um ein End-to-End-Batchsystem bereitzustellen. Kueue implementiert die Jobwarteschlangen und entscheidet anhand der Kontingente und einer Hierarchie für die faire Freigabe von Ressourcen, wann Jobs warten und wann sie gestartet werden sollen.

Kueue hat die folgenden Eigenschaften:

  • Es ist für Cloud-Architekturen optimiert, in denen Ressourcen heterogen, austauschbar und skalierbar sind.
  • Es bietet eine Reihe von APIs zum Verwalten von elastischen Kontingenten und zum Verwalten der Jobwarteschlange.
  • Es werden keine vorhandenen Funktionen wie Autoscaling, Pod-Planung oder Job-Lebenszyklusverwaltung neu implementiert.
  • Kueue bietet integrierte Unterstützung für die Kubernetesbatch/v1.Job API.
  • Sie kann in andere Job-APIs eingebunden werden.

Kueue bezieht sich auf Jobs, die mit einer beliebigen API als Arbeitslasten definiert sind, um Verwechslungen mit der spezifischen Kubernetes-Job-API zu vermeiden.

ResourceFlavor erstellen

Ein ResourceFlavor ist ein Objekt, das die Variationen der in Ihrem Cluster verfügbaren Knoten darstellt, indem es sie mit Knotenlabels und Markierungen verknüpft. Sie können mit ResourceFlavors beispielsweise VMs mit unterschiedlichen Garantien (z. B. Spot im Vergleich zu On-Demand), Architekturen (z. B. x86 im Vergleich zu ARM-CPUs), Marken und Modellen (z. B. Nvidia A100 im Vergleich zu T4-GPUs) darstellen.

In dieser Anleitung hat der kueue-autopilot-Cluster homogene Ressourcen. Erstellen Sie daher einen einzelnen ResourceFlavor für CPU, Arbeitsspeicher, temporären Speicher und GPUs ohne Labels oder Taints.

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: default-flavor # This ResourceFlavor will be used for all the resources
Stellen Sie das ResourceFlavor bereit:

kubectl apply -f flavors.yaml

ClusterQueue erstellen

Eine ClusterQueue ist ein clusterbezogenes Objekt, das einen Pool von Ressourcen wie CPU, Arbeitsspeicher und GPU verwaltet. Sie verwaltet die ResourceFlavors, begrenzt die Nutzung und legt die Reihenfolge fest, in der Arbeitslasten zugelassen werden.

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: cluster-queue
spec:
  namespaceSelector: {} # Available to all namespaces
  queueingStrategy: BestEffortFIFO # Default queueing strategy
  resourceGroups:
  - coveredResources: ["cpu", "memory", "nvidia.com/gpu", "ephemeral-storage"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 10
      - name: "memory"
        nominalQuota: 10Gi
      - name: "nvidia.com/gpu"
        nominalQuota: 10
      - name: "ephemeral-storage"
        nominalQuota: 10Gi

ClusterQueue bereitstellen:

kubectl apply -f cluster-queue.yaml

Die Reihenfolge der Nutzung wird durch .spec.queueingStrategy bestimmt. Es gibt zwei Konfigurationen:

  • BestEffortFIFO

    • Die Standardkonfiguration der Warteschlangenstrategie.
    • Die Aufnahme von Arbeitslasten erfolgt nach der FIFO-Regel (first in first out). Wenn jedoch das Kontingent nicht ausreicht, um die Arbeitslast am Anfang der Warteschlange aufzunehmen, wird die nächste in der Reihe versucht.
  • StrictFIFO

    • Garantiert FIFO-Semantik.
    • Arbeitslasten am Anfang der Warteschlange können die Warteschlange blockieren, bis die Arbeitslast zugelassen werden kann.

In cluster-queue.yaml erstellen Sie eine neue ClusterQueue namens cluster-queue. Diese ClusterQueue verwaltet vier Ressourcen: cpu, memory, nvidia.com/gpu und ephemeral-storage mit dem in flavors.yaml erstellten Flavor. Das Kontingent wird durch die Anfragen in den Arbeitslast-Pod-Spezifikationen verbraucht.

Für jede Variante gelten Nutzungslimits, die als .spec.resourceGroups[].flavors[].resources[].nominalQuota dargestellt werden. In diesem Fall werden Arbeitslasten in die ClusterQueue aufgenommen, wenn:

  • Die Summe der CPU-Anforderungen ist kleiner oder gleich 10.
  • Die Summe der Speicheranfragen ist kleiner oder gleich 10 Gi.
  • Die Summe der GPU-Anfragen ist kleiner oder gleich 10.
  • Die Summe des verwendeten Speichers ist kleiner oder gleich 10 Gi.

LocalQueue erstellen

Eine LocalQueue ist ein Namespace-Objekt, das Arbeitslasten von Nutzern im Namespace akzeptiert. LocalQueues aus verschiedenen Namespaces können auf dieselbe ClusterQueue verweisen, in der sie sich das Ressourcenkontingent teilen können. In diesem Fall verweisen LocalQueue aus dem Namespace team-a und team-b auf dieselbe ClusterQueue cluster-queue unter .spec.clusterQueue.

apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-a # LocalQueue under team-a namespace
  name: lq-team-a
spec:
  clusterQueue: cluster-queue # Point to the ClusterQueue
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-b # LocalQueue under team-b namespace
  name: lq-team-b
spec:
  clusterQueue: cluster-queue # Point to the ClusterQueue

Jedes Team sendet seine Arbeitslasten an die LocalQueue im eigenen Namespace. Diesen werden dann von der ClusterQueue Ressourcen zugewiesen.

LocalQueues bereitstellen:

kubectl apply -f local-queue.yaml

Jobs erstellen und zugelassene Arbeitslasten beobachten

In diesem Abschnitt erstellen Sie Kubernetes-Jobs im Namespace team-a. Ein Jobcontroller in Kubernetes erstellt einen oder mehrere Pods und sorgt dafür, dass sie eine bestimmte Aufgabe erfolgreich ausführen.

Der Job im Namespace team-a hat die folgenden Attribute:

  • Sie verweist auf die LocalQueue lq-team-a.
  • GPU-Ressourcen werden angefordert, indem das Feld nodeSelector auf nvidia-tesla-t4 gesetzt wird.
  • Er besteht aus drei Pods, die parallel zehn Sekunden lang in den Ruhemodus wechseln. Jobs werden entsprechend dem Wert im Feld ttlSecondsAfterFinished nach 60 Sekunden bereinigt.
  • Dafür sind 1.500 milliCPU, 1.536 Mi Arbeitsspeicher, 1.536 Mi sitzungsspezifischer Speicher und drei GPUs erforderlich, da es drei Pods gibt.
apiVersion: batch/v1
kind: Job
metadata:
  namespace: team-a # Job under team-a namespace
  generateName: sample-job-team-a-
  annotations:
    kueue.x-k8s.io/queue-name: lq-team-a # Point to the LocalQueue
spec:
  ttlSecondsAfterFinished: 60 # Job will be deleted after 60 seconds
  parallelism: 3 # This Job will have 3 replicas running at the same time
  completions: 3 # This Job requires 3 completions
  suspend: true # Set to true to allow Kueue to control the Job when it starts
  template:
    spec:
      nodeSelector:
        cloud.google.com/gke-accelerator: "nvidia-tesla-t4" # Specify the GPU hardware
      containers:
      - name: dummy-job
        image: gcr.io/k8s-staging-perf-tests/sleep:latest
        args: ["10s"] # Sleep for 10 seconds
        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
            ephemeral-storage: "512Mi"
            nvidia.com/gpu: "1"
          limits:
            cpu: "500m"
            memory: "512Mi"
            ephemeral-storage: "512Mi"
            nvidia.com/gpu: "1"
      restartPolicy: Never

Jobs werden auch unter der Datei job-team-b.yaml erstellt, deren Namespace zu team-b gehört, mit Anfragen, die verschiedene Teams mit unterschiedlichen Bedürfnissen repräsentieren.

Weitere Informationen finden Sie unter GPU-Arbeitslasten in Autopilot bereitstellen.

  1. Beobachten Sie in einem neuen Terminal den Status der ClusterQueue, der alle zwei Sekunden aktualisiert wird:

    watch -n 2 kubectl get clusterqueue cluster-queue -o wide
    
  2. Beobachten Sie in einem neuen Terminal den Status der Knoten:

    watch -n 2 kubectl get nodes -o wide
    
  3. Erstellen Sie in einem neuen Terminal alle 10 Sekunden Jobs für die LocalQueue aus den Namespaces team-a und team-b:

    ./create_jobs.sh job-team-a.yaml job-team-b.yaml 10
    
  4. Beobachten Sie, wie Jobs in die Warteschlange gestellt und in der ClusterQueue zugelassen werden und wie Knoten mit GKE Autopilot erstellt werden.

  5. Job aus dem Namespace team-a abrufen:

    kubectl -n team-a get jobs
    

    Die Ausgabe sieht in etwa so aus:

    NAME                      COMPLETIONS   DURATION   AGE
    sample-job-team-b-t6jnr   3/3           21s        3m27s
    sample-job-team-a-tm7kc   0/3                      2m27s
    sample-job-team-a-vjtnw   3/3           30s        3m50s
    sample-job-team-b-vn6rp   0/3                      40s
    sample-job-team-a-z86h2   0/3                      2m15s
    sample-job-team-b-zfwj8   0/3                      28s
    sample-job-team-a-zjkbj   0/3                      4s
    sample-job-team-a-zzvjg   3/3           83s        4m50s
    
  6. Kopieren Sie einen Jobnamen aus dem vorherigen Schritt und beobachten Sie den Zulassungsstatus und die Ereignisse für einen Job über die Workloads API:

    kubectl -n team-a describe workload JOB_NAME
    
  7. Wenn die ausstehenden Jobs von der ClusterQueue-Ressource zunehmen, beenden Sie das Script, indem Sie im ausgeführten Script CTRL + C drücken.

  8. Nach Abschluss aller Jobs werden die Knoten herunterskaliert.