Kubernetes para machine learning (Visão geral)

Por que o Kubernetes aparece em pilhas de ML (ML stacks)

Sistemas modernos de aprendizado de máquina (machine learning, ML) raramente são “um programa em uma máquina”. Mesmo uma equipe pequena frequentemente precisa de:

  • Diferentes cargas de trabalho (workloads): treinamento (training), inferência em lote (batch inference), inferência online (online inference), pré-processamento de dados (data preprocessing), avaliação (evaluation), indexação vetorial (vector indexing), retreinamento agendado (scheduled retraining).
  • Diferentes formatos de recursos (resource shapes): ETL intensivo em CPU (CPU-heavy ETL), treinamento intensivo em GPU (GPU-heavy training), tarefas de incorporação intensivas em memória (memory-heavy embedding jobs), treinamento distribuído intensivo em rede (network-heavy distributed training).
  • Infraestrutura compartilhada (shared infrastructure): muitos usuários e equipes competindo pelas mesmas GPUs, armazenamento e rede.
  • Reprodutibilidade e repetibilidade (reproducibility and repeatability): o mesmo ambiente em dev, staging e produção.
  • Garantias operacionais (operational guarantees): reinício em caso de falha, rollouts graduais, verificações de saúde, escalonamento, isolamento, controle de acesso.

O Kubernetes (K8s) aparece porque é um sistema operacional de cluster de propósito geral (general-purpose cluster operating system): ele padroniza como você empacota código (contêineres (containers)), solicita recursos, executa tarefas de forma confiável, expõe serviços, anexa armazenamento e gerencia multilocação (multi-tenancy). Em muitas organizações ele se torna o substrato (substrate) sob plataformas de ML como Kubeflow, Ray, Spark, Argo Workflows e pilhas de serving de modelos (model serving).

O Kubernetes não é “uma ferramenta de ML” por si só, mas fornece as primitivas (primitives) sobre as quais as ferramentas de ML constroem.

O modelo mental: Kubernetes em um parágrafo

Kubernetes é um sistema declarativo (declarative): você descreve o estado desejado (desired state) (por exemplo, “executar 3 réplicas deste servidor de inferência” ou “executar este job de treinamento até a conclusão”), e o Kubernetes tenta continuamente fazer com que a realidade corresponda a esse estado. Ele faz isso por meio de controladores (controllers) (como Implantações (Deployments) e Tarefas (Jobs)) e de um agendador (scheduler) que posiciona o trabalho em nós (nodes) com base em requisições e restrições de recursos.

Isso é especialmente valioso em ML, onde falhas (término de instância spot (spot instance termination), reinicializações de nó (node reboots), redes instáveis (flaky networks)) são comuns e os jobs são de longa duração.

As primitivas que mais importam para ML

Pods: a unidade atômica de execução

Um Pod (Pod) é a menor unidade agendável no Kubernetes. Um Pod contém um ou mais contêineres que compartilham:

  • namespace de rede (network namespace) (mesmo IP)
  • volumes (montagens de sistema de arquivos compartilhadas)
  • ciclo de vida (lifecycle) (agendados juntos, reiniciados juntos)

Em ML, um Pod frequentemente corresponde a:

  • um worker de treinamento (por exemplo, um processo de GPU)
  • uma réplica de inferência
  • um fragmento (shard) de tarefa de pré-processamento

Por que isso importa: cargas de trabalho de ML frequentemente precisam de contêineres auxiliares (sidecars) (por exemplo, agentes de envio de logs, exportadores de métricas) ou contêineres de inicialização (init containers) (por exemplo, baixar um artefato do modelo (model artifact) antes do servidor iniciar).

Controladores: manter coisas rodando (ou terminando)

Os controladores do Kubernetes gerenciam Pods para diferentes necessidades de ciclo de vida:

  • Implantação (Deployment): “manter N réplicas rodando” (melhor para serving online sem estado (stateless))
  • Conjunto com Estado (StatefulSet): “manter N réplicas com identidades e armazenamento estáveis” (útil para serviços com estado, como repositórios de atributos (feature stores), bancos de dados vetoriais (vector DBs) ou qualquer coisa que precise de IDs de rede estáveis (stable network IDs))
  • Tarefa (Job): “executar até a conclusão” (melhor para execuções de treinamento, avaliação, inferência em lote)
  • Tarefa Cron (CronJob): “executar em um cronograma” (avaliações noturnas, gatilhos periódicos de retreinamento)

O mapeamento para ML é direto:

  • Inferência online → Implantação (+ escalonamento automático (autoscaling))
  • Treinamento → Tarefa (ou operadores/definições de recursos personalizadas (Custom Resource Definitions, CRDs) de nível mais alto)
  • Pontuação em lote (batch scoring) → Tarefa/Tarefa Cron

Recursos: requisições, limites e GPUs

O agendamento do Kubernetes é orientado por requisições (requests) de recursos (o que você pede) e limites (limits) (o máximo que você pode usar).

Recursos-chave para ML:

  • cpu, memory
  • nvidia.com/gpu (ou outros recursos de acelerador via plugins de dispositivo (device plugins))
  • ephemeral-storage (frequentemente negligenciado; pode importar para caches grandes de datasets ou fragmentos temporários)

Uma prática comum em ML é definir requisições iguais a limites para desempenho previsível (especialmente para memória) e para evitar problemas de “vizinho barulhento” (noisy-neighbor).

Detalhes de agendamento de GPU frequentemente se tornam centrais em escala — fragmentação (fragmentation), equidade (fairness), prioridades (priorities) e preempção (preemption). Veja Agendamento de GPU e Filas de Cluster.

Plugins de dispositivo de GPU e MIG

O Kubernetes em si não “conhece GPUs”. Um plugin de dispositivo (device plugin) (comumente o da NVIDIA) anuncia recursos de GPU para o agendador.

Em alguns sistemas, recursos como MIG (Multi-Instance GPU) podem particionar uma GPU em fatias; essas fatias podem ser expostas como recursos agendáveis. Se isso é bom depende do perfil da carga de trabalho (latência vs throughput vs necessidades de memória).

Restrições de agendamento: colocando cargas de ML nos nós certos

Clusters de ML raramente são homogêneos. Você pode ter:

  • nós A100/H100 vs GPUs mais baratas
  • nós apenas CPU
  • nós de alta memória
  • nós com cache NVMe local

O Kubernetes oferece primitivas para expressar posicionamento:

  • nodeSelector / afinidade de nó (node affinity): “rodar apenas em nós com o rótulo X”
  • contaminações (taints) e tolerâncias (tolerations): “manter cargas gerais fora de nós de GPU, a menos que optem explicitamente”
  • afinidade/anti-afinidade de Pod (pod affinity/anti-affinity): “espalhar réplicas entre nós” (disponibilidade) ou “co-localizar” (localidade de dados)
  • restrições de espalhamento de topologia (topology spread constraints): distribuição controlada entre zonas/racks

Para treinamento distribuído (distributed training), a topologia importa porque os custos de comunicação dependem de interconexões (interconnects) como NVLink e InfiniBand. Veja Interconexões (NVLink/InfiniBand).

Rede: Serviços e Ingress (e por que ML se importa)

Para inferência online você precisa de rede estável:

  • Serviço (Service): IP virtual estável + balanceamento de carga para um conjunto de Pods
  • Entrada (Ingress) (ou Gateway API em configurações mais novas): roteamento HTTP de fora do cluster para Serviços

Considerações para serving de ML:

  • verificações de prontidão (readiness checks) devem refletir “o modelo está carregado e aquecido”
  • tráfego sensível à latência (latency-sensitive) pode exigir roteamento L7 (L7 routing), tempos limite (timeouts) e retentativas (retries) ajustados com cuidado
  • gRPC vs HTTP importa para desempenho e evolução de esquema

Configuração e segredos: ConfigMaps e Secrets

Sistemas de ML tendem a ter muita configuração:

  • nome/versão do modelo
  • flags de funcionalidade (feature flags)
  • endpoints de backend (backend endpoints)
  • tamanhos de lote (batch sizes), máximo de tokens (max tokens), limites de concorrência (concurrency limits)

O Kubernetes oferece:

  • Mapa de Configuração (ConfigMap) para configuração não sensível
  • Segredo (Secret) para credenciais (senhas de BD, chaves de armazenamento de objetos, tokens de API)

Na prática, muitas equipes integram gerenciadores externos de segredos (external secret managers) (cloud KMS/Secrets Manager/Vault), mas os Segredos do Kubernetes continuam sendo o ponto de integração.

Armazenamento: PV/PVC, StorageClasses e as realidades de dados em ML

Armazenamento em ML geralmente é a parte mais difícil operacionalmente:

  • treinamento lê datasets grandes e majoritariamente sequenciais
  • treinamento grava checkpoints frequentes
  • inferência precisa de carregamento rápido de artefatos do modelo
  • jobs em lote podem derramar (spill) intermediários enormes

Primitivas de armazenamento do Kubernetes:

  • Volume Persistente (PersistentVolume, PV) / Reivindicação de Volume Persistente (PersistentVolumeClaim, PVC): solicitar armazenamento durável
  • Classe de Armazenamento (StorageClass): define o tipo de backend (EBS, PD, NFS, Ceph etc.)
  • drivers CSI (CSI drivers): plugins que implementam provisionamento/anexação de armazenamento

No entanto, muitos datasets de ML vivem em armazenamento de objetos (object storage) (S3/GCS/Azure Blob), não em sistemas de arquivos POSIX (POSIX filesystems). Um padrão comum é:

  • fazer streaming diretamente do armazenamento de objetos, ou
  • fazer staging para SSD local do nó por velocidade, ou
  • montar via uma solução especializada de CSI/FUSE (com compromissos (tradeoffs))

Para um contexto mais profundo, veja Armazenamento e Memória e Largura de Banda (porque pipelines de entrada frequentemente são limitados por largura de banda, não por computação — bandwidth-bound vs compute-bound).

Escalonamento automático: casando capacidade com demanda

O Kubernetes suporta múltiplas camadas de escalonamento (scaling layers):

  • Autoescalador Horizontal de Pods (Horizontal Pod Autoscaler, HPA): escalar réplicas com base em CPU/memória ou métricas personalizadas (custom metrics) (QPS, tamanho de fila)
  • Autoescalador Vertical de Pods (Vertical Pod Autoscaler, VPA): ajustar requisições de recursos do pod (nem sempre ideal para serving sensível à latência)
  • Autoescalonamento do cluster (cluster autoscaling): adicionar/remover nós (Cluster Autoscaler, Karpenter, equivalentes cloud-native)

Em serving de ML, o HPA é comumente guiado por:

  • utilização de CPU para inferência em CPU
  • métricas personalizadas como requisições/seg (QPS), requisições em andamento (in-flight requests) ou utilização de GPU para inferência em GPU

Em treinamento, escalonamento automático é mais complicado: muitos jobs distribuídos querem um número fixo de trabalhadores (workers). Escalar treinamento dinamicamente é possível, mas geralmente exige suporte no nível do framework.

Observabilidade: logs, métricas e eventos

Operações de ML exigem responder perguntas como:

  • Por que esta execução de treinamento está mais lenta do que na semana passada?
  • Qual versão do modelo está servindo esta requisição?
  • As GPUs estão subutilizadas? Estamos limitados por entrada (input-bound)?
  • Quais pods estão reiniciando e por quê?

O Kubernetes oferece:

  • Eventos (Events) (falhas de agendamento, erros ao puxar imagem, OOM kills)
  • pontos de integração para métricas (Prometheus), logs, tracing

A maioria das equipes de ML eventualmente padroniza em:

  • logs estruturados (structured logs) com IDs de execução (run IDs) / versão do modelo
  • métricas de GPU (exportador DCGM para NVIDIA)
  • telemetria por requisição (request-level telemetry) para serving

Padrões de carga de trabalho de ML no Kubernetes

Padrão 1: Treinamento como uma Tarefa

Uma execução de treinamento de nó único (single-node) mapeia naturalmente para uma Tarefa do Kubernetes.

Propriedades que você geralmente quer:

  • requisitar GPUs explicitamente
  • montar credenciais para acesso a dados
  • gravar checkpoints em armazenamento durável
  • tolerar reinícios (spot/preempção) via checkpointing

Exemplo: uma Tarefa simples de treinamento em GPU que grava checkpoints em um PVC e lê credenciais de um Segredo:

apiVersion: batch/v1
kind: Job
metadata:
  name: train-resnet
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: trainer
        image: ghcr.io/example/ml-trainer:latest
        args: ["python", "train.py", "--epochs=10", "--ckpt_dir=/checkpoints"]
        resources:
          requests:
            cpu: "4"
            memory: "16Gi"
            nvidia.com/gpu: "1"
          limits:
            cpu: "4"
            memory: "16Gi"
            nvidia.com/gpu: "1"
        volumeMounts:
        - name: checkpoints
          mountPath: /checkpoints
        env:
        - name: S3_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              name: objectstore-creds
              key: access_key
        - name: S3_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: objectstore-creds
              key: secret_key
      volumes:
      - name: checkpoints
        persistentVolumeClaim:
          claimName: ckpt-pvc

Treinamento distribuído: “um pod por worker” + coordenação

Treinamento distribuído adiciona complexidade:

  • Você precisa que múltiplos Pods iniciem juntos (ou ao menos se coordenem)
  • Workers precisam se descobrir (hostnames/service)
  • Desempenho de rede importa muito (veja Interconexões (NVLink/InfiniBand))
  • Você frequentemente quer agendamento em grupo (gang scheduling): não iniciar a menos que todas as GPUs estejam disponíveis

