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
┌─────────────────────────────────────────────────────────────┐
│ 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 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘| Componente | Funcion | Equivalente Enterprise |
|---|---|---|
| k3s | Kubernetes ligero | EKS, GKE, AKS |
| Kubeflow Pipelines | Orquestacion ML | Vertex AI Pipelines, SageMaker Pipelines |
| SeaweedFS | Almacenamiento artefactos (S3-compatible) | AWS S3, Azure Blob, GCS |
| ML Metadata | Linaje y tracking | MLflow Tracking, Vertex Experiments |
| KServe | Model serving | SageMaker Endpoints, Vertex Prediction |
Requisitos previos
| Recurso | Minimo | Recomendado |
|---|---|---|
| RAM | 8 GB | 16 GB |
| CPU | 4 cores | 8 cores |
| Disco | 30 GB libres | 50 GB libres |
| SO | Linux (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-pressurey ningun pod podra arrancar. Verifica condf -h /antes de empezar.
Software necesario:
# Verificar que tienes estas herramientas
docker --version # Docker 20.10+
curl --version # Para descargas
kubectl version --client # Si no lo tienes, k3s lo incluyeSi 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:
curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644Esto instala k3s como servicio systemd. Verificamos que el cluster esta operativo:
# 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+k3s1Configurar kubectl para usarlo sin sudo:
# 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' >> ~/.bashrcVerificacion:
kubectl cluster-info
# Kubernetes control plane is running at https://127.0.0.1:6443Que 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
# 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:
# 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 podsDeberias ver algo similar a:
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 5mNota: Es normal que
metadata-grpctenga algunos restarts iniciales mientras espera a que MySQL arranque. Se estabiliza solo.
2.3 Acceder al Dashboard
# 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:
# 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:
- Descarga datos — Dataset de clasificacion
- Preprocesa — Limpieza y split train/test
- Entrena — Modelo de clasificacion con scikit-learn
- Evalua — Metricas de accuracy, precision, recall
- Exporta — Guarda el modelo serializado en SeaweedFS
4.1 Definir los componentes
Crea un fichero mlops_pipeline.py:
"""
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
# 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
# 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.
# 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
# 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/ --recursiveVeras los artefactos organizados por pipeline/run/componente:
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_outEn 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
# 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=180s6.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):
# 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:
# Eliminar NetworkPolicy que bloquea acceso cross-namespace
kubectl -n kubeflow delete networkpolicy seaweedfs6.4 Crear credenciales S3 para KServe
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
EOF6.5 Preparar y desplegar el modelo
Copiamos el modelo entrenado a una ruta limpia en el storage:
# 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.joblibTip: Sustituye
ypor los valores reales. Puedes verlos conmc ls local/mlpipeline/v2/artifacts/ --recursive | grep model_out
Ahora desplegamos el InferenceService:
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"
EOFEsperamos a que este Ready:
# 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=180s6.6 Hacer predicciones contra el modelo
# 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.toolRespuesta esperada:
{
"predictions": [0]
}Prueba con multiples muestras para verificar las 3 clases 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],
[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.toolEl 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:
# 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# 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:
# .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.pyComparativa: tu lab vs. Cloud enterprise
| Aspecto | Tu Lab (k3s) | Produccion (EKS/GKE) |
|---|---|---|
| Kubernetes | k3s (single-node) | EKS/GKE (multi-node, HA) |
| Storage | SeaweedFS (local, S3-compat) | S3/GCS/Azure Blob |
| Pipeline engine | Mismo Kubeflow Pipelines | Mismo Kubeflow Pipelines |
| Model serving | KServe RawDeployment | KServe + Knative + GPU nodes |
| Auth | Ninguna (lab) | Dex + OIDC + RBAC |
| Monitoring | Prometheus + Grafana | Datadog/CloudWatch + PagerDuty |
| Networking | localhost | Istio service mesh + mTLS |
| Scaling | Manual | HPA + 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:
# 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.shProximos pasos
Una vez dominas este lab, los siguientes pasos para acercarte a produccion enterprise son:
- Multi-tenancy — Anadir Dex + OIDC para autenticacion y namespaces por equipo
- GPU scheduling — Anadir NVIDIA device plugin para entrenamiento acelerado
- Feature Store — Integrar Feast para gestion de features compartidas entre equipos
- A/B testing — Configurar canary deployments en KServe para despliegues graduales
- Data versioning — Anadir DVC para versionar datasets junto con el codigo
- 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