Introducción

Los equipos de desarrollo de GPU enfrentan un dilema arquitectónico costoso: dedicar un control plane completo por cada equipo resulta en una sobrecarga de recursos innecesaria, mientras que compartir un único clúster monolítico debilita el aislamiento de seguridad y la autonomía operativa. Además, la asignación tradicional de GPU en Kubernetes suele ser binaria (tarjeta completa o nada), desperdiciando memoria y cómputo cuando las cargas de trabajo solo requieren una fracción del dispositivo.

Esta guía demuestra cómo resolver ambos problemas combinando Kamaji (para virtualizar control planes ligeros) y HAMi (Heterogeneous AI Computing Virtualization Middleware) para la compartición de GPU. El objetivo es construir una plataforma multi-tenant reproducible en una única máquina Linux sin GPU física, utilizando el plugin simulado de HAMi. Al finalizar, tendrás dos inquilinos aislados (tenant-a y tenant-b) capaces de solicitar GPU fraccionada, con validación de que el tráfico de admisión atraviesa correctamente el túnel Konnectivity de Kamaji.

Qué es y para qué sirve

La solución se basa en una arquitectura de dos capas. La capa de infraestructura utiliza Kind (Kubernetes in Docker) para alojar un clúster de gestión que ejecuta Kamaji. Kamaji permite crear «Tenant Control Planes» como pods dentro del clúster de gestión, eliminando la necesidad de nodos dedicados para cada API server de cliente. Esto reduce drásticamente el consumo de CPU y RAM.

La capa de virtualización de hardware utiliza HAMi. En entornos reales, HAMi instala un webhook y un scheduler que interceptan las solicitudes de nvidia.com/gpu y las subdividen en gpumem (memoria) y gpucores (núcleos de cómputo). Para este ejercicio de validación lógica sin hardware, utilizamos el mock device plugin de HAMi. Este plugin no requiere drivers NVIDIA; en su lugar, lee anotaciones de nodo para simular tarjetas GPU y registrar capacidades falsas. La integración crítica que verificamos aquí es el flujo de admisión: cuando un pod se crea en el tenant, el API server (gestionado por Kamaji) debe llamar al webhook de HAMi (que corre en los nodos trabajadores del tenant) a través del túnel Konnectivity.

Prerequisitos

Antes de iniciar, asegurate de cumplir con los siguientes requisitos. La configuración está probada para consumos mínimos, pero el uso de contenedores de control plane puede picar CPU durante el arranque.

  • Sistema Operativo: Linux (Ubuntu 22.04/24.04 recomendado).
  • Recursos: 4 vCPU, 8 GB de RAM libre, 20 GB de disco.
  • Software:

* Docker Engine 24.0+.

* Docker Compose (opcional, pero útil para limpieza).

* kind v0.20+.

* kubectl v1.28+.

* helm v3.13+.

  • Permisos: Usuario con permisos para ejecutar docker y kubectl contra el clúster de gestión.

Guía paso a paso

Paso 1: Preparación de la red y clúster de gestión

Primero, iniciamos el clúster Kind. Es crucial identificar el rango de IPv4 de la red de Docker para configurar MetalLB correctamente, evitando conflictos con IPv6 que suelen romper la asignación de IPs externas.

# Crear el archivo de configuración kind-config.yaml
cat <Instala cert-manager y MetalLB. MetalLB necesita ese rango específico para asignar IPs a los servicios LoadBalancer de los tenants si es necesario, aunque en esta arquitectura el acceso principal es vía API server.

# Instalar cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml

# Instalar MetalLB con el rango obtenido
helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb
# Esperar a que los pods estén listos
kubectl wait –for=condition=ready pod -l app=metallb -n metallb-system –timeout=180s

# Crear el pool de IPs (ajusta el range según SUBNET detectado)
cat <Paso 2: Instalación de Kamaji

Kamaji actúa como el orquestador de control planes. Lo instalamos en el clúster de gestión.

helm repo add kamaji https://clastix.github.io/charts
helm repo update
helm install kamaji kamaji/kamaji \
–namespace kamaji-system –create-namespace \
–set etcd.image.tag=v3.5.9

Verifica que el pod de control plane de Kamaji esté corriendo:

kubectl get pods -n kamaji-system
# Resultado esperado: kamaji-control-plane-… Running

Paso 3: Creación de los Tenant Control Planes

Definimos dos tenants. Cada uno tendrá su propio API server, pero compartirán el datastore etcd gestionado por Kamaji (aislado lógicamente).

# tenant-a.yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: tenant-a
spec:
kubernetesVersion: v1.29.0
controlPlane:
deployment:
replicas: 1
networkProfile:
serviceCIDR: 10.96.0.0/16
podCIDR: 10.244.0.0/16
—
# tenant-b.yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: tenant-b
spec:
kubernetesVersion: v1.29.0
controlPlane:
deployment:
replicas: 1
networkProfile:
serviceCIDR: 10.97.0.0/16
podCIDR: 10.245.0.0/16

Aplica los manifestos:

kubectl apply -f tenant-a.yaml
kubectl apply -f tenant-b.yaml

Una vez que los pods tenant-a-control-plane y tenant-b-control-plane estén Running, extrae los kubeconfigs:

