Kubernetes para SaaS: cuándo es la opción correcta, cuándo gana ECS, y qué elegimos
Kubernetes vs ECS vs Lambda para plataformas SaaS. Aislamiento multi-tenant, estrategias de despliegue, networking, optimización de costos, y el framework de decisión honesto tras operar los tres en producción.
La decisión de Kubernetes
Todo equipo de ingeniería llega al punto de preguntarse: ¿deberíamos usar Kubernetes? La respuesta honesta: depende de lo que estés ejecutando, cuántos servicios tienes, y si puedes permitirte el overhead operacional.
Nosotros ejecutamos tres estrategias de compute diferentes en producción. Una plataforma corre sobre Kubernetes (7+ servicios, Pimcore, OpenSearch, workers). Otra corre sobre ECS Fargate + Lambda (serverless-first, event-driven). Una tercera usa una mezcla de ambos. Cada una fue la decisión correcta para su contexto.
Este artículo cubre el framework de decisión y los patrones de implementación para cada enfoque. Para cómo gestionamos la Infrastructure as Code detrás de estos despliegues, consulta nuestra guía de IaC. Para las arquitecturas de aplicación que corren encima, consulta nuestra guía de arquitectura de sistemas.
La comparación honesta
| Criterio | Kubernetes (EKS/AKS/GKE) | ECS Fargate | Lambda |
|---|---|---|---|
| Complejidad operacional | Alta (actualizaciones de cluster, networking, RBAC) | Media (definiciones de tareas, service mesh) | Baja (solo despliega funciones) |
| Cold start | Ninguno (los pods siempre están corriendo) | Ninguno (las tasks siempre están corriendo) | 100ms-5s (depende del runtime/paquete) |
| Velocidad de escalado | Minutos (scheduling de pods + scaling de nodes) | Segundos (lanzamiento de tasks) | Milisegundos (invocaciones concurrentes) |
| Costo en reposo | Alto (mínimo 2-3 nodes corriendo siempre) | Medio (pago por task en ejecución) | Cero (pago por invocación) |
| Costo bajo carga | Bajo (packing eficiente, spot instances) | Medio (packing menos eficiente) | Puede ser alto (precio por invocación) |
| Workloads stateful | Bueno (PVCs, StatefulSets) | Limitado (solo EFS) | No soportado |
| Procesos de larga duración | Ilimitado | Ilimitado | 15 min máximo |
| Ecosistema | Enorme (Helm, operators, service mesh) | Nativo de AWS | Nativo de AWS |
| Multi-cloud | Sí (mismos manifests, diferentes providers) | Solo AWS | Solo AWS |
| Requisito de habilidades del equipo | Alto (se necesita expertise en K8s) | Medio (conocimiento de AWS) | Bajo (solo escribe funciones) |
| Ideal para | Sistemas multi-servicio complejos, workloads stateful | Microservicios simples, containers sin overhead de K8s | Event-driven, endpoints de API, tareas programadas |
El desglose real de costos
Para una plataforma SaaS típica con 5 servicios:
| Componente | Kubernetes (EKS) | ECS Fargate | Lambda + API Gateway |
|---|---|---|---|
| Compute (mensual) | ~$600 (3 nodes t3.large + pods) | ~$450 (5 servicios, 0.5 vCPU cada uno) | ~$50-500 (depende del tráfico) |
| Control plane | $73/mes (tarifa EKS) | Gratis | Gratis |
| Load balancer | $25/mes (ALB) | $25/mes (ALB) | Incluido en API GW |
| Networking (NAT) | $45/mes | $45/mes | $45/mes |
| Monitoring | $50-200/mes | $50-200/mes | $50-200/mes |
| Total (tráfico bajo) | ~$800-1,000/mes | ~$570-720/mes | ~$200-800/mes |
| Total (tráfico alto) | ~$1,500-3,000/mes | ~$2,000-4,000/mes | ~$3,000-10,000/mes |
Kubernetes es el más barato bajo carga (bin-packing eficiente, spot instances, capacidad reservada). Lambda es el más barato con poco tráfico (no pagas nada en reposo). ECS Fargate es el punto medio.
Cuándo elegir Kubernetes
Elige Kubernetes cuando tengas:
Sistemas multi-servicio complejos. Si estás corriendo 7+ servicios con interdependencias, configuración compartida, service discovery y despliegues coordinados, Kubernetes orquesta esto bien. Los containers Docker individuales en ECS se vuelven difíciles de gestionar a esta escala.
Workloads stateful. Bases de datos, motores de búsqueda (OpenSearch, MeiliSearch), message brokers (RabbitMQ) y clusters de cache (Redis) se benefician de los StatefulSets, PersistentVolumeClaims y operators de Kubernetes. Ejecutarlos en ECS requiere servicios administrados externos para cada componente stateful.
Requisitos multi-cloud. Los manifests de Kubernetes funcionan en cualquier proveedor cloud. ECS y Lambda son exclusivos de AWS. Si necesitas correr en AWS y Azure (o podrías necesitarlo en el futuro), Kubernetes es la opción portable.
Un equipo de plataforma. Kubernetes requiere mantenimiento continuo: actualizaciones del cluster (cada 3-4 meses para parches de seguridad), gestión de grupos de nodes, configuración de red (ingress controllers, network policies) y gestión de RBAC. Sin una persona o equipo dedicado manejando esto, el overhead operacional ralentizará toda la organización de ingeniería.
Arquitectura Kubernetes para una plataforma PIM/Commerce
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Ingress │ │ Cert-Manager│ │ External DNS │ │
│ │ (Nginx/Traefik)│ │ (Let's Encrypt)│ │ (Route53 sync) │ │
│ └──────┬───────┘ └─────────────┘ └─────────────────────┘ │
│ │ │
│ ┌──────▼──────────────────────────────────────────────────┐ │
│ │ Namespaces │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ production namespace │ │ │
│ │ │ │ │ │
│ │ │ pimcore-web (2-4 réplicas) │ │ │
│ │ │ pimcore-worker (1-3 réplicas) │ │ │
│ │ │ pimcore-ops (1 réplica, mantenimiento) │ │ │
│ │ │ frontend (2-3 réplicas) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ data namespace │ │ │
│ │ │ │ │ │
│ │ │ mysql (StatefulSet, 1 réplica o administrado) │ │ │
│ │ │ redis (StatefulSet, 1 réplica o administrado) │ │ │
│ │ │ opensearch (StatefulSet, 2-3 réplicas) │ │ │
│ │ │ rabbitmq (StatefulSet, 1-3 réplicas) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ flux-system namespace (controlador GitOps) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Estrategia de despliegue: GitOps con Flux
Usamos Flux para despliegues basados en GitOps. El repositorio Git es la fuente única de verdad. Flux reconcilia el estado del cluster con el repositorio cada minuto.
# flux-system/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: platform
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: infrastructure
path: ./kubernetes/resources/overlay/prod
prune: true # Eliminar recursos borrados de Git
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: pimcore
namespace: production
Ventajas sobre kubectl apply o despliegues dirigidos por CI:
- Detección y corrección de drift. Si alguien cambia un recurso manualmente, Flux lo revierte en menos de 1 minuto.
- Git como registro de auditoría. Cada cambio es un commit de Git con autor, timestamp y diff.
- Sin credenciales del cluster en el CI. Flux tira desde Git. El CI empuja a Git. El pipeline de CI nunca necesita acceso a kubectl.
- El rollback es git revert. Revierte el commit, Flux reconcilia, rollback completado.
Kustomize para overlays de entorno
kubernetes/resources/
├── base/
│ ├── deployments/
│ │ ├── pimcore.yaml
│ │ ├── frontend.yaml
│ │ └── worker.yaml
│ ├── services/
│ ├── configmaps/
│ └── kustomization.yaml
├── overlay/
│ ├── prod/
│ │ ├── patches/
│ │ │ ├── pimcore-replicas.yaml # 4 réplicas
│ │ │ ├── resource-limits.yaml # CPU/memoria más altos
│ │ │ └── env-secrets.yaml # Secrets de producción
│ │ └── kustomization.yaml
│ ├── staging/
│ │ ├── patches/
│ │ │ ├── pimcore-replicas.yaml # 1 réplica
│ │ │ └── resource-limits.yaml # Límites más bajos
│ │ └── kustomization.yaml
│ └── dev/
│ └── kustomization.yaml
Los manifests base definen la estructura común. Los overlays parchean las diferencias específicas de cada entorno (réplicas, límites de recursos, secrets, dominios). Misma aplicación, diferente configuración por entorno.
Cuándo gana ECS Fargate
Elegimos ECS Fargate + Lambda para una plataforma de comercio en lugar de Kubernetes. Las razones:
Operaciones más simples. Sin actualizaciones de cluster, sin gestión de nodes, sin configuración RBAC. ECS maneja el scheduling, el scaling y los health checks. El equipo se enfoca en el código de la aplicación, no en la infraestructura.
Escalado más rápido. ECS Fargate lanza nuevas tasks en segundos. Kubernetes necesita programar pods, potencialmente esperar al scaling de nodes (minutos) y pasar health checks. Para picos de tráfico, Fargate responde más rápido.
Mejor costo para workloads variables. Pago por task en ejecución, no por node. Si el tráfico cae a cero por la noche, los costos bajan proporcionalmente. Los nodes de Kubernetes siguen corriendo (y cobrando) independientemente de la carga.
// Definición del servicio ECS (vía CDK)
const service = new ecs.FargateService(this, 'ApiService', {
cluster,
taskDefinition,
desiredCount: 2,
assignPublicIp: false,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
circuitBreaker: { rollback: true }, // Rollback automático en caso de fallo de despliegue
capacityProviderStrategies: [
{ capacityProvider: 'FARGATE_SPOT', weight: 2 }, // 66% spot
{ capacityProvider: 'FARGATE', weight: 1 }, // 33% on-demand
],
});
// Auto-scaling
const scaling = service.autoScaleTaskCount({ minCapacity: 2, maxCapacity: 10 });
scaling.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 70 });
scaling.scaleOnRequestCount('RequestScaling', {
targetGroup,
requestsPerTarget: 1000,
});
Lambda para workloads event-driven
Las funciones Lambda manejan workloads event-driven que no justifican un servicio persistente:
// Lambda para procesamiento de webhooks
const webhookHandler = new lambda.Function(this, 'WebhookHandler', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'webhook.handler',
timeout: cdk.Duration.seconds(30),
memorySize: 256,
environment: {
TABLE_NAME: table.tableName,
QUEUE_URL: queue.queueUrl,
},
});
// API Gateway dispara Lambda
const api = new apigateway.RestApi(this, 'WebhookApi');
api.root.addResource('webhook').addMethod('POST',
new apigateway.LambdaIntegration(webhookHandler)
);
El híbrido: ECS + Lambda
La arquitectura que usamos más frecuentemente para plataformas de comercio:
| Componente | Corre en | Por qué |
|---|---|---|
| Commerce API (Vendure) | ECS Fargate | De larga duración, sesiones stateful |
| Servicio worker | ECS Fargate | Consumidor de cola persistente |
| Handlers de webhook | Lambda | Event-driven, tráfico esporádico |
| Tareas programadas | Lambda + EventBridge | Tipo cron, no necesita proceso persistente |
| Procesamiento de imágenes | Lambda | CPU-intensivo, paralelizable |
| Indexación de búsqueda | Lambda + SQS | Event-driven, por ráfagas |
| Dashboard de admin | ECS Fargate o S3+CloudFront | Assets estáticos o SSR |
La API de comercio y los workers corren en Fargate (persistente, de larga duración). Todo lo event-driven corre en Lambda (pago por uso, auto-scaling). La combinación es más barata que correr todo en Kubernetes y más simple que correr todo en Lambda.
Aislamiento multi-tenant en Kubernetes
Si corres un SaaS multi-tenant en Kubernetes, el aislamiento de tenants necesita configuración explícita:
Aislamiento por namespace
# Network policy: los pods en el namespace tenant-a solo pueden comunicarse entre sí
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: tenant-isolation
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: tenant-a
egress:
- to:
- namespaceSelector:
matchLabels:
tenant: tenant-a
- to: # Permitir resolución DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
Resource quotas
Evita que un tenant consuma todos los recursos del cluster:
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "4" # Máx 4 núcleos CPU
requests.memory: "8Gi" # Máx 8GB de RAM
limits.cpu: "8"
limits.memory: "16Gi"
pods: "20" # Máx 20 pods
services: "10"
persistentvolumeclaims: "5"
El problema del vecino ruidoso
Incluso con resource quotas, un workload con I/O intensivo de un tenant puede afectar a otros en el mismo node. Soluciones:
| Estrategia | Nivel de aislamiento | Impacto en el costo |
|---|---|---|
| Nodes compartidos, resource quotas | Suave (CPU/memoria limitados, I/O compartido) | El más bajo |
| Node affinity (pools de nodes dedicados) | Medio (nodes dedicados por tenant) | Más alto |
| Clusters dedicados | Completo (infraestructura completamente separada) | El más alto |
Para la mayoría de aplicaciones SaaS, los nodes compartidos con resource quotas son suficientes. Reserva pools de nodes dedicados para tenants enterprise con requisitos estrictos de aislamiento. Para los patrones de aislamiento a nivel de aplicación (middleware de API, filtros de consulta, políticas), consulta nuestra guía de diseño multi-tenant.
Optimización de costos
Spot instances (Kubernetes)
Las spot instances son un 60-90% más baratas que on-demand. Úsalas para workloads stateless que toleren interrupciones:
# EKS managed node group con spot instances
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: production
region: eu-central-1
managedNodeGroups:
- name: spot-workers
instanceTypes: ["t3.large", "t3.xlarge", "m5.large"]
spot: true
minSize: 2
maxSize: 10
desiredCapacity: 3
labels:
node-type: spot
- name: on-demand-workers
instanceTypes: ["t3.large"]
minSize: 1
maxSize: 3
desiredCapacity: 1
labels:
node-type: on-demand
Corre los servicios stateless (servidores web, workers) en spot. Corre los servicios stateful (bases de datos, motores de búsqueda) en on-demand. Usa pod anti-affinity para distribuir las réplicas entre nodes de forma que una interrupción spot no tire todas las réplicas.
Dimensionamiento correcto
La mayoría de equipos sobre-aprovisionan. Un servicio solicitando 1 CPU y 2GB de RAM quizás en realidad usa 0.2 CPU y 400MB. El sobre-aprovisionamiento desperdicia dinero. El sub-aprovisionamiento causa OOM kills.
# Verificar el uso real de recursos vs las solicitudes
kubectl top pods -n production
# Comparar con las solicitudes de recursos en los manifests de despliegue
kubectl get pods -n production -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].resources.requests}{"\n"}{end}'
Usa el Vertical Pod Autoscaler (VPA) en modo recomendación para ver lo que tus pods realmente necesitan:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: pimcore-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: pimcore
updatePolicy:
updateMode: "Off" # Solo recomendación, no aplicar automáticamente
Autoscaling
El Horizontal Pod Autoscaler (HPA) escala basándose en métricas:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: pimcore-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: pimcore
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # Esperar 5 min antes de reducir
policies:
- type: Pods
value: 1
periodSeconds: 60 # Eliminar máx 1 pod por minuto
Los stabilizationWindowSeconds previenen el flapping (escalar arriba, escalar abajo, escalar arriba). Las scaleDown.policies previenen un scale-down agresivo que podría causar problemas de capacidad durante el siguiente pico de tráfico.
El campo minado del networking
El networking de Kubernetes es donde la mayoría de equipos se quedan atascados.
Ingress controllers
| Controlador | Ideal para | Complejidad |
|---|---|---|
| Nginx Ingress | Uso general, el más común | Baja |
| Traefik | Auto-discovery, Let's Encrypt integrado | Baja |
| AWS ALB Ingress | Nativo de AWS, integración con WAF | Media |
| Istio Gateway | Service mesh, mTLS, gestión de tráfico | Alta |
Para la mayoría de plataformas SaaS, Nginx Ingress + cert-manager (Let's Encrypt) es suficiente. Agrega un service mesh (Istio, Linkerd) solo si necesitas mTLS entre servicios, enrutamiento de tráfico avanzado (canary deployments, traffic splitting), u observabilidad detallada servicio-a-servicio.
Problemas de resolución DNS
Un problema frecuente en producción: los pods no pueden resolver hostnames externos porque la configuración DNS está mal.
# Encontrar la IP correcta del servicio DNS en tu cluster
kubectl get svc -n kube-system kube-dns -o jsonpath='{.spec.clusterIP}'
# Si las configs de nginx referencian un resolver, usa esta IP
# Error común: usar 10.0.0.10 cuando el DNS real está en 10.2.0.10
Si tu sidecar de nginx hace proxy de peticiones a servicios externos (almacenamiento cloud, APIs externas), la directiva resolver debe apuntar a la IP de kube-dns del cluster, no a un valor hardcodeado.
Errores comunes
-
Elegir Kubernetes porque "todos lo usan." Si tienes 3 servicios y un equipo pequeño, ECS Fargate es más simple y más barato. Kubernetes tiene sentido a partir de 7+ servicios con un equipo de plataforma.
-
Sin GitOps.
kubectl applydesde el portátil de un desarrollador no es una estrategia de despliegue. Usa Flux o ArgoCD para despliegues basados en reconciliación. -
Cluster compartido sin resource quotas. Un tenant o un pod descontrolado consume todos los recursos. Cada namespace necesita resource quotas.
-
Todos los pods en instancias on-demand. Las spot instances son un 60-90% más baratas para workloads stateless. Úsalas para servidores web y workers.
-
Sobre-aprovisionar recursos. Pods solicitando 2 CPU y usando 0.2 CPU desperdician dinero. Usa las recomendaciones de VPA para dimensionar correctamente.
-
Autoscaling agresivo. Reducir demasiado rápido causa problemas de capacidad en el siguiente pico. Usa ventanas de estabilización y políticas de scale-down graduales.
-
Sin network policies. Sin ellas, cualquier pod puede comunicarse con cualquier otro pod en el cluster. En un setup multi-tenant, esto es un problema de seguridad.
-
Ignorar las actualizaciones del cluster. Las versiones de Kubernetes llegan a su fin de vida cada 12-15 meses. Planifica ventanas de actualización trimestrales. Quedarte atrás crea vulnerabilidades de seguridad y bloquea nuevas funcionalidades.
-
Mezclar stateful y stateless en los mismos nodes. Un pod de OpenSearch y un pod de servidor web compitiendo por I/O en el mismo node degrada a ambos. Usa node affinity para separarlos.
-
Sin sealed secrets. Hacer commit de secrets en texto plano a Git es una brecha de seguridad esperando a ocurrir. Usa Sealed Secrets, External Secrets Operator, o AWS Secrets Manager.
Puntos clave
-
Kubernetes para plataformas multi-servicio complejas. 7+ servicios, workloads stateful, requisitos multi-cloud, y un equipo que pueda manejar el overhead operacional.
-
ECS Fargate para workloads de containers más simples. Mismos containers, menos complejidad operacional. Mejor para equipos sin expertise en Kubernetes.
-
Lambda para workloads event-driven. Webhooks, tareas programadas, procesamiento de imágenes, y cualquier workload que sea por ráfagas y de corta duración. Cero costo en reposo.
-
El híbrido (ECS + Lambda) suele ser la mejor respuesta. Servicios persistentes en Fargate, trabajo event-driven en Lambda. Más barato que todo en Kubernetes, más simple que todo en Lambda.
-
GitOps con Flux ofrece reconciliación real. No solo automatización de despliegues. Detección de drift, registro de auditoría, y rollback vía git revert.
-
Las spot instances ahorran un 60-90% en workloads stateless. Corre servidores web y workers en spot. Las bases de datos y motores de búsqueda en on-demand.
-
El aislamiento multi-tenant necesita network policies y resource quotas. El aislamiento por namespace solo no es suficiente. Impón fronteras de red y límites de recursos por tenant.
Nosotros desplegamos y gestionamos infraestructura Kubernetes, ECS y Lambda como parte de nuestros servicios cloud. Si necesitas ayuda eligiendo una estrategia de compute u optimizando tu despliegue actual, habla con nuestro equipo o solicita un presupuesto. Consulta también nuestra guía de migración de Pimcore para patrones de despliegue Kubernetes específicos de Pimcore.
Temas cubiertos
Guías relacionadas
Guía Empresarial de Sistemas de IA Agéntica
Guia tecnica de sistemas de IA agentica en entornos empresariales. Descubre la arquitectura, capacidades y aplicaciones de agentes IA autonomos.
Leer guíaComercio Agéntico: Cómo Dejar que los Agentes IA Compren de Forma Segura
Cómo diseñar comercio iniciado por agentes IA con gobernanza. Motores de políticas, puertas de aprobación HITL, recibos HMAC, idempotencia, aislamiento de tenants y el Agentic Checkout Protocol completo.
Leer guíaLos 9 Puntos Donde Tu Sistema de IA Filtra Datos (y Cómo Sellar Cada Uno)
Un mapa sistemático de cada lugar donde se filtran datos en sistemas de IA. Prompts, embeddings, logs, llamadas a herramientas, memoria de agentes, mensajes de error, caché, datos de fine-tuning y handoffs entre agentes.
Leer guía¿Listo para construir sistemas de IA listos para producción?
Nuestro equipo se especializa en sistemas de IA listos para producción. Hablemos de cómo podemos ayudar.
Iniciar una conversación