Volver al blog
KubernetesCost OptimizationSpot InstancesKarpenterCase Study

Cómo recortamos un 48 % la factura de Kubernetes de un cliente con instancias spot

Caso real: una factura de Kubernetes un 48 % más baja, de 38.000 a 19.610 dólares al mes, con right-sizing, NodePools spot de Karpenter y horarios.

P
Davi Nunes
March 1, 2026Actualizado November 12, 202613 min de lectura

La factura de Kubernetes de una empresa SaaS europea había pasado de 15.000 a 38.000 dólares al mes en 18 meses, mientras su base de usuarios solo se duplicaba. En cinco semanas la bajamos a 19.610 dólares, un recorte del 48 %, sin cambios en el código de la aplicación, sin reducir réplicas en producción y sin caídas. La capacidad spot fue lo que más ahorró, 7.400 dólares al mes, pero llegó en segundo lugar: primero el right-sizing y, al final, los compromisos de uso.

A continuación están las cinco fases en orden, cada una con el manifiesto, la consulta o el comando para repetirla. Nuestra guía de optimización de costes en Kubernetes con prácticas SRE explica cómo medir el desperdicio en general. Este artículo profundiza en lo que esa guía solo esboza: ejecutar cargas en capacidad spot sin que los usuarios lo noten.

El punto de partida: tres clústeres al 18 % de CPU

La empresa usaba Kubernetes gestionado en una gran nube pública, con tres clústeres: producción, staging y desarrollo. Todos funcionaban día y noche sobre instancias on-demand de uso general con 8 vCPU y 32 GiB. Nuestra auditoría encontró:

  • Uso medio de los nodos: 18 % de CPU y 34 % de memoria.
  • No producción encendida 24/7: staging y desarrollo solo se usaban en horario laboral, de 09:00 a 19:00 CET.
  • Sin autoescalado: el número de nodos era fijo.
  • Requests inflados: la mayoría de los pods pedía entre dos y cuatro veces lo que usaba.
  • Almacenamiento olvidado: 23 volúmenes huérfanos de PVC antiguos, que costaban 420 dólares al mes.

Juntos, estos datos dicen que los nodos estaban dimensionados para los requests, no para la carga. Eso fijó el orden: reducir los requests, dejar que menos nodos los soporten y, después, abaratar cada nodo.

Fase 1: right-sizing de los requests antes que nada (semanas 1-2)

Ejecutamos el Vertical Pod Autoscaler en modo recomendación en todos los namespaces durante 14 días. VPA necesita un objeto por carga; este bucle crea uno para cada Deployment de un namespace:

bash
NS=orders
for d in $(kubectl -n "$NS" get deployments -o jsonpath='{.items[*].metadata.name}'); do
kubectl apply -f - <<EOF
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: $d
  namespace: $NS
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: $d
  updatePolicy:
    updateMode: "Off"
EOF
done
kubectl -n "$NS" get vpa

Fija updateMode: "Off" de forma explícita: el valor por defecto de la API, Recreate, desaloja pods para aplicar los nuevos valores.

Después, los requests y los limits se fijaron a partir del P95 de uso de cada contenedor más un margen del 30 %. El target por defecto de VPA es el percentil 90 más un 15 %; los flags del recommender --target-cpu-percentile=0.95, --target-memory-percentile=0.95 y --recommendation-margin-fraction=0.3 hacen que aplique la regla del P95 más el 30 %. Las consultas de Prometheus de nuestra guía de optimización de costes hacen lo mismo a partir de las métricas en bruto.

Ejemplo práctico. Una API en Java pedía 2 CPU y 4Gi por réplica, pero usaba unos 400m y 800Mi. Tenía 6 réplicas. Tomando esas cifras de uso como el P95:

  • Request de CPU: 400m × 1,3 = 520m, frente a 2.000m.
  • Request de memoria: 800Mi × 1,3 = 1.040Mi, frente a 4.096Mi.
  • En las 6 réplicas: 3,12 núcleos en lugar de 12, y unos 6,1Gi en lugar de 24Gi.

Son casi 9 núcleos liberados por un solo servicio. En los 47 servicios, producción pasó de 24 nodos worker a 14. Ahorro: 5.200 dólares al mes.

Fase 2: decidir qué cargas pueden ir a spot (semana 3)

La capacidad spot es capacidad sobrante de un proveedor, vendida con descuento y recuperada cuando el proveedor la necesita. El aviso antes de una recuperación va de dos minutos a 30 segundos o menos, según el proveedor y su configuración. Diseña para el aviso más corto que dé el tuyo.

Una carga solo es candidata a spot si se cumplen todas estas condiciones:

  • Sin estado: sin PersistentVolumeClaim y sin datos locales que deban sobrevivir al pod.
  • Dos o más réplicas, repartidas entre zonas: una recuperación se lleva una parte de la capacidad, no toda.
  • Se apaga dentro del aviso: deja de aceptar trabajo, termina o devuelve lo que tiene en curso y sale antes de que el nodo desaparezca.
  • Trabajo idempotente: un consumidor o un job soporta un mensaje entregado de nuevo o se reanuda desde un checkpoint.
  • Arranque rápido: una réplica nueva está lista antes de que las demás se queden sin margen.

Esto lista las réplicas, los volume claims y el grace period de cada carga:

bash
kubectl get deployments,statefulsets -A -o json | jq -r '
  .items[] | [
    .kind,
    .metadata.namespace + "/" + .metadata.name,
    "replicas=\(.spec.replicas)",
    "pvcs=\(([.spec.template.spec.volumes[]? | select(.persistentVolumeClaim)] | length) + ((.spec.volumeClaimTemplates // []) | length))",
    "grace=\(.spec.template.spec.terminationGracePeriodSeconds // 30)s"
  ] | @tsv'

Una sola réplica, un volume claim o un grace period más largo que el aviso dejan la carga en on-demand. Aquí, el reparto fue:

  • En spot: las API sin estado (12 servicios, 34 pods), los frontends web y los workers en segundo plano y consumidores de colas.
  • En on-demand: las bases de datos y los servicios con estado, es decir, PostgreSQL, Redis y el clúster de búsqueda.

Un nodo de base de datos recuperado significa un failover y una réplica reconstruida desde cero. Además, los volúmenes de almacenamiento en bloque son zonales, así que el pod solo puede reiniciarse en la zona de su volumen, que quizá es la que se acaba de quedar sin capacidad spot.

Dos NodePools: spot primero para lo que no tiene estado, on-demand para lo que sí

Karpenter sustituyó al cluster autoscaler y podía elegir entre 15 o más tipos de instancia en 3 zonas de disponibilidad. Dos NodePools de Karpenter v1 que implementan este reparto tienen este aspecto:

yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: stateless
spec:
  template:
    metadata:
      labels:
        workload-class: stateless
    spec:
      nodeClassRef:
        group: PROVIDER_GROUP
        kind: PROVIDER_NODECLASS
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: node.kubernetes.io/instance-type
          operator: Exists
          minValues: 15
      expireAfter: 720h
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 5m
    budgets:
      - nodes: "10%"
  limits:
    cpu: "400"
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: stateful
spec:
  template:
    metadata:
      labels:
        workload-class: stateful
    spec:
      nodeClassRef:
        group: PROVIDER_GROUP
        kind: PROVIDER_NODECLASS
        name: default
      taints:
        - key: workload-class
          value: stateful
          effect: NoSchedule
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
      expireAfter: Never
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 30m
    budgets:
      - nodes: "1"
  limits:
    cpu: "96"