KAMAJI_POD=$(kubectl get pod -l app.kubernetes.io/name=kamaji -n kamaji-system -o jsonpath='{.items[0].metadata.name}’)
kubectl exec -n kamaji-system $KAMAJI_POD — cat /etc/kamaji/tenant-a/admin.conf > tenant-a.kubeconfig
kubectl exec -n kamaji-system $KAMAJI_POD — cat /etc/kamaji/tenant-b/admin.conf > tenant-b.kubeconfig

Paso 4: Unión de Nodos Trabajadores (Workers)

Kamaji no gestiona nodos workers automáticamente. Debemos crear contenedores kindest/node manualmente y unirlos al tenant correspondiente.

# Generar token y hash para tenant-a
TENANT_A_KUBECONFIG=tenant-a.kubeconfig
TOKEN=$(kubectl –kubeconfig $TENANT_A_KUBECONFIG token create –print-join-command | cut -d ‘ ‘ -f 2)
HASH=$(kubectl –kubeconfig $TENANT_A_KUBECONFIG token list -o jsonpath='{.items[0].tokenHash}’)

# Iniciar contenedor worker para tenant-a
docker run -d –name tenant-a-worker –network kind \
kindest/node:v1.29.0

# Obtener IP del worker
WORKER_IP=$(docker inspect tenant-a-worker -f ‘{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}’)

# Ejecutar join dentro del contenedor (requiere acceso root en el contenedor kind)
docker exec tenant-a-worker kubeadm join :6443 –token $TOKEN –discovery-token-ca-cert-hash sha256:$HASH

Nota: El IP del API server del tenant se puede obtener describiendo el servicio kubernetes en el namespace del tenant en el clúster de gestión, o usando el endpoint interno de Kamaji.

Repite esto para tenant-b. Luego, instala CNI (Calico) en cada tenant usando sus respectivos kubeconfigs.

# Ejemplo para tenant-a
kubectl –kubeconfig tenant-a.kubeconfig apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
# Asegúrate de ajustar la ipPool si es necesario

Paso 5: Configuración de HAMi con GPUs Simuladas

Aquí es donde ocurre la magia. Instalamos HAMi en los tenants. No etiquetes los nodos con gpu=on o etiquetas que disparen el plugin real de NVIDIA, ya que fallará por falta de drivers.

Edita la configuración de HAMi para usar el mock plugin. En el values.yaml de HAMi, asegurate de que el dispositivo plugin sea el mock.

Anotamos el nodo worker del tenant para simular una GPU:

kubectl –kubeconfig tenant-a.kubeconfig patch node $(docker inspect tenant-a-worker -f ‘{{.Name}}’ | cut -c2-) -p ‘{«metadata»:{«annotations»:{«hami.io/node-mock-gpu»:»[{\»device_uuid\»:\»GPU-MOCK-A0\»,\»device_type\»:\»NVIDIA A100\»,\»device_memory\»:8192,\»device_cores\»:100}]»}}}’

Verifica que el nodo reporte la capacidad:

kubectl –kubeconfig tenant-a.kubeconfig describe node | grep -A 5 Capacity
# Deberías ver gpumem: 8192 y gpucores: 100

Consideraciones y buenas prácticas

Errores comunes y soluciones:

  • Problemas de IPv6 con MetalLB: Kind a veces mezcla IPv4 e IPv6 en la salida de docker network inspect. Si MetalLB no asigna IPs, verifica que estés escribiendo rangos IPv4 puros en el IPAddressPool.
  • Bloqueo de nodo (Node Lock Contention): Con el plugin mock, HAMi bloquea el nodo al asignar una GPU pero no lo libera automáticamente porque no hay un proceso real de inicialización de hardware. El lock dura 5 minutos. Para pruebas rápidas, elimina la anotación hami.io/bind-phase: allocating del pod o espera, pero ten en cuenta que esto es un artefacto de la simulación, no un bug de producción.
  • Aislamiento de Red: Ten cuidado con las CIDRs. Los pods de tenant-a y tenant-b deben tener rangos de Pod CIDR distintos para evitar colisiones de rutas si comparten el host físico.
  • Seguridad:

    Esta configuración valida la lógica de scheduling y aislamiento de API. En producción, asegúrate de que las políticas de NetworkPolicy en el clúster de gestión impidan que los tenants se comuniquen entre sí directamente a nivel de red, confiando únicamente en el aislamiento lógico de los control planes.

    Conclusión

    La combinación de Kamaji y HAMi permite ofrecer GPU-as-a-Service de forma multi-tenant sin sacrificar el aislamiento ni la eficiencia de recursos. Kamaji elimina el costo del control plane por equipo, mientras que HAMi permite granularidad en la asignación de GPU. La validación en hardware simulado demuestra que el punto de integración más frágil —la llamada del webhook de HAMi a través del túnel Konnectivity de Kamaji— funciona correctamente. Una vez validada la lógica en este entorno de bajo costo, la migración a hardware real implica simplemente reemplazar el mock plugin por el plugin de NVIDIA de HAMi.

    Fuentes

    https://www.cncf.io/blog/2026/10/23/multi-tenant-gpu-kubernetes-on-simulated-hardware-with-kamaji-and-hami/

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *