Implémenter un système de mise en file d'attente de jobs avec un partage de quota entre espaces de noms dans GKE


Ce tutoriel utilise Kueue pour vous montrer comment implémenter un système de mise en file d'attente de tâches, configurer les ressources de charge de travail et le partage de quotas entre différents espaces de noms sur Google Kubernetes Engine (GKE) et optimiser l'utilisation de votre cluster.

Contexte

En tant qu'ingénieur d'infrastructure ou administrateur de cluster, il est très important d'optimiser l'utilisation entre les espaces de noms. Un lot de tâches dans un espace de noms peut ne pas utiliser pleinement le quota attribué à l'espace de noms, tandis qu'un autre espace de noms peut avoir plusieurs tâches en attente. Pour utiliser efficacement les ressources de cluster entre les tâches dans différents espaces de noms et augmenter la flexibilité de gestion des quotas, vous pouvez configurer des cohortes dans Kueue. Une cohorte est un groupe de ressources ClusterQueue qui peuvent emprunter un quota inutilisé les uns des autres. Une ressources ClusterQueue régit un pool de ressources telles que le processeur, la mémoire et les accélérateurs matériels.

Vous trouverez une définition plus détaillée de tous ces concepts dans la documentation de Kueue.

Objectifs

Ce tutoriel s'adresse aux ingénieurs d'infrastructure ou aux administrateurs de cluster souhaitant mettre en œuvre un système de mise en file d'attente de tâches sur Kubernetes à l'aide de Kueue avec le partage de quotas.

Ce tutoriel imite deux équipes dans deux espaces de noms différents, où chaque équipe dispose de ses ressources dédiées, mais peut emprunter celles de l'autre. Un troisième ensemble de ressources peut être utilisé comme débordement lorsque des tâches s'accumulent.

Utilisez l'opérateur Prometheus pour surveiller les tâches et l'allocation des ressources dans différents espaces de noms.

Ce tutoriel couvre les étapes suivantes :

  1. Créer un cluster GKE
  2. Créer les ressources ResourceFlavors
  3. Pour chaque équipe, créez une ressources ClusterQueue et une ressources LocalQueue
  4. (Facultatif) Déployer kube-prometheus et surveiller les charges de travail à l'aide de Prometheus
  5. Créer des jobs et observer les charges de travail acceptées
  6. Emprunter le quota inutilisé avec des cohortes
  7. Ajouter une ressources QueueQueue de débordement régissant les VM Spot

Coûts

Ce tutoriel utilise les composants facturables Google Cloud suivants :

Utilisez le simulateur de coût pour générer une estimation des coûts en fonction de votre utilisation prévue.

Une fois que vous avez terminé ce tutoriel, évitez de continuer à payer des frais en supprimant les ressources que vous avez créées. Pour en savoir plus, consultez la section Effectuer un nettoyage.

Avant de commencer

Configurer votre projet

  1. Connectez-vous à votre compte Google Cloud. Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de nos produits en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.
  2. Dans Google Cloud Console, sur la page de sélection du projet, cliquez sur Créer un projet pour commencer à créer un projet Google Cloud.

    Accéder au sélecteur de projet

  3. Vérifiez que la facturation est activée pour votre projet Google Cloud.

  4. Activez l'API GKE

    Activer l'API

  5. Dans Google Cloud Console, sur la page de sélection du projet, cliquez sur Créer un projet pour commencer à créer un projet Google Cloud.

    Accéder au sélecteur de projet

  6. Vérifiez que la facturation est activée pour votre projet Google Cloud.

  7. Activez l'API GKE

    Activer l'API

Définir des valeurs par défaut pour Google Cloud CLI

  1. Dans la console Google Cloud, démarrez une instance Cloud Shell :
    Ouvrir Cloud Shell

  2. Téléchargez le code source pour cet exemple d'application :

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  3. Définissez les variables d'environnement par défaut :

    gcloud config set project PROJECT_ID
    gcloud config set compute/region COMPUTE_REGION
    

    Remplacez les valeurs suivantes :

Créer un cluster GKE

  1. Créez un cluster GKE nommé kueue-cohort :

    Vous allez créer un cluster avec 6 nœuds (2 par zone) dans le pool par défaut, sans autoscaling. Ce seront toutes les ressources disponibles pour les équipes dès le départ, elles devront donc entrer en concurrence pour elles.

    Vous verrez plus tard comment Kueue gère les charges de travail que les deux équipes envoient aux files d'attente respectives.

      gcloud container clusters create kueue-cohort --region COMPUTE_REGION \
      --release-channel rapid --machine-type e2-standard-4 --num-nodes 2
    

    Une fois le cluster créé, le résultat ressemble à ce qui suit:

      kubeconfig entry generated for kueue-cohort.
      NAME: kueue-cohort
      LOCATION: us-central1
      MASTER_VERSION: 1.26.2-gke.1000
      MASTER_IP: 35.224.108.58
      MACHINE_TYPE: e2-medium
      NODE_VERSION: 1.26.2-gke.1000
      NUM_NODES: 6
      STATUS: RUNNING
    

    STATUS est RUNNING pour kueue-cluster.

  2. Créez un pool de nœuds nommé spot.

    Ce pool de nœuds utilise une VM Spot et dispose de la fonctionnalité d'autoscaling. Il commence avec 0 nœud, mais vous le mettrez à la disposition des équipes en tant que capacité de surcharge.

    gcloud container node-pools create spot --cluster=kueue-cohort --region COMPUTE_REGION  \
    --spot --enable-autoscaling --max-nodes 20 --num-nodes 0 \
    --machine-type e2-standard-4
    
  3. Installez la version disponible de Kueue sur le cluster :

    VERSION=VERSION
    kubectl apply -f \
      https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml
    

    Remplacez VERSION par la lettre v suivant la dernière version de Kueue, par exemple v0.4.0. Pour en savoir plus sur les versions de Kueue, consultez la page Versions de Kueue.

    Attendez que le contrôleur Kueue soit prêt :

    watch kubectl -n kueue-system get pods
    

    Avant de continuer, le résultat doit ressembler à ce qui suit :

    NAME                                        READY   STATUS    RESTARTS   AGE
    kueue-controller-manager-6cfcbb5dc5-rsf8k   2/2     Running   0          3m
    
  4. Créez deux espaces de noms appelés team-a et team-b :

    kubectl create namespace team-a
    kubectl create namespace team-b
    

    Les tâches seront générées sur chaque espace de noms.

Créer les ressources ResourceFlavors

Une ressource ResourceFlavor représente les variations de ressources dans vos nœuds de cluster, telles que différentes VM (par exemple, Spot ou à la demande), les architectures (par exemple, processeurs x86 et ARM), les marques et les modèles (par exemple, Nvidia A100 par rapport aux GPU T4).

Les ressources ResourceFlavors utilisent des libellés et des rejets de nœuds pour correspondre à un ensemble de nœuds du cluster.

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: on-demand # This ResourceFlavor will be used for the CPU resource
spec:
  nodeLabels:
    cloud.google.com/gke-provisioning: standard # This label was applied automatically by GKE
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: spot # This ResourceFlavor will be used as added resource for the CPU resource
spec:
  nodeLabels:
    cloud.google.com/gke-provisioning: spot # This label was applied automatically by GKE

Dans le fichier manifeste :

  • Le libellé de la ressource ResourceFlavor on-demand est défini sur cloud.google.com/gke-provisioning: standard.
  • Le libellé de la ressource ResourceFlavor spot est défini sur cloud.google.com/gke-provisioning: spot.

Lorsqu'une charge de travail est attribuée à une ressource ResourceFlavor, Kueue en attribue les pods aux nœuds correspondant aux libellés de nœud définis pour la ressource ResourceFlavor.

Déployez la ressource ResourceFlavor :

kubectl apply -f flavors.yaml

Créer les ressources ClusterQueue et LocalQueue

Créez deux ressources ClusterQueue cq-team-a et cq-team-b, ainsi que les ressources LocalQueues lq-team-a et lq-team-b correspondantes, respectivement liées à team-a et team-b.

Les ressources QueueQueue sont des objets à l'échelle d'un cluster qui régissent un pool de ressources telles que le processeur, la mémoire et les accélérateurs matériels. Les administrateurs de traitement par lots peuvent limiter la visibilité de ces objets aux utilisateurs par lots.

Les ressources LocalQueue sont des objets d'espace de noms que les utilisateurs peuvent répertorier. Elles pointent vers des ressources CluterQueues à partir desquelles les ressources sont allouées pour exécuter les charges de travail LocalQueue.

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: cq-team-a
spec:
  cohort: all-teams # cq-team-a and cq-team-b share the same cohort
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: team-a #Only team-a can submit jobs direclty to this queue, but will be able to share it through the cohort
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: on-demand
      resources:
      - name: "cpu"
        nominalQuota: 10
        borrowingLimit: 5
      - name: "memory"
        nominalQuota: 10Gi
        borrowingLimit: 15Gi
    - name: spot # This ClusterQueue doesn't have nominalQuota for spot, but it can borrow from others
      resources:
      - name: "cpu"
        nominalQuota: 0
      - name: "memory"
        nominalQuota: 0
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-a # LocalQueue under team-a namespace
  name: lq-team-a
spec:
  clusterQueue: cq-team-a # Point to the ClusterQueue team-a-cq

Les ressources ClusterQueue permettent d'avoir plusieurs types de ressources. Dans ce cas, les deux files d'attente ont deux types, on-demand et spot, chacun fournissant des ressources cpu. Le quota de la ressource ResourceFlavor spot est défini sur 0 et ne sera pas utilisé pour le moment.

Les deux ressources ClusterQueue partagent la même cohorte appelée all-teams, définie dans .spec.cohort. Lorsque deux ou plusieurs ressources ClusterQueue partagent la même cohorte, elles peuvent s'emprunter leur quota inutilisé.

Pour en savoir plus sur le fonctionnement des cohortes et la sémantique d'emprunt, consultez la documentation de Kuueue.

Déployez les ressources ClusterQueue et LocalQueues :

kubectl apply -f cq-team-a.yaml
kubectl apply -f cq-team-b.yaml

(Facultatif) Déployer kube-prometheus et surveiller les charges de travail à l'aide de Prometheus

Vous pouvez utiliser Prometheus pour surveiller les charges de travail en attente et les charges de travail actives de Kueue. Pour surveiller les charges de travail en cours et observer la charge sur chaque ressource ClusterQueue, configurez Prometheus dans le cluster sous la surveillance des espaces de noms.

  1. Téléchargez le code source de l'opérateur Prometheus pour la surveillance :

    cd
    git clone https://github.com/prometheus-operator/kube-prometheus.git
    
  2. Créez les ressources CustomResourceDefinition(CRD) :

    kubectl create -f kube-prometheus/manifests/setup
    
  3. Créez les composants de surveillance :

    kubectl create -f kube-prometheus/manifests
    
  4. Autorisez prometheus-operator pour récupérer les métriques des composants Kueue :

    kubectl apply -f https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/prometheus.yaml
    
  5. Accédez au répertoire de travail :

    cd kubernetes-engine-samples/batch/kueue-cohort
    
  6. Démarrez un nouveau terminal pour accéder à Prometheus en transférant le port du service :

    kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090
    
  7. Ouvrez Prometheus sur localhost:9090 dans le navigateur.

  8. Si vous utilisez Cloud Shell, cliquez sur Aperçu sur le Web, sélectionnez le port de modification, définissez le numéro de port sur 9090, puis sélectionnez Change and Preview.

  9. Saisissez la requête du premier panneau qui surveille la ressource ClusterQueue cq-team-a active :

    kueue_pending_workloads{cluster_queue="cq-team-a", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-a"}
    
  10. Ajoutez un autre panneau et saisissez la requête qui surveille la ressource ClusterQueue cq-team-b active :

    kueue_pending_workloads{cluster_queue="cq-team-b", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-b"}
    
  11. Ajoutez un autre panneau et saisissez la requête qui surveille le nombre de nœuds dans le cluster :

    count(kube_node_info)
    

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

Générez des tâches sur les deux ressources ClusterQueue, qui resteront en veille pendant 10 secondes, avec trois tâches parallèles et seront effectuées avec trois tâches terminées. Cela sera ensuite nettoyé au bout de 60 secondes.

apiVersion: batch/v1
kind: Job
metadata:
  namespace: team-a # Job under team-a namespace
  generateName: sample-job-team-a-
  labels:
    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:
      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"
      restartPolicy: Never

job-team-a.yaml crée des tâches sous l'espace de noms team-a et pointe vers la ressource LocalQueue lq-team-a et la ressource ClusterQueue cq-team-a.

De même, job-team-b.yaml crée des tâches sous l'espace de noms team-b, et pointe vers la ressource LocalQueue lq-team-b et la ressource ClusterQueue cq-team-b.

  1. Démarrez un nouveau terminal et exécutez ce script pour générer une tâche toutes les secondes :

    ./create_jobs.sh job-team-a.yaml 1
    
  2. Démarrez un autre terminal et créez des tâches pour l'espace de noms team-b :

    ./create_jobs.sh job-team-b.yaml 1
    
  3. Observez les tâches en train d'être mises en file d'attente dans Prometheus. Ou à l'aide de la commande suivante :

    watch -n 2 kubectl get clusterqueues -o wide
    

La sortie devrait ressembler à ce qui suit :

    NAME        COHORT      STRATEGY         PENDING WORKLOADS   ADMITTED WORKLOADS
    cq-team-a   all-teams   BestEffortFIFO   0                   5
    cq-team-b   all-teams   BestEffortFIFO   0                   4

Emprunter le quota inutilisé avec les cohortes

Les ressources ClusterQueue ne sont pas toujours saturées. L'utilisation des quotas n'est pas maximisée lorsque les charges de travail ne sont pas réparties uniformément entre les ressources ClusterQueue. Si les ressources ClusterQueue partagent la même cohorte entre elles, elles peuvent emprunter des quotas d'autres ressources ClusterQueue pour maximiser l'utilisation du quota.

  1. Une fois que des tâches sont mises en file d'attente pour les ressources ClusterQueue cq-team-a et cq-team-b, arrêtez le script destiné à l'espace de noms team-b en appuyant sur CTRL+c dans le terminal correspondant.

  2. Une fois que toutes les tâches en attente de l'espace de noms team-b sont traitées, les tâches de l'espace de noms team-a peuvent emprunter les ressources disponibles dans cq-team-b :

    kubectl describe clusterqueue cq-team-a
    

    Comme cq-team-a et cq-team-b partagent la même cohorte appelée all-teams, ces ressources ClusterQueue peuvent partager des ressources qui ne sont pas utilisées.

      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  5
          Name:      cpu
          Total:     15
          Borrowed:  5Gi
          Name:      memory
          Total:     15Gi
    
  3. Réactivez le script pour l'espace de noms team-b.

    ./create_jobs.sh job-team-b.yaml 3
    

    Observez comment les ressources empruntées de cq-team-a reviennent à 0, tandis que les ressources de cq-team-b sont utilisées pour leurs propres charges de travail :

    kubectl describe clusterqueue cq-team-a
    
      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  0
          Name:      cpu
          Total:     9
          Borrowed:  0
          Name:      memory
          Total:     9Gi
    

Augmenter le quota avec des VM Spot

Lorsque le quota doit être temporairement augmenté, par exemple pour répondre à une demande élevée dans les charges de travail en attente, vous pouvez configurer Kueue pour répondre à la demande en ajoutant d'autres ressources ClusterQueue à la cohorte. Les ressources ClusterQueue avec des ressources inutilisées peuvent partager ces ressources avec d'autres ressources ClusterQueue appartenant à la même cohorte.

Au début du tutoriel, vous avez créé un pool de nœuds nommé spot à l'aide de VM Spot et une ressource ResourceFlavor nommée spot avec le libellé défini sur cloud.google.com/gke-provisioning: spot. Créez une ressource ClusterQueue pour utiliser ce pool de nœuds et la ressource ResourceFlavor qui le représente :

  1. Créez une nouvelle ressource ClusterQueue appelée cq-spot avec la cohorte définie sur all-teams :

    apiVersion: kueue.x-k8s.io/v1beta1
    kind: ClusterQueue
    metadata:
      name: spot-cq
    spec:
      cohort: all-teams # Same cohort as cq-team-a and cq-team-b
      resourceGroups:
      - coveredResources: ["cpu", "memory"]
        flavors:
        - name: spot
          resources:
          - name: "cpu"
            nominalQuota: 40
          - name: "memory"
            nominalQuota: 144Gi

    Étant donné que cette ressource ClusterQueue partage la même cohorte avec cq-team-a et cq-team-b, les ressources ClusterQueue cq-team-a et cq-team-b peuvent emprunter des ressources jusqu'à 15 requêtes de processeur et 15 Gio de mémoire.

    kubectl apply -f cq-spot.yaml
    
  2. Dans Prometheus, observez comment les charges de travail acceptées dépassent cq-team-a et cq-team-b grâce au quota ajouté par cq-spot, qui partage la même cohorte. Ou à l'aide de la commande suivante :

    watch -n 2 kubectl get clusterqueues -o wide
    
  3. Dans Prometheus, observez le nombre de nœuds dans le cluster. Ou à l'aide de la commande suivante :

    watch -n 2 kubectl get nodes -o wide
    
  4. Arrêtez les deux scripts en appuyant sur CTRL+c pour l'espace de noms team-a et team-b.

Effectuer un nettoyage

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez les ressources individuelles.

Supprimer le projet

  1. Dans la console Google Cloud, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Supprimer la ressource individuelle

  1. Supprimez le système de quota Kueue :

    kubectl delete -n team-a localqueue lq-team-a
    kubectl delete -n team-b localqueue lq-team-b
    kubectl delete clusterqueue cq-team-a
    kubectl delete clusterqueue cq-team-b
    kubectl delete clusterqueue cq-spot
    kubectl delete resourceflavor default
    kubectl delete resourceflavor on-demand
    kubectl delete resourceflavor spot
    
  2. Supprimez le fichier manifeste Kueue :

    VERSION=VERSION
    kubectl delete -f \
      https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml
    
  3. Supprimez le cluster à l'aide de la commande suivante :

    gcloud container clusters delete kueue-cohort --region=COMPUTE_REGION
    

Étapes suivantes