Déployer un système de traitement par lots à l'aide de Kueue

Ce tutoriel vous explique comment optimiser les ressources disponibles en planifiant des jobs sur Google Kubernetes Engine (GKE) avec Kueue. Dans ce tutoriel, vous allez apprendre à utiliser Kueue pour gérer et planifier efficacement les jobs par lot, améliorer l'utilisation des ressources et simplifier la gestion des charges de travail. Vous configurez un cluster partagé pour deux équipes locataires, où chaque équipe dispose de son propre espace de noms et crée des jobs qui partagent des ressources globales. Vous configurez également Kueue pour planifier les jobs en fonction des quotas de ressources que vous définissez.

Ce tutoriel s'adresse aux architectes cloud et aux ingénieurs de plate-forme qui souhaitent implémenter un système de traitement par lot à l'aide de GKE. Pour en savoir plus sur les rôles courants et les exemples de tâches que nous citons dans le contenu Google Cloud, consultez Rôles utilisateur et tâches courantes de GKE.

Avant de lire cette page, assurez-vous de connaître les éléments suivants :

Arrière-plan

Les jobs sont des applications qui s'exécutent jusqu'à la fin, telles que le machine learning, le rendu, la simulation, l'analyse, les pipelines CI/CD et d'autres charges de travail similaires.

Kueue est un planificateur de jobs cloud natif qui fonctionne avec le programmeur Kubernetes par défaut, le contrôleur de jobs et l'autoscaler de cluster pour fournir un système de traitement par lots de bout en bout. Kueue met en œuvre la mise en file d'attente des jobs, en déterminant quand les tâches doivent attendre et quand elles doivent démarrer, en fonction des quotas et d'une hiérarchie de partage équitable des ressources entre les équipes.

Kueue présente les caractéristiques suivantes :

  • Il est optimisé pour les architectures cloud, où les ressources sont hétérogènes, interchangeables et évolutives.
  • Il fournit un ensemble d'API pour gérer les quotas élastiques et la file d'attente des jobs.
  • Il ne remet pas en œuvre les fonctionnalités existantes telles que l'autoscaling, la planification des pods ou la gestion du cycle de vie des jobs.
  • Kueue est compatible avec l'API Kubernetes batch/v1.Job.
  • Il peut s'intégrer à d'autres API de jobs.

Kueue fait référence aux jobs définis avec n'importe quelle API en tant que charges de travail, afin d'éviter toute confusion avec l'API de tâches Kubernetes spécifique.

Créer la ressource ResourceFlavor

Une ressource ResourceFlavor est un objet qui représente les variations dans les nœuds disponibles dans votre cluster en les associant à des libellés de nœuds et à des rejets. Par exemple, vous pouvez utiliser des ressources ResourceFlavors pour représenter des VM avec différentes garanties de provisionnement (par exemple, spot ou à la demande), d'architectures (par exemple, processeurs x86 ou processeurs ARM), marques et modèles (par exemple, GPU Nvidia A100 et GPU T4).

Dans ce tutoriel, le cluster kueue-autopilot dispose de ressources homogènes. Par conséquent, créez une seule ressource ResourceFlavor pour le processeur, la mémoire, le stockage éphémère et les GPU, sans libellés ni rejets.

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: default-flavor # This ResourceFlavor will be used for all the resources
Déployez la ressource ResourceFlavor :

kubectl apply -f flavors.yaml

Créer la ressource ClusterQueue

Une ressource ClusterQueue est un objet à l'échelle d'un cluster qui gère un pool de ressources telles que le processeur, la mémoire et le GPU. Elle gère la ressource ResourceFlavors, limite l'utilisation et détermine l'ordre d'admission des charges de travail.

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

Déployez le ClusterQueue :

kubectl apply -f cluster-queue.yaml

L'ordre de consommation est déterminé par .spec.queueingStrategy, qui permet deux configurations :

  • BestEffortFIFO

    • Configuration de la stratégie de mise en file d'attente par défaut.
    • L'admission de la charge de travail suit la première règle FIFO (premier entré, premier sorti), mais si le quota est insuffisant pour accepter la charge de travail en tête de la file d'attente, une nouvelle tentative sera exécutée avec la charge de travail suivante dans la liste.
  • StrictFIFO

    • Garantit la sémantique FIFO.
    • La charge de travail en tête de la file d'attente peut bloquer la file d'attente jusqu'à l'admission de la charge de travail.