O Kubernetes consegue fazer isso, mas muitas equipes usam operadores/CRDs (recursos personalizados (custom resources)) como:

  • PyTorchJob / TFJob do Kubeflow
  • MPI Operator (comum para lançamentos no estilo Horovod/MPI)
  • operador do Ray para Ray Train

Em escala maior, você também verá camadas de enfileiramento (queueing layers) (por exemplo, Kueue, Volcano) para gerenciar equidade e prioridades; isso é coberto conceitualmente em Agendamento de GPU e Filas de Cluster.

Padrão 2: Inferência online como uma Implantação + Serviço

Serving online prioriza:

  • endpoints estáveis
  • rollouts/rollbacks rápidos
  • escalonamento automático
  • verificações de saúde que reflitam prontidão do modelo

Exemplo: uma Implantação de inferência com sondas de prontidão (readiness probes)/vivacidade (liveness probes) e um Serviço:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: text-embedder
spec:
  replicas: 2
  selector:
    matchLabels:
      app: text-embedder
  template:
    metadata:
      labels:
        app: text-embedder
    spec:
      containers:
      - name: server
        image: ghcr.io/example/embedder-server:1.3.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: "2"
            memory: "8Gi"
          limits:
            cpu: "2"
            memory: "8Gi"
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

---
apiVersion: v1
kind: Service
metadata:
  name: text-embedder
spec:
  selector:
    app: text-embedder
  ports:
  - port: 80
    targetPort: 8080

Se o modelo estiver hospedado em GPU, adicione nvidia.com/gpu: "1" e regras apropriadas de posicionamento de nós.

Autoescalonamento do serving (HPA)

Exemplo de HPA escalando por CPU (muitas equipes substituem isso por métricas personalizadas para QPS/latência):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: text-embedder-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: text-embedder
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60

Para serving de modelos de linguagem grandes (large language models, LLMs), utilização de GPU e concorrência de requisições (request concurrency) frequentemente são mais significativas do que CPU. O sinal de escalonamento (scaling signal) correto depende da estratégia de agrupamento (batching), da distribuição do comprimento de contexto (context length distribution) e da eficiência de kernel (kernel efficiency) (relacionado a Noções Básicas de CUDA e Compiladores e Runtimes).

Padrão 3: Inferência em lote / ETL como Tarefas paralelas

Pontuação em lote frequentemente se parece com:

  • um grande dataset de entrada dividido em fragmentos
  • muitos workers paralelos processando fragmentos
  • saídas gravadas em armazenamento de objetos ou em um banco de dados

Tarefas do Kubernetes suportam parallelism e completions, mas muitos sistemas em produção também usam um padrão de fila de trabalho (work-queue pattern) (SQS/PubSub/Kafka/Redis) para balanceamento dinâmico (dynamic balancing).

Tarefas Cron são usadas quando a execução em lote é agendada (por exemplo, backfill noturno), embora muitas equipes usem motores de workflow (workflow engines) (Argo Workflows, Airflow) para grafos de dependência (dependency graphs) mais ricos.

Multilocação e segurança: namespaces, quotas e RBAC

Clusters de ML geralmente são compartilhados. O Kubernetes oferece:

  • Namespaces (Namespaces): fronteira de isolamento para nomenclatura e política
  • Controle de acesso baseado em papéis (Role-Based Access Control, RBAC): quem pode criar/ler/atualizar recursos
  • Cotas de Recursos (ResourceQuotas) e Faixas de Limites (LimitRanges): impedem que uma equipe consuma todas as GPUs/memória
  • Classes de Prioridade (PriorityClasses): garantem que inferência crítica não seja “fomeada” por treinamento oportunista

Essa camada de governança (governance layer) se torna essencial quando GPUs são escassas e caras.

Exemplo prático de posicionamento: manter jobs de GPU em nós de GPU

Um padrão comum é aplicar contaminações (tainting) em nós de GPU e exigir tolerâncias.

Trecho de exemplo para um Pod que deve rodar em nós de GPU:

spec:
  tolerations:
  - key: "nvidia.com/gpu"
    operator: "Exists"
    effect: "NoSchedule"
  nodeSelector:
    accelerator: "nvidia"

Seu cluster deve rotular nós apropriadamente (por exemplo, accelerator=nvidia) e aplicar as contaminações (ação do administrador do cluster).

Ajustes de confiabilidade que importam para ML

Sondas de saúde e aquecimento

