Saltar al contenido

Gestión de recursos y estrategias de autoscaling en Kubernetes

Requests, limits y clases de QoS en Kubernetes, más HPA, VPA y autoscaling de clúster, con configuraciones que equilibran coste y fiabilidad.

5 min de lectura
Arquitectura de autoscaling en Kubernetes que muestra el HPA escalando pods según métricas, el VPA ajustando los requests de recursos y el cluster autoscaler añadiendo nodos cuando los pods no se pueden programar

La gestión de recursos en Kubernetes marca la diferencia entre un cluster eficiente y responsivo, y uno que desperdicia dinero en capacidad sin usar o se cae durante picos de tráfico. Hacerlo bien requiere entender cómo interactúan los requests y los limits con el scheduler, cómo las clases de Quality of Service determinan el orden de desalojo, y cómo se complementan entre sí los tres tipos de autoscaler.

La mayoría de los equipos configuran los recursos de forma demasiado conservadora —desperdiciando el 60 % de la capacidad de su cluster— o directamente los omiten y terminan descubriendo OOM kills en producción.

Requests, Limits y clases de QoS

Los requests le indican al scheduler cuánta capacidad necesita un pod, y están garantizados. Los limits limitan el máximo que un pod puede consumir. La combinación entre ambos determina la clase de Quality of Service del pod.

ymlyaml
# ❌ No resource specifications — worst reliability
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: api
          image: api:latest
          # BestEffort QoS — first to be evicted under pressure
          # Scheduler doesn't know how much capacity is needed
ymlyaml
# ✅ Properly configured resources
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: api
          image: api:v2.1.0
          resources:
            requests:
              cpu: "250m"      # 0.25 CPU cores guaranteed
              memory: "256Mi"  # 256 MiB guaranteed
            limits:
              cpu: "1000m"     # Can burst to 1 core
              memory: "512Mi"  # Hard cap — OOMKilled if exceeded
          # Burstable QoS — requests != limits
          # Evicted after BestEffort pods under memory pressure
 
        - name: sidecar-proxy
          image: envoy:v1.28
          resources:
            requests:
              cpu: "100m"
              memory: "64Mi"
            limits:
              cpu: "100m"
              memory: "64Mi"
          # Guaranteed QoS — requests == limits
          # Last to be evicted, highest priority
tstypescript
// Decision framework for setting resources
interface ResourceStrategy {
  workloadType: string;
  requestGuidance: string;
  limitGuidance: string;
  qosTarget: "Guaranteed" | "Burstable" | "BestEffort";
}
 
const strategies: ResourceStrategy[] = [
  {
    workloadType: "Latency-sensitive API",
    requestGuidance:
      "Set to p95 usage from production metrics",
    limitGuidance:
      "CPU: 2-4x request (allow burst). " +
      "Memory: 1.5-2x request (prevent OOM)",
    qosTarget: "Burstable",
  },
  {
    workloadType: "Background worker",
    requestGuidance:
      "Set to average usage — can tolerate scheduling delays",
    limitGuidance:
      "CPU: no limit (let it use idle capacity). " +
      "Memory: 2x request",
    qosTarget: "Burstable",
  },
  {
    workloadType: "Database / Stateful",
    requestGuidance:
      "Set requests == limits for predictable performance",
    limitGuidance:
      "Same as requests — no bursting, no throttling",
    qosTarget: "Guaranteed",
  },
  {
    workloadType: "Batch job",
    requestGuidance:
      "Minimal requests — tolerates preemption",
    limitGuidance:
      "Memory limit only — let it use available CPU",
    qosTarget: "Burstable",
  },
];

Horizontal Pod Autoscaler (HPA)

El HPA ajusta la cantidad de réplicas según métricas. El escalado basado en CPU por defecto funciona bien para cargas de trabajo limitadas por cómputo, pero las métricas personalizadas cubren el resto de los casos.

ymlyaml
# CPU-based HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-server-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
 
    # Custom metric: requests per second from Prometheus
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "100"
 
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60
        - type: Pods
          value: 4
          periodSeconds: 60
      selectPolicy: Max # Use the policy that allows more pods
 
    scaleDown:
      stabilizationWindowSeconds: 300 # Wait 5 min before scaling down
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
      selectPolicy: Min # Use the policy that removes fewer pods

La sección behavior es fundamental. Sin ella, el HPA reduce réplicas de forma agresiva durante caídas breves de tráfico, lo que genera problemas de capacidad cuando el tráfico se recupera. La ventana de estabilización y una política de reducción conservadora evitan ese comportamiento errático.

Vertical Pod Autoscaler (VPA)

El VPA ajusta los requests de recursos según el uso real. Es especialmente útil en cargas de trabajo donde no resulta obvio cuáles son los valores de recursos correctos: observa y recomienda.

ymlyaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-server-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  updatePolicy:
    updateMode: "Off"  # Recommendation-only mode
    # "Auto" would restart pods to apply new resources
    # Start with "Off" to review recommendations first
 
  resourcePolicy:
    containerPolicies:
      - containerName: api
        minAllowed:
          cpu: "100m"
          memory: "128Mi"
        maxAllowed:
          cpu: "2000m"
          memory: "2Gi"
        controlledResources: ["cpu", "memory"]
shbash
# Check VPA recommendations
kubectl describe vpa api-server-vpa
 
# Output includes:
# Recommendation:
#   Container Recommendations:
#     Container Name: api
#     Lower Bound:    Cpu: 150m, Memory: 200Mi
#     Target:         Cpu: 350m, Memory: 384Mi
#     Uncapped Target: Cpu: 350m, Memory: 384Mi
#     Upper Bound:    Cpu: 800m, Memory: 700Mi

El VPA y el HPA no deberían controlar la CPU del mismo deployment al mismo tiempo. El patrón típico es que el VPA gestione los requests de memoria y el HPA gestione la cantidad de réplicas según la CPU o métricas personalizadas.

Configuración del Cluster Autoscaler

El cluster autoscaler agrega o quita nodos cuando los pods no se pueden programar o cuando los nodos están subutilizados.

ymlyaml
# Cluster autoscaler configuration (Helm values)
autoDiscovery:
  clusterName: production
  tags:
    - k8s.io/cluster-autoscaler/enabled
    - k8s.io/cluster-autoscaler/production
 
extraArgs:
  # Don't scale down nodes with running pods from
  # kube-system (except DaemonSets)
  skip-nodes-with-system-pods: "true"
 
  # Wait 10 minutes before considering a node for removal
  scale-down-unneeded-time: "10m"
 
  # Node must be below 50% utilization to be removed
  scale-down-utilization-threshold: "0.5"
 
  # Maximum time to wait for a node to be ready
  max-node-provision-time: "15m"
 
  # Don't scale down more than 1 node at a time
  max-graceful-termination-sec: "600"
 
  # Balance similar node groups
  balance-similar-node-groups: "true"
 
  # Handle GPU node groups separately
  expander: "priority"
tstypescript
// Node pool strategy for mixed workloads
interface NodePoolConfig {
  name: string;
  instanceType: string;
  minNodes: number;
  maxNodes: number;
  taints: string[];
  labels: Record<string, string>;
  useCase: string;
}
 
const nodePools: NodePoolConfig[] = [
  {
    name: "general",
    instanceType: "m5.xlarge",
    minNodes: 3,
    maxNodes: 20,
    taints: [],
    labels: { "workload-type": "general" },
    useCase: "API servers, web frontends, background workers",
  },
  {
    name: "memory-optimized",
    instanceType: "r5.2xlarge",
    minNodes: 0,
    maxNodes: 10,
    taints: ["workload=memory:NoSchedule"],
    labels: { "workload-type": "memory" },
    useCase: "In-memory caches, data processing",
  },
  {
    name: "spot-batch",
    instanceType: "c5.2xlarge",
    minNodes: 0,
    maxNodes: 50,
    taints: ["lifecycle=spot:NoSchedule"],
    labels: { "workload-type": "batch", lifecycle: "spot" },
    useCase:
      "Batch jobs, CI runners — tolerant of interruption",
  },
];

Uniendo todo: la pila de autoscaling

Los tres autoscalers trabajan juntos en un sistema de capas.

ymlyaml
# Complete autoscaling setup for a production API
 
# 1. Deployment with right-sized resources (from VPA recommendations)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: payment-api
          resources:
            requests:
              cpu: "350m"
              memory: "384Mi"
            limits:
              cpu: "1000m"
              memory: "768Mi"
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: payment-api
---
# 2. HPA scales pods based on request rate
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: payment-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-api
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "80"
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
---
# 3. VPA tunes memory requests (recommendation mode)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: payment-api-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-api
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: payment-api
        controlledResources: ["memory"]
# 4. Cluster autoscaler adds nodes when HPA creates
#    pods that can't be scheduled

Puntos clave

Los requests de recursos son asignaciones garantizadas que el scheduler utiliza para tomar decisiones de ubicación: configurarlos demasiado bajos provoca que se programen pods en nodos sobrecomprometidos, mientras que configurarlos demasiado altos desperdicia capacidad del cluster. Las clases de Quality of Service (Guaranteed, Burstable, BestEffort) determinan el orden de desalojo bajo presión de memoria, por lo que las cargas de trabajo sensibles a la latencia deberían tener requests cercanos a los limits para lograr un rendimiento estable. Las políticas de behavior del HPA evitan el escalado errático: una ventana de estabilización de 5 minutos antes de reducir réplicas y tasas de eliminación conservadoras (10 % por minuto) evitan problemas de capacidad durante caídas breves de tráfico. El VPA debería usarse primero en modo de recomendación, observando el uso real para sugerir valores de recursos bien dimensionados antes de habilitar actualizaciones automáticas que reinician los pods. Los tres autoscalers se combinan de forma natural: el VPA dimensiona correctamente los recursos de cada pod, el HPA ajusta la cantidad de réplicas según la carga, y el cluster autoscaler aprovisiona nodos cuando hay pods pendientes que no se pueden programar. Tener múltiples grupos de nodos con taints y tolerations te permite asignar cada tipo de carga de trabajo al tipo de instancia adecuado: instancias spot para trabajos por lotes, nodos optimizados para memoria para cachés, y nodos de uso general para APIs.

Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX