# Conectando los mundos - Platform Engineering meets ML

**Terraform, Helm, GitOps... ¿para entrenar modelos? Sí.**

Hoy: **Cómo gestionar todo eso como código.**

---

**El problema:**

Tienes tu stack de Platform Engineering:

* Terraform para infraestructura
    
* Helm para aplicaciones en K8s
    
* ArgoCD para GitOps
    
* CI/CD pipelines
    

Y ahora llega ML al juego.

**Primera reacción:** "Solo es otra aplicación, ¿no?"

**Spoiler: No.**

---

**Lo que descubrí:**

ML no es una aplicación más. Es un **ecosistema completo** que necesita:

**Infraestructura tradicional:**

* Compute (K8s clusters, VMs)
    
* Storage (S3, persistent volumes)
    
* Networking (load balancers, ingress)
    

**PERO TAMBIÉN infraestructura específica de ML:**

* Feature stores
    
* Model registries
    
* Experiment tracking
    
* Data versioning
    
* Training pipelines
    
* Serving infrastructure
    

**Y todo debe ser código. Reproducible. Versionado. Con GitOps.**

---

**Los 4 niveles de IaC para MLOps:**

Terraform es el ganador indiscutible, pero existen AWS CDK, Pulumi que son dignos competidores.

### **Nivel 1: Infraestructura base (Fundational)**

```ini
# terraform/main.tf

# Cluster para ML workloads
resource "aws_eks_cluster" "ml_cluster" {
  name     = "ml-${var.environment}"
  role_arn = aws_iam_role.ml_cluster.arn
  
  vpc_config {
    subnet_ids = var.private_subnets
  }
}

# Node group con GPUs
resource "aws_eks_node_group" "gpu_nodes" {
  cluster_name    = aws_eks_cluster.ml_cluster.name
  node_group_name = "gpu-nodes"
  
  instance_types = ["p3.2xlarge"]
  
  scaling_config {
    desired_size = 2
    max_size     = 10
    min_size     = 0  # Scale to zero cuando no se use
  }
}

# Storage para datasets
resource "aws_s3_bucket" "ml_data" {
  bucket = "ml-data-${var.environment}"
  
  versioning {
    enabled = true  # Importante para data versioning
  }
}
```

**Esto es familiar. Es Terraform normal.**

---

### **Nivel 2: ML Platform components**

yaml

```yaml
# helm/values.yaml

# MLflow para experiment tracking
mlflow:
  enabled: true
  backend_store: postgresql
  artifact_store: s3://ml-artifacts-prod
  
# Kubeflow Pipelines (si lo usas)
kubeflow:
  enabled: true
  pipelines:
    persistence:
      storageClass: gp3
      size: 100Gi

# Model serving (Seldon, KServe, etc.)
seldon:
  enabled: true
  replicas: 3
  resources:
    limits:
      nvidia.com/gpu: 1
```

**Esto es donde Platform Engineering y ML se encuentran.**

---

### **Nivel 3: Training pipelines como código**

yaml

```yaml
# pipelines/training-pipeline.yaml

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  name: training-pipeline
spec:
  entrypoint: train-model
  
  arguments:
    parameters:
    - name: dataset-version
      value: "v2.3.1"
    - name: environment
      value: "staging"
  
  templates:
  - name: train-model
    steps:
    - - name: ingest-data
        template: ingest
    - - name: validate-data
        template: validate
    - - name: train
        template: training
    - - name: evaluate
        template: evaluation
    - - name: register
        template: model-registration
  
  - name: ingest
    container:
      image: ml-pipeline/ingest:{{workflow.parameters.dataset-version}}
      command: [python, ingest.py]
      env:
      - name: DATASET_VERSION
        value: "{{workflow.parameters.dataset-version}}"
      - name: S3_BUCKET
        value: "ml-data-{{workflow.parameters.environment}}"
```

**Aquí es donde todo se conecta: infra + pipelines + GitOps.**

---

### **Nivel 4: GitOps para ML**

Y como saben, en este lugar somos GitOps Frendly

```yaml
# argocd/ml-application.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ml-platform
  namespace: argocd
spec:
  project: default
  
  source:
    repoURL: https://github.com/company/ml-platform
    targetRevision: main
    path: k8s/
    
    helm:
      values: |
        environment: staging
        
        mlflow:
          enabled: true
          version: "2.9.0"
        
        training_pipelines:
          schedule: "0 2 * * 0"  # Weekly
          resources:
            gpu: true
            memory: 32Gi
  
  destination:
    server: https://kubernetes.default.svc
    namespace: ml-staging
  
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
```

**Commit → Push → ArgoCD sync → Pipeline deployed.**

**Igual que tus otras aplicaciones. Pero para ML.**

---

**La integración completa:**

```ini
┌─────────────────────────────────────────────────┐
│              Git Repository                     │
│                                                 │
│  terraform/        → Infra base                 │
│  helm/            → ML platform                 │
│  pipelines/       → Training workflows          │
│  models/          → Model definitions           │
└─────────────┬───────────────────────────────────┘
              │
              │ git push
              │
     ┌────────▼────────┐
     │   CI Pipeline   │
     │                 │
     │  - Validate     │
     │  - Test         │
     │  - Build images │
     └────────┬────────┘
              │
              │ if tests pass
              │
     ┌────────▼────────┐
     │    ArgoCD       │
     │                 │
     │  - Sync infra   │
     │  - Deploy ML    │
     │  - Trigger      │
     └────────┬────────┘
              │
              │
     ┌────────▼─────────────────────────────┐
     │         Kubernetes                   │
     │                                      │
     │  - ML Platform running               │
     │  - Pipelines scheduled               │
     │  - Models serving                    │
     └──────────────────────────────────────┘
```

**Ejemplo real: Deploy de un nuevo modelo**

**Antes (manual):**

bash

```bash
# 1. SSH al cluster
ssh ml-cluster-prod

# 2. Actualizar manualmente
kubectl apply -f model-deployment.yaml

# 3. Esperar y rezar
kubectl get pods -w

# 4. Si falla, rollback manual
kubectl rollback deployment/model-server
```

**Ahora (GitOps):**

bash

```bash
# 1. Update en Git
cat > k8s/model-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: model-server
spec:
  template:
    spec:
      containers:
      - name: model
        image: models/fraud-detection:v2.3.1  # Cambio aquí
EOF

# 2. Commit y push
git add k8s/model-deployment.yaml
git commit -m "Deploy model v2.3.1 to staging"
git push

# 3. ArgoCD hace el resto
# - Detecta cambio
# - Valida
# - Canary deploy
# - Rollback automático si falla
```

---

**Los componentes clave:**

**Terraform para infraestructura:**

hcl

```ini
module "ml_platform" {
  source = "./modules/ml-platform"
  
  environment = var.environment
  
  # Compute
  gpu_nodes     = 4
  cpu_nodes     = 10
  
  # Storage
  data_bucket   = "ml-data-${var.environment}"
  model_bucket  = "ml-models-${var.environment}"
  
  # Networking
  vpc_id        = module.vpc.vpc_id
  
  # ML-specific
  mlflow_backend    = module.rds.endpoint
  feature_store     = "enabled"
  model_registry    = "enabled"
}
```

**Helm para componentes de ML:**

yaml

```yaml
# Chart.yaml
dependencies:
- name: mlflow
  version: "0.7.0"
  repository: https://charts.mlflow.org
  
- name: kubeflow-pipelines
  version: "2.0.0"
  repository: https://kubeflow.github.io/pipelines

- name: seldon-core
  version: "1.15.0"
  repository: https://storage.googleapis.com/seldon-charts
```

**ArgoCD para sincronización:**

yaml

```yaml
# Sync policy para ML workloads
syncPolicy:
  automated:
    prune: true
    selfHeal: true
  
  syncOptions:
  - CreateNamespace=true
  
  retry:
    limit: 5
    backoff:
      duration: 5s
      factor: 2
      maxDuration: 3m
```

**CI/CD para pipelines:**

```yaml
# .github/workflows/ml-pipeline.yaml
name: ML Pipeline CI

on:
  push:
    paths:
    - 'pipelines/**'
    - 'models/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
    - name: Validate pipeline syntax
      run: |
        argo lint pipelines/training-pipeline.yaml
    
    - name: Test with sample data
      run: |
        pytest tests/pipeline_test.py
    
    - name: Build container images
      run: |
        docker build -t ml-pipeline/ingest:${{ github.sha }}
```

---

**Los desafíos específicos de IaC para ML:**

**DESAFÍO 1: State management para modelos**

```ini
Software tradicional:
- Deploy nueva versión
- Old version se borra

ML:
- Deploy modelo v2
- Modelo v1 sigue sirviendo (A/B testing)
- Modelo v0 en rollback standby
- ¿Cómo manejas state?
```

**Solución:**

hcl

```ini
# Terraform state para múltiples versiones
resource "kubernetes_deployment" "model" {
  for_each = var.model_versions
  
  metadata {
    name = "model-${each.key}"
  }
  
  spec {
    replicas = each.value.traffic_percentage > 0 ? 2 : 0
  }
}
```

