Implemente um sistema de processamento em lote com o Kueue

Este tutorial mostra como otimizar os recursos disponíveis agendando tarefas no Google Kubernetes Engine (GKE) com o Kueue. Neste tutorial, vai aprender a usar o Kueue para gerir e agendar eficazmente trabalhos em lote, melhorar a utilização de recursos e simplificar a gestão da carga de trabalho. Configura um cluster partilhado para duas equipas de inquilinos em que cada equipa tem o seu próprio espaço de nomes e cada equipa cria tarefas que partilham recursos globais. Também configura o Kueue para agendar as tarefas com base nas quotas de recursos que definir.

Este tutorial destina-se a arquitetos da nuvem e engenheiros de plataformas que tenham interesse em implementar um sistema de processamento em lote com o GKE. Para saber mais sobre as funções comuns e as tarefas de exemplo referenciadas no Google Cloud conteúdo, consulte Funções e tarefas comuns de utilizadores do GKE.

Antes de ler esta página, certifique-se de que conhece o seguinte:

Contexto

As tarefas são aplicações executadas até à conclusão, como aprendizagem automática, renderização, simulação, análise, CI/CD e cargas de trabalho semelhantes.

O Kueue é um programador de tarefas nativo da nuvem que funciona com o programador do Kubernetes predefinido, o controlador de tarefas e o dimensionamento automático de clusters para fornecer um sistema de processamento em lote completo. O Kueue implementa o enfileiramento de tarefas, decidindo quando as tarefas devem esperar e quando devem começar, com base em quotas e numa hierarquia para partilhar recursos de forma justa entre as equipas.

O Kueue tem as seguintes características:

  • Está otimizado para arquiteturas na nuvem, onde os recursos são heterogéneos, intercambiáveis e escaláveis.
  • Oferece um conjunto de APIs para gerir quotas elásticas e gerir o enfileiramento de tarefas.
  • Não reimplementa capacidades existentes, como o dimensionamento automático, o agendamento de pods ou a gestão do ciclo de vida de tarefas.
  • O Kueue tem suporte incorporado para a API Kubernetesbatch/v1.Job.
  • Pode integrar-se com outras APIs de emprego.

O Kueue refere-se a tarefas definidas com qualquer API como cargas de trabalho, para evitar a confusão com a API Kubernetes Job específica.

Crie o ResourceFlavor

Um ResourceFlavor é um objeto que representa as variações nos nós disponíveis no seu cluster, associando-os a etiquetas e taints de nós. Por exemplo, pode usar ResourceFlavors para representar VMs com diferentes garantias de aprovisionamento (por exemplo, spot versus a pedido), arquiteturas (por exemplo, CPUs x86 versus ARM), marcas e modelos (por exemplo, GPUs Nvidia A100 versus T4).

Neste tutorial, o cluster kueue-autopilot tem recursos homogéneos. Como resultado, crie um único ResourceFlavor para CPU, memória, armazenamento temporário e GPUs, sem etiquetas nem restrições.

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

kubectl apply -f flavors.yaml

Crie a ClusterQueue

Uma ClusterQueue é um objeto com âmbito de cluster que gere um conjunto de recursos, como CPU, memória e GPU. Faz a gestão dos ResourceFlavors, limita a utilização e determina a ordem em que as cargas de trabalho são admitidas.

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

Implemente a ClusterQueue:

kubectl apply -f cluster-queue.yaml

A ordem de consumo é determinada por .spec.queueingStrategy, em que existem duas configurações:

  • BestEffortFIFO

    • A configuração da estratégia de colocação em fila predefinida.
    • A admissão da carga de trabalho segue a regra de primeiro a entrar, primeiro a sair (FIFO), mas se não houver quota suficiente para admitir a carga de trabalho no início da fila, é tentada a seguinte na fila.
  • StrictFIFO

    • Garante a semântica FIFO.
    • A carga de trabalho no início da fila pode bloquear a colocação em fila até que a carga de trabalho possa ser admitida.

Em cluster-queue.yaml, cria uma nova ClusterQueue denominada cluster-queue. Esta ClusterQueue gere quatro recursos: cpu, memory, nvidia.com/gpu e ephemeral-storage com o tipo criado em flavors.yaml. A quota é consumida pelos pedidos nas especificações do pod da carga de trabalho.

Cada opção inclui limites de utilização representados como .spec.resourceGroups[].flavors[].resources[].nominalQuota. Neste caso, o ClusterQueue admite cargas de trabalho se e só se:

  • A soma dos pedidos de CPU é inferior ou igual a 10
  • A soma dos pedidos de memória é inferior ou igual a 10 Gi
  • A soma dos pedidos de GPU é inferior ou igual a 10
  • A soma do armazenamento usado é inferior ou igual a 10 Gi

Crie o LocalQueue

Uma LocalQueue é um objeto com espaço de nomes que aceita cargas de trabalho de utilizadores no espaço de nomes. As LocalQueues de diferentes espaços de nomes podem apontar para a mesma ClusterQueue, onde podem partilhar a quota de recursos. Neste caso, LocalQueue do espaço de nomes team-a e team-b aponta para a mesma ClusterQueue cluster-queue em .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 equipa envia as respetivas cargas de trabalho para a LocalQueue no seu próprio espaço de nomes. Em seguida, o ClusterQueue atribui recursos.

Implemente as LocalQueues:

kubectl apply -f local-queue.yaml

Crie tarefas e observe as cargas de trabalho admitidas

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

O trabalho no espaço de nomes team-a tem os seguintes atributos:

  • Aponta para o lq-team-a LocalQueue.
  • Pede recursos de GPU definindo o campo nodeSelector como nvidia-tesla-t4.
  • É composto por três pods que ficam em suspensão durante 10 segundos em paralelo. As tarefas são limpas após 60 segundos, de acordo com o valor definido no campo ttlSecondsAfterFinished.
  • Requer 1500 milliCPU, 1536 Mi de memória, 1536 Mi de armazenamento efémero e três GPUs, uma vez que existem três 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

As tarefas também são criadas no ficheiro job-team-b.yaml onde o respetivo espaço de nomes pertence a team-b, com pedidos para representar diferentes equipas com diferentes necessidades.

Para saber mais, consulte o artigo sobre a implementação de cargas de trabalho de GPU no Autopilot.

  1. Num novo terminal, observe o estado da ClusterQueue que é atualizado a cada dois segundos:

    watch -n 2 kubectl get clusterqueue cluster-queue -o wide
    
  2. Num novo terminal, observe o estado dos nós:

    watch -n 2 kubectl get nodes -o wide
    
  3. Num novo terminal, crie trabalhos para LocalQueue a partir do espaço de nomes team-a e team-b a cada 10 segundos:

    ./create_jobs.sh job-team-a.yaml job-team-b.yaml 10
    
  4. Observe as tarefas a serem colocadas em fila, admitidas na ClusterQueue e os nós a serem iniciados com o GKE Autopilot.

  5. Obter uma tarefa do espaço de nomes team-a:

    kubectl -n team-a get jobs
    

    O resultado é semelhante ao seguinte:

    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. Copie um nome de trabalho do passo anterior e observe o estado de admissão e os eventos de um trabalho através da API Workloads:

    kubectl -n team-a describe workload JOB_NAME
    
  7. Quando os trabalhos pendentes começarem a aumentar a partir da ClusterQueue, termine o script premindo CTRL + C no script em execução.

  8. Quando todas as tarefas estiverem concluídas, repare que os nós estão a ser reduzidos.