Desplegar un sistema por lotes con Kueue

En este tutorial se muestra cómo optimizar los recursos disponibles programando trabajos en Google Kubernetes Engine (GKE) con Kueue. En este tutorial, aprenderás a usar Kueue para gestionar y programar de forma eficaz trabajos por lotes, mejorar el uso de los recursos y simplificar la gestión de las cargas de trabajo. Configuras un clúster compartido para dos equipos de clientes, donde cada equipo tiene su propio espacio de nombres y crea trabajos que comparten recursos globales. También puedes configurar Kueue para programar las tareas en función de las cuotas de recursos que definas.

Este tutorial está dirigido a arquitectos de Cloud e ingenieros de plataformas que quieran implementar un sistema de procesamiento por lotes con GKE. Para obtener más información sobre los roles habituales y las tareas de ejemplo que se mencionan en el contenido, consulta Roles y tareas de usuario habituales de GKE. Google Cloud

Antes de leer esta página, asegúrese de que conoce los siguientes conceptos:

Fondo

Los trabajos son aplicaciones que se ejecutan hasta completarse, como el aprendizaje automático, el renderizado, la simulación, las analíticas, la integración y la entrega continuas (CI/CD) y cargas de trabajo similares.

Kueue es un programador de tareas nativo de la nube que funciona con el programador predeterminado de Kubernetes, el controlador de tareas y el autoescalador de clústeres para proporcionar un sistema de procesamiento por lotes integral. Kueue implementa la puesta en cola de trabajos, decidiendo cuándo deben esperar los trabajos y cuándo deben iniciarse, en función de las cuotas y una jerarquía para compartir recursos de forma equitativa entre los equipos.

Kueue tiene las siguientes características:

  • Está optimizado para arquitecturas de nube, donde los recursos son heterogéneos, intercambiables y escalables.
  • Proporciona un conjunto de APIs para gestionar cuotas elásticas y colas de trabajos.
  • No vuelve a implementar funciones ya disponibles, como el escalado automático, la programación de pods o la gestión del ciclo de vida de los trabajos.
  • Kueue tiene compatibilidad integrada con la API de Kubernetesbatch/v1.Job.
  • Se puede integrar con otras APIs de empleo.

Kueue se refiere a los trabajos definidos con cualquier API como cargas de trabajo para evitar la confusión con la API de trabajo específica de Kubernetes.

Crear el ResourceFlavor

Un ResourceFlavor es un objeto que representa las variaciones de los nodos disponibles en tu clúster asociándolos con etiquetas y taints de nodo. Por ejemplo, puedes usar ResourceFlavors para representar máquinas virtuales con diferentes garantías de aprovisionamiento (por ejemplo, de tipo spot o bajo demanda), arquitecturas (por ejemplo, CPUs x86 o ARM), marcas y modelos (por ejemplo, GPUs Nvidia A100 o T4).

En este tutorial, el clúster kueue-autopilot tiene recursos homogéneos. Por lo tanto, crea un único ResourceFlavor para CPU, memoria, almacenamiento efímero y GPUs, sin etiquetas ni taints.

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

kubectl apply -f flavors.yaml

Crear el ClusterQueue

Un ClusterQueue es un objeto con ámbito de clúster que gestiona un conjunto de recursos, como CPU, memoria y GPU. Gestiona los ResourceFlavors, limita el uso y determina el orden en el que se admiten las cargas de trabajo.

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

Despliega ClusterQueue:

kubectl apply -f cluster-queue.yaml

El orden de consumo se determina mediante .spec.queueingStrategy, donde hay dos configuraciones:

  • BestEffortFIFO

    • La configuración predeterminada de la estrategia de colas.
    • La admisión de cargas de trabajo sigue la regla de FIFO (primero en entrar, primero en salir), pero si no hay suficiente cuota para admitir la carga de trabajo que está al principio de la cola, se intenta con la siguiente.
  • StrictFIFO

    • Garantiza la semántica FIFO.
    • La carga de trabajo al principio de la cola puede bloquear la puesta en cola hasta que se pueda admitir la carga de trabajo.

