Inicio Linux & Systems Cybersecurity Cloud & DevOps Networks & Infrastructure SIEM & Monitoring DFIR & Threat Intel Development & Other Todas las categorias Proyectos Sobre Herramientas

Kubeflow + k3s: Plataforma MLOps Enterprise en tu Laboratorio

Read in English

Tabla de contenidos

Introduccion

MLOps es a Machine Learning lo que DevOps fue al desarrollo de software: la disciplina que convierte experimentos de ML en sistemas reproducibles, automatizados y mantenibles en produccion. Sin MLOps, tu modelo es un notebook de Jupyter que funciona en tu portatil. Con MLOps, es un pipeline que entrena, valida, despliega y monitoriza modelos de forma continua.

Kubeflow es la plataforma MLOps open-source de referencia. Nacio en Google en 2017, se graduo como proyecto CNCF y hoy lo utilizan organizaciones como Bloomberg, Spotify, Cisco, US Department of Defense y cientos de empresas del Fortune 500. Su proposito: hacer que los despliegues de ML en Kubernetes sean simples, portables y escalables.

El problema: Kubeflow esta disenado para clusters Kubernetes de produccion. Montarlo en local parece inviable.

La solucion: k3s — una distribucion certificada de Kubernetes que consume 512MB de RAM y se instala en 30 segundos. Mismo API, mismos manifests, misma experiencia. Lo que despliegues aqui funciona igual en EKS, GKE o AKS.

En este lab vamos a montar una plataforma MLOps enterprise-grade completa en tu maquina local:

  • Kubeflow Pipelines — Orquestacion de workflows ML como DAGs
  • SeaweedFS — Almacenamiento de artefactos S3-compatible
  • ML Metadata (MLMD) — Tracking de experimentos y linaje
  • KServe — Serving de modelos en produccion
  • Pipeline real — Entrenar, evaluar y servir un modelo funcional

Todo con comandos que puedes copiar y pegar directamente.


Arquitectura del laboratorio

CODE
┌─────────────────────────────────────────────────────────────┐
│                        Tu Maquina (k3s)                     │
│                                                             │
│  ┌──────────────┐    ┌──────────────────┐                  │
│  │   Kubeflow   │    │  Kubeflow        │                  │
│  │   Dashboard  │◄───│  Pipelines API   │                  │
│  │   (UI)       │    │  (Orchestrator)  │                  │
│  └──────────────┘    └────────┬─────────┘                  │
│                               │                             │
│         ┌─────────────────────┼─────────────────┐          │
│         │                     │                 │          │
│         ▼                     ▼                 ▼          │
│  ┌─────────────┐    ┌──────────────┐   ┌─────────────┐    │
│  │  SeaweedFS   │    │     ML       │   │   KServe    │    │
│  │  (Artifacts) │    │   Metadata   │   │  (Serving)  │    │
│  │  S3-compat  │    │   (MySQL)    │   │             │    │
│  └─────────────┘    └──────────────┘   └─────────────┘    │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │               k3s (Kubernetes ligero)                 │  │
│  │          Mismo API que EKS / GKE / AKS               │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
ComponenteFuncionEquivalente Enterprise
k3sKubernetes ligeroEKS, GKE, AKS
Kubeflow PipelinesOrquestacion MLVertex AI Pipelines, SageMaker Pipelines
SeaweedFSAlmacenamiento artefactos (S3-compatible)AWS S3, Azure Blob, GCS
ML MetadataLinaje y trackingMLflow Tracking, Vertex Experiments
KServeModel servingSageMaker Endpoints, Vertex Prediction

Requisitos previos

RecursoMinimoRecomendado
RAM8 GB16 GB
CPU4 cores8 cores
Disco30 GB libres50 GB libres
SOLinux (cualquier distro)Ubuntu 22.04+ / Debian 12+

Importante: El espacio en disco es critico. Si tu particion tiene menos de 30 GB libres, Kubernetes activara el taint disk-pressure y ningun pod podra arrancar. Verifica con df -h / antes de empezar.

Software necesario:

BASH
# Verificar que tienes estas herramientas
docker --version    # Docker 20.10+
curl --version      # Para descargas
kubectl version --client  # Si no lo tienes, k3s lo incluye

Si no tienes kubectl instalado, no te preocupes — k3s incluye su propio binario y lo configuraremos mas adelante.


Paso 1: Instalar k3s

k3s es Kubernetes certificado en un solo binario de 60MB. Un comando y tienes un cluster funcional:

BASH
curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644

Esto instala k3s como servicio systemd. Verificamos que el cluster esta operativo:

BASH
# Verificar nodos
sudo k3s kubectl get nodes

# Deberias ver algo como:
# NAME         STATUS   ROLES                  AGE   VERSION
# tu-maquina   Ready    control-plane,master   30s   v1.36.x+k3s1

Configurar kubectl para usarlo sin sudo:

BASH
# Copiar kubeconfig para tu usuario
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
export KUBECONFIG=~/.kube/config

# Anadirlo al .bashrc para que persista
echo 'export KUBECONFIG=~/.kube/config' >> ~/.bashrc

Verificacion:

BASH
kubectl cluster-info
# Kubernetes control plane is running at https://127.0.0.1:6443

Que acabamos de hacer: Instalar un cluster Kubernetes completo con API server, scheduler, controller-manager, etcd (SQLite en k3s) y kubelet. Todo en un solo proceso que consume ~500MB de RAM.


Paso 2: Desplegar Kubeflow Pipelines

Kubeflow Pipelines (KFP) es el componente central de la plataforma. Gestiona workflows ML como grafos dirigidos aciclicos (DAGs) donde cada nodo es un contenedor independiente.

2.1 Desplegar Kubeflow Pipelines con manifests oficiales

BASH
# Definir la version (usar la ultima estable)
export PIPELINE_VERSION="2.16.1"

# Aplicar manifests de Kubeflow Pipelines (standalone)
kubectl apply -k "github.com/kubeflow/pipelines/manifests/kustomize/cluster-scoped-resources?ref=$PIPELINE_VERSION"

# Esperar a que los CRDs se registren
kubectl wait --for condition=established --timeout=60s crd/applications.app.k8s.io

# Desplegar los componentes en el namespace kubeflow
kubectl apply -k "github.com/kubeflow/pipelines/manifests/kustomize/env/platform-agnostic?ref=$PIPELINE_VERSION"

2.2 Verificar el despliegue

La primera vez tarda 8-10 minutos porque necesita descargar las imagenes de los contenedores. Ten paciencia:

BASH
# Esperar a que todos los pods esten Running
kubectl -n kubeflow wait --for=condition=Ready pods --all --timeout=600s

# Ver el estado de los pods
kubectl -n kubeflow get pods

Deberias ver algo similar a:

CODE
NAME                                               READY   STATUS    RESTARTS   AGE
cache-deployer-deployment-xxxxx                    1/1     Running   0          5m
cache-server-xxxxx                                 1/1     Running   0          5m
metadata-envoy-deployment-xxxxx                    1/1     Running   0          5m
metadata-grpc-deployment-xxxxx                     1/1     Running   0          5m
metadata-writer-xxxxx                              1/1     Running   0          5m
ml-pipeline-xxxxx                                  1/1     Running   0          5m
ml-pipeline-persistenceagent-xxxxx                 1/1     Running   0          5m
ml-pipeline-scheduledworkflow-xxxxx                1/1     Running   0          5m
ml-pipeline-ui-xxxxx                               1/1     Running   0          5m
ml-pipeline-viewer-crd-xxxxx                       1/1     Running   0          5m
ml-pipeline-visualizationserver-xxxxx              1/1     Running   0          5m
mysql-xxxxx                                        1/1     Running   0          5m
seaweedfs-xxxxx                                    1/1     Running   0          5m
workflow-controller-xxxxx                          1/1     Running   0          5m

Nota: Es normal que metadata-grpc tenga algunos restarts iniciales mientras espera a que MySQL arranque. Se estabiliza solo.

2.3 Acceder al Dashboard

BASH
# Port-forward del UI de Kubeflow Pipelines
kubectl -n kubeflow port-forward svc/ml-pipeline-ui 8080:80 &

# Abrir en el navegador
echo "Dashboard disponible en: http://localhost:8080"

Abre http://localhost:8080 en tu navegador. Veras el dashboard de Kubeflow Pipelines con secciones para Pipelines, Experiments, Runs y Artifacts.

Que tenemos ahora: Un orquestador ML enterprise con UI web, almacenamiento de artefactos S3-compatible (SeaweedFS), base de datos de metadata (MySQL) y motor de ejecucion de pipelines (Argo Workflows). Todo corriendo en tu maquina.


Paso 3: Instalar el SDK de Kubeflow Pipelines

El SDK de Python nos permite definir pipelines como codigo y subirlos al cluster:

BASH
# Crear un entorno virtual (obligatorio en distros modernas con PEP 668)
python3 -m venv ~/mlops-venv
source ~/mlops-venv/bin/activate

# Instalar el SDK (misma version que el servidor)
pip install kfp==2.16.1

# Verificar instalacion
python3 -c "import kfp; print(f'KFP SDK version: {kfp.__version__}')"

Importante: Activa siempre el venv antes de trabajar con el SDK: source ~/mlops-venv/bin/activate


Paso 4: Crear tu primer Pipeline ML

Vamos a crear un pipeline real que:

  1. Descarga datos — Dataset de clasificacion
  2. Preprocesa — Limpieza y split train/test
  3. Entrena — Modelo de clasificacion con scikit-learn
  4. Evalua — Metricas de accuracy, precision, recall
  5. Exporta — Guarda el modelo serializado en SeaweedFS

4.1 Definir los componentes

Crea un fichero mlops_pipeline.py:

PYTHON
"""
Pipeline MLOps: Clasificacion de vinos con Kubeflow Pipelines
Cada funcion decorada con @component se ejecuta como un contenedor independiente
"""
from kfp import dsl
from kfp.dsl import Input, Output, Dataset, Model, Metrics

# ─────────────────────────────────────────────
# Componente 1: Descarga de datos
# ─────────────────────────────────────────────
@dsl.component(
    base_image="python:3.11-slim",
    packages_to_install=["scikit-learn", "pandas"]
)
def download_data(dataset_out: Output[Dataset]):
    """Descarga el dataset Wine de sklearn y lo guarda como CSV."""
    from sklearn.datasets import load_wine
    import pandas as pd

    wine = load_wine(as_frame=True)
    df = wine.frame
    df.to_csv(dataset_out.path, index=False)
    print(f"Dataset descargado: {df.shape[0]} muestras, {df.shape[1]} columnas")


# ─────────────────────────────────────────────
# Componente 2: Preprocesamiento
# ─────────────────────────────────────────────
@dsl.component(
    base_image="python:3.11-slim",
    packages_to_install=["scikit-learn", "pandas"]
)
def preprocess_data(
    dataset_in: Input[Dataset],
    train_data: Output[Dataset],
    test_data: Output[Dataset],
    test_size: float = 0.2
):
    """Split de datos en train/test con estratificacion."""
    import pandas as pd
    from sklearn.model_selection import train_test_split

    df = pd.read_csv(dataset_in.path)

    # Separar features y target
    X = df.drop("target", axis=1)
    y = df["target"]

    # Split estratificado
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=test_size, random_state=42, stratify=y
    )

    # Guardar splits
    train = pd.concat([X_train, y_train], axis=1)
    test = pd.concat([X_test, y_test], axis=1)

    train.to_csv(train_data.path, index=False)
    test.to_csv(test_data.path, index=False)

    print(f"Train: {train.shape[0]} muestras | Test: {test.shape[0]} muestras")


# ─────────────────────────────────────────────
# Componente 3: Entrenamiento
# ─────────────────────────────────────────────
@dsl.component(
    base_image="python:3.11-slim",
    packages_to_install=["scikit-learn", "pandas", "joblib"]
)
def train_model(
    train_data: Input[Dataset],
    model_out: Output[Model],
    n_estimators: int = 100,
    max_depth: int = 10
):
    """Entrena un RandomForest con los hiperparametros dados."""
    import pandas as pd
    from sklearn.ensemble import RandomForestClassifier
    import joblib

    # Cargar datos
    df = pd.read_csv(train_data.path)
    X = df.drop("target", axis=1)
    y = df["target"]

    # Entrenar modelo
    model = RandomForestClassifier(
        n_estimators=n_estimators,
        max_depth=max_depth,
        random_state=42,
        n_jobs=-1
    )
    model.fit(X, y)

    # Guardar modelo serializado
    joblib.dump(model, model_out.path)

    print(f"Modelo entrenado: {n_estimators} arboles, profundidad {max_depth}")
    print(f"Accuracy en train: {model.score(X, y):.4f}")


# ─────────────────────────────────────────────
# Componente 4: Evaluacion
# ─────────────────────────────────────────────
@dsl.component(
    base_image="python:3.11-slim",
    packages_to_install=["scikit-learn", "pandas", "joblib"]
)
def evaluate_model(
    model_in: Input[Model],
    test_data: Input[Dataset],
    metrics_out: Output[Metrics]
):
    """Evalua el modelo con metricas de clasificacion."""
    import pandas as pd
    from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
    import joblib

    # Cargar modelo y datos de test
    model = joblib.load(model_in.path)
    df = pd.read_csv(test_data.path)
    X = df.drop("target", axis=1)
    y = df["target"]

    # Predicciones
    y_pred = model.predict(X)

    # Calcular metricas
    accuracy = accuracy_score(y, y_pred)
    precision = precision_score(y, y_pred, average="weighted")
    recall = recall_score(y, y_pred, average="weighted")
    f1 = f1_score(y, y_pred, average="weighted")

    # Registrar metricas en Kubeflow
    metrics_out.log_metric("accuracy", accuracy)
    metrics_out.log_metric("precision", precision)
    metrics_out.log_metric("recall", recall)
    metrics_out.log_metric("f1_score", f1)

    print(f"--- Resultados de Evaluacion ---")
    print(f"Accuracy:  {accuracy:.4f}")
    print(f"Precision: {precision:.4f}")
    print(f"Recall:    {recall:.4f}")
    print(f"F1-Score:  {f1:.4f}")


# ─────────────────────────────────────────────
# Pipeline: Orquestacion completa
# ─────────────────────────────────────────────
@dsl.pipeline(
    name="wine-classification-pipeline",
    description="Pipeline MLOps: descarga, preprocesa, entrena y evalua un modelo de clasificacion de vinos"
)
def ml_pipeline(
    test_size: float = 0.2,
    n_estimators: int = 100,
    max_depth: int = 10
):
    """Pipeline parametrizable — los mismos parametros que ajustarias en produccion."""

    # Paso 1: Descargar datos
    download_task = download_data()

    # Paso 2: Preprocesar
    preprocess_task = preprocess_data(
        dataset_in=download_task.outputs["dataset_out"],
        test_size=test_size
    )

    # Paso 3: Entrenar modelo
    train_task = train_model(
        train_data=preprocess_task.outputs["train_data"],
        n_estimators=n_estimators,
        max_depth=max_depth
    )

    # Paso 4: Evaluar
    evaluate_model(
        model_in=train_task.outputs["model_out"],
        test_data=preprocess_task.outputs["test_data"]
    )

4.2 Compilar y subir el Pipeline

BASH
# Compilar el pipeline a formato YAML (artefacto portable)
python3 -c "
from kfp import compiler
from mlops_pipeline import ml_pipeline

compiler.Compiler().compile(
    pipeline_func=ml_pipeline,
    package_path='wine_pipeline.yaml'
)
print('Pipeline compilado: wine_pipeline.yaml')
"

El fichero wine_pipeline.yaml es un artefacto portable — puedes subirlo a cualquier instalacion de Kubeflow (local, cloud, enterprise) y se ejecutara identico.

4.3 Ejecutar el Pipeline

BASH
# Ejecutar via SDK
python3 -c "
import kfp

# Conectar al cluster local
client = kfp.Client(host='http://localhost:8080')

# Crear un experimento (agrupa runs relacionados)
experiment = client.create_experiment(name='wine-classification')

# Lanzar el pipeline con parametros
run = client.create_run_from_pipeline_package(
    pipeline_file='wine_pipeline.yaml',
    experiment_name='wine-classification',
    run_name='wine-run-v1',
    arguments={
        'test_size': 0.2,
        'n_estimators': 150,
        'max_depth': 12
    }
)

print(f'Run lanzado: {run.run_id}')
print(f'Dashboard: http://localhost:8080/#/runs/details/{run.run_id}')
"

Abre el enlace del dashboard y veras el pipeline ejecutandose en tiempo real. Cada componente aparece como un nodo en el grafo, con logs individuales, artefactos de entrada/salida y metricas.

Nota: La primera ejecucion tarda ~7-8 minutos porque cada componente descarga su imagen base (python:3.11-slim) e instala dependencias. Las siguientes ejecuciones usan cache y tardan ~2 minutos.


Paso 5: Almacenamiento de artefactos con SeaweedFS

Kubeflow Pipelines despliega SeaweedFS como almacenamiento de artefactos. SeaweedFS expone una API 100% compatible con S3 — cualquier herramienta o SDK que funcione con AWS S3 funciona sin cambios.

BASH
# Port-forward del endpoint S3 de SeaweedFS
kubectl -n kubeflow port-forward svc/seaweedfs 9000:9000 &

# Credenciales por defecto
echo "S3 Endpoint: http://localhost:9000"
echo "Access Key: minio"
echo "Secret Key: minio123"

Verificar artefactos

BASH
# Instalar cliente MinIO (compatible con cualquier S3)
curl -sL https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc

# Configurar alias
mc alias set local http://localhost:9000 minio minio123

# Listar buckets
mc ls local/

# Ver artefactos del pipeline
mc ls local/mlpipeline/ --recursive

Veras los artefactos organizados por pipeline/run/componente:

CODE
v2/artifacts/wine-classification-pipeline/<run-id>/download-data/.../dataset_out
v2/artifacts/wine-classification-pipeline/<run-id>/preprocess-data/.../train_data
v2/artifacts/wine-classification-pipeline/<run-id>/preprocess-data/.../test_data
v2/artifacts/wine-classification-pipeline/<run-id>/train-model/.../model_out

En produccion, simplemente apuntas a S3/GCS/Azure Blob y el codigo de tus pipelines no cambia.


Paso 6: Servir modelos con KServe

KServe es el estandar para serving de modelos en Kubernetes. Proporciona:

  • Multi-framework (sklearn, TensorFlow, PyTorch, XGBoost, ONNX)
  • Canary deployments (trafico gradual a nuevas versiones)
  • Request batching y GPU scheduling
  • RawDeployment mode — funciona sin Knative ni Istio (ideal para k3s)

6.1 Instalar KServe

BASH
# Instalar Cert-Manager (prerequisito de KServe)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.0/cert-manager.yaml

# Esperar a que cert-manager este listo
kubectl -n cert-manager wait --for=condition=Ready pods --all --timeout=180s

# Instalar KServe (server-side apply necesario por tamano de CRDs)
kubectl apply --server-side --force-conflicts \
  -f https://github.com/kserve/kserve/releases/download/v0.14.0/kserve.yaml

# Instalar runtimes por defecto (sklearn, tensorflow, etc.)
kubectl apply --server-side --force-conflicts \
  -f https://github.com/kserve/kserve/releases/download/v0.14.0/kserve-cluster-resources.yaml

# Esperar a que KServe este operativo
kubectl -n kserve wait --for=condition=Ready pods --all --timeout=180s

6.2 Configurar KServe para k3s (RawDeployment)

En k3s no tenemos Knative, asi que configuramos KServe en modo RawDeployment (usa Deployments y Services estandar de Kubernetes):

BASH
# Cambiar modo de despliegue a RawDeployment
kubectl -n kserve patch configmap inferenceservice-config \
  --type merge \
  -p '{"data":{"deploy":"{\"defaultDeploymentMode\": \"RawDeployment\"}"}}'

6.3 Permitir acceso cross-namespace al storage

KServe necesita acceder a SeaweedFS (en namespace kubeflow) desde el namespace default. Eliminamos la NetworkPolicy restrictiva:

BASH
# Eliminar NetworkPolicy que bloquea acceso cross-namespace
kubectl -n kubeflow delete networkpolicy seaweedfs

6.4 Crear credenciales S3 para KServe

BASH
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: seaweedfs-secret
  namespace: default
  annotations:
    serving.kserve.io/s3-endpoint: seaweedfs.kubeflow.svc.cluster.local:9000
    serving.kserve.io/s3-usehttps: "0"
    serving.kserve.io/s3-region: us-east-1
type: Opaque
stringData:
  AWS_ACCESS_KEY_ID: minio
  AWS_SECRET_ACCESS_KEY: minio123
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kserve-sa
  namespace: default
secrets:
  - name: seaweedfs-secret
EOF

6.5 Preparar y desplegar el modelo

Copiamos el modelo entrenado a una ruta limpia en el storage:

BASH
# Copiar modelo del pipeline a ruta KServe-compatible
# (KServe sklearn espera un fichero model.joblib en el directorio)
mc cp local/mlpipeline/v2/artifacts/wine-classification-pipeline/<RUN_ID>/train-model/<ARTIFACT_ID>/model_out /tmp/model.joblib
mc mb local/mlpipeline/models/wine-classifier/
mc cp /tmp/model.joblib local/mlpipeline/models/wine-classifier/model.joblib

Tip: Sustituye y por los valores reales. Puedes verlos con mc ls local/mlpipeline/v2/artifacts/ --recursive | grep model_out

Ahora desplegamos el InferenceService:

BASH
cat <<'EOF' | kubectl apply -f -
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: wine-classifier
  namespace: default
spec:
  predictor:
    serviceAccountName: kserve-sa
    model:
      modelFormat:
        name: sklearn
      storageUri: "s3://mlpipeline/models/wine-classifier"
      resources:
        requests:
          cpu: "100m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"
EOF

Esperamos a que este Ready:

BASH
# Verificar que el modelo esta sirviendo
kubectl get inferenceservice wine-classifier

# Esperar a que este Ready (puede tardar 1-2 min por pull de imagen)
kubectl wait --for=condition=Ready inferenceservice/wine-classifier --timeout=180s

6.6 Hacer predicciones contra el modelo

BASH
# Port-forward al servicio del modelo
kubectl port-forward svc/wine-classifier-predictor 8081:80 &

# Realizar prediccion (una muestra de vino)
curl -s -X POST http://localhost:8081/v1/models/wine-classifier:predict \
  -H "Content-Type: application/json" \
  -d '{
    "instances": [
      [14.23, 1.71, 2.43, 15.6, 127.0, 2.80, 3.06, 0.28, 2.29, 5.64, 1.04, 3.92, 1065.0]
    ]
  }' | python3 -m json.tool

Respuesta esperada:

JSON
{
    "predictions": [0]
}

Prueba con multiples muestras para verificar las 3 clases de vino:

BASH
curl -s -X POST http://localhost:8081/v1/models/wine-classifier:predict \
  -H "Content-Type: application/json" \
  -d '{
    "instances": [
      [14.23, 1.71, 2.43, 15.6, 127.0, 2.80, 3.06, 0.28, 2.29, 5.64, 1.04, 3.92, 1065.0],
      [12.37, 0.94, 1.36, 10.6, 88.0, 1.98, 0.57, 0.28, 0.42, 1.95, 1.05, 1.82, 520.0],
      [13.73, 1.50, 2.70, 22.5, 101.0, 3.0, 2.60, 0.26, 1.86, 5.10, 1.04, 3.57, 1190.0]
    ]
  }' | python3 -m json.tool

El modelo esta sirviendo predicciones en tiempo real con latencia sub-segundo.


Paso 7: Monitorizar la plataforma

7.1 Metricas con Prometheus + Grafana

k3s no incluye monitoring por defecto, pero podemos anadirlo con el stack de kube-prometheus:

BASH
# Anadir repositorio de Helm (necesitas Helm instalado)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# Instalar kube-prometheus-stack (Prometheus + Grafana + alertas)
helm install monitoring prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.adminPassword=admin \
  --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
BASH
# Acceder a Grafana
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80 &

echo "Grafana: http://localhost:3000"
echo "Usuario: admin | Password: admin"

7.2 Dashboard recomendado

En Grafana, importa el dashboard 13332 (Kubernetes cluster monitoring) para ver recursos del cluster. Podras monitorizar el consumo de CPU/RAM de cada componente de la plataforma MLOps y detectar cuellos de botella durante el entrenamiento.


Paso 8: Automatizar con CI/CD (bonus enterprise)

En un entorno enterprise, los pipelines ML se disparan automaticamente. Aqui tienes un ejemplo de GitHub Actions que reune todo:

YAML
# .github/workflows/ml-pipeline.yml
name: ML Pipeline - Train & Deploy

on:
  push:
    paths:
      - 'ml/**'  # Solo cuando cambia codigo de ML

jobs:
  run-pipeline:
    runs-on: self-hosted  # Runner con acceso al cluster
    steps:
      - uses: actions/checkout@v4

      - name: Install KFP SDK
        run: pip install kfp==2.16.1

      - name: Compile pipeline
        run: python3 ml/compile_pipeline.py

      - name: Submit pipeline run
        env:
          KFP_HOST: ${{ secrets.KUBEFLOW_HOST }}
        run: |
          python3 -c "
          import kfp
          client = kfp.Client(host='${KFP_HOST}')
          client.create_run_from_pipeline_package(
              pipeline_file='wine_pipeline.yaml',
              experiment_name='production',
              run_name='auto-$(date +%Y%m%d-%H%M%S)'
          )
          "

      - name: Wait for completion
        run: |
          # Esperar a que el run termine y verificar metricas
          python3 ml/wait_and_validate.py

Comparativa: tu lab vs. Cloud enterprise

AspectoTu Lab (k3s)Produccion (EKS/GKE)
Kubernetesk3s (single-node)EKS/GKE (multi-node, HA)
StorageSeaweedFS (local, S3-compat)S3/GCS/Azure Blob
Pipeline engineMismo Kubeflow PipelinesMismo Kubeflow Pipelines
Model servingKServe RawDeploymentKServe + Knative + GPU nodes
AuthNinguna (lab)Dex + OIDC + RBAC
MonitoringPrometheus + GrafanaDatadog/CloudWatch + PagerDuty
NetworkinglocalhostIstio service mesh + mTLS
ScalingManualHPA + Cluster Autoscaler

La diferencia clave: el codigo de tus pipelines no cambia. Los componentes, el DAG, la logica — todo es identico. Solo cambia la infraestructura subyacente.


Limpieza

Si quieres eliminar todo el lab:

BASH
# Eliminar modelo servido
kubectl delete inferenceservice wine-classifier

# Eliminar KServe
kubectl delete namespace kserve

# Eliminar Kubeflow Pipelines
kubectl delete namespace kubeflow

# Eliminar monitoring
helm uninstall monitoring -n monitoring
kubectl delete namespace monitoring

# Eliminar cert-manager
kubectl delete namespace cert-manager

# Desinstalar k3s completamente
/usr/local/bin/k3s-uninstall.sh

Proximos pasos

Una vez dominas este lab, los siguientes pasos para acercarte a produccion enterprise son:

  1. Multi-tenancy — Anadir Dex + OIDC para autenticacion y namespaces por equipo
  2. GPU scheduling — Anadir NVIDIA device plugin para entrenamiento acelerado
  3. Feature Store — Integrar Feast para gestion de features compartidas entre equipos
  4. A/B testing — Configurar canary deployments en KServe para despliegues graduales
  5. Data versioning — Anadir DVC para versionar datasets junto con el codigo
  6. Model registry — Implementar approval workflows antes de promover modelos a produccion

Conclusion

Has desplegado la misma plataforma MLOps que organizaciones del Fortune 500 utilizan en produccion — en tu propia maquina, con herramientas 100% open-source. No es un simulacro: los manifests que has aplicado, el SDK que has usado y los conceptos de pipelines como DAGs son exactamente los mismos que encontrarias en un cluster de 200 nodos en GKE.

La diferencia entre tu lab y un entorno enterprise no es la tecnologia — es la escala, la autenticacion y la redundancia. Pero el conocimiento que has adquirido es directamente transferible.

Kubeflow Pipelines te da algo que ningun notebook de Jupyter puede ofrecer: reproducibilidad, auditabilidad y automatizacion. Cada run queda registrado, cada artefacto versionado, cada metrica almacenada. Eso es lo que separa un experimento de un sistema de produccion.

Comentarios