{"id":631,"date":"2026-09-18T13:39:47","date_gmt":"2026-09-18T12:39:47","guid":{"rendered":"https:\/\/www.darifer.net\/?p=631"},"modified":"2026-09-18T15:12:10","modified_gmt":"2026-09-18T14:12:10","slug":"del-laboratorio-a-una-plataforma-de-organizacion","status":"publish","type":"post","link":"https:\/\/www.darifer.net\/index.php\/2026\/09\/18\/del-laboratorio-a-una-plataforma-de-organizacion\/","title":{"rendered":"Del Laboratorio a una plataforma de organizacion"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Hola a tod@s,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En el art\u00edculo anterior vimos c\u00f3mo desplegar y servir un LLM con vLLM en un Dell Pro Max GB10. La idea era sencilla y muy \u00fatil para laboratorio o para un entorno peque\u00f1o: Linux, GPU NVIDIA, Docker, un modelo descargado desde Hugging Face y una API compatible con OpenAI.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pero al llevar esta misma necesidad a una organizaci\u00f3n aparecen nuevas preguntas. \u00bfC\u00f3mo damos servicio a varios equipos? \u00bfC\u00f3mo evitamos que el modelo dependa de un \u00fanico equipo? \u00bfC\u00f3mo controlamos actualizaciones, identidades, red, secretos, capacidad y observabilidad? Aqu\u00ed es donde Azure Local puede convertirse en la base de una plataforma de IA ejecutada dentro de nuestro propio centro de datos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Azure Local<\/strong> no sustituye al motor de inferencia. vLLM seguir\u00e1 siendo quien cargue el modelo y responda a las peticiones. Lo que aporta Azure Local es la capa de infraestructura h\u00edbrida: c\u00f3mputo, almacenamiento, redes, virtualizaci\u00f3n, integraci\u00f3n con <strong>Azure Arc<\/strong> y una ruta hacia Kubernetes para operar el servicio con criterios empresariales.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>QU\u00c9 VAMOS A CONSEGUIR<\/strong> Dise\u00f1aremos dos caminos: una m\u00e1quina virtual Linux con GPU para empezar de forma controlada y una plataforma con AKS habilitado por <strong>Azure Arc<\/strong> para escalar. El laboratorio principal se centrar\u00e1 en Kubernetes porque permite separar infraestructura, runtime y aplicaci\u00f3n.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Me viene a la cabeza una pregunta que me hizo un amigo de la comunidad tecnol\u00f3gica.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">\u00bfPor qu\u00e9 ejecutar modelos de IA en local?<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mantener prompts, documentos, c\u00f3digo y respuestas dentro del per\u00edmetro definido por la organizaci\u00f3n.<\/li>\n\n\n\n<li>Reducir latencia cuando las aplicaciones y los datos ya est\u00e1n en el datacenter o en el Edge.<\/li>\n\n\n\n<li>Operar en ubicaciones con conectividad limitada o con requisitos estrictos de soberan\u00eda.<\/li>\n\n\n\n<li>Controlar el ciclo de vida del runtime, del modelo y de la infraestructura.<\/li>\n\n\n\n<li>Crear una API interna reutilizable por asistentes, automatizaciones, RAG y herramientas de desarrollo.<\/li>\n\n\n\n<li>Predecir el coste cuando la demanda es estable y el hardware se aprovecha de forma sostenida.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Tener el modelo en local no elimina los riesgos. Al contrario, nos convierte en responsables del parcheado, la protecci\u00f3n del endpoint, las licencias del modelo, la gesti\u00f3n de secretos, el filtrado de datos y la capacidad. La soberan\u00eda no consiste solo en guardar los pesos en nuestro CPD; tambi\u00e9n implica gobernar todo el servicio.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Azure Local como base para IA privada<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos ver la soluci\u00f3n en cuatro capas:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Capa<\/strong><\/td><td><strong>Componentes<\/strong><\/td><td><strong>Responsabilidad<\/strong><\/td><\/tr><tr><td>Infraestructura<\/td><td>Nodos Azure Local, red, almacenamiento, GPU<\/td><td>Disponibilidad, rendimiento y ciclo de vida del hardware.<\/td><\/tr><tr><td>Plataforma<\/td><td>VM Linux o AKS habilitado por Azure Arc<\/td><td>Aislamiento, scheduling, actualizaciones y pol\u00edticas.<\/td><\/tr><tr><td>Serving (publicar un modelo de IA como servicio para que se puedan utilizar)<\/td><td>vLLM, imagen de contenedor, cach\u00e9 de modelos<\/td><td>Carga de pesos, KV cache, batching y API.<\/td><\/tr><tr><td>Consumo (Quien va a utilizar el modelo y como lo va a utilizar)<\/td><td>Aplicaciones, RAG, agentes, portales internos<\/td><td>Autenticaci\u00f3n, experiencia, datos y casos de uso.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Dos caminos de despliegue<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Opci\u00f3n<\/strong><\/td><td><strong>Cuando elegirla<\/strong><\/td><td><strong>Ventajas<\/strong><\/td><td><strong>L\u00edmites<\/strong><\/td><\/tr><tr><td>VM Linux + GPU<\/td><td>Piloto, un modelo, pocos consumidores<\/td><td>Sencilla, cercana al art\u00edculo anterior, troubleshooting directo.<\/td><td>Escalado y alta disponibilidad m\u00e1s manuales.<\/td><\/tr><tr><td>AKS + GPU<\/td><td>Varios modelos o equipos, GitOps, necesidad de estandarizaci\u00f3n<\/td><td>Despliegues declarativos, servicios, rolling updates y pol\u00edticas.<\/td><td>M\u00e1s componentes, curva operativa y soporte GPU dependiente de versi\u00f3n.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Mi recomendaci\u00f3n es comenzar con una VM si todav\u00eda estamos validando el modelo y el patr\u00f3n de consumo. Cuando la API pase a ser un servicio compartido, tengamos varios consumidores o necesitemos una operaci\u00f3n repetible, Kubernetes empieza a aportarnos valor.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Arquitectura de referencia<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">La siguiente arquitectura nos muestra c\u00f3mo podemos transformar un laboratorio basado en una \u00fanica GPU en una plataforma empresarial gobernada desde <strong>Azure.<\/strong> Los usuarios consumen una API interna protegida por un gateway, mientras que vLLM ejecuta los modelos sobre nodos con GPU dentro de Kubernetes. <strong>Azure Arc<\/strong> proporciona gobierno, pol\u00edticas y operaciones, mientras que <strong>Azure Local <\/strong>aporta la infraestructura de ejecuci\u00f3n.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"691\" height=\"460\" src=\"https:\/\/www.darifer.net\/wp-content\/uploads\/2026\/09\/image-3.png\" alt=\"\" class=\"wp-image-632\" srcset=\"https:\/\/www.darifer.net\/wp-content\/uploads\/2026\/09\/image-3.png 691w, https:\/\/www.darifer.net\/wp-content\/uploads\/2026\/09\/image-3-300x200.png 300w\" sizes=\"auto, (max-width: 691px) 100vw, 691px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">El endpoint de vLLM no deber\u00eda publicarse directamente en Internet. En producci\u00f3n lo normal es situarlo detr\u00e1s de una capa que termine TLS, autentique al cliente, aplique cuotas, limite el tama\u00f1o de las peticiones y genere trazabilidad.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Requisitos previos<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Una instancia de <strong>Azure Local<\/strong> instalada, registrada y saludable.<\/li>\n\n\n\n<li>Hardware validado para la versi\u00f3n desplegada, incluida la opci\u00f3n de GPU cuando corresponda.<\/li>\n\n\n\n<li>AKS habilitado por <strong>Azure Arc<\/strong> operativo, o una VM Linux si elegimos el camino inicial.<\/li>\n\n\n\n<li>Driver y device plug-in compatibles con la GPU y con la versi\u00f3n de Kubernetes.<\/li>\n\n\n\n<li>Registro de contenedores autorizado y repositorio para manifiestos.<\/li>\n\n\n\n<li>Almacenamiento suficiente para imagen, pesos, cach\u00e9 y posibles variantes del modelo.<\/li>\n\n\n\n<li>Conectividad controlada hacia el registro y el repositorio del modelo, o un proceso offline de importaci\u00f3n.<\/li>\n\n\n\n<li>Licencia del modelo revisado y, si es necesario, credenciales de Hugging Face con permisos m\u00ednimos.<\/li>\n\n\n\n<li>DNS, certificados y una red privada para consumidores internos.<\/li>\n\n\n\n<li>Una estimaci\u00f3n de memoria GPU que incluya pesos, KV cache, contexto y concurrencia.<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>DECISI\u00d3N CR\u00cdTICA<\/strong> No seleccion\u00e9is un modelo solo por su n\u00famero de par\u00e1metros. La cuantizaci\u00f3n, la longitud de contexto, la la concurrencia y el runtime modifican de forma importante el consumo real de memoria.<\/p>\n<\/blockquote>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 1. Validar el cl\u00faster y localizar la GPU<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de desplegar vLLM comprobaremos que Kubernetes ve el recurso acelerador. Los nombres exactos de los nodos y del recurso pueden variar seg\u00fan el plug-in utilizado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl get nodes -o wide<br><br>kubectl describe nodes | grep -i -E \u00abnvidia.com\/gpu|gpu\u00bb<br><br>kubectl get pods -A | grep -i nvidia<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El resultado esperado es que al menos un nodo publique capacidad GPU y que los componentes del device plug-in est\u00e9n en estado Running. Si Kubernetes no anuncia la GPU, no merece la pena continuar con el modelo: primero debemos resolver driver, firmware, asignaci\u00f3n y compatibilidad.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 2. Preparar namespace, secretos y almacenamiento<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Separaremos la carga en un namespace y as\u00ed evitaremos incluir tokens directamente en el manifiesto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl create namespace ai-inference<br><br>kubectl -n ai-inference create secret generic hf-token \\<br>&nbsp; &#8211;from-literal=token=\u00bbhf_REEMPLAZAR\u00bb<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para la cach\u00e9 del modelo necesitamos una StorageClass disponible en el cl\u00faster. El siguiente ejemplo solicitamos 200 GiB; ajustamos el tama\u00f1o del modelo y a vuestra pol\u00edtica de almacenamiento.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">apiVersion: v1<br>kind: PersistentVolumeClaim<br>metadata:<br>&nbsp; name: hf-cache<br>&nbsp; namespace: ai-inference<br>spec:<br>&nbsp; accessModes:<br>&nbsp;&nbsp;&nbsp; &#8211; ReadWriteOnce<br>&nbsp; resources:<br>&nbsp;&nbsp;&nbsp; requests:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; storage: 200Gi<br>&nbsp; storageClassName: REEMPLAZAR_STORAGECLASS<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 3. Desplegar vLLM con GPU<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Este manifiesto es una base de laboratorio. Fijaremos una imagen versionada en lugar de latest, solicitaremos una GPU y montaremos la cach\u00e9. Debemos sustituir el modelo, la etiqueta de imagen y los valores de capacidad por versiones validadas en nuestro entorno.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">apiVersion: apps\/v1<br>kind: Deployment<br>metadata:<br>&nbsp; name: vllm<br>&nbsp; namespace: ai-inference<br>spec:<br>&nbsp; replicas: 1<br>&nbsp; selector:<br>&nbsp;&nbsp;&nbsp; matchLabels:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app: vllm<br>&nbsp; template:<br>&nbsp;&nbsp;&nbsp; metadata:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; labels:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app: vllm<br>&nbsp;&nbsp;&nbsp; spec:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; containers:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: vllm<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; image: vllm\/vllm-openai:REEMPLAZAR_VERSION<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; args:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; &#8211;model<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; REEMPLAZAR_MODELO<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; &#8211;max-model-len<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; \u00ab8192\u00bb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; &#8211;gpu-memory-utilization<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; \u00ab0.80\u00bb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; env:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: HF_TOKEN<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; valueFrom:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; secretKeyRef:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name: hf-token<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; key: token<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ports:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: http<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; containerPort: 8000<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; resources:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requests:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cpu: \u00ab4\u00bb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memory: 24Gi<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nvidia.com\/gpu: \u00ab1\u00bb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; limits:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cpu: \u00ab8\u00bb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memory: 48Gi<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nvidia.com\/gpu: \u00ab1\u00bb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; readinessProbe:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; httpGet:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; path: \/health<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port: http<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; initialDelaySeconds: 30<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; periodSeconds: 10<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; failureThreshold: 60<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; volumeMounts:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: hf-cache<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mountPath: \/root\/.cache\/huggingface<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; volumes:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: hf-cache<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; persistentVolumeClaim:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; claimName: hf-cache<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En un cl\u00faster con nodos heterog\u00e9neos a\u00f1adiremos labels, nodeSelector o affinity para que el pod solo se programe donde exista la GPU adecuada. Tambi\u00e9n es habitual aplicar taints a los nodos acelerados para reservarlos a las cargas de IA.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 4. Exponer el servicio dentro de la red<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">apiVersion: v1<br>kind: Service<br>metadata:<br>&nbsp; name: vllm<br>&nbsp; namespace: ai-inference<br>spec:<br>&nbsp; selector:<br>&nbsp;&nbsp;&nbsp; app: vllm<br>&nbsp; ports:<br>&nbsp;&nbsp;&nbsp; &#8211; name: http<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port: 8000<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; targetPort: http<br>&nbsp; type: ClusterIP<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ClusterIP mantiene el servicio accesible \u00fanicamente dentro de Kubernetes. Para consumidores externos al cl\u00faster utilizaremos un ingress controller, un reverse proxy o un balanceador interno con TLS y autenticaci\u00f3n. No cambiaremos a LoadBalancer sin haber definido antes red, exposici\u00f3n y controles de acceso.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 5. Aplicar y validar<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl apply -f pvc.yaml<br>kubectl apply -f deployment.yaml<br>kubectl apply -f service.yaml<br><br>kubectl -n ai-inference rollout status deployment\/vllm<br>kubectl -n ai-inference get pods -o wide<br>kubectl -n ai-inference logs -f deployment\/vllm<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La primera carga ser\u00e1 m\u00e1s lenta porque se debe descargar o leer los pesos. Esperaremos a que la readiness probe est\u00e9 correcta y despu\u00e9s haremos una prueba temporal mediante port-forward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl -n ai-inference port-forward service\/vllm 8000:8000<br><br>curl http:\/\/127.0.0.1:8000\/health<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 6. Probar la API compatible con OpenAI<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">curl http:\/\/127.0.0.1:8000\/v1\/chat\/completions \\<br>&nbsp; -H \u00abContent-Type: application\/json\u00bb \\<br>&nbsp; -d &#8216;{<br>&nbsp;&nbsp;&nbsp; \u00abmodel\u00bb: \u00abREEMPLAZAR_MODELO\u00bb,<br>&nbsp;&nbsp;&nbsp; \u00abmessages\u00bb: [<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {\u00abrole\u00bb: \u00absystem\u00bb, \u00abcontent\u00bb: \u00abResponde en espa\u00f1ol y de forma concisa.\u00bb},<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {\u00abrole\u00bb: \u00abuser\u00bb, \u00abcontent\u00bb: \u00abExplica qu\u00e9 aporta Azure Local a una plataforma de IA privada.\u00bb}<br>&nbsp;&nbsp;&nbsp; ],<br>&nbsp;&nbsp;&nbsp; \u00abmax_tokens\u00bb: 300,<br>&nbsp;&nbsp;&nbsp; \u00abtemperature\u00bb: 0.2<br>&nbsp; }&#8217;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si obtenemos un array choices y contenido en message.content, el flujo b\u00e1sico funciona. Esto solo valida funcionalidad, no capacidad ni seguridad.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Paso 7. Consumirlo desde Python<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">from openai import OpenAI<br><br>client = OpenAI(<br>&nbsp;&nbsp;&nbsp; base_url=\u00bbhttps:\/\/ia.interna.ejemplo\/v1&#8243;,<br>&nbsp;&nbsp;&nbsp; api_key=\u00bbtoken-obtenido-del-gateway\u00bb<br>)<br><br>respuesta = client.chat.completions.create(<br>&nbsp;&nbsp;&nbsp; model=\u00bbREEMPLAZAR_MODELO\u00bb,<br>&nbsp;&nbsp;&nbsp; messages=[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {\u00abrole\u00bb: \u00abuser\u00bb, \u00abcontent\u00bb: \u00abResume las ventajas de Azure Local.\u00bb}<br>&nbsp;&nbsp;&nbsp; ],<br>&nbsp;&nbsp;&nbsp; max_tokens=300,<br>&nbsp;&nbsp;&nbsp; temperature=0.2<br>)<br><br>print(respuesta.choices[0].message.content)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La compatibilidad con el formato OpenAI simplifica la integraci\u00f3n, pero no significa que todas las extensiones o comportamientos sean id\u00e9nticos. Debemos probar el cliente, el modelo y el endpoint concretos.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Seguridad: lo m\u00ednimo para producci\u00f3n<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Control<\/strong><\/td><td><strong>Aplicaci\u00f3n pr\u00e1ctica<\/strong><\/td><\/tr><tr><td>Red<\/td><td>Endpoint privado, segmentaci\u00f3n, firewall y acceso solo desde redes autorizadas.<\/td><\/tr><tr><td>Identidad<\/td><td>Autenticaci\u00f3n en gateway; evitar confiar en una clave de laboratorio colocada en el cliente.<\/td><\/tr><tr><td>TLS<\/td><td>Certificados internos o p\u00fablicos seg\u00fan el escenario; rotaci\u00f3n automatizada.<\/td><\/tr><tr><td>Secretos<\/td><td>Secretos fuera de YAML y repositorios; rotaci\u00f3n y privilegio m\u00ednimo.<\/td><\/tr><tr><td>Im\u00e1genes<\/td><td>Versiones fijadas, an\u00e1lisis de vulnerabilidades y registro autorizado.<\/td><\/tr><tr><td>Modelos<\/td><td>Licencia, procedencia, hash, versionado y proceso de aprobaci\u00f3n.<\/td><\/tr><tr><td>Datos<\/td><td>Clasificaci\u00f3n, DLP, retenci\u00f3n y prevenci\u00f3n de prompts con informaci\u00f3n innecesaria.<\/td><\/tr><tr><td>Abuso<\/td><td>Cuotas, l\u00edmites de tokens, tama\u00f1o m\u00e1ximo de prompt, timeout y l\u00edmites por consumidor.<\/td><\/tr><tr><td>Auditor\u00eda<\/td><td>Registrar identidad, modelo, latencia, tokens y resultado t\u00e9cnico sin almacenar datos sensibles por defecto.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>NO OLVIDEMOS<\/strong> Un modelo local puede inventar informaci\u00f3n, revelar contenido presente en su contexto o producir una respuesta insegura. La ubicaci\u00f3n del modelo no sustituye la validaci\u00f3n de salida, el dise\u00f1o responsable ni los controles de aplicaci\u00f3n.<\/p>\n<\/blockquote>\n\n\n\n<h1 class=\"wp-block-heading\">Alta disponibilidad y escalado<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Aumentar r\u00e9plicas de un Deployment no siempre es suficiente. Cada r\u00e9plica puede requerir una GPU completa y su propia carga de pesos. La estrategia depender\u00e1 del modelo, la memoria y el runtime.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Una r\u00e9plica por GPU y balanceo entre r\u00e9plicas cuando cada modelo cabe en una GPU.<\/li>\n\n\n\n<li>Tensor parallel o pipeline parallel cuando un modelo necesita varias GPU, validando topolog\u00eda y rendimiento.<\/li>\n\n\n\n<li>Replicas separadas por modelo para evitar que modelos con perfiles distintos compitan por memoria.<\/li>\n\n\n\n<li>Escalado basado en cola o concurrencia, no solo en CPU.<\/li>\n\n\n\n<li>PodDisruptionBudget, anti-affinity y pruebas reales de reinicio y mantenimiento.<\/li>\n\n\n\n<li>Cach\u00e9 precargada o repositorio local para reducir el tiempo de recuperaci\u00f3n.<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Observabilidad y pruebas de capacidad<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>M\u00e9trica<\/strong><\/td><td><strong>Qu\u00e9 nos dice<\/strong><\/td><td><strong>Se\u00f1al de alerta<\/strong><\/td><\/tr><tr><td>TTFT<\/td><td>Tiempo hasta el primer token<\/td><td>Cola, prompt largo o modelo saturado.<\/td><\/tr><tr><td>Tokens\/s<\/td><td>Velocidad de generaci\u00f3n<\/td><td>Ca\u00eddas al aumentar concurrencia.<\/td><\/tr><tr><td>Concurrencia<\/td><td>Sesiones atendidas<\/td><td>Errores, timeouts o esperas largas.<\/td><\/tr><tr><td>Memoria GPU<\/td><td>Margen para pesos y KV cache<\/td><td>OOM, evictions o reinicios.<\/td><\/tr><tr><td>Utilizaci\u00f3n GPU<\/td><td>Aprovechamiento del acelerador<\/td><td>Muy baja con cola, o sostenida al 100 %.<\/td><\/tr><tr><td>Latencia total<\/td><td>Experiencia extremo a extremo<\/td><td>Gateway, red, retrieval o inferencia.<\/td><\/tr><tr><td>Errores HTTP<\/td><td>Salud del servicio<\/td><td>429, 5xx y timeouts crecientes.<\/td><\/tr><tr><td>Tama\u00f1o de prompt<\/td><td>Presi\u00f3n de contexto<\/td><td>Usuarios enviando documentos completos sin control.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Las pruebas se deben utilizar prompts y contextos representativos. Una pregunta corta desde curl no predice c\u00f3mo funcionar\u00e1 un asistente RAG con documentos extensos y varios usuarios simult\u00e1neos.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Operaci\u00f3n desconectada y soberan\u00eda<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Si el entorno debe operar con conectividad limitada, prepararemos un flujo de suministro controlado: im\u00e1genes copiadas a un registro local, pesos descargados y validados previamente, dependencias congeladas, certificados disponibles y documentaci\u00f3n para renovar o importar artefactos. Tambi\u00e9n debemos distinguir entre ejecutar la carga sin Internet y administrar toda la plataforma de forma completamente desconectada; no son el mismo requisito y pueden implicar capacidades, licencias y procedimientos diferentes.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Errores comunes y troubleshooting<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>S\u00edntoma<\/strong><\/td><td><strong>Causa probable<\/strong><\/td><td><strong>Comprobaci\u00f3n<\/strong><\/td><\/tr><tr><td>Pod Pending<\/td><td>No hay GPU disponible o selector incorrecto<\/td><td>kubectl describe pod y capacidad del nodo.<\/td><\/tr><tr><td>CrashLoopBackOff<\/td><td>Modelo incompatible, argumentos err\u00f3neos u OOM<\/td><td>Logs del contenedor y eventos del pod.<\/td><\/tr><tr><td>CUDA no disponible<\/td><td>Driver o device plug-in no operativo<\/td><td>DaemonSet, runtime y recurso nvidia.com\/gpu.<\/td><\/tr><tr><td>Descarga interminable<\/td><td>Token, licencia, DNS o salida bloqueada<\/td><td>Secret, acceso al repositorio y proxy.<\/td><\/tr><tr><td>Readiness no pasa<\/td><td>Carga lenta o ruta de salud incorrecta<\/td><td>Logs, \/health e initialDelay\/failureThreshold.<\/td><\/tr><tr><td>OOM con pocas peticiones<\/td><td>Contexto o KV cache sobredimensionados<\/td><td>Reducir max-model-len y medir concurrencia.<\/td><\/tr><tr><td>Respuesta lenta<\/td><td>Modelo grande, almacenamiento lento o cola<\/td><td>TTFT, tokens\/s, GPU y latencia de cach\u00e9.<\/td><\/tr><tr><td>Servicio inaccesible<\/td><td>DNS, ingress, firewall o Service mal definido<\/td><td>Endpoints, NetworkPolicy y ruta completa.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">\u00bfCu\u00e1ndo no elegir esta arquitectura?<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Si nuestra demanda es muy variable y no justifica mantener GPU propia.<\/li>\n\n\n\n<li>Si necesitamos modelos propietarios disponibles \u00fanicamente como servicio cloud.<\/li>\n\n\n\n<li>Si el equipo no puede operar Kubernetes, GPU, seguridad y observabilidad de forma sostenida.<\/li>\n\n\n\n<li>Si el objetivo es una prueba r\u00e1pida y una VM aislada cubre el caso.<\/li>\n\n\n\n<li>Si no existe un requisito real de latencia, soberan\u00eda o integraci\u00f3n local que compense la complejidad.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La decisi\u00f3n no es local contra cloud como si fueran opciones excluyentes. Muchas organizaciones terminar\u00e1n con un enfoque h\u00edbrido: modelos locales para informaci\u00f3n sensible o baja latencia y servicios cloud para capacidades espec\u00edficas, picos de demanda o modelos que no pueden alojar por s\u00ed mismas.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusi\u00f3n<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">En el art\u00edculo anterior demostramos que un Dell Pro Max GB10 se puede convertir en un laboratorio de inferencia muy capaz con vLLM. Con <strong>Azure Local <\/strong>damos el siguiente paso: transformamos ese patr\u00f3n t\u00e9cnico en una plataforma que puede integrarse con la infraestructura, la red, el almacenamiento y el gobierno de una organizaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La pieza clave es separar responsabilidades. <strong>Azure Local <\/strong>aporta la base h\u00edbrida; AKS habilitado por <strong>Azure Arc<\/strong> organiza la ejecuci\u00f3n; la GPU acelera la inferencia; vLLM sirve el modelo; y la capa de acceso protege y gobierna el consumo. Cuando cada capa tiene l\u00edmites y propietarios claros, la IA local deja de ser un experimento conectado a una GPU y empieza a comportarse como un servicio de plataforma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mi recomendaci\u00f3n es avanzar por etapas: primero un modelo y un caso de uso medible, despu\u00e9s seguridad y observabilidad, y finalmente alta disponibilidad y automatizaci\u00f3n. Y, como siempre, probadlo primero en laboratorio antes de llevarlo a producci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Saludos, nos vemos en el pr\u00f3ximo art\u00edculo.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hola a tod@s, En el art\u00edculo anterior vimos c\u00f3mo desplegar y servir un LLM con vLLM en un Dell Pro Max GB10. La idea era sencilla y muy \u00fatil para laboratorio o para un entorno peque\u00f1o: Linux, GPU NVIDIA, Docker, un modelo descargado desde Hugging Face y una API compatible con OpenAI. Pero al llevar [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":368,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14,6,12,36,35],"tags":[8,13,10,37],"class_list":["post-631","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-azure-arc","category-azure-local","category-ia","category-linux","tag-azure","tag-azure-local","tag-hci","tag-ia-linux"],"_links":{"self":[{"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/posts\/631","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/comments?post=631"}],"version-history":[{"count":13,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/posts\/631\/revisions"}],"predecessor-version":[{"id":646,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/posts\/631\/revisions\/646"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/media\/368"}],"wp:attachment":[{"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/media?parent=631"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/categories?post=631"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.darifer.net\/index.php\/wp-json\/wp\/v2\/tags?post=631"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}