MinIO para Almacenamiento de Modelos y Datasets de ML

MinIO es un servidor de almacenamiento de objetos compatible con la API de AWS S3 que puedes instalar en tu propio servidor, ideal para almacenar modelos de machine learning, datasets y artefactos de experimentos sin depender de servicios cloud externos. Con soporte para versionado, high availability y un Python SDK que funciona exactamente igual que boto3 con S3, MinIO se integra perfectamente con MLflow, DVC y los principales frameworks de ML. Esta guía cubre la instalación, organización de buckets, versionado y la construcción de pipelines de datos de alto rendimiento.

Requisitos Previos

  • Servidor Linux (Ubuntu 20.04+, CentOS 8+)
  • Al menos 4 GB de RAM
  • Disco dedicado para almacenamiento (SSD recomendado para mejor rendimiento)
  • Acceso root o sudo

Instalación de MinIO

Instalación como binario único

# Descargar el binario de MinIO (servidor)
wget https://dl.min.io/server/minio/release/linux-amd64/minio -O /usr/local/bin/minio
chmod +x /usr/local/bin/minio

# Verificar la instalación
minio --version

Crear el entorno de MinIO

# Crear el usuario del sistema para MinIO
sudo useradd -r -s /bin/false -d /opt/minio minio-user

# Crear los directorios necesarios
sudo mkdir -p /opt/minio/data
sudo mkdir -p /etc/minio

# Asignar permisos
sudo chown -R minio-user:minio-user /opt/minio

# Crear el archivo de configuración de entorno
sudo cat > /etc/minio/minio.env << 'EOF'
# Credenciales de acceso (equivalente a AWS Access Key y Secret Key)
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=cambia-esta-clave-muy-segura

# Directorio de datos
MINIO_VOLUMES=/opt/minio/data

# URL pública de acceso (para generar URLs firmadas correctamente)
MINIO_SERVER_URL=https://minio.tudominio.com

# URL de la consola web
MINIO_BROWSER_REDIRECT_URL=https://minio-console.tudominio.com
EOF

sudo chmod 600 /etc/minio/minio.env

Configurar MinIO como servicio systemd

sudo cat > /etc/systemd/system/minio.service << 'EOF'
[Unit]
Description=MinIO Object Storage
After=network.target
Wants=network-online.target

[Service]
User=minio-user
Group=minio-user
EnvironmentFile=/etc/minio/minio.env
ExecStart=/usr/local/bin/minio server \
    --console-address ":9001" \
    --address ":9000" \
    ${MINIO_VOLUMES}
Restart=always
RestartSec=10

# Deshabilitar los límites de archivos abiertos
LimitNOFILE=65536

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable minio
sudo systemctl start minio

# Verificar el estado
sudo systemctl status minio

# Ver los logs
sudo journalctl -u minio -f

Accede a la consola web:

  • API S3: http://IP_SERVIDOR:9000
  • Consola Web: http://IP_SERVIDOR:9001

Configuración Inicial

Instalar el cliente mc (MinIO Client)

# Descargar mc
wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc
chmod +x /usr/local/bin/mc

# Configurar la conexión al servidor MinIO
mc alias set minio-local http://localhost:9000 minioadmin cambia-esta-clave-muy-segura

# Verificar la conexión
mc admin info minio-local

Crear la estructura inicial de buckets

# Crear los buckets principales para ML
mc mb minio-local/modelos
mc mb minio-local/datasets
mc mb minio-local/experimentos
mc mb minio-local/artifacts

# Verificar los buckets creados
mc ls minio-local

Organización de Buckets para ML

Una estructura de buckets bien organizada es fundamental para equipos de ML.

# Estructura recomendada para proyectos ML

# Bucket: modelos
# modelos/
# ├── produccion/
# │   ├── clasificador-spam/
# │   │   ├── v1.0/model.pkl
# │   │   ├── v1.1/model.pkl
# │   │   └── latest -> v1.1/
# │   └── detector-fraude/
# │       └── v2.3/model.pkl
# └── staging/
#     └── clasificador-spam/
#         └── v1.2-rc1/model.pkl

# Bucket: datasets
# datasets/
# ├── raw/          <- Datos originales sin procesar
# ├── processed/    <- Datos procesados y listos para entrenamiento
# ├── features/     <- Features extraídas
# └── splits/       <- Train/val/test splits

# Crear la estructura
mc mb minio-local/modelos/produccion
mc mb minio-local/modelos/staging
mc mb minio-local/datasets/raw
mc mb minio-local/datasets/processed
mc mb minio-local/datasets/features

Configurar permisos por bucket

