Introdução ao Mecanismo de Escalonamento HAMi
O HAMi implementa um escalonador personalizado para alocar recursos de vGPU, denominado hami-scheduler. Para garatnir que pods que requisitam vGPU sejam roteados para esse escalonador específico, utiliza-se um webhook de admissão mutante. Este componente intercepta a criação de pods e modifica dinamicamente o nome do escalonador designado.
Estrutura de Inicialização
O processo de inicialização do HAMi Scheduler compreende duas partes principais que residem no mesmo binário:
- Inicialização dos dispositivos de hardware suportados (NVIDIA, AMD, etc.)
- Configuração dos endpoints HTTP que servem tanto o webhook quanto as rotas de escalonamento
O ponto de entrada principal segue esta estrutura simplificada:
package main
import (
"net/http"
"k8s.io/klog/v2"
"github.com/julienschmidt/httprouter"
"myproject/device"
"myproject/scheduler"
"myproject/routes"
)
var (
schedulerInstance *scheduler.Scheduler
httpServerAddr string = ":8080"
)
func main() {
initializeHardwareDrivers()
setupHTTPServer()
}
func initializeHardwareDrivers() {
device.RegisterDriver("nvidia", newNvidiaDriver())
device.RegisterDriver("amd", newAMDDriver())
}
func setupHTTPServer() {
router := httprouter.New()
router.POST("/mutate-pod", handlePodAdmission)
router.POST("/schedule", handleScheduling)
router.GET("/status", handleHealthCheck)
klog.Infof("Iniciando servidor de admissão em %s", httpServerAddr)
log.Fatal(http.ListenAndServe(httpServerAddr, router))
}
Configuração do Webhook no Kubernetes
Para que o webhook seja acionado automaticamente, é necessário registrar uma MutatingWebhookConfiguration no cluster. Esta configuração instrui a API do Kubernetes a enviar requisições de admissão para o nosso endpoint sempre que um pod for criado.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: vgpu-admission-controller
webhooks:
- name: pod.vgpu.admission.local
admissionReviewVersions: ["v1"]
sideEffects: None
clientConfig:
service:
name: hami-admission-service
namespace: kube-system
path: "/mutate-pod"
port: 443
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
namespaceSelector:
matchExpressions:
- key: vgpu.admission
operator: NotIn
values: ["disabled"]
Esta configuração filtra apenas eventos de criação de pods em namespaces que não possuam o rótulo vgpu.admission=disabled.
Implementação do Manipulador de Admissão
A lógica central do webhook avalia se um pod faz uso de recursos de vGPU gerenciados pelo HAMi. O processo segue estas etapas:
- Decodificar o pod da requisição de admissão
- Verificar se o pod utiliza modo privilegiado (ignorado caso positivo)
- Inspecionar os limites de recursos dos containers
- Se encontrar recursos de vGPU configurados, alterar o nome do escalonador
- Retornar o patch JSON com as modificações
package webhook
import (
"context"
"encoding/json"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
type PodAdmissionHandler struct {
Decoder *admission.Decoder
TargetSchedulerName string
}
func (h *PodAdmissionHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
pod := &corev1.Pod{}
if err := h.Decoder.Decode(req, pod); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if h.shouldSkipPod(pod) {
admission.Allowed("pod ignorado")
}
if h.requiresCustomScheduler(pod) {
pod.Spec.SchedulerName = h.TargetSchedulerName
}
patchedPod, err := json.Marshal(pod)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(req.Object.Raw, patchedPod)
}
func (h *PodAdmissionHandler) shouldSkipPod(pod *corev1.Pod) bool {
if pod.Spec.NodeName != "" {
return true // Pod já possui nó designado
}
for _, container := range pod.Spec.Containers {
if container.SecurityContext != nil &&
container.SecurityContext.Privileged != nil &&
*container.SecurityContext.Privileged {
return true // Container em modo privilegiado
}
}
return false
}
func (h *PodAdmissionHandler) requiresCustomScheduler(pod *corev1.Pod) bool {
gpuResourceNames := []string{
"nvidia.com/gpu",
"amd.com/gpu",
"vgpu.example.com/memory",
"vgpu.example.com/cores",
}
for _, container := range pod.Spec.Containers {
for resourceName := range container.Resources.Limits {
for _, gpuResource := range gpuResourceNames {
if string(resourceName) == gpuResource {
return true
}
}
}
}
return false
}
Fluxo Completo de Admissão
O processo integrado ocorre da seguinte forma:
- Submissão do Pod: O usuário aplica um manifesto YAML que inclui requisitos de vGPU
- Interceptação pelo API Server: Baseado na configuração do webhook, a API do Kubrenetes envia uma requisição POST para o endpoint configurado
- Avaliação do Webhook: O handler analisa o pod, verifica condições de exclusão e detecta recursos de vGPU
- Modificação do Escalonador: Se aplicável, o campo
schedulerNameé alterado parahami-scheduler - Aplicação do Patch: O pod modificado é retornado como uma resposta de patch JSON
- Escalonamento HAMi: O escalonador personalizado então aloca os recursos de vGPU baseado nas políticas configuradas
Cenários Especiais e Comportamentos
Duas situações merecem atenção especial:
- Pods com Privilegiados: Containers que executam em modo privilegiado têm acesso irrestrito ao hardware. O webhook ignora esses pods, deixando-os serem escalonados pelo escalonador padrão.
- Nó Pré-definido: Se um pod já tiver um
nodeNameespecificado, o webhook rejeita a admissão. Isso evita alocações inadequadas onde o nó escolhido pode não ter recursos suficientes.
Este design assegura que apenas pods elegíveis aproveitem as funcionalidades avançadas de escalonamento de vGPU do HAMi, enquanto mantém compatibilidade com workloads tradicionais do Kubernetes.