Crea tu propio chatbot LLM en Linux: guía paso a paso para sysadmins
TL;DR — Lo que vas a tener al final
- Tiempo de setup: 2-3 horas para el primer LLM funcionando.
- Requisitos: GPU NVIDIA con 12+ GB VRAM (RTX 3060 mínimo).
- Resultado: LLM local con API OpenAI-compatible, integrable con n8n/Open WebUI.
- Troubleshooting: nvidia-smi, ollama ps, OOM con modelos grandes.
Por qué un chatbot LLM propio, en tu propio hardware
Si administras un servidor Linux o un homelab con suficiente RAM, puedes tener un asistente conversacional afinado a tus propios datos sin pagar tokens a nadie. El articulo de Rob Gibbon en Ubuntu Blog (septiembre 2026) muestra una receta completa: levantar Charmed Kubeflow sobre MicroK8s, ingestar un dataset en Postgres, registrarlo como feature store con Feast, hacer fine-tuning con LoRA en Kubeflow Trainer v2 y servir el modelo con KServe. El resultado: una API OpenAI-compatible apuntando a TU modelo, en TU red.
La gracia no es solo ahorrar costos de inferencia. Es soberanía de datos, repetibilidad del pipeline y, sobre todo, poder elegir tú el modelo base y los datos con los que aprende. Esta guia traduce esa receta a un paso a paso concreto para sysadmins que ya manejan Kubernetes básico y quieren saldar el débito técnico de haber entrenado un modelo en su propia infra.
Que vas a necesitar
- Ubuntu 24.04 LTS o superior, con 32 GB de RAM y 16 cores como minimo (mas es mejor).
- MicroK8s 1.34 (canal strict) como distribucion de Kubernetes.
- Juju 3.6 como motor de operaciones para desplegar Charmed Kubeflow.
- Terraform para aplicar los modulos de la solucion charmed-kubeflow-solutions.
- Una cuenta en Hugging Face y aceptar los terminos del dataset
nampdn-ai/tiny-webtext. - Disco extra para el PVC donde se persisten los checkpoints (20 GB alcanza).
El autor del articulo destaca que correr todo esto en una sola maquina es perfectamente viable para una primera iteracion. El camino a escala (AI factory multi-nodo con GPUs) sigue siendo el mismo pipeline, solo cambian los parametros del TrainJob y los limites del KServe InferenceService.
Levantar el cluster base
Los primeros comandos instalan el cluster Kubernetes minimalista, exponen servicios via MetalLB y bootstrappean Juju sobre el cluster. Esto te lleva entre 5 y 10 minutos dependiendo de la red.
sudo snap install microk8s --channel 1.34-strict/stable
sudo microk8s enable storage dns
sudo microk8s enable metallb 10.64.140.43-10.64.140.49
sudo snap install juju --channel 3.6/stable
sudo microk8s.config | juju add-k8s my-k8s --client
juju bootstrap my-k8s
sudo snap install terraform
sudo apt install git -y
git clone https://github.com/canonical/charmed-kubeflow-solutions.git
cd charmed-kubeflow-solutions
git checkout track/1.11-rc
cd terraform/products/kubeflow
El rango MetalLB que asignes debe estar dentro de tu red y no chocar con DHCP. Si tu homelab ya usa 10.64.140.x, cambiá el rango por otro libre (por ejemplo 192.168.50.50-192.168.50.60). Esto es clave para que el dashboard de Kubeflow sea accesible despues en una IP estable.
Desplegar Charmed Kubeflow con los modulos correctos
A diferencia de la guia oficial, no queremos activar todos los modulos. Katib, MLFlow, TensorBoard y la federated login pueden esperar. Activamos solo lo que el pipeline va a usar: el Trainer v2 (la API nueva para training jobs), KServe (serving del modelo) y Feast (feature store).
export DEX_USERNAME=admin
export DEX_PASSWORD="$(openssl rand -base64 16)"
terraform init
terraform apply -auto-approve
-var dex_static_username="${DEX_USERNAME}"
-var dex_static_password="${DEX_PASSWORD}"
-var enable_feast=true
-var enable_katib=false
-var enable_kserve=true
-var enable_training_v2=true
-var enable_training_v1=false
-var enable_observability=false
-var enable_tensorboard=false
El terraform apply puede demorar entre 10 y 15 minutos. Monitoreá el avance con juju status en otra terminal. Cuando veas todos los charms en estado “active” o “waiting” (no “error” ni “blocked”), el cluster esta listo para autenticar.
Crear el Jupyter Notebook con acceso al pipeline
Entrá al dashboard en http://10.64.140.40 (o la primera IP de tu rango MetalLB) con las credenciales DEX que seteaste. El primer wizard de setup es pura configuracion inicial del perfil, se puede aceptar con los defaults.
En el menu izquierdo, abre Notebooks y lanza uno nuevo. En la seccion Resources pedi 3 CPUs y 6 GB de RAM como minimo: el fine-tuning va a hacer bastante computo. En Advanced Options → Configurations, asegurate de tildar “Allow access to Kubeflow Pipelines” y “Allow access to Feast”. Sin esto, el notebook no tendra las variables de entorno necesarias para hablar con Feast y con el Trainer v2.
Arquitectura del pipeline
El flujo de datos es asi: Hugging Face sirve el dataset, Postgres lo absorbe como tabla cruda, Feast lo registra como BatchFeatureView reutilizable, Kubeflow Trainer v2 corre el job de LoRA consumiendo esas features, los checkpoints caen en un PVC, y KServe los expone como API REST OpenAI-compatible.
| Componente | Rol | Persiste en |
|---|---|---|
Hugging Face nampdn-ai/tiny-webtext |
Fuente del dataset | Remoto, cache en PVC |
Postgres offline_store |
Tabla cruda de features | Cluster (PVC Juju) |
| Feast | Feature store + BatchFeatureView |
Metadata en Postgres |
| Kubeflow Trainer v2 | Job de fine-tuning LoRA | Checkpoints en PVC dedicado |
| KServe InferenceService | Serving HTTP OpenAI-compatible | PVC de checkpoints |
La separacion Feast/Trainer es la pieza clave: podes versionar features como codigo (features.py) y reutilizarlas en multiples jobs de training sin re-ingestar el dataset cada vez.
Ingestar el dataset en Postgres via Feast
Una vez que el notebook esta corriendo, abre una terminal dentro de el y baja los archivos del repo del autor.
conda install gcc gxx_linux-64
git clone https://github.com/grobbie/fine-tune-your-own-custom-llm-with-canonical-charmed-kubeflow-and-feast.git
mv fine-tune-your-own-custom-llm-with-canonical-charmed-kubeflow-and-feast kubeflow-feast-finetune
cd kubeflow-feast-finetune
pip install -r requirements.txt
Antes de ingestar, logueate en Hugging Face. Si no lo hiciste antes, tambien acepta los terminos del dataset en la pagina del repo (sin esto el script falla con 403 al intentar bajarlo).
hf auth login
export POSTGRES_HOST="postgres.default.svc.cluster.local"
export POSTGRES_PORT="5432"
export POSTGRES_DB="offline_store"
export POSTGRES_USER="your_user"
export POSTGRES_PASSWORD="your_password"
python kubeflow-feast-finetune/ingest_data.py
Las credenciales de Postgres las encontras en el archivo /feast/feature_store.yaml dentro del pod del Jupyter Notebook. Si el modulo Feast se desplego correctamente, ese archivo ya tiene los valores correctos para tu instancia.
Registrar las features en Feast
Una vez que Postgres tiene los datos crudos, corres feast apply para que Feast los registre como una BatchFeatureView versionada. Esto es lo que hace que el pipeline sea reproducible: cualquier job futuro puede pedirle a Feast las mismas features sin tocar el dataset original.
cd kubeflow-feast-finetune
cp /feast/feature_store.yaml .
feast apply
cd ..
Si ves Registered entity tiny_webtext and feature view tiny_webtext_features en la salida, Feast ya conoce el dataset. A partir de aca, cualquier training job puede invocarlo con feast.get_feature_service("tiny_webtext_fs").get_batch_features() desde train.py.
Empaquetar el training script como ConfigMap
Para que el Trainer v2 pueda levantar el script desde el cluster, lo cargamos como ConfigMap en Kubernetes. Asi no dependemos de que el PVC del notebook este montado en el pod de training.
cd kubeflow-feast-finetune
python3 -c '
import yaml
cm = {
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {"name": "feast-train-code", "namespace": "kubeflow"},
"data": {
"feature_store.yaml": open("feature_store.yaml").read(),
"features.py": open("features.py").read(),
"train.py": open("train.py").read()
}
}
print(yaml.dump(cm))
' | kubectl apply -f -
cd ..
Si kubectl apply responde configmap/feast-train-code created, el Trainer ya puede leer los scripts al levantar el pod de training. Si responde forbidden, te falta el ServiceAccount adecuado (en Charmed Kubeflow suele ser default-editor en el namespace kubeflow).
Crear el PVC para los checkpoints
Sin este PVC, los checkpoints se pierden cuando el job termina. El YAML del autor pide 20 GB, que alcanza para modelos pequenos como GPT-2 o Qwen2.5-0.5B. Si vas a entrenar un modelo de mas de 1B de parametros, subilo a 100 GB.
cat kubeflow-feast-finetune/create_pvc.yaml | kubectl apply -f -
kubectl get pvc -n kubeflow | grep model-checkpoint-pvc
El STATUS del PVC debe pasar a Bound en menos de 30 segundos. Si queda Pending, el cluster no tiene una StorageClass default; revisa kubectl get storageclass y, si hay varias, tildá la correcta con kubectl patch storageclass <nombre> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'.
Lanzar el job de fine-tuning
El TrainJob corre torch-distributed en una sola instancia. Podes notar. Podes pasar --model-name para elegir el modelo base; por defecto el script usa GPT-2 (2019) que termina rapido pero da respuestas mediocres.
cat kubeflow-feast-finetune/train_job_no_registry.yaml | kubectl apply -f -
kubectl get trainjobs -n kubeflow
El job crea un pod cuyo nombre empieza con feast-tiny-webtext-finetune. Segui los logs con kubectl logs -f -l batch.kubernetes.io/job-name=feast-tiny-webtext-finetune -n kubeflow. El autor reporta alrededor de una hora con GPT-2 y “todo el dia” con Qwen2.5-0.5B-Instruct sin GPU.
Servir el modelo con KServe
Una vez que el job reporta Training job completed, los pesos quedan en el PVC. KServe los expone como una API REST compatible con OpenAI. Esto significa que cualquier cliente OpenAI (incluso curl como muestra la guia) puede hablarle sin saber que es un modelo local.
echo "apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: tiny-webtext-llm
namespace: kubeflow
spec:
predictor:
model:
modelFormat:
name: huggingface
storageUri: "pvc://model-checkpoint-pvc/"
resources:
limits:
cpu: "4"
memory: "8Gi"
requests:
cpu: "2"
memory: "4Gi"
args:
- --model_id=/mnt/models
- --backend=huggingface
- --task=text_generation
" | kubectl apply -f -
KServe tarda entre 2 y 5 minutos en levantar el pod y descargar los pesos del PVC. Cuando kubectl get isvc -n kubeflow tiny-webtext-llm muestre READY: True, el modelo esta listo para recibir prompts.
Charlar con tu LLM via API OpenAI-compatible
KServe publica el InferenceService con una IP interna del cluster. La sacamos con un kubectl get services filtrando por el nombre del predictor y consultamos con curl exactamente igual que si fuera la API de ChatGPT.
KSERVE_IP=$(kubectl get services -n kubeflow | grep tiny-webtext-llm-predictor | grep private | awk '{ print $3 }')
curl -i http://${KSERVE_IP}/openai/v1/chat/completions
-H "Content-Type: application/json"
-d '{
"model": "tiny-webtext-llm",
"messages": [{"role": "user", "content": "What is your favorite color?"}],
"max_tokens": 80,
"temperature": 0.2,
"top_p": 0.9,
"stop": ["nHuman:", ""]
}'
Con GPT-2 como base, vas a obtener respuestas razonables pero algo incoherentes, del estilo “I love the way it looks” repetido. Es lo esperado: GPT-2 es un modelo chico pensado para validacion de pipeline. Para respuestas utiles, pasale a train.py el flag --model-name=Qwen/Qwen2.5-0.5B-Instruct antes de relanzar el TrainJob.
Troubleshooting: lo que probablemente va a fallar
El articulo es prolijo pero omite algunos tropiezos comunes cuando uno replica esto en hardware real:
- DEX login no carga el dashboard: el primer arranque de Dex puede demorar 3-5 minutos. Si despues de 10 minutos sigue blanco,
juju status dex-authpara ver si quedo en waiting por falta de certificado. Reaplica conjuju config dex-auth public-url="http://10.64.140.40". - ingest_data.py falla con connection refused a Postgres: el nombre DNS
postgres.default.svc.cluster.localsolo funciona si el modulo Feast se desplego en el namespacedefault. Verifica conkubectl get svc -A | grep postgresy ajusta la variablePOSTGRES_HOST. - TrainJob queda Pending indefinidamente: usualmente porque el PVC no esta Bound (ver seccion anterior) o porque no hay suficientes recursos en el cluster.
kubectl describe trainjobte dice el evento exacto. - KServe InferenceService queda NotReady: en el 90% de los casos es porque el pod predictor no puede montar el PVC. Verifica que
kubectl get pvc model-checkpoint-pvc -n kubefloweste Bound y que el ServiceAccount del predictor tenga permisosgetsobre el PVC.
Vale la pena? Para quien si, para quien no
Este pipeline vale la pena si cumplis al menos dos de estas tres condiciones: tenes datos propios que no queres mandar a un proveedor externo, necesitas reproducibilidad (versionar features + modelo en Git), o simplemente queres aprender MLOps end-to-end sin pagarle a OpenAI por las dudas. La curva de entrada es real (unas 4-6 horas de instalacion si todo sale bien, un dia si algo falla), pero una vez que esta arriba podes iterar rapido: cambiar modelo base, agregar mas features, entrenar varias versiones.
No vale la pena si solo queres chatear con un LLM: llama.cpp + Ollama te dejan un modelo local andando en 20 minutos sin todo este stack. Tampoco si lo que necesitas es RAG sobre documentos: ahi el camino mas corto es llama.cpp + un vector store local, no Kubeflow. El encanto de Charmed Kubeflow es la pieza de “trail” de entrenamiento versionado y reproducible, no el chatbot en si.
Si lo que te interesa es comparar motores de inferencia antes de meterte en training, vLLM vs llama.cpp vs Ollama repasa por la guia. Y si lo que buscas es entender mejor Python antes de tocar Feast o KServe, vale la pena revisar primero como configurar un entorno de desarrollo Python en Linux.
Conclusion
Tener un LLM afinado corriendo en tu propio cluster Kubernetes ya no es un proyecto de investigacion: es un fin de semana largo con la guia correcta. Charmed Kubeflow le pone el cascaron MLOps (Trainer v2, KServe, Feast) sobre infraestructura que ya administras, asi no tenes que aprender un framework nuevo ademas de Kubernetes. Lo que sigue despues es ajustar hiperparametros, probar otros datasets, y eventualmente mover el cluster a una maquina con GPU para iterar mas rapido.
Lee tambien
- vLLM vs llama.cpp vs Ollama: como elegir motor de inferencia LLM en 2026
- Configura un entorno de desarrollo Python en Linux
- Gestion de cargas de trabajo complejas en Kubernetes: desafios y soluciones
- MariaDB Vector y los LLMs: por que tu coding assistant no lo sugiere
Preguntas frecuentes
¿Por qué Crea tu propio chatbot LLM en Linux: guía paso a paso para sysadmins es relevante para sysadmins Linux?
Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.
¿Qué necesito para empezar con Crea tu propio chatbot LLM?
Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.
¿Cuáles son los errores más comunes con Crea tu propio chatbot LLM?
Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.
¿Cuándo usar Crea tu propio chatbot LLM y cuándo una alternativa?
Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.
¿Cómo migro desde una configuración previa a Crea tu propio chatbot LLM?
Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.
Lee también
- Guía Fedora 44: Novedades, Instalación y Upgrade para Sysadmins Linux
- Configura VLANs en Linux con nmcli: Guía paso a paso para sysadmins
- Protege tu SSH: Guía esencial para configurar Fail2ban en Linux
- Guía Definitiva: Configura Kubernetes con kubeadm en Ubuntu para Sysadmins Linux
- Configura Prometheus y Grafana: Guía DevOps para SysAdmins Linux