Qué hace cada parte:

  • nodeClassRef apunta a una NodeClass, que es específica de tu nube. Usa el group y el kind que instala la versión de Karpenter de tu proveedor.
  • Los dos tipos de capacidad en el pool sin estado. Karpenter lanza spot y solo recurre a on-demand cuando no hay ninguna oferta spot disponible. Un pool solo de spot dejaría los pods en Pending.
  • minValues: 15 mantiene al menos 15 tipos de instancia en cada decisión de lanzamiento, de modo que el pool nunca depende de un solo tipo en una sola zona. El campo es alpha y, con la política Strict por defecto, el pool no lanza nada si los demás requisitos dejan menos de 15 tipos. Acota tamaños o familias con las etiquetas de instancia de tu proveedor y Gte o Lte, no con una lista de nombres de tipos.
  • Consolidación y spot. Por defecto, Karpenter solo elimina nodos spot infrautilizados. Sustituir uno por un nodo spot más barato requiere la feature gate SpotToSpotConsolidation (alpha, desactivada por defecto), y una sustitución de un solo nodo exige esa misma flexibilidad de 15 tipos.
  • El pool con estado tiene un taint para que nada más aterrice en él. Solo consolida nodos vacíos y sus nodos nunca caducan por tiempo. Se sustituyen cuando algo cambia, como una nueva imagen de nodo, de uno en uno.
  • limits limitan lo que cada pool puede lanzar. La fase 3 usa ese límite como interruptor.

Programar los pods sin estado para que perder un nodo no sea noticia

Los NodePools deciden qué se puede lanzar; la spec del pod decide dónde cae cada réplica y cómo se va. Un Deployment sin estado para spot, con su PodDisruptionBudget:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
  namespace: orders
spec:
  replicas: 6
  selector:
    matchLabels:
      app: orders-api
  template:
    metadata:
      labels:
        app: orders-api
    spec:
      terminationGracePeriodSeconds: 45
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: karpenter.sh/capacity-type
                    operator: In
                    values: ["spot"]
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: orders-api
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: orders-api
      containers:
        - name: api
          image: registry.example.com/orders-api:1.4.2
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 2
          lifecycle:
            preStop:
              sleep:
                seconds: 10
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              memory: 1Gi
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: orders-api
  namespace: orders
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: orders-api

Cómo encajan las piezas:

  • Afinidad preferente por spot. El scheduler favorece los nodos spot y Karpenter intenta primero esa preferencia, pero el servicio sigue funcionando cuando se acaba el spot.
  • Reparto por zonas con DoNotSchedule. Seis réplicas quedan dos por zona, así que una recuperación en una zona se lleva como mucho un tercio del servicio. El reparto por hostname evita, cuando es posible, que las réplicas compartan nodo.
  • Reparto entre tipos de capacidad. Karpenter también acepta karpenter.sh/capacity-type como clave de topología. Con maxSkew: 1, cerca de la mitad de las réplicas corre en on-demand, lo que resiste una recuperación masiva pero devuelve la mitad del descuento. Resérvalo para servicios en los que perder todas las réplicas spot a la vez rompería el SLO.
  • El sleep del preStop. El endpoint de un pod que termina se marca como no listo de inmediato, pero los proxies y los balanceadores tardan unos segundos en dejar de enviarle tráfico. El sleep mantiene el proceso atendiendo durante esa ventana; el SIGTERM llega después. La acción sleep la ejecuta el kubelet, así que la imagen no necesita shell (activada por defecto desde Kubernetes 1.30 y estable desde la 1.34).
  • El grace period cubre el hook preStop y el apagado posterior. Ejemplo práctico: un sleep de 10 segundos más hasta 20 segundos para terminar las peticiones en curso caben en 45 segundos. Eso cabe en un aviso de dos minutos aunque el drenado empiece tarde, pero no en uno de 30 segundos. La readiness probe funciona al revés: un pod nuevo solo recibe tráfico cuando responde.
  • El PodDisruptionBudget no puede impedir una recuperación; el nodo desaparece al llegar el plazo, pase lo que pase. Rige las interrupciones voluntarias, que son frecuentes en un pool spot: consolidación, caducidad y drift.

Los pods con estado reciben lo contrario, un selector obligatorio y una toleration, en la plantilla de pod de cada StatefulSet:

yaml
      nodeSelector:
        karpenter.sh/capacity-type: on-demand
        workload-class: stateful
      tolerations:
        - key: workload-class
          operator: Equal
          value: stateful
          effect: NoSchedule

Para la combinación de instancias que usaba esta empresa, los precios spot quedaron de media un 68 % por debajo de on-demand. Ahorro: 7.400 dólares al mes.

¿Planeas mover cargas a spot? Solicita una evaluación gratuita de costes de Kubernetes: clasificamos tus cargas según estos criterios, revisamos tus NodePools y las specs de tus pods y te entregamos una lista de lo que puede pasar a spot y de lo que necesita antes.

Gestión de interrupciones y qué vigilar después

La gestión de interrupciones de Karpenter está desactivada hasta que los avisos de tu proveedor le llegan. En el proveedor donde nació Karpenter, eso significa una cola de mensajes alimentada por las reglas de eventos de la nube, cuyo nombre se indica en la configuración de Karpenter; consulta en la versión de Karpenter de tu proveedor cómo los recibe. Ante un aviso, Karpenter aplica un taint al nodo, lo drena a través de la Eviction API y lanza un sustituto en paralelo. Sin esto, Karpenter solo se entera de la recuperación cuando la instancia ya no existe: los pods mueren sin drenado y los sustitutos arrancan tarde.

Después del cambio, vigila la rotación de nodos por tipo de capacidad. Las métricas de Karpenter llevan una etiqueta capacity_type:

promql
sum by (nodepool, capacity_type, reason) (increase(karpenter_nodeclaims_disrupted_total[1d]))

sum by (nodepool, capacity_type) (increase(karpenter_pods_disruption_initiated_total[1d]))

sum by (nodepool, capacity_type, zone) (increase(karpenter_nodeclaims_terminated_total[1d]))

La primera muestra por qué se fueron los nodos (interrupción, consolidación, caducidad, drift; los nombres de los motivos de interrupción varían según el proveedor). La segunda cuenta los pods movidos, y la tercera qué zonas pierden más. Un pool spot que pierde muchos nodos al día en una zona necesita requisitos de instancia más amplios.

Desde la línea de comandos, la antigüedad de los nodos y los desalojos recientes:

bash
kubectl get nodes -L karpenter.sh/capacity-type,topology.kubernetes.io/zone --sort-by=.metadata.creationTimestamp
kubectl get events -A --field-selector reason=Evicted

Los eventos se guardan una hora por defecto, así que conserva el histórico en las métricas, junto a la latencia y las tasas de error de cada servicio en tu instalación de Prometheus. El objetivo: que la rotación se mueva mientras las gráficas del SLO siguen planas.

Fase 3: poner la no producción en horario (semana 3)

Desarrollo bajaba a cero nodos fuera del horario de 08:00 a 20:00 CET, de lunes a viernes. Staging mantenía un mínimo de 2 nodos fuera del horario laboral, con capacidad completa durante las ejecuciones de CI/CD. Se hizo con el escalado de nodos de Karpenter más CronJobs que aplican cordon y drain.

Drenar no basta: Karpenter lanza nodos nuevos para los pods desalojados. Por eso, un job que implementa esto pone primero a cero el límite de CPU del pool, lo que bloquea los lanzamientos pero nunca elimina nodos. Después drena los nodos y borra sus NodeClaims, y Karpenter termina las instancias. El número de réplicas no se toca; los pods esperan en Pending hasta que se levanta el límite.

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: night-shift
  namespace: ops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: night-shift
rules:
  - apiGroups: ["karpenter.sh"]
    resources: ["nodepools"]
    verbs: ["get", "patch"]
  - apiGroups: ["karpenter.sh"]
    resources: ["nodeclaims"]
    verbs: ["list", "delete"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "patch"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["pods/eviction"]
    verbs: ["create"]
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: night-shift
subjects:
  - kind: ServiceAccount
    name: night-shift
    namespace: ops
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: night-shift
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: dev-nodes-off
  namespace: ops
spec:
  schedule: "0 20 * * 1-5"
  timeZone: Europe/Paris
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      backoffLimit: 1
      activeDeadlineSeconds: 1800
      template:
        spec:
          serviceAccountName: night-shift
          restartPolicy: Never
          securityContext:
            runAsNonRoot: true
            runAsUser: 65534
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                  - matchExpressions:
                      - key: karpenter.sh/nodepool
                        operator: DoesNotExist
          initContainers:
            - name: freeze
              image: registry.k8s.io/kubectl:v1.37.2
              args: ["patch", "nodepool", "dev", "--type=merge", "-p", '{"spec":{"limits":{"cpu":"0"}}}']
            - name: drain
              image: registry.k8s.io/kubectl:v1.37.2
              args: ["drain", "--selector=karpenter.sh/nodepool=dev", "--ignore-daemonsets", "--delete-emptydir-data", "--timeout=15m"]
          containers:
            - name: remove
              image: registry.k8s.io/kubectl:v1.37.2
              args: ["delete", "nodeclaims", "--selector=karpenter.sh/nodepool=dev", "--wait=false"]

Detalles que importan:

  • Los init containers se ejecutan en orden y se detienen en el primer fallo, así que no se drena nada si la congelación no funcionó. (La imagen de kubectl no tiene shell).
  • El job corre fuera del pool que vacía: la afinidad DoesNotExist lo mantiene en nodos que Karpenter no gestiona.
  • drain rechaza los pods sin controlador a menos que añadas --force, que los borra para siempre. Un drain fallido deja los nodos acordonados: quítales el cordon o borra sus NodeClaims a mano.
  • Por la mañana: un segundo CronJob a las 0 8 * * 1-5 ejecuta solo el patch, con tu límite diurno, por ejemplo {"spec":{"limits":{"cpu":"64"}}}. Karpenter lanza entonces nodos para los pods en espera.
  • Staging usa el mismo job con la CPU de dos nodos en lugar de cero: 16 para dos nodos de 8 vCPU. Karpenter recupera como máximo dos nodos. Un pipeline de CI que necesite el entorno completo sube el límite durante la ejecución y lo restaura después; la consolidación elimina los nodos sobrantes cuando se vacían.

El cómputo de no producción bajó un 65 %. Ahorro: 3.800 dólares al mes. Nuestra guía de optimización de costes incluye un CronJob que escala cargas en lugar de nodos; los dos se combinan bien.

Fase 4: el almacenamiento que nadie usaba (semana 4)

Cuatro cambios:

  • Eliminamos los 23 volúmenes huérfanos: 420 dólares al mes.
  • Pasamos el almacenamiento de logs de volúmenes de almacenamiento en bloque a almacenamiento de objetos con reglas de ciclo de vida: 280 dólares al mes.
  • Pasamos las bases de datos de desarrollo de volúmenes con IOPS aprovisionadas a volúmenes SSD de uso general: 190 dólares al mes.
  • Nos aseguramos de que los PVC se limpian cuando se borra un namespace.

Los volúmenes huérfanos son de dos tipos. El primero es visible para Kubernetes: un PersistentVolume en la fase Released, cuyo claim ya no existe pero cuyo disco, con una reclaim policy Retain, se sigue facturando. Esto los lista con el ID del disco:

bash
kubectl get pv -o json | jq -r '.items[] | select(.status.phase == "Released") |
  [.metadata.name, .spec.capacity.storage, .spec.persistentVolumeReclaimPolicy,
   "\(.spec.claimRef.namespace)/\(.spec.claimRef.name)", (.spec.csi.volumeHandle // "-")] | @tsv'

Borrar un PersistentVolume en estado Released no borra su disco. Haz un snapshot si alguien puede necesitar los datos y, después, elimina también el disco a través de tu proveedor.

El segundo tipo es invisible para Kubernetes: discos cuyo objeto PersistentVolume se borró mientras el disco seguía existiendo. Compara los volume handles que conoce el clúster con la lista de discos sin adjuntar de tu proveedor, un ID por línea:

bash
kubectl get pv -o jsonpath='{range .items[*]}{.spec.csi.volumeHandle}{"\n"}{end}' | sort -u > in-cluster.txt
sort -u provider-unattached.txt > provider-sorted.txt
comm -13 in-cluster.txt provider-sorted.txt

Cada ID que aparece es un disco que ningún PersistentVolume reclama. El formato de los handles varía según el driver CSI, así que comprueba que coincide con la exportación de tu proveedor.

La limpieza al borrar un namespace requiere dos ajustes. Borrar un namespace borra sus PVC, pero los discos solo se eliminan con la reclaim policy Delete, la que viene por defecto en la StorageClass, así que cuestiona cualquier clase Retain en no producción. Además, los StatefulSets conservan sus claims por defecto; persistentVolumeClaimRetentionPolicy con whenDeleted: Delete, estable desde Kubernetes 1.32, los elimina junto con el StatefulSet. Ahorro: 890 dólares al mes.

Fase 5: comprometerse al final (semana 5)

Un compromiso de uso abarata todo lo que ejecutas, desperdicio incluido. Comprado antes del right-sizing, fija unos requests inflados; comprado antes de spot, te compromete con capacidad on-demand que estás a punto de abandonar. Por eso llegó tras cuatro semanas de uso optimizado, cuando la base permanente ya era visible: las cargas con estado de producción y la capacidad mínima de staging. Un compromiso de cómputo a un año cubrió el 70 % de la base on-demand restante. Ahorro: 1.100 dólares al mes.

Para encontrar esa base en tus clústeres, toma el mínimo de vCPU on-demand de cuatro semanas. Un compromiso paga instancias completas, así que mide la capacidad de los nodos, no la allocatable:

promql
min_over_time(
  sum(
    kube_node_status_capacity{resource="cpu", unit="core"}
      * on (node) group_left ()
        kube_node_labels{label_karpenter_sh_capacity_type="on-demand"}
  )[28d:1h]
)

kube_node_labels solo lleva el tipo de capacidad si kube-state-metrics se ejecuta con --metric-labels-allowlist=nodes=[karpenter.sh/capacity-type]. Convierte el resultado en gasto por hora con tus precios on-demand antes de comparar ofertas. Cubrir menos que todo el mínimo deja margen para que baje a medida que más servicios pasan por el right-sizing o se mueven a spot.

El resultado y lo que no hicimos

Las cinco fases, por ahorro mensual:

  • Right-sizing: 5.200 dólares.
  • Capacidad spot para cargas sin estado: 7.400 dólares.
  • Horarios para no producción: 3.800 dólares.
  • Limpieza de almacenamiento: 890 dólares.
  • Compromiso de cómputo a un año: 1.100 dólares.

El total es de 18.390 dólares al mes. La factura pasó de 38.000 a 19.610 dólares, una reducción del 48 %. La aplicación atendió el mismo tráfico con la misma latencia.

Lo que no hicimos:

  • Cambiar código de la aplicación.
  • Reducir el número de réplicas de ningún servicio de producción.
  • Comprometer la disponibilidad ni la recuperación ante desastres.
  • Introducir un nuevo punto único de fallo.
  • Provocar ninguna caída durante el trabajo.

Para ver adónde va tu gasto antes de empezar, nuestra guía de asignación de costes de Kubernetes por equipo convierte las mismas métricas en un coste mensual por equipo, para que cada equipo vea lo que cuestan sus requests.

Evaluación gratuita de costes de Kubernetes

Si tu factura crece más deprisa que tu tráfico, nuestro equipo de consultoría Kubernetes hará este análisis en tus clústeres sin coste. La evaluación cubre:

  • Requests frente a uso: por contenedor, el request actual, su P95 de 14 días y el request que fijaríamos.
  • Preparación para spot: qué cargas cumplen los criterios anteriores, qué bloquea al resto y un diseño de NodePools y de scheduling para tu proveedor.
  • Capacidad ociosa y restos: horas de no producción, volúmenes Released y sin adjuntar, y el mínimo on-demand que podría cubrir un compromiso.

Recibes una lista escrita de cambios ordenados por ahorro y por riesgo, sin ningún compromiso.

Solicita tu evaluación gratuita de costes de Kubernetes o descubre cómo la consultoría SRE y el platform engineering evitan que el ahorro se vuelva a escapar.