Implemente um sistema de filas de tarefas com partilha de quotas entre espaços de nomes no GKE

Este tutorial usa o Kueue para mostrar como implementar um sistema de colocação em fila de tarefas, configurar a partilha de recursos e quotas de cargas de trabalho entre diferentes espaços de nomes no Google Kubernetes Engine (GKE) e maximizar a utilização do seu cluster.

Contexto

Como engenheiro de infraestrutura ou administrador de clusters, maximizar a utilização entre espaços de nomes é muito importante. Um lote de trabalhos num espaço de nomes pode não usar totalmente a quota atribuída ao espaço de nomes, enquanto outro espaço de nomes pode ter vários trabalhos pendentes. Para usar eficientemente os recursos do cluster entre tarefas em diferentes espaços de nomes e aumentar a flexibilidade da gestão de quotas, pode configurar coortes no Kueue. Uma coorte é um grupo de ClusterQueues que podem pedir emprestado quota não utilizada entre si. Uma ClusterQueue rege um conjunto de recursos, como a CPU, a memória e os aceleradores de hardware.

Pode encontrar uma definição mais detalhada de todos estes conceitos na documentação do Kueue

Crie os ResourceFlavors

Um ResourceFlavor representa variações de recursos nos nós do cluster, como VMs diferentes (por exemplo, spot versus a pedido), arquiteturas (por exemplo, CPUs x86 vs ARM), marcas e modelos (por exemplo, GPUs Nvidia A100 versus T4).

Os ResourceFlavors usam etiquetas de nós e taints para corresponder a um conjunto de nós no 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

Neste manifesto:

  • O ResourceFlavor on-demand tem a etiqueta definida como cloud.google.com/gke-provisioning: standard.
  • O ResourceFlavor spot tem a etiqueta definida como cloud.google.com/gke-provisioning: spot.

Quando é atribuído um ResourceFlavor a uma carga de trabalho, o Kueue atribui os pods da carga de trabalho a nós que correspondem às etiquetas de nós definidas para o ResourceFlavor.

Implemente o ResourceFlavor:

kubectl apply -f flavors.yaml

Crie o ClusterQueue e o LocalQueue

Crie duas ClusterQueues cq-team-a e cq-team-b, e as respetivas LocalQueues lq-team-a e lq-team-b com espaço de nomes team-a e team-b, respetivamente.

As ClusterQueues são objetos com âmbito de cluster que regem um conjunto de recursos, como CPU, memória e aceleradores de hardware. Os administradores de lotes podem restringir a visibilidade destes objetos aos utilizadores de lotes.

LocalQueues são objetos com espaço de nomes que os utilizadores podem listar em lote. Apontam para CluterQueues, a partir dos quais são alocados recursos para executar as cargas de trabalho 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

O ClusterQueues permite que os recursos tenham vários tipos. Neste caso, ambas as ClusterQueues têm duas variantes, on-demand e spot, cada uma a fornecer recursos cpu. A quota do ResourceFlavor spot está definida como 0 e não vai ser usada por agora.

Ambas as ClusterQueues partilham a mesma coorte denominada all-teams, definida em .spec.cohort. Quando duas ou mais ClusterQueues partilham a mesma coorte, podem pedir emprestada quota não utilizada umas às outras.

Pode saber mais sobre como funcionam as coortes e a semântica de empréstimo na documentação do Kueue

Implemente as ClusterQueues e as LocalQueues:

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

(Opcional) Monitorize cargas de trabalho com o kube-prometheus

