Implementa un sistema por lotes con Kueue

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

Este instructivo está dirigido a arquitectos de Cloud y a ingenieros de plataformas interesados en implementar un sistema de procesamiento por lotes con GKE. Para obtener más información sobre los roles comunes y las tareas de ejemplo a los que se hace referencia en el contenido de Google Cloud, consulta Roles y tareas comunes de los usuarios de GKE.

Antes de leer esta página, asegúrate de estar familiarizado con lo siguiente:

Fondo

Los Jobs son aplicaciones que se ejecutan hasta su finalización, como el aprendizaje automático, la renderización, la simulación, las estadísticas, la CI/CD y las cargas de trabajo similares.

Kueue es un programador de trabajos nativo de la nube que funciona con el programador predeterminado de Kubernetes, el controlador de trabajos y el escalador automático del clúster para proporcionar un sistema por lotes de extremo a extremo. Kueue implementa la cola de Jobs, decide cuándo deben esperar los Jobs y cuándo deben iniciarse según las cuotas y una jerarquía para compartir recursos de manera equitativa entre los equipos.

Kueue tiene las siguientes características:

  • Está optimizado para arquitecturas en la nube, en las que los recursos son heterogéneos, intercambiables y escalables.
  • Proporciona un conjunto de APIs para administrar cuotas elásticas y una cola de Jobs.
  • No vuelve a implementar las funciones existentes, como el ajuste de escala automático, la programación de Pods o la administración del ciclo de vida de los trabajos.
  • Kueue tiene compatibilidad integrada con la API de batch/v1.Job de Kubernetes.
  • Se puede integrar en otras API de trabajo.

Kueue hace referencia a los trabajos definidos con cualquier API como cargas de trabajo para evitar confusiones con la API de trabajo de Kubernetes específica.

Crea ResourceFlavor

Un ResourceFlavor es un objeto que representa las variaciones en los nodos disponibles en tu clúster mediante la asociación de etiquetas y taints de nodo. Por ejemplo, puedes usar los objetos ResourceFlavor para representar las VMs con diferentes garantías de aprovisionamiento (por ejemplo, puntual y a pedido), arquitecturas (por ejemplo, CPU x86 frente a CPU ARM), marcas y modelos (por ejemplo, GPU Nvidia A100 frente a GPU T4).

En este instructivo, el clúster kueue-autopilot tiene recursos homogéneos. Como resultado, crea un solo objeto ResourceFlavor por CPU, memoria, almacenamiento efímero y GPU, 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
Implementa ResourceFlavor:

kubectl apply -f flavors.yaml

Crea ClusterQueue

Un ClusterQueue es un objeto con permisos de clúster que administra un grupo de recursos, como CPU, memoria y GPU. Administra los objetos ResourceFlavor, limita el uso y determina el orden en 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

Implementa ClusterQueue:

kubectl apply -f cluster-queue.yaml

El orden de consumo se determina mediante .spec.queueingStrategy, en el que hay dos parámetros de configuración:

  • BestEffortFIFO

    • La configuración predeterminada de la estrategia de cola.
    • La admisión de carga de trabajo sigue la regla primero en entrar, primero en salir (FIFO), pero si no hay suficiente cuota para admitir la carga de trabajo en el encabezado de la cola, se prueba la siguiente en la línea.
  • StrictFIFO

    • Garantiza la semántica de FIFO.
    • La carga de trabajo que está en el encabezado de la cola puede bloquear la cola hasta que se pueda admitir la carga de trabajo.

En cluster-queue.yaml, debes crear un objeto ClusterQueue nuevo llamado cluster-queue. Este objeto ClusterQueue administra cuatro recursos, cpu, memory, nvidia.com/gpu y ephemeral-storage con la variante creada en flavors.yaml. Las solicitudes en las especificaciones del Pod de la carga de trabajo consumen la cuota.

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

  • La suma de las solicitudes de CPU es menor o igual que 10
  • La suma de las solicitudes de memoria es menor o igual que 10Gi
  • La suma de solicitudes de GPU es menor o igual que 10
  • La suma del almacenamiento usado es menor o igual que 10Gi

Crea LocalQueue

Un LocalQueue es un objeto con espacio de nombres que acepta cargas de trabajo de los usuarios en el espacio de nombres. Los objetos LocalQueue de diferentes espacios de nombres pueden apuntar al mismo ClusterQueue en el que pueden compartir la cuota de los recursos. En este caso, el objeto LocalQueue del espacio 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 al objeto LocalQueue en su propio espacio de nombres. Luego, ClusterQueue asigna recursos.

Implementa los objetos LocalQueue:

kubectl apply -f local-queue.yaml

Crea Jobs y observa las cargas de trabajo admitidas

En esta sección, crearás Jobs de Kubernetes en el espacio de nombres team-a. Un controlador de Job en Kubernetes crea uno o más Pods y garantiza que ejecuten correctamente una tarea específica.

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

  • Apunta al objeto LocalQueue lq-team-a.
  • Solicita recursos de GPU configurando el campo nodeSelector en nvidia-tesla-t4.
  • Se compone de tres Pods que se suspenden durante 10 segundos en paralelo. Los trabajos se limpian después de 60 segundos según el valor definido en el campo ttlSecondsAfterFinished.
  • Requiere 1,500 millicores de CPU, 1,536 Mi de memoria, 1,536 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, en el que su espacio de nombres pertenece a team-b, con solicitudes para representar diferentes equipos con diferentes necesidades.

Para obtener más información, consulta Implementa cargas de trabajo de GPU 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 una terminal nueva, crea Jobs 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 cómo se ponen en cola los Jobs, cómo se admiten en ClusterQueue y cómo se activan los nodos con Autopilot de GKE.

  5. Obtén un Job 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 Job del paso anterior y observa el estado de admisión y los eventos para un Job a través de la API de cargas de trabajo:

    kubectl -n team-a describe workload JOB_NAME
    
  7. Cuando los Jobs pendientes comiencen a aumentar desde el objeto ClusterQueue, presiona CTRL + C en la secuencia de comandos en ejecución para finalizarla.

  8. Una vez que se hayan completado todos los Jobs, observa que los nodos se reducen.