Guía Completa de Kubernetes cost optimization
Kubernetes simplifica la orquestación de contenedores, pero también puede convertirse en un sumidero de costos si no se gestiona con intención. Estudios de la industria estiman que entre el 30% y el 50% del gasto en cloud de organizaciones que usan Kubernetes se desperdicia en recursos sobreaprovisionados, nodos ociosos y configuraciones por defecto que nadie reviso. La buena noticia es que existen estrategias concretas y herramientas especializadas para recuperar ese gasto sin sacrificar rendimiento ni disponibilidad. Esta guía cubre desde el right-sizing básico hasta el uso de Karpenter y herramientas de visibilidad de costos.
Right-sizing: el primer paso
El right-sizing consiste en ajustar los recursos asignados a cada workload para que coincidan con lo que realmente consume. Es la optimización con mayor impacto inmediato porque no requiere cambios arquitecturales.
El problema del sobreaprovisionamiento
Cuando un equipo despliega una aplicación sin datos de consumo real, tiende a pedir mas recursos de los necesarios “por si acaso”. Un pod que pide 1 CPU y 2Gi de memoria pero usa 200m y 400Mi esta ocupando espacio que otros pods podrian usar, y el cluster necesita mas nodos para acomodar esa capacidad fantasma.
Como diagnosticar
Compara requests vs uso real con métricas de Prometheus:
# CPU: ratio de uso real vs request
sum(rate(container_cpu_usage_seconds_total{namespace="production"}[5m])) by (pod)
/
sum(kube_pod_container_resource_requests{resource="cpu",namespace="production"}) by (pod)
# Memoria: ratio de uso real vs request
sum(container_memory_working_set_bytes{namespace="production"}) by (pod)
/
sum(kube_pod_container_resource_requests{resource="memory",namespace="production"}) by (pod)
Si el ratio esta consistentemente por debajo de 0.5, los requests estan sobredimensionados.
Resource requests y limits: configuración correcta
Estrategia recomendada
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-catalogo
spec:
template:
spec:
containers:
- name: api
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Las reglas prácticas para definir requests y limits:
- Requests: Basalos en el percentil 95 de consumo real observado durante al menos una semana. Esto es lo que el scheduler usa para colocar pods en nodos.
- Limits de CPU: Algunos equipos prefieren no poner limit de CPU para evitar throttling. Si los pones, que sean 2-3x el request.
- Limits de memoria: Siempre pon limit de memoria. Un pod sin memory limit puede causar OOMKill en otros pods del nodo.
Vertical Pod Autoscaler para recomendaciones
VPA en modo recomendación analiza el consumo histórico y sugiere valores:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-catalogo-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-catalogo
updatePolicy:
updateMode: "Off" # Solo recomienda, no modifica
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: 50m
memory: 64Mi
maxAllowed:
cpu: 2
memory: 2Gi
Revisa las recomendaciones con kubectl describe vpa api-catalogo-vpa y ajusta manualmente.
Spot instances y nodos preemptibles
Las instancias spot (AWS), preemptible (GCP) o spot VMs (Azure) cuestan entre un 60% y un 90% menos que las instancias on-demand. Son ideales para workloads que toleran interrupciones.
Workloads candidatos para spot
- Jobs de CI/CD y pipelines de datos.
- Batch processing y ETL.
- Workloads stateless con multiples replicas (el HPA compensa la perdida de un nodo).
- Ambientes de desarrollo y staging.
Workloads que NO deben correr en spot
- Bases de datos stateful con una sola replica.
- Componentes críticos sin redundancia.
- Workloads con tiempos de arranque muy largos.
Configuración en EKS con node groups mixtos
managedNodeGroups:
- name: on-demand-base
instanceType: m6i.xlarge
minSize: 3
maxSize: 3
capacityType: ON_DEMAND
labels:
capacity-type: on-demand
- name: spot-workers
instanceTypes: [m6i.xlarge, m5.xlarge, m5a.xlarge, m6a.xlarge]
minSize: 0
maxSize: 20
capacityType: SPOT
labels:
capacity-type: spot
taints:
- key: spot
value: "true"
effect: PreferNoSchedule
Usar multiples tipos de instancia en el grupo spot aumenta la disponibilidad al diversificar los pools de capacidad.
Cluster Autoscaler vs Karpenter
Cluster Autoscaler
Es la solución tradicional. Observa pods pendientes y escala node groups predefinidos:
- Funciona con node groups estaticos (debes predefinir tipos de instancia).
- Escalado mas lento (minutos).
- Soporta multiples cloud providers.
Karpenter
Es la alternativa moderna, creada por AWS pero con soporte creciente para otros providers:
- No requiere node groups predefinidos. Karpenter elige el tipo de instancia optimo para los pods pendientes.
- Escalado mas rápido (segundos).
- Consolida nodos subutilizados automáticamente.
- Permite definir restricciones flexibles:
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["m", "c", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["4"]
limits:
cpu: 100
memory: 200Gi
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h
La política consolidationPolicy: WhenUnderutilized es clave: Karpenter automáticamente reemplaza nodos subutilizados por instancias mas pequeñas, optimizando costos de forma continua.
Detección de recursos ociosos
Namespaces de desarrollo olvidados
Es comun que ambientes de desarrollo o staging queden corriendo indefinidamente. Implementa políticas de ciclo de vida:
# Encontrar namespaces sin actividad reciente
kubectl get namespaces -o json | jq -r '.items[] |
select(.metadata.labels["environment"]=="dev") |
.metadata.name' | while read ns; do
LAST_POD=$(kubectl get pods -n $ns --sort-by=.metadata.creationTimestamp \
-o jsonpath='{.items[-1].metadata.creationTimestamp}' 2>/dev/null)
echo "Namespace: $ns - Ultimo pod: $LAST_POD"
done
PVCs sin usar
Los volumenes persistentes que quedaron huerfanos despues de borrar deployments siguen generando costos de almacenamiento:
# Listar PVCs no montados por ningun pod
kubectl get pvc --all-namespaces -o json | jq -r '
.items[] | select(.status.phase=="Bound") |
"\(.metadata.namespace)/\(.metadata.name)"' | while read pvc; do
NS=$(echo $pvc | cut -d/ -f1)
NAME=$(echo $pvc | cut -d/ -f2)
PODS=$(kubectl get pods -n $NS -o json | jq -r \
".items[].spec.volumes[]?.persistentVolumeClaim.claimName" | grep -c "$NAME")
if [ "$PODS" -eq 0 ]; then
echo "PVC sin usar: $pvc"
fi
done
Namespace quotas y governance
Las quotas previenen que un equipo consuma todos los recursos del cluster:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-backend-quota
namespace: backend
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
persistentvolumeclaims: "10"
pods: "50"
Complementa las quotas con LimitRanges para establecer defaults y maximos por pod, evitando que un solo deployment acapare la cuota completa del namespace.
Herramientas de visibilidad de costos
Kubecost
Kubecost asigna costos reales a namespaces, deployments, labels y equipos. Funcionalidades principales:
- Dashboard con desglose de costos por namespace, label o equipo.
- Alertas cuando el gasto supera umbrales definidos.
- Recomendaciones de right-sizing basadas en consumo real.
- Savings insights que identifican recursos ociosos y sobreaprovisionados.
OpenCost
Es la alternativa open source, donada a la CNCF por Kubecost. Proporciona las funcionalidades core de asignación de costos sin las features enterprise. Ideal para equipos que quieren visibilidad básica sin costo de licencia.
Métricas clave a monitorear
- Cost per namespace/team: Permite chargeback o showback interno.
- CPU/Memory efficiency: Ratio de uso real vs allocated. Objetivo: mantenerlo por encima de 0.6.
- Idle cost: Costo de recursos reservados pero no utilizados.
- Cluster efficiency: Porcentaje de la capacidad total que esta siendo usada efectivamente.
Checklist de optimización de costos
- Configurar resource requests y limits en todos los deployments.
- Desplegar VPA en modo recomendación y revisar semanalmente.
- Implementar HPA para workloads con tráfico variable.
- Usar spot instances para workloads tolerantes a interrupciones.
- Evaluar Karpenter para reemplazar Cluster Autoscaler.
- Establecer ResourceQuotas por namespace.
- Auditar PVCs huerfanos y namespaces inactivos mensualmente.
- Desplegar Kubecost u OpenCost para visibilidad continua.
- Configurar alertas de gasto por equipo/namespace.
- Revisar recomendaciones de right-sizing cada sprint.
Conclusion
La optimización de costos en Kubernetes no es un proyecto puntual sino una disciplina continua. Requiere visibilidad (saber cuanto se gasta y donde), governance (quotas, policies, ownership) y herramientas que automaticen las decisiones de escalado y right-sizing. La combinación de configuraciones correctas de recursos, uso estrategico de spot instances, autoescalado inteligente con Karpenter y herramientas de visibilidad como Kubecost puede reducir el gasto en cloud entre un 30% y un 60% sin impactar la experiencia del usuario.