Dans cluster-queue.yaml, vous créez une ressource ClusterQueue appelée cluster-queue. Cette ressource ClusterQueue gère quatre ressources : cpu, memory, nvidia.com/gpu et ephemeral-storage avec le type créé dans flavors.yaml. Le quota est consommé par les requêtes dans les spécifications de pod de la charge de travail.

Chaque saveur inclut des limites d'utilisation représentées par .spec.resourceGroups[].flavors[].resources[].nominalQuota. Dans ce cas, la ressource ClusterQueue n'accepte les charges de travail que si les conditions suivantes sont respectées :

  • La somme des requêtes de processeur est inférieure ou égale à 10.
  • La somme des requêtes de mémoire est inférieure ou égale à 10 Gi.
  • La somme des requêtes GPU est inférieure ou égale à 10.
  • La somme de l'espace de stockage utilisé est inférieure ou égale à 10 Gi.

Créer la ressource LocalQueue

Une ressource LocalQueue est un objet d'espace de noms qui accepte les charges de travail des utilisateurs dans l'espace de noms. Les ressources LocalQueues de différents espaces de noms peuvent pointer vers la même ressource ClusterQueue, où elles peuvent partager le quota des ressources. Dans ce cas, la ressource LocalQueue de l'espace de noms team-a et team-b pointe vers la même ressource ClusterQueue cluster-queue sous .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

Chaque équipe envoie ses charges de travail à la ressource LocalQueue dans son propre espace de noms. Ces ressources sont ensuite allouées par ClusterQueue.

Déployez les ressources LocalQueue :

kubectl apply -f local-queue.yaml

Créer des jobs et observer les charges de travail acceptées

Dans cette section, vous allez créer des jobs Kubernetes dans l'espace de noms team-a. Dans Kubernetes, un contrôleur Job crée un ou plusieurs pods et s'assure qu'ils exécutent correctement une tâche spécifique.

Le Job dans l'espace de noms team-a présente les attributs suivants :

  • Il pointe vers la LocalQueue lq-team-a.
  • Il demande des ressources GPU en définissant le champ nodeSelector sur nvidia-tesla-t4.
  • Il est composé de trois pods qui dorment pendant 10 secondes en parallèle. Les jobs sont supprimés après 60 secondes selon la valeur définie dans le champ ttlSecondsAfterFinished.
  • Il nécessite 1 500 milliCPU, 1 536 Mi de mémoire, 1 536 Mi de stockage éphémère et trois GPU, car il existe trois pods.
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

Les jobs sont également créés dans le fichier job-team-b.yaml, où l'espace de noms appartient à team-b, avec des requêtes permettant de représenter différentes équipes avec des besoins différents.

Pour en savoir plus, consultez Déployer des charges de travail GPU dans Autopilot.

  1. Dans un nouveau terminal, observez l'état de la ressource ClusterQueue qui s'actualise toutes les deux secondes :

    watch -n 2 kubectl get clusterqueue cluster-queue -o wide
    
  2. Dans un nouveau terminal, observez l'état des nœuds :

    watch -n 2 kubectl get nodes -o wide
    
  3. Dans un nouveau terminal, créez des jobs pour la ressource LocalQueue à partir de l'espace de noms team-a et team-b toutes les 10 secondes :

    ./create_jobs.sh job-team-a.yaml job-team-b.yaml 10
    
  4. Observez les jobs en file d'attente, acceptés dans la ressource ClusterQueue et les nœuds restaurés avec GKE Autopilot.

  5. Obtenez un job à partir de l'espace de noms team-a :

    kubectl -n team-a get jobs
    

    Le résultat ressemble à ce qui suit :

    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. Copiez un nom de job obtenu à l'étape précédente, et observez l'état d'admission et les événements d'un job via l'API Workloads :

    kubectl -n team-a describe workload JOB_NAME
    
  7. Lorsque les jobs en attente commencent à augmenter à partir de la ressource ClusterQueue, arrêtez le script en appuyant sur CTRL + C sur le script en cours d'exécution.

  8. Une fois tous les jobs terminés, notez le scaling à la baisse des nœuds.