Implementar um sistema de enfileiramento de jobs com compartilhamento de cota entre namespaces no GKE


Neste tutorial, usamos o Kueue para mostrar como implementar um sistema de enfileiramento de jobs, configurar o recurso de carga de trabalho e o compartilhamento de cota entre diferentes namespaces no Google Kubernetes Engine (GKE) e maximizar a utilização do cluster.

Segundo plano

Como engenheiro de infraestrutura ou administrador de cluster, maximizar a utilização entre namespaces é muito importante. Um lote de jobs em um namespace pode não utilizar totalmente a cota completa atribuída ao namespace, enquanto outro namespace pode ter vários jobs pendentes. Para utilizar com eficiência os recursos do cluster entre jobs em namespaces diferentes e aumentar a flexibilidade do gerenciamento de cotas, configure coortes no Kueue. Uma coorte é um grupo de ClusterQueues que podem pedir emprestados cotas não utilizadas uns dos outros. Um ClusterQueue controla um conjunto de recursos, como aceleradores de hardware, memória e CPU.

Você pode encontrar uma definição mais detalhada de todos esses conceitos na documentação do Kueue

Objetivos

Este tutorial é para engenheiros de infraestrutura ou administradores de cluster que querem implementar um sistema de enfileiramento de jobs no Kubernetes usando o Kueue com compartilhamento de cota.

Este tutorial imitará duas equipes em dois namespaces diferentes, em que cada uma tem recursos dedicados, mas pode usar as outras. Um terceiro conjunto de recursos pode ser usado como sobrecarga quando os jobs se acumulam.

Use o operador Prometheus para monitorar jobs e alocação de recursos em namespaces diferentes.

Este tutorial inclui as etapas a seguir:

  1. Criar um cluster do GKE
  2. Crie os ResourceFlavors.
  3. Para cada equipe, crie um ClusterQueue e um LocalQueue
  4. (Opcional) Implantar o kube-prometheus e monitorar as cargas de trabalho usando o Prometheus
  5. Criar Jobs e observar as cargas de trabalho admitidas
  6. Pedir emprestado a cota não utilizada com coortes
  7. Adicionar um spilover ClusterQueue que rege as VMs spot

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
    
  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 GKE chamado kueue-cohort:

    Você vai criar um cluster com seis nós (dois por zona) no pool padrão e sem escalonamento automático. Esses serão todos os recursos disponíveis para as equipes no início, então elas precisarão competir por elas.

    Você verá como o Kueue gerencia as cargas de trabalho que as duas equipes enviarão às respectivas filas.

      gcloud container clusters create kueue-cohort --region COMPUTE_REGION \
      --release-channel rapid --machine-type e2-standard-4 --num-nodes 2
    

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

      kubeconfig entry generated for kueue-cohort.
      NAME: kueue-cohort
      LOCATION: us-central1
      MASTER_VERSION: 1.26.2-gke.1000
      MASTER_IP: 35.224.108.58
      MACHINE_TYPE: e2-medium
      NODE_VERSION: 1.26.2-gke.1000
      NUM_NODES: 6
      STATUS: RUNNING
    

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

  2. Crie um pool de nós chamado spot.

    Este pool de nós usa a VM do Spot, e o escalonamento automático está ativado. Ele começa com 0 nós, mas depois você o disponibilizará para as equipes para uso como capacidade de sobrecarga.

    gcloud container node-pools create spot --cluster=kueue-cohort --region COMPUTE_REGION  \
    --spot --enable-autoscaling --max-nodes 20 --num-nodes 0 \
    --machine-type e2-standard-4
    
  3. Instale a versão de lançamento do Kueue no cluster:

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

    Substitua VERSION pela letra v após a versão mais recente do Kueue, por exemplo, v0.4.0. Para conferir mais informações sobre as versões do Kueue, consulte as Versões do Kueue.

    Aguarde até que o controlador do Kueue esteja pronto:

    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-6cfcbb5dc5-rsf8k   2/2     Running   0          3m
    
  4. Crie dois novos namespaces chamados team-a e team-b:

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

    Os jobs serão gerados em cada namespace.

Criar o ResourceFlavors

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

Os ResourceFlavors usam rótulos e taints de nós para fazer a correspondência com 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

Nesse manifesto:

  • O ResourceFlavor on-demand tem o rótulo definido como cloud.google.com/gke-provisioning: standard.
  • O ResourceFlavor spot tem o rótulo definido como cloud.google.com/gke-provisioning: spot.

Quando uma carga de trabalho é atribuída a um ResourceFlavor, o Kueue atribui os pods da carga aos nós que correspondem aos rótulos de nós definidos para o ResourceFlavor.

Implantar o ResourceFlavor:

kubectl apply -f flavors.yaml

Criar o ClusterQueue e a LocalQueue

Crie duas ClusterQueues cq-team-a e cq-team-b e as LocalQueues lq-team-a e lq-team-b correspondentes com os namespaces team-a e team-b, respectivamente.

ClusterQueues são objetos com escopo de cluster que controlam um pool de recursos, como aceleradores de hardware, memória e CPU. Os administradores em lote podem restringir a visibilidade desses objetos a usuários em lote.

LocalQueues são objetos com namespace que os usuários em lote podem listar. Eles apontam para CluterQueues, a partir dos quais os recursos são alocados para executar as cargas de trabalho da 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 os recursos tenham diversas variações. Nesse caso, as duas QueueQueues têm duas variações, on-demand e spot, cada uma fornecendo recursos cpu. A cota de spot do ResourceFlavor está definida como 0 e não será usada no momento.