---

**DESAFÍO 2: Recursos dinámicos**

```ini
Training pipeline necesita:
- 8 GPUs por 4 horas
- Después, scale a 0

Serving necesita:
- 2 GPUs 24/7
- Auto-scale basado en requests
```

**Solución:**

yaml

```yaml
# Karpenter para auto-scaling inteligente
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: gpu-training
spec:
  requirements:
  - key: node.kubernetes.io/instance-type
    operator: In
    values: ["p3.2xlarge", "p3.8xlarge"]
  
  ttlSecondsAfterEmpty: 300  # Terminate si no se usa
  
  limits:
    resources:
      nvidia.com/gpu: 32  # Max GPUs
```

---

**DESAFÍO 3: Data versioning en IaC**

```ini
Código: Git
Containers: Registry
Infraestructura: Terraform state

¿Datasets de gigantes?
```

**Solución:**

yaml

```yaml
# DVC como parte del pipeline
apiVersion: v1
kind: ConfigMap
metadata:
  name: dvc-config
data:
  .dvc/config: |
    [core]
        remote = s3
    ['remote "s3"']
        url = s3://ml-data-prod/dvc-cache
        
# Referencia en pipeline
- name: fetch-data
  container:
    image: dvc:latest
    command: [dvc, pull, data/training.dvc]
    env:
    - name: DVC_REMOTE
      value: s3
```

---

**Stack completo recomendado:**

```bash
Infrastructure Layer:
├── Terraform         → Cloud resources (K8s, S3, RDS)
├── Helm             → ML platform components
└── ArgoCD           → GitOps sync

ML Platform Layer:
├── MLflow           → Experiment tracking
├── DVC              → Data versioning
├── Kubeflow/Airflow → Pipeline orchestration
└── Seldon/KServe    → Model serving

Pipeline Layer:
├── Argo Workflows   → Training execution
├── GitHub Actions   → CI/CD
└── Prometheus       → Monitoring

Data Layer:
├── S3/GCS           → Data lake
├── PostgreSQL       → Metadata
└── Feature Store    → Feature management
```

**Mi workflow real:**

**1\. Definir infra (Terraform):**

bash

```bash
cd terraform/environments/staging
terraform plan
terraform apply
```

**2\. Deploy ML platform (Helm + ArgoCD):**

bash

```bash
git add helm/ml-platform/
git commit -m "Deploy MLflow + Kubeflow to staging"
git push  # ArgoCD picks it up
```

**3\. Definir pipeline (YAML):**

bash

```bash
git add pipelines/training-v2.yaml
git commit -m "Update training pipeline"
git push  # ArgoCD deploys
```

**4\. Trigger entrenamiento:**

bash

```bash
# Scheduled (automático via CronWorkflow)
# O manual:
argo submit pipelines/training-v2.yaml \
  --parameter dataset-version=v2.3.1
```

**5\. Deploy modelo (GitOps):**

```bash
git add k8s/model-server.yaml
git commit -m "Deploy model v2.3.1"
git push  # ArgoCD deploys con canary
```

**Todo como código. Todo en Git. Todo reproducible.**

---

**Los errores que he visto:**

**ERROR 1: "Infra manual, pipelines automatizados"**

```bash
Resultado: Pipelines automatizados corriendo en infra manual
Alguien borra el cluster por error
Pipelines fallan, nadie sabe cómo reconstruir
```

**ERROR 2: "Terraform para todo, incluso modelos"**

```bash
Resultado: Model registry en Terraform state
Deploy nuevo modelo = terraform apply (lento)
Rollback = terraform plan/apply (muy lento)
No es el tool correcto para esto
```

**ERROR 3: "GitOps sin CI"**

```bash
Resultado: Push directo a main sin validación
Pipeline syntax error deployado a prod
Cluster crashea
```

**ERROR 4: "IaC solo en prod"**

```bash
Resultado: Dev y staging son snowflakes
"Funciona en mi máquina" nivel infraestructura
Debugging imposible
```

---

**Mi recomendación pragmática:**

**Fase 1 - Infra as Code:**

```bash
- Terraform para cluster base
- Manual deploy de ML platform
- Scripts para pipelines
```

**Fase 2 - Platform as Code:**

```bash
- Helm para ML components
- CI/CD para validación
- Semi-automated deploys
```

**Fase 3 - Everything as Code:**

```bash
- Full GitOps con ArgoCD
- Pipelines como YAML en Git
- Automated canary deploys
```

**Empieza en Fase 1. Evoluciona cuando necesites escalar.**