# Crear una política de solo lectura para acceso a modelos de producción
cat > /tmp/policy-modelos-readonly.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::modelos/*",
        "arn:aws:s3:::modelos"
      ]
    }
  ]
}
EOF

# Crear el usuario para inferencia (solo lectura)
mc admin user add minio-local usuario-inferencia clave-segura-2
mc admin policy create minio-local politica-solo-lectura /tmp/policy-modelos-readonly.json
mc admin policy attach minio-local politica-solo-lectura --user usuario-inferencia

Versionado de Modelos

# Habilitar el versionado en el bucket de modelos
mc version enable minio-local/modelos

# Verificar que el versionado está activo
mc version info minio-local/modelos

# Habilitar también para datasets
mc version enable minio-local/datasets

Trabajar con versiones

#!/usr/bin/env python3
# Gestión de versiones de modelos con MinIO

import boto3
from botocore.client import Config
import json

# Configurar el cliente S3 compatible con MinIO
s3 = boto3.client(
    's3',
    endpoint_url='http://localhost:9000',
    aws_access_key_id='minioadmin',
    aws_secret_access_key='cambia-esta-clave-muy-segura',
    config=Config(signature_version='s3v4'),
    region_name='us-east-1'
)

def subir_modelo_con_metadata(ruta_local, bucket, clave, version_metadata):
    """Subir un modelo con metadatos de versión."""
    with open(ruta_local, 'rb') as f:
        s3.put_object(
            Bucket=bucket,
            Key=clave,
            Body=f,
            # Metadatos personalizados para el modelo
            Metadata={
                'framework': version_metadata.get('framework', ''),
                'accuracy': str(version_metadata.get('accuracy', '')),
                'f1-score': str(version_metadata.get('f1_score', '')),
                'training-date': version_metadata.get('training_date', ''),
                'git-commit': version_metadata.get('git_commit', '')
            }
        )
    print(f"Modelo subido: {bucket}/{clave}")

def listar_versiones(bucket, prefijo):
    """Listar todas las versiones de un modelo."""
    paginator = s3.get_paginator('list_object_versions')
    versiones = []
    
    for pagina in paginator.paginate(Bucket=bucket, Prefix=prefijo):
        for version in pagina.get('Versions', []):
            versiones.append({
                'version_id': version['VersionId'],
                'modificado': version['LastModified'],
                'tamano': version['Size'],
                'es_ultima': version['IsLatest']
            })
    
    return sorted(versiones, key=lambda x: x['modificado'], reverse=True)

# Ejemplo de uso
metadata = {
    'framework': 'sklearn',
    'accuracy': 0.956,
    'f1_score': 0.951,
    'training_date': '2024-01-15',
    'git_commit': 'abc123'
}

subir_modelo_con_metadata(
    'modelo_clasificador.pkl',
    'modelos',
    'produccion/clasificador-spam/model.pkl',
    metadata
)

versiones = listar_versiones('modelos', 'produccion/clasificador-spam/')
print(f"Versiones encontradas: {len(versiones)}")
for v in versiones[:5]:
    print(f"  - {v['version_id'][:8]}... | {v['modificado']} | {'ACTUAL' if v['es_ultima'] else ''}")

Integración con Python (boto3)

#!/usr/bin/env python3
# Pipeline completo de ML con MinIO

import boto3
import joblib
import numpy as np
from io import BytesIO
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from botocore.client import Config

# Cliente MinIO compatible con S3
s3 = boto3.client(
    's3',
    endpoint_url='http://localhost:9000',
    aws_access_key_id='minioadmin',
    aws_secret_access_key='cambia-esta-clave',
    config=Config(signature_version='s3v4'),
    region_name='us-east-1'
)

def guardar_dataset_en_minio(X, y, nombre):
    """Guardar un dataset de NumPy en MinIO."""
    buffer = BytesIO()
    np.savez_compressed(buffer, X=X, y=y)
    buffer.seek(0)
    
    s3.put_object(
        Bucket='datasets',
        Key=f'processed/{nombre}.npz',
        Body=buffer
    )
    print(f"Dataset guardado: datasets/processed/{nombre}.npz")

def cargar_dataset_desde_minio(nombre):
    """Cargar un dataset desde MinIO."""
    respuesta = s3.get_object(
        Bucket='datasets',
        Key=f'processed/{nombre}.npz'
    )
    buffer = BytesIO(respuesta['Body'].read())
    datos = np.load(buffer)
    return datos['X'], datos['y']

def guardar_modelo(modelo, nombre, version):
    """Guardar un modelo scikit-learn en MinIO."""
    buffer = BytesIO()
    joblib.dump(modelo, buffer)
    buffer.seek(0)
    
    s3.put_object(
        Bucket='modelos',
        Key=f'produccion/{nombre}/{version}/model.pkl',
        Body=buffer
    )
    print(f"Modelo guardado: modelos/produccion/{nombre}/{version}/model.pkl")

def cargar_modelo(nombre, version='latest'):
    """Cargar un modelo desde MinIO."""
    respuesta = s3.get_object(
        Bucket='modelos',
        Key=f'produccion/{nombre}/{version}/model.pkl'
    )
    buffer = BytesIO(respuesta['Body'].read())
    return joblib.load(buffer)

# Pipeline de entrenamiento completo
if __name__ == '__main__':
    # Generar datos de ejemplo
    X, y = make_classification(n_samples=10000, n_features=20, random_state=42)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
    
    # Guardar el dataset en MinIO
    guardar_dataset_en_minio(X_train, y_train, 'clasificacion-train-v1')
    guardar_dataset_en_minio(X_test, y_test, 'clasificacion-test-v1')
    
    # Entrenar el modelo
    modelo = RandomForestClassifier(n_estimators=100, random_state=42)
    modelo.fit(X_train, y_train)
    
    accuracy = modelo.score(X_test, y_test)
    print(f"Accuracy: {accuracy:.4f}")
    
    # Guardar el modelo en MinIO
    guardar_modelo(modelo, 'clasificador-ejemplo', 'v1.0')
    
    # Cargar el modelo y hacer predicciones
    modelo_cargado = cargar_modelo('clasificador-ejemplo', 'v1.0')
    predicciones = modelo_cargado.predict(X_test[:5])
    print(f"Predicciones de prueba: {predicciones}")

Integración con MLflow

# Usar MinIO como artifact store para MLflow
import mlflow

# Configurar MLflow para usar MinIO como backend de artefactos
# Las variables de entorno deben estar definidas antes de iniciar mlflow server

import os
os.environ['MLFLOW_S3_ENDPOINT_URL'] = 'http://localhost:9000'
os.environ['AWS_ACCESS_KEY_ID'] = 'minioadmin'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'cambia-esta-clave'

# Iniciar el servidor MLflow con MinIO como artifact store
# mlflow server \
#   --backend-store-uri sqlite:///mlflow.db \
#   --default-artifact-root s3://artifacts/mlflow/ \
#   --host 0.0.0.0 \
#   --port 5000

mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("experimento-minio")

with mlflow.start_run():
    # Los artefactos se guardan automáticamente en MinIO
    mlflow.log_param("n_estimators", 100)
    mlflow.log_metric("accuracy", 0.956)
    
    # Guardar el modelo en MinIO via MLflow
    modelo = RandomForestClassifier(n_estimators=100)
    mlflow.sklearn.log_model(modelo, "modelo")

CLI de MinIO (mc)

# Copiar archivos a MinIO
mc cp modelo.pkl minio-local/modelos/produccion/clasificador/v1.0/

# Sincronizar un directorio completo
mc mirror ./experimentos/ minio-local/experimentos/

# Ver el contenido de un bucket
mc ls minio-local/modelos/

# Ver las versiones de un archivo
mc ls --versions minio-local/modelos/produccion/clasificador/

# Restaurar una versión anterior
mc cp minio-local/modelos/produccion/clasificador/model.pkl \
  --version-id "abc123..." \
  ./modelo-restaurado.pkl

# Crear una política de ciclo de vida (eliminar versiones antiguas)
mc ilm rule add \
  --noncurrent-expire-days 30 \
  minio-local/modelos

# Crear una URL firmada temporal (válida 24 horas)
mc share download minio-local/modelos/produccion/clasificador/v1.0/model.pkl \
  --expire 24h

# Ver estadísticas del servidor
mc admin info minio-local

# Ver el uso de disco
mc du minio-local

Solución de Problemas

El servicio MinIO no inicia

# Ver los logs detallados
sudo journalctl -u minio -n 50 --no-pager

# Verificar permisos del directorio de datos
ls -la /opt/minio/data/
sudo chown -R minio-user:minio-user /opt/minio/

# Probar iniciar manualmente como el usuario minio
sudo -u minio-user minio server /opt/minio/data --console-address :9001

Error de conexión desde Python/boto3

# Verificar que el servidor responde
curl http://localhost:9000/minio/health/live

# Verificar que las credenciales son correctas
mc alias set test http://localhost:9000 minioadmin tu-clave
mc ls test

Las versiones no se almacenan

# Verificar que el versionado está habilitado
mc version info minio-local/modelos
# Debe mostrar: Versioning is enabled

# Habilitarlo si no lo está
mc version enable minio-local/modelos

Conclusión

MinIO proporciona una solución de almacenamiento de objetos compatible con S3 que puedes instalar directamente en tu servidor, eliminando la dependencia de proveedores cloud externos para los datos más sensibles de tus proyectos de ML. Con el versionado de modelos, la integración nativa con MLflow y un Python SDK idéntico a boto3, MinIO se convierte en el componente central de cualquier plataforma MLOps privada y de alto rendimiento.