Pods de inferência frequentemente precisam de tempo para:

  • baixar um modelo
  • carregar pesos na memória/GPU
  • construir kernels / compilar via JIT (às vezes)
  • aquecer caches

Use:

  • readinessProbe para bloquear tráfego até estar pronto
  • sonda de inicialização (startupProbe) (onde disponível) para inicializações longas sem disparar reinícios
  • livenessProbe para se recuperar de deadlocks

Rollouts e versionamento

Implantações do Kubernetes suportam atualizações graduais (rolling updates), mas serving de ML frequentemente precisa de controle mais estrito:

  • fazer canário (canary) de uma nova versão do modelo para 1% do tráfego
  • comparar métricas (latência, taxa de erro, sinais de qualidade)
  • promover ou fazer rollback rapidamente

Equipes frequentemente adicionam ferramentas como Argo Rollouts ou roteamento de malha de serviço (service mesh routing), mas o padrão base ainda é “Pods + Serviços + rótulos (labels)”.

Onde o Kubernetes termina e as plataformas de ML começam

O Kubernetes te dá primitivas, não uma experiência do usuário de ML (ML UX). A maioria das organizações coloca sistemas adicionais por cima:

  • Operadores de treinamento (training operators) (Kubeflow, MPI Operator, Ray): definem “um job de treinamento distribuído” como um único recurso
  • Motores de workflow: grafos acíclicos direcionados (directed acyclic graphs, DAGs) para pré-processamento → treinamento → avaliação → registro
  • Registros de modelos (model registries) e repositórios de artefatos (artifact stores)
  • Repositórios de atributos
  • Serving especializado (KServe, Seldon, Triton etc.)

Pense no Kubernetes como o plano de controle portátil (portable control plane) por baixo. A plataforma de ML adiciona conceitos de nível mais alto como experimentos, datasets, linhagem (lineage), aprovações e escalonamento automático específico de modelo.

Armadilhas comuns (e como evitá-las)

  • Sem requisições de recursos: o agendador não consegue tomar boas decisões de posicionamento; você obtém superalocação (overcommit) e vizinhos barulhentos.
  • Ignorar pipelines de dados: treinamento “usa GPUs”, mas frequentemente é limitado por entrada; otimize armazenamento e largura de banda (veja Armazenamento, Memória e Largura de Banda).
  • Tratar treinamento distribuído como pods independentes: muitos jobs exigem agendamento em grupo e posicionamento cuidadoso de rede (veja Agendamento de GPU e Filas de Cluster).
  • Imagens enormes e pulls lentos: desacelera escalonamento automático e recuperação; mantenha imagens enxutas, use cache de camadas (layer caching).
  • Carregamento de modelo no contêiner principal sem sondas: tráfego chega a um pod “rodando”, mas não utilizável. Use sondas de prontidão/inicialização e contêineres de inicialização para downloads.
  • Segredos em imagens ou em variáveis de ambiente (env vars) sem controle: prefira contas de serviço de menor privilégio (least-privilege service accounts) e integração externa de segredos (external secret integration) quando possível.

Quando Kubernetes é a escolha certa (ou errada)

O Kubernetes tende a ser uma boa opção quando você tem:

  • múltiplas cargas de trabalho de ML compartilhando um pool de GPUs/CPUs
  • forte necessidade de reprodutibilidade, isolamento e políticas
  • serviços de longa duração com atualizações graduais e escalonamento automático
  • hardware heterogêneo (diferentes tipos de GPU, tamanhos de memória)

Pode ser exagero quando:

  • tudo roda em uma única máquina ou em um único serviço gerenciado de treinamento (managed training service)
  • você não precisa de multilocação ou agendamento personalizado
  • sua equipe não consegue sustentar a complexidade operacional (rede, armazenamento, upgrades, segurança)

Muitas equipes começam com serviços gerenciados de ML, depois adotam Kubernetes quando ultrapassam as limitações — ou quando querem um sistema unificado tanto para ML quanto para microserviços em geral.

Principal conclusão

O Kubernetes aparece em pilhas de ML porque fornece uma forma confiável e escalável de executar computação conteinerizada (containerized compute) em um cluster com primitivas padronizadas: Pods, Tarefas/Implantações, Serviços, claims de armazenamento (storage claims) e agendamento consciente de recursos (resource-aware scheduling). Plataformas de ML constroem sobre essas primitivas para adicionar ergonomia (ergonomics) específica de ML (treinamento distribuído, pipelines, rollout de modelos), mas entender os fundamentos do Kubernetes é o que permite depurar desempenho, controlar custo e operar sistemas de ML com confiança — especialmente em torno de GPUs, armazenamento e agendamento.