Conectando los mundos - Platform Engineering meets ML

Platform Engineer + CNCF Ambassador, AWS community builder. I design scalable cloud-native platforms and love making teams faster and safer.
Search for a command to run...

Platform Engineer + CNCF Ambassador, AWS community builder. I design scalable cloud-native platforms and love making teams faster and safer.
No comments yet. Be the first to comment.
There's a narrative I keep hearing in tech communities across LATAM and beyond: "Kubernetes is too complex now. It had its moment." I think that narrative confuses two very different things: the accid

The Platform Is the Architecture There's a distinction that comes up a lot in platform engineering conversations, and it's worth making explicit. There's a difference between a team that operates infrastructure and a team that designs a platform. Bot...
Your Deployment Strategy Shouldn't Live in Your Pipeline Here's a situation most platform teams have been in. A team wants to do a canary deployment. So they modify the pipeline. They add a step that deploys to 10% of traffic, waits, checks some metr...
The Glue Is the Platform Here's something that doesn't show up in architecture diagrams. You have Kubernetes. You have Terraform. You have GitHub Actions, ArgoCD, Datadog, Vault, and a developer portal. Each of those tools is well-documented, widely ...
A Git Commit Is Not Just a Change. It's a Command. Here's a scenario that plays out in a lot of platform teams. Your CI/CD pipeline runs kubectl apply as a job step. It exits zero. The pipeline goes green. Deployment successful — or so the dashboard ...
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.
# 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.
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.
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.
Y como saben, en este lugar somos GitOps Frendly
# 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:
┌─────────────────────────────────────────────────┐
│ 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
# 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
# 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
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
# 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
# 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:
# .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
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
# 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
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
# 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
Código: Git
Containers: Registry
Infraestructura: Terraform state
¿Datasets de gigantes?
Solución:
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:
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
cd terraform/environments/staging
terraform plan
terraform apply
2. Deploy ML platform (Helm + ArgoCD):
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
git add pipelines/training-v2.yaml
git commit -m "Update training pipeline"
git push # ArgoCD deploys
4. Trigger entrenamiento:
bash
# Scheduled (automático via CronWorkflow)
# O manual:
argo submit pipelines/training-v2.yaml \
--parameter dataset-version=v2.3.1
5. Deploy modelo (GitOps):
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"
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"
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"
Resultado: Push directo a main sin validación
Pipeline syntax error deployado a prod
Cluster crashea
ERROR 4: "IaC solo en prod"
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:
- Terraform para cluster base
- Manual deploy de ML platform
- Scripts para pipelines
Fase 2 - Platform as Code:
- Helm para ML components
- CI/CD para validación
- Semi-automated deploys
Fase 3 - Everything as Code:
- Full GitOps con ArgoCD
- Pipelines como YAML en Git
- Automated canary deploys
Empieza en Fase 1. Evoluciona cuando necesites escalar.