Pode usar o Prometheus para monitorizar as cargas de trabalho ativas e pendentes do Kueue. Para monitorizar as cargas de trabalho que estão a ser iniciadas e observar a carga em cada ClusterQueue, implemente o kube-prometheus no cluster no espaço de nomes monitoring:

  1. Transfira o código-fonte do operador do Prometheus:

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

    kubectl create -f kube-prometheus/manifests/setup
    
  3. Crie os componentes de monitorização:

    kubectl create -f kube-prometheus/manifests
    
  4. Permitir que o prometheus-operator extraia métricas dos componentes do Kueue:

    kubectl apply -f https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/prometheus.yaml
    
  5. Mude para o diretório de trabalho:

    cd kubernetes-engine-samples/batch/kueue-cohort
    
  6. Configure o encaminhamento de portas para o serviço Prometheus em execução no seu cluster do GKE:

    kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090
    
  7. Abra a IU Web do Prometheus em localhost:9090 no navegador.

    No Cloud Shell:

    1. Clique em Pré-visualização Web.

    2. Clique em Alterar porta e defina o número da porta para 9090.

    3. Clique em Alterar e pré-visualizar.

    É apresentada a seguinte IU Web do Prometheus.

    Captura de ecrã da IU Web do Prometheus

  8. Na caixa de consulta Expressão, introduza a seguinte consulta para criar o primeiro painel que monitoriza as cargas de trabalho ativas para 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. Clique em Adicionar painel.

  10. Na caixa de consulta Expressão, introduza a seguinte consulta para criar outro painel que monitorize as cargas de trabalho ativas para 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. Clique em Adicionar painel.

  12. Na caixa de consulta Expressão, introduza a seguinte consulta para criar um painel que monitorize o número de nós no cluster:

    count(kube_node_info)
    

(Opcional) Monitorize cargas de trabalho com o serviço gerido do Google Cloud para Prometheus

Pode usar o serviço gerido do Google Cloud para Prometheus para monitorizar as suas cargas de trabalho do Kueue ativas e pendentes. Pode encontrar uma lista completa de métricas na documentação do Kueue.

  1. Configure a identidade e o RBAC para o acesso às métricas:

    A seguinte configuração cria 4 recursos do Kubernetes que fornecem acesso a métricas para os coletores do Google Cloud Managed Service for Prometheus.

    • Uma ServiceAccount denominada kueue-metrics-reader no espaço de nomes kueue-system é usada para autenticar o acesso às métricas do Kueue.

    • Um segredo associado à conta de serviço kueue-metrics-reader, armazena um token de autenticação que é usado pelo coletor para fazer a autenticação no ponto final de métricas exposto pela implementação do Kueue.

    • Uma função denominada kueue-secret-reader no espaço de nomes kueue-system, que permite ler o segredo que contém o token da conta de serviço.

    • Um ClusterRoleBinding que concede à conta de serviço kueue-metrics-reader o ClusterRole kueue-metrics-reader.

    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. Configure RoleBinding para o serviço gerido do Google Cloud para Prometheus:

    Consoante esteja a usar um cluster do Autopilot ou padrão, tem de criar o RoleBinding no espaço de nomes gke-gmp-system ou gmp-system. Este recurso permite que a conta de serviço do coletor aceda ao segredo kueue-metrics-reader-token para autenticar e extrair as métricas do Kueue.

    Piloto automático

      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
    

    Standard

      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. Configure o recurso de monitorização de pods:

    O recurso seguinte configura a monitorização da implementação do Kueue. Especifica que as métricas são expostas no caminho /metrics através de HTTPS. Usa o segredo kueue-metrics-reader-token para autenticação ao extrair as 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
    

Consulte métricas exportadas

Exemplos de consultas PromQL para monitorizar sistemas baseados em Kueue

Estas consultas PromQL permitem-lhe monitorizar as principais métricas do Kueue, como o débito de tarefas, a utilização de recursos por fila e os tempos de espera da carga de trabalho, para compreender o desempenho do sistema e identificar potenciais estrangulamentos.

Tráfego transmitido de tarefas

Isto calcula a taxa por segundo de cargas de trabalho admitidas durante 5 minutos para cada cluster_queue. Esta métrica pode ajudar a discriminar por fila, o que ajuda a identificar gargalos, e a somá-la fornece o débito geral do sistema.

Consulta:

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

Utilização de recursos

Isto pressupõe que a funcionalidade metrics.enableClusterQueueResources está ativada. Calcula a relação entre a utilização atual da CPU e a quota nominal da CPU para cada fila. Um valor próximo de 1 indica uma utilização elevada. Pode adaptar isto para a memória ou outros recursos alterando a etiqueta do recurso.

Para instalar uma versão lançada do Kueue configurada de forma personalizada no seu cluster, siga a documentação do Kueue.

Consulta:

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

Tempos de espera na fila

Isto indica o tempo de espera do percentil 90 para cargas de trabalho numa fila específica. Pode modificar o valor do quantil (por exemplo, 0,5 para a mediana, 0,99 para o percentil 99) para compreender a distribuição do tempo de espera.

Consulta:

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

Crie tarefas e observe as cargas de trabalho admitidas

Nesta secção, cria tarefas do Kubernetes no espaço de nomes team-a e team-b. Um controlador de tarefas no Kubernetes cria um ou mais pods e garante que executam com êxito uma tarefa específica.

Gere tarefas para ambas as ClusterQueues que vão ficar inativas durante 10 segundos, com três tarefas paralelas e que vão ser concluídas com três conclusões. Em seguida, é limpa após 60 segundos.

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 cria tarefas no espaço de nomes team-a e aponta para LocalQueue lq-team-a e ClusterQueue cq-team-a.

Da mesma forma, job-team-b.yaml cria tarefas no espaço de nomes team-b e aponta para LocalQueue lq-team-b e ClusterQueue cq-team-b.

  1. Inicie um novo terminal e execute este script para gerar uma tarefa a cada segundo:

    ./create_jobs.sh job-team-a.yaml 1
    
  2. Inicie outro terminal e crie trabalhos para o espaço de nomes team-b:

    ./create_jobs.sh job-team-b.yaml 1
    
  3. Observe as tarefas que estão a ser colocadas em fila no Prometheus. Em alternativa, com este comando:

    watch -n 2 kubectl get clusterqueues -o wide
    

O resultado deve ser semelhante ao seguinte:

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

Peça emprestada quota não usada com coortes

As ClusterQueues podem não estar sempre com capacidade total. A utilização de quotas não é maximizada quando as cargas de trabalho não estão distribuídas uniformemente entre as ClusterQueues. Se os ClusterQueues partilharem a mesma coorte entre si, podem pedir emprestadas quotas a outros ClusterQueues para maximizar a utilização das quotas.

  1. Quando existirem tarefas em fila para as ClusterQueues cq-team-a e cq-team-b, pare o script para o espaço de nomes team-b premindo CTRL+c no terminal correspondente.

  2. Assim que todas as tarefas pendentes do espaço de nomes team-b forem processadas, as tarefas do espaço de nomes team-a podem usar os recursos disponíveis em cq-team-b:

    kubectl describe clusterqueue cq-team-a
    

    Uma vez que cq-team-a e cq-team-b partilham a mesma coorte denominada all-teams, estas ClusterQueues podem partilhar recursos que não são utilizados.

      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  5
          Name:      cpu
          Total:     15
          Borrowed:  5Gi
          Name:      memory
          Total:     15Gi
    
  3. Retome o script para o espaço de nomes team-b.

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

    Observe como os recursos emprestados de cq-team-a regressam a 0, enquanto os recursos de cq-team-b são usados para as suas próprias cargas de trabalho:

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

Aumente a quota com VMs do Spot

Quando a quota tem de ser aumentada temporariamente, por exemplo, para satisfazer a elevada procura em cargas de trabalho pendentes, pode configurar o Kueue para satisfazer a procura adicionando mais ClusterQueues ao grupo. As ClusterQueues com recursos não usados podem partilhar esses recursos com outras ClusterQueues que pertencem à mesma coorte.

No início do tutorial, criou um conjunto de nós denominado spot com VMs de Spot e um ResourceFlavor denominado spot com a etiqueta definida como cloud.google.com/gke-provisioning: spot. Crie uma ClusterQueue para usar este node pool e o ResourceFlavor que o representa:

  1. Crie uma nova ClusterQueue denominada cq-spot com o conjunto de coortes definido como 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

    Uma vez que esta ClusterQueue partilha a mesma coorte com cq-team-a e cq-team-b, tanto a ClusterQueue cq-team-a como a cq-team-b podem pedir emprestados recursos até 15 pedidos de CPU e 15 Gi de memória.

    kubectl apply -f cq-spot.yaml
    
  2. No Prometheus, observe como os volumes de trabalho admitidos aumentam para cq-team-a e cq-team-b graças à quota adicionada por cq-spot, que partilha a mesma coorte. Em alternativa, com este comando:

    watch -n 2 kubectl get clusterqueues -o wide
    
  3. No Prometheus, observe o número de nós no cluster. Em alternativa, com este comando:

    watch -n 2 kubectl get nodes -o wide
    
  4. Pare ambos os scripts premindo CTRL+c para o espaço de nomes team-a e team-b.