As duas ClusterQueues compartilham a mesma coorte chamada all-teams, definida em .spec.cohort. Quando duas ou mais ClusterQueues compartilham a mesma coorte, é possível pedir emprestado uma cota não utilizada entre si.

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

Implante as ClusterQueues e LocalQueues:

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

(Opcional) Implantar o kube-prometheus e monitorar as cargas de trabalho usando o Prometheus

É possível usar o Prometheus para monitorar cargas de trabalho pendentes e ativas do Kueue. Para monitorar as cargas de trabalho que estão sendo criadas e observar a carga em cada ClusterQueue, configure o Prometheus para o cluster no monitoramento de namespace.

  1. Faça o download do código-fonte do operador do Prometheus para monitoramento:

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

    kubectl create -f kube-prometheus/manifests/setup
    
  3. Crie os componentes de monitoramento:

    kubectl create -f kube-prometheus/manifests
    
  4. Permita que o operador prometheus colete as 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. Inicie um novo terminal para acessar o Prometheus encaminhando a porta do serviço:

    kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090
    
  7. Abra o Prometheus no localhost:9090 no navegador.

  8. Se estiver usando o Cloud Shell, clique em Visualização da Web, selecione "Alterar porta", defina o número da porta como 9090 e selecione Change and Preview.

  9. Insira a consulta do primeiro painel que monitora o ClusterQueue ativo cq-team-a:

    kueue_pending_workloads{cluster_queue="cq-team-a", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-a"}
    
  10. Adicione outro painel e insira a consulta que monitora o cq-team-b da ClusterQueue ativa:

    kueue_pending_workloads{cluster_queue="cq-team-b", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-b"}
    
  11. Adicione outro painel e insira a consulta que monitora o número de nós no cluster:

    count(kube_node_info)
    

Criar Jobs e observar as cargas de trabalho admitidas

Gerar jobs para os ClusterQueues que ficarão suspensos por 10 segundos, com três jobs em paralelo e com três conclusões concluídas. Em seguida, ele será limpo 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 jobs no namespace team-a e aponta para o LocalQueue lq-team-a e o ClusterQueue cq-team-a.

Da mesma forma, job-team-b.yaml cria jobs no namespace team-b e aponta para o LocalQueue lq-team-b e a ClusterQueue cq-team-b.

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

    ./create_jobs.sh job-team-a.yaml 1
    
  2. Inicie outro terminal e crie jobs para o namespace team-b:

    ./create_jobs.sh job-team-b.yaml 1
    
  3. Observe os jobs que estão sendo enfileirados no Prometheus. Ou com este comando:

    watch -n 2 kubectl get clusterqueues -o wide
    

A saída será semelhante a esta:

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

Pedir emprestado cota não utilizada com coortes

As ClusterQueues podem não estar com capacidade total o tempo todo. O uso de cotas não é maximizado quando as cargas de trabalho não são distribuídas uniformemente entre os ClusterQueues. Se ClusterQueues compartilharem a mesma coorte entre si, elas poderão pegar emprestado cotas de outras ClusterQueues para maximizar a utilização da cota.

  1. Quando houver jobs enfileirados para os ClusterQueues cq-team-a e cq-team-b, interrompa o script do namespace team-b pressionando CTRL+c no terminal correspondente.

  2. Depois que todos os jobs pendentes do namespace team-b forem processados, os jobs do namespace team-a poderão usar os recursos disponíveis em cq-team-b:

    kubectl describe clusterqueue cq-team-a
    

    Como cq-team-a e cq-team-b compartilham a mesma coorte chamada all-teams, essas ClusterQueues podem compartilhar 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 namespace team-b.

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

    Observe como os recursos emprestados de cq-team-a retornam a 0, enquanto os recursos de cq-team-b são usados para as 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
    

Aumentar a cota com VMs do Spot

Quando a cota precisar ser aumentada temporariamente, por exemplo, para atender à alta demanda em cargas de trabalho pendentes, é possível configurar o Kueue para acomodar a demanda adicionando mais ClusterQueues à coorte. ClusterQueues com recursos não utilizados podem compartilhá-los com outras ClusterQueues que pertencem à mesma coorte.

No início do tutorial, você criou um pool de nós chamado spot usando VMs do Spot e um ResourceFlavor chamado spot com o rótulo definido como cloud.google.com/gke-provisioning: spot. Crie um ClusterQueue para usar este pool de nós e o ResourceFlavor que o representa:

  1. Crie um novo ClusterQueue chamado cq-spot com a coorte definida 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

    Como essa ClusterQueue compartilha a mesma coorte com cq-team-a e cq-team-b, a cq-team-a e a cq-team-b podem receber recursos de até 15 solicitações de CPU e 15 Gi de memória.

    kubectl apply -f cq-spot.yaml
    
  2. No Prometheus, observe como as cargas de trabalho admitidas aumentam para cq-team-a e cq-team-b graças à cota adicionada por cq-spot que compartilha a mesma coorte. Ou com este comando:

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

    watch -n 2 kubectl get nodes -o wide
    
  4. Interrompa os dois scripts pressionando CTRL+c para os namespaces team-a e team-b.

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 cq-team-a
    kubectl delete clusterqueue cq-team-b
    kubectl delete clusterqueue cq-spot
    kubectl delete resourceflavor default
    kubectl delete resourceflavor on-demand
    kubectl delete resourceflavor spot
    
  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-cohort --region=COMPUTE_REGION
    

A seguir