Implementar un sistema de colas de tareas con cuotas compartidas entre espacios de nombres en GKE

En este tutorial se usa Kueue para mostrarte cómo implementar un sistema de colas de trabajos, configurar el uso compartido de recursos y cuotas de cargas de trabajo entre diferentes espacios de nombres en Google Kubernetes Engine (GKE) y maximizar la utilización de tu clúster.

Fondo

Como ingeniero de infraestructura o administrador de clústeres, es muy importante maximizar la utilización entre espacios de nombres. Es posible que un lote de trabajos de un espacio de nombres no utilice por completo la cuota asignada al espacio de nombres, mientras que otro espacio de nombres puede tener varios trabajos pendientes. Para usar de forma eficiente los recursos del clúster entre los trabajos de diferentes espacios de nombres y aumentar la flexibilidad de la gestión de cuotas, puedes configurar cohortes en Kueue. Una cohorte es un grupo de ClusterQueues que pueden tomar prestada cuota no utilizada entre sí. Un ClusterQueue gestiona un conjunto de recursos, como CPU, memoria y aceleradores de hardware.

Puedes consultar una definición más detallada de todos estos conceptos en la documentación de Kueue.

Crear los ResourceFlavors

ResourceFlavor representa las variaciones de recursos en los nodos de tu clúster, como diferentes máquinas virtuales (por ejemplo, máquinas virtuales esporádicas frente a máquinas virtuales bajo demanda), arquitecturas (por ejemplo, CPUs x86 frente a CPUs ARM), marcas y modelos (por ejemplo, GPUs Nvidia A100 frente a GPUs T4).

ResourceFlavors usa etiquetas de nodo y taints para coincidir con un conjunto de nodos del clúster.

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

En este manifiesto:

  • El ResourceFlavor on-demand tiene la etiqueta cloud.google.com/gke-provisioning: standard.
  • El ResourceFlavor spot tiene la etiqueta cloud.google.com/gke-provisioning: spot.

Cuando se asigna un ResourceFlavor a una carga de trabajo, Kueue asigna los pods de la carga de trabajo a los nodos que coinciden con las etiquetas de nodo definidas para el ResourceFlavor.

Despliega el ResourceFlavor:

kubectl apply -f flavors.yaml

Crear ClusterQueue y LocalQueue

Crea dos ClusterQueues, cq-team-a y cq-team-b, y sus LocalQueues correspondientes, lq-team-a y lq-team-b, respectivamente, en los espacios de nombres team-a y team-b.

ClusterQueues son objetos centrados en clústeres que rigen un conjunto de recursos, como CPU, memoria y aceleradores de hardware. Los administradores de lotes pueden restringir la visibilidad de estos objetos a los usuarios de lotes.

LocalQueues son objetos con espacio de nombres que los usuarios por lotes pueden enumerar. Apuntan a ClusterQueues, desde donde se asignan recursos para ejecutar las cargas de trabajo de 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

ClusterQueues permite que los recursos tengan varios tipos. En este caso, ambos ClusterQueues tienen dos variantes, on-demand y spot, y cada una proporciona cpu recursos. La cuota de ResourceFlavor spot se ha definido como 0 y no se usará por el momento.

Ambas ClusterQueues comparten la misma cohorte, llamada all-teams, definida en .spec.cohort. Cuando dos o más ClusterQueues comparten la misma cohorte, pueden tomar prestada la cuota no utilizada de los demás.

Puedes consultar más información sobre cómo funcionan las cohortes y la semántica de préstamo en la documentación de Kueue.

Despliega ClusterQueues y LocalQueues:

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

(Opcional) Monitorizar cargas de trabajo con kube-prometheus