En cluster-queue.yaml, crea un ClusterQueue llamado cluster-queue. Este ClusterQueue gestiona cuatro recursos: cpu, memory, nvidia.com/gpu y ephemeral-storage, con el sabor creado en flavors.yaml. La cuota se consume con las solicitudes de las especificaciones de los pods de la carga de trabajo.

Cada variante incluye límites de uso representados como .spec.resourceGroups[].flavors[].resources[].nominalQuota. En este caso, ClusterQueue admite cargas de trabajo si y solo si:

  • La suma de las solicitudes de CPU es inferior o igual a 10
  • La suma de las solicitudes de memoria es inferior o igual a 10 Gi
  • La suma de las solicitudes de GPU es inferior o igual a 10
  • La suma del almacenamiento utilizado es inferior o igual a 10 Gi

Crea el LocalQueue

LocalQueue es un objeto con espacio de nombres que acepta cargas de trabajo de los usuarios del espacio de nombres. Las LocalQueues de diferentes espacios de nombres pueden apuntar a la misma ClusterQueue, donde pueden compartir la cuota de recursos. En este caso, LocalQueue de los espacios de nombres team-a y team-b apunta al mismo ClusterQueue cluster-queue en .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

Cada equipo envía sus cargas de trabajo a LocalQueue en su propio espacio de nombres. A continuación, ClusterQueue les asigna recursos.

Implementa LocalQueues:

kubectl apply -f local-queue.yaml

Crea trabajos y observa las cargas de trabajo admitidas

En esta sección, creará trabajos de Kubernetes en el espacio de nombres team-a. Un controlador de trabajo de Kubernetes crea uno o varios pods y se asegura de que ejecuten correctamente una tarea específica.

El trabajo del espacio de nombres team-a tiene los siguientes atributos:

  • Apunta a lq-team-a LocalQueue.
  • Solicita recursos de GPU asignando el valor nvidia-tesla-t4 al campo nodeSelector.
  • Se compone de tres pods que duermen durante 10 segundos en paralelo. Las tareas se eliminan después de 60 segundos, según el valor definido en el campo ttlSecondsAfterFinished.
  • Requiere 1500 miliCPU, 1536 Mi de memoria, 1536 Mi de almacenamiento efímero y tres GPUs, ya que hay tres 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

Los trabajos también se crean en el archivo job-team-b.yaml cuyo espacio de nombres pertenece a team-b, con solicitudes para representar diferentes equipos con diferentes necesidades.

Para obtener más información, consulta el artículo sobre cómo desplegar cargas de trabajo de GPUs en Autopilot.

  1. En una terminal nueva, observa el estado de ClusterQueue, que se actualiza cada dos segundos:

    watch -n 2 kubectl get clusterqueue cluster-queue -o wide
    
  2. En una terminal nueva, observa el estado de los nodos:

    watch -n 2 kubectl get nodes -o wide
    
  3. En un terminal nuevo, crea tareas en LocalQueue desde el espacio de nombres team-a y team-b cada 10 segundos:

    ./create_jobs.sh job-team-a.yaml job-team-b.yaml 10
    
  4. Observa las tareas que se ponen en cola, se admiten en ClusterQueue y los nodos que se activan con Autopilot de GKE.

  5. Obtener un trabajo del espacio de nombres team-a:

    kubectl -n team-a get jobs
    

    El resultado es similar al siguiente:

    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. Copia el nombre de un trabajo del paso anterior y observa el estado de admisión y los eventos de un trabajo a través de la API Workloads:

    kubectl -n team-a describe workload JOB_NAME
    
  7. Cuando los trabajos pendientes empiecen a aumentar en ClusterQueue, finaliza la secuencia de comandos pulsando CTRL + C en la secuencia de comandos en ejecución.

  8. Una vez que se hayan completado todos los trabajos, verás que los nodos se reducen.