Implantar um sistema em lote usando o Kueue


Neste tutorial, mostramos como implantar um sistema em lote usando o Kueue para realizar o enfileiramento do Job no Google Kubernetes Engine (GKE). Conclua este tutorial para saber como configurar o GKE e o Kueue para executar jobs em um modelo "primeiro a entrar, primeiro a sair" (FIFO, na sigla em inglês).

Segundo plano

Os jobs são aplicativos executados até a conclusão, como machine learning, renderização, simulação, análise, CI/CD e cargas de trabalho semelhantes.

O Kueue é um programador de jobs nativos da nuvem que funciona com o programador padrão do Kubernetes, o controlador de jobs e o escalonador automático de clusters para fornecer um sistema de lotes de ponta a ponta. O Kueue implementa o enfileiramento de jobs para decidir quando os jobs devem esperar e quando devem começar, com base em cotas e em uma hierarquia para compartilhar recursos entre as equipes.

O Kueue tem as seguintes características:

  • Ele é otimizado para arquiteturas de nuvem, em que os recursos são heterogêneos, intercambiáveis e escalonáveis.
  • Ele oferece um conjunto de APIs para gerenciar cotas e gerenciar jobs na fila.
  • Ele não reimplementa as funcionalidades atuais, como escalonamento automático, programação de pods ou gerenciamento do ciclo de vida do job.
  • O Kueue tem suporte integrado para a API batch/v1.Job do Kubernetes.
  • Pode ser integrado a outras APIs de job.

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

Objetivos

Este tutorial é destinado a operadores de cluster e outros usuários que querem implementar um sistema em lote no Kubernetes. Neste tutorial, você vai configurar um cluster compartilhado para duas equipes de locatários. Cada equipe tem o próprio namespace, em que cria jobs e compartilha os mesmos recursos globais que são controlados com as cotas correspondentes.

Este tutorial inclui as etapas a seguir:

  1. Criar um cluster do GKE
  2. Criar o ResourceFlavor.
  3. Criar o ClusterQueue.
  4. Criar o LocalQueue.
  5. Criar Jobs e observar as cargas de trabalho admitidas

Custos

Neste tutorial, usamos o seguinte componente faturável do Google Cloud:

Use a calculadora de preços para gerar uma estimativa de custo com base no uso previsto.

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Para mais informações, consulte Limpeza.

Antes de começar

Crie o projeto

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No console do Google Cloud, na página do seletor de projetos, clique em Criar projeto para começar a criar um novo projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  4. Ative a API GKE.

    Ative a API

  5. No console do Google Cloud, na página do seletor de projetos, clique em Criar projeto para começar a criar um novo projeto do Google Cloud.

    Acessar o seletor de projetos

  6. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  7. Ative a API GKE.

    Ative a API

Definir padrões para a Google Cloud CLI

  1. No console do Google Cloud, inicie uma instância do Cloud Shell:
    Abrir o Cloud Shell

  2. Faça o download do código-fonte para este app de amostra:

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    cd kubernetes-engine-samples/batch/kueue-intro
    
  3. Defina as variáveis de ambiente padrão:

    gcloud config set project PROJECT_ID
    gcloud config set compute/region COMPUTE_REGION
    

    Substitua os seguintes valores:

Criar um cluster do GKE

  1. Crie um cluster do Autopilot do GKE chamado kueue-autopilot:

    gcloud container clusters create-auto kueue-autopilot \
      --release-channel "rapid" --region COMPUTE_REGION
    

    Os clusters do Autopilot são totalmente gerenciados e têm escalonamento automático integrado. Saiba mais sobre o GKE do Autopilot.

    O Kueue também oferece suporte ao GKE padrão com provisionamento automático de nós e pools de nós com escalonamento automático regular.

    Após a criação do cluster, o resultado será semelhante a este:

      NAME: kueue-autopilot
      LOCATION: us-central1
      MASTER_VERSION: 1.26.2-gke.1000
      MASTER_IP: 35.193.173.228
      MACHINE_TYPE: e2-medium
      NODE_VERSION: 1.26.2-gke.1000
      NUM_NODES: 3
      STATUS: RUNNING
    

    Em que a STATUS é RUNNING para a kueue-autopilot.

  2. Conseguir as credenciais de autenticação do cluster.

    gcloud container clusters get-credentials kueue-autopilot
    
  3. Instale o Kueue no cluster:

    VERSION=VERSION
    kubectl apply --server-side -f \
      https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml
    

    Substitua VERSION pela versão mais recente do Kueue. Para conferir mais informações sobre as versões do Kueue, consulte as Versões do Kueue.

  4. Aguarde até que os pods do Kueue estejam prontos:

    watch kubectl -n kueue-system get pods
    

    A saída será semelhante a esta antes de continuar:

    NAME                                        READY   STATUS    RESTARTS   AGE
    kueue-controller-manager-66d8bb946b-wr2l2   2/2     Running   0          3m36s
    
  5. Crie dois novos namespaces chamados team-a e team-b:

    kubectl create namespace team-a
    kubectl create namespace team-b
    

Criar o ResourceFlavor

Um ResourceFlavor é um objeto que representa as variações nos nós disponíveis no seu cluster, associando-os a rótulos e taints de nó. Por exemplo, é possível usar o ResourceFlavors para representar VMs com diferentes garantias de provisionamento (por exemplo, spot vs. sob demanda), arquiteturas (por exemplo, CPUs x86 vs. ARM), marcas e modelos (por exemplo, GPUs Nvidia A100 vs. T4).

Neste tutorial, o cluster kueue-autopilot tem recursos homogêneos. Como resultado, crie um único ResourceFlavor para o CPU, memória, armazenamento temporário e GPUs, sem identificadores ou taints.

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

kubectl apply -f flavors.yaml

Criar o ClusterQueue

Um ClusterQueue é um objeto com escopo de cluster que gerencia um pool de recursos, como CPU, memória e GPU. Ele gerencia os ResourceFlavors e limita o uso 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

Implante o ClusterQueue:

kubectl apply -f cluster-queue.yaml

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

  • BestEffortFIFO

    • A configuração padrão da estratégia de enfileiramento.
    • A entrada da carga de trabalho segue a regra "primeiro a entrar, primeiro a sair" (FIFO, na sigla em inglês). No entanto, se não houver cota suficiente para admitir a carga de trabalho no cabeçalho da fila, a próxima na linha será testada.
  • StrictFIFO

    • Garante a semântica FIFO.
    • A carga de trabalho no cabeçalho da fila pode bloquear a fila até que ela possa ser aceita.

Em cluster-queue.yaml, você cria um novo ClusterQueue chamado cluster-queue. Esse ClusterQueue gerencia quatro recursos, cpu, memory, nvidia.com/gpu e ephemeral-storage, com a variação criada em flavors.yaml. A cota é consumida pelas solicitações nas especificações do pod da carga de trabalho.

Cada variação inclui limites de uso representados como .spec.resourceGroups[].flavors[].resources[].nominalQuota. Nesse caso, o ClusterQueue aceitará cargas de trabalho se:

  • A soma das solicitações da CPU for menor ou igual a 10
  • A soma das solicitações de memória for menor ou igual a 10 Gi
  • A soma das solicitações da GPU for menor ou igual a 10
  • A soma do armazenamento usado for menor ou igual a 10 Gi

Criar o LocalQueue

Um LocalQueue é um objeto com namespace que aceita cargas de trabalho de usuários no namespace. LocalQueues de diferentes namespaces podem apontar para o mesmo ClusterQueue em que podem compartilhar a cota dos recursos. Nesse caso, o LocalQueue do namespace team-a e team-b aponta para o mesmo 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 equipe envia as cargas de trabalho para o LocalQueue no próprio namespace. Em seguida, são alocados recursos pelo ClusterQueue.

Implantar os LocalQueues:

kubectl apply -f local-queue.yaml

Criar Jobs e observar as cargas de trabalho admitidas

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

Os jobs são criados no namespace team-a. Esse job aponta para o LocalQueue lq-team-a. Para solicitar recursos de GPU, nodeSelector está definido como nvidia-tesla-t4.

O job é composto por três pods que ficam suspensos por 10 segundos em paralelo. Os jobs são limpos após 60 segundos de acordo com ttlSecondsAfterFinished.

Esse job requer 1.500 miliCPU, 1.536 Mi de memória, 1.536 Mi de armazenamento temporário e três GPUs, já que há três pods.

Os jobs também são criados no arquivo job-team-b.yaml, em que o namespace pertence a team-b, com solicitações para representar equipes com necessidades distintas.

Para saber mais, consulte Como implantar cargas de trabalho da GPU no Autopilot.

  1. Em um novo terminal, observe o status do ClusterQueue que é atualizado a cada dois segundos:

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

    watch -n 2 kubectl get nodes -o wide
    
  3. Em um novo terminal, crie jobs para a fila local a partir do namespace team-a e team-b a cada 10 segundos:

    ./create_jobs.sh job-team-a.yaml job-team-b.yaml 10
    
  4. Observe os jobs sendo enfileirados, aceitos no ClusterQueue e os nós sendo criados com o Autopilot do GKE.

  5. Consiga um job do namespace team-a:

    kubectl -n team-a get jobs
    

    O resultado será assim:

    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 o nome de um job da etapa anterior e observe o status e os eventos de admissão de um job usando a API do Workloads:

    kubectl -n team-a describe workload JOB_NAME
    
  7. Quando os jobs pendentes começam a aumentar a partir do ClusterQueue, encerre o script ao pressionar CTRL + C no script em execução.

  8. Quando todos os jobs forem concluídos, observe que os nós estão sendo reduzidos.

Limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados no tutorial, exclua o projeto que os contém ou mantenha o projeto e exclua os recursos individuais.

Excluir o projeto

  1. No Console do Google Cloud, acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Excluir o recurso individual

  1. Excluir o sistema de cotas do Kueue:

    kubectl delete -n team-a localqueue lq-team-a
    kubectl delete -n team-b localqueue lq-team-b
    kubectl delete clusterqueue cluster-queue
    kubectl delete resourceflavor default-flavor
    
  2. Excluir o manifesto do Kueue:

    VERSION=VERSION
    kubectl delete -f \
      https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml
    
  3. Exclua o cluster:

    gcloud container clusters delete kueue-autopilot --region=COMPUTE_REGION
    

A seguir