Puedes usar Prometheus para monitorizar tus cargas de trabajo de Kueue activas y pendientes. Para monitorizar las cargas de trabajo que se están poniendo en marcha y observar la carga de cada ClusterQueue, implementa kube-prometheus en el clúster del espacio de nombres monitoring:

  1. Descarga el código fuente del operador de Prometheus:

    cd
    git clone https://github.com/prometheus-operator/kube-prometheus.git
    
  2. Crea los CustomResourceDefinitions(CRDs):

    kubectl create -f kube-prometheus/manifests/setup
    
  3. Crea los componentes de monitorización:

    kubectl create -f kube-prometheus/manifests
    
  4. Permite que prometheus-operator extraiga métricas de los componentes de Kueue:

    kubectl apply -f https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/prometheus.yaml
    
  5. Cambia al directorio de trabajo:

    cd kubernetes-engine-samples/batch/kueue-cohort
    
  6. Configura el reenvío de puertos al servicio de Prometheus que se ejecuta en tu clúster de GKE:

    kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090
    
  7. Abre la interfaz web de Prometheus en localhost:9090 en el navegador.

    En Cloud Shell:

    1. Haz clic en Vista previa web.

    2. Haz clic en Cambiar puerto y define el número de puerto como 9090.

    3. Haz clic en Cambiar y obtener vista previa.

    Aparecerá la siguiente interfaz web de Prometheus.

    Captura de pantalla de la interfaz web de Prometheus

  8. En el cuadro de consulta Expression (Expresión), introduce la siguiente consulta para crear el primer panel que monitoriza las cargas de trabajo activas de cq-team-a ClusterQueue:

    kueue_pending_workloads{cluster_queue="cq-team-a", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-a"}
    
  9. Haz clic en Añadir panel.

  10. En el cuadro de consulta Expresión, introduce la siguiente consulta para crear otro panel que monitorice las cargas de trabajo activas de cq-team-b ClusterQueue:

    kueue_pending_workloads{cluster_queue="cq-team-b", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-b"}
    
  11. Haz clic en Añadir panel.

  12. En el cuadro de consulta Expression (Expresión), introduce la siguiente consulta para crear un panel que monitorice el número de nodos del clúster:

    count(kube_node_info)
    

Opcional: Monitorizar cargas de trabajo con Google Cloud Managed Service para Prometheus

Puedes usar Google Cloud Managed Service para Prometheus para monitorizar tus cargas de trabajo de Kueue activas y pendientes. Puedes consultar la lista completa de métricas en la documentación de Kueue.

  1. Configura la identidad y el control de acceso basado en roles para acceder a las métricas:

    La siguiente configuración crea 4 recursos de Kubernetes que proporcionan acceso a las métricas de los recopiladores de Google Cloud Managed Service para Prometheus.

    • Se usará una cuenta de servicio llamada kueue-metrics-reader en el espacio de nombres kueue-system para autenticar el acceso a las métricas de Kueue.

    • Un secreto asociado a la cuenta de servicio kueue-metrics-reader almacena un token de autenticación que usa el recopilador para autenticarse en el endpoint de métricas expuesto por la implementación de Kueue.

    • Un rol llamado kueue-secret-reader en el espacio de nombres kueue-system, que permite leer el secreto que contiene el token de la cuenta de servicio.

    • Un ClusterRoleBinding que otorgue a la kueue-metrics-reader cuenta de servicio el kueue-metrics-reader ClusterRole.

    apiVersion: v1
    kind: ServiceAccount
    metadata:
     name: kueue-metrics-reader
     namespace: kueue-system
    ---
    apiVersion: v1
    kind: Secret
    metadata:
     name: kueue-metrics-reader-token
     namespace: kueue-system
     annotations:
       kubernetes.io/service-account.name: kueue-metrics-reader
    type: kubernetes.io/service-account-token
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
     name: kueue-secret-reader
     namespace: kueue-system
    rules:
    -   resources:
     -   secrets
     apiGroups: [""]
     verbs: ["get", "list", "watch"]
     resourceNames: ["kueue-metrics-reader-token"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
     name: kueue-metrics-reader
    subjects:
    -   kind: ServiceAccount
     name: kueue-metrics-reader
     namespace: kueue-system
    roleRef:
     kind: ClusterRole
     name: kueue-metrics-reader
     apiGroup: rbac.authorization.k8s.io
    
  2. Configura RoleBinding para Google Cloud Managed Service para Prometheus:

    En función de si usas un clúster de Autopilot o Standard, tendrás que crear el RoleBinding en el espacio de nombres gke-gmp-system o gmp-system. Este recurso permite que la cuenta de servicio del recopilador acceda al kueue-metrics-reader-tokensecreto para autenticar y obtener las métricas de Kueue.

    Autopilot

      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: gmp-system:collector:kueue-secret-reader
        namespace: kueue-system
      roleRef:
        name: kueue-secret-reader
        kind: Role
        apiGroup: rbac.authorization.k8s.io
      subjects:
      -   name: collector
        namespace: gke-gmp-system
        kind: ServiceAccount
    

    Estándar

      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: gmp-system:collector:kueue-secret-reader
        namespace: kueue-system
      roleRef:
        name: kueue-secret-reader
        kind: Role
        apiGroup: rbac.authorization.k8s.io
      subjects:
      -   name: collector
        namespace: gmp-system
        kind: ServiceAccount
    
  3. Configura el recurso de PodMonitoring:

    El siguiente recurso configura la monitorización de la implementación de Kueue y especifica que las métricas se exponen en la ruta /metrics a través de HTTPS. Usa el secreto kueue-metrics-reader-token para la autenticación al raspar las métricas.

    apiVersion: monitoring.googleapis.com/v1
    kind: PodMonitoring
    metadata:
    name: kueue
    namespace: kueue-system
    spec:
    selector:
     matchLabels:
       control-plane: controller-manager
    endpoints:
    -   port: 8443
     interval: 30s
     path: /metrics
     scheme: https
     tls:
       insecureSkipVerify: true
     authorization:
       type: Bearer
       credentials:
         secret:
           name: kueue-metrics-reader-token
           key: token
    

Consultar métricas exportadas

Consultas de PromQL de ejemplo para monitorizar sistemas basados en Kueue

Estas consultas de PromQL te permiten monitorizar métricas clave de Kueue, como el rendimiento de los trabajos, la utilización de recursos por cola y los tiempos de espera de las cargas de trabajo, para comprender el rendimiento del sistema e identificar posibles cuellos de botella.

Rendimiento de las tareas

Calcula la tasa por segundo de las cargas de trabajo admitidas durante 5 minutos para cada cluster_queue. Esta métrica puede ayudar a desglosarlo por colas para identificar los cuellos de botella y, al sumarlo, se obtiene el rendimiento general del sistema.

Consulta:

sum(rate(kueue_admitted_workloads_total[5m])) by (cluster_queue)

Uso de recursos

Se presupone que metrics.enableClusterQueueResources está habilitada. Calcula la relación entre el uso actual de la CPU y la cuota nominal de CPU de cada cola. Un valor cercano a 1 indica una utilización alta. Puedes adaptar este ejemplo a la memoria u otros recursos cambiando la etiqueta del recurso.

Para instalar una versión lanzada de Kueue con una configuración personalizada en tu clúster, sigue la documentación de Kueue.

Consulta:

sum(kueue_cluster_queue_resource_usage{resource="cpu"}) by (cluster_queue) / sum(kueue_cluster_queue_nominal_quota{resource="cpu"}) by (cluster_queue)

Tiempo de espera en la cola

Proporciona el tiempo de espera del percentil 90 de las cargas de trabajo de una cola específica. Puedes modificar el valor del cuantil (por ejemplo, 0,5 para la mediana o 0,99 para el percentil 99) para conocer la distribución del tiempo de espera.

Consulta:

histogram_quantile(0.9, kueue_admission_wait_time_seconds_bucket{cluster_queue="QUEUE_NAME"})

Crea trabajos y observa las cargas de trabajo admitidas

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

Genera tareas para ambas ClusterQueues que se suspenderán durante 10 segundos, con tres tareas paralelas, y se completarán con tres finalizaciones. Después de 60 segundos, se eliminará.

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 crea tareas en el espacio de nombres team-a y apunta a LocalQueue lq-team-a y ClusterQueue cq-team-a.

Del mismo modo, job-team-b.yaml crea tareas en el espacio de nombres team-b y apunta a LocalQueue lq-team-b y ClusterQueue cq-team-b.

  1. Inicia un nuevo terminal y ejecuta esta secuencia de comandos para generar un trabajo cada segundo:

    ./create_jobs.sh job-team-a.yaml 1
    
  2. Abre otra terminal y crea trabajos para el espacio de nombres team-b:

    ./create_jobs.sh job-team-b.yaml 1
    
  3. Observa los trabajos que se ponen en cola en Prometheus. O con este comando:

    watch -n 2 kubectl get clusterqueues -o wide
    

La salida debería ser similar a la siguiente:

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

Pedir prestada cuota no utilizada con cohortes

Es posible que ClusterQueues no tenga la capacidad máxima en todo momento. El uso de las cuotas no se maximiza cuando las cargas de trabajo no se distribuyen de forma uniforme entre las ClusterQueues. Si los ClusterQueues comparten la misma cohorte entre sí, pueden tomar prestadas cuotas de otros ClusterQueues para maximizar el uso de las cuotas.

  1. Cuando haya tareas en cola para las ClusterQueues cq-team-a y cq-team-b, detén la secuencia de comandos del espacio de nombres team-b pulsando CTRL+c en la terminal correspondiente.

  2. Una vez que se hayan procesado todos los trabajos pendientes del espacio de nombres team-b, los trabajos del espacio de nombres team-a podrán usar los recursos disponibles de cq-team-b:

    kubectl describe clusterqueue cq-team-a
    

    Como cq-team-a y cq-team-b comparten la misma cohorte llamada all-teams, estas ClusterQueues pueden compartir recursos que no se utilizan.

      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  5
          Name:      cpu
          Total:     15
          Borrowed:  5Gi
          Name:      memory
          Total:     15Gi
    
  3. Reanuda la secuencia de comandos del espacio de nombres team-b.

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

    Observa cómo los recursos prestados de cq-team-a vuelven a 0, mientras que los recursos de cq-team-b se usan para sus propias cargas de trabajo:

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

Aumentar la cuota con máquinas virtuales de acceso puntual

Cuando sea necesario aumentar la cuota temporalmente, por ejemplo, para satisfacer la alta demanda de cargas de trabajo pendientes, puede configurar Kueue para que se adapte a la demanda añadiendo más ClusterQueues al cohorte. Los ClusterQueues con recursos sin usar pueden compartir esos recursos con otros ClusterQueues que pertenezcan a la misma cohorte.

Al principio del tutorial, creaste un grupo de nodos llamado spot con máquinas virtuales Spot y un ResourceFlavor llamado spot con la etiqueta cloud.google.com/gke-provisioning: spot. Crea un ClusterQueue para usar este grupo de nodos y el ResourceFlavor que lo representa:

  1. Crea un ClusterQueue llamado cq-spot con el valor all-teams en el campo Cohort:

    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

    Como este ClusterQueue comparte la misma cohorte que cq-team-a y cq-team-b, tanto ClusterQueue cq-team-a como cq-team-b pueden tomar prestados recursos de hasta 15 solicitudes de CPU y 15 Gi de memoria.

    kubectl apply -f cq-spot.yaml
    
  2. En Prometheus, observa cómo se dispara el número de cargas de trabajo admitidas tanto para cq-team-a como para cq-team-b gracias a la cuota añadida por cq-spot, que comparte la misma cohorte. O con este comando:

    watch -n 2 kubectl get clusterqueues -o wide
    
  3. En Prometheus, observa el número de nodos del clúster. O con este comando:

    watch -n 2 kubectl get nodes -o wide
    
  4. Detén ambas secuencias de comandos pulsando CTRL+c para el espacio de nombres team-a y team-b.