Kubernetes 1.35: In-Place Pod Resize es GA — Escala Verticalmente Sin Reiniciar
Actualiza CPU y memoria de Pods en ejecución sin recrearlos
El Problema: Cambios “Simples” que Causan Disrupciones
Un servicio en producción muestra throttling de CPU. La latencia P95 está subiendo. La solución es obvia: aumentar el límite de CPU de 500m a 700m.
En versiones anteriores de Kubernetes, este “cambio simple” desencadenaba una cascada de eventos no deseados:
# Cambiar esto...
resources:
limits:
cpu: "500m"
# ...a esto
resources:
limits:
cpu: "700m"
Consecuencias en K8s ≤1.34:
- Pod completamente recreado
- Conexiones activas terminadas
- Cache en memoria perdido
- Estado local eliminado
- Jobs en progreso interrumpidos
No se cambió código. No se cambió comportamiento. Solo se ajustó un número. Pero el sistema trató ese ajuste como un deployment completo.
Kubernetes 1.35 cambia esto fundamentalmente.
La Solución: In-Place Resource Update (GA)
A partir de Kubernetes 1.35, el in-place update de recursos de Pod es GA (Generally Available). Esto significa que CPU y memoria pueden modificarse en Pods en ejecución, y el nodo aplica los nuevos valores sin el ciclo automático de recreación.
Características clave:
- Cambios de CPU aplicados en caliente (sin restart)
- Cambios de memoria configurables (con o sin restart de container)
- Pod nunca es recreado — solo se ajustan los cgroups
- Estado, conexiones y cache preservados
- Observable via Pod status y conditions
Arquitectura: Cómo Funciona el Resize
El proceso involucra varios componentes de Kubernetes trabajando en coordinación:
El Nuevo Modelo Mental: Desired vs Actual vs Allocated
Kubernetes 1.35 introduce una distinción importante en cómo se reportan los recursos:
| Campo | Descripción |
|---|---|
spec.containers[].resources |
Lo que el operador desea (desired) |
status.containerStatuses[].allocatedResources |
Lo que el nodo reservó |
status.containerStatuses[].resources |
Lo que el container está usando |
status.conditions |
Estado del resize (Pending, InProgress) |
status.observedGeneration |
Confirmación de que kubelet procesó el cambio |
Esta visibilidad permite saber exactamente en qué estado está el resize en cualquier momento.
Configuración: resizePolicy
El comportamiento del resize se configura por tipo de recurso usando resizePolicy en el spec del container:
Spec de Pod con Resize Policy
apiVersion: v1
kind: Pod
metadata:
name: app-con-resize
spec:
containers:
- name: app
image: mi-app:latest
ports:
- containerPort: 8080
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired # CPU en caliente
- resourceName: memory
restartPolicy: RestartContainer # Memoria con restart
resources:
requests:
cpu: "300m"
memory: "256Mi"
limits:
cpu: "300m"
memory: "256Mi"
Recomendación para producción:
- CPU:
NotRequired— Los cambios de CPU son seguros de aplicar en caliente - Memory:
RestartContainer— Más predecible que esperar que la app libere memoria
Demo: Verificando que el Resize Funciona
Para demostrar que el resize realmente funciona sin restart, se puede crear un servidor simple que exponga su PID y los límites de cgroup actuales.
Servidor de Demo (Go)
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func read(path string) string {
b, err := os.ReadFile(path)
if err != nil {
return fmt.Sprintf("unavailable (%v)", err)
}
return strings.TrimSpace(string(b))
}
func handler(w http.ResponseWriter, r *http.Request) {
pid := os.Getpid()
// cgroup v2 paths
cpuMax := read("/sys/fs/cgroup/cpu.max")
memMax := read("/sys/fs/cgroup/memory.max")
io.WriteString(w, fmt.Sprintf("pid=%d\n", pid))
io.WriteString(w, fmt.Sprintf("cpu.max=%s\n", cpuMax))
io.WriteString(w, fmt.Sprintf("memory.max=%s\n", memMax))
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("listening on :8080")
http.ListenAndServe(":8080", nil)
}
Dockerfile
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY . .
RUN go build -o app .
FROM alpine:3.20
WORKDIR /app
COPY --from=build /src/app /app/app
EXPOSE 8080
ENTRYPOINT ["/app/app"]
Desplegar y Verificar
# Crear el Pod
kubectl apply -f pod-resize-demo.yaml
# Port-forward para acceder
kubectl port-forward pod/app-con-resize 8080:8080 &
# Verificar estado inicial
curl localhost:8080
# pid=7
# cpu.max=30000 100000
# memory.max=268435456
Ejecutando el Resize
En Kubernetes 1.35, el resize se ejecuta contra el subresource resize del Pod:
Aumentar CPU (Sin Restart)
kubectl patch pod app-con-resize --subresource resize --type merge -p '
{
"spec": {
"containers": [
{
"name": "app",
"resources": {
"requests": { "cpu": "700m" },
"limits": { "cpu": "700m" }
}
}
]
}
}'
Verificar que Funcionó
# El endpoint debe mostrar:
# - Mismo PID (sin restart)
# - Nuevo valor de cpu.max
curl localhost:8080
# pid=7 <-- MISMO PID
# cpu.max=70000 100000 <-- NUEVO LÍMITE
# memory.max=268435456
# Confirmar que no hubo restart
kubectl get pod app-con-resize -o jsonpath='{.status.containerStatuses[0].restartCount}'
# 0
Si el PID se mantiene y cpu.max cambió, el resize in-place funcionó correctamente.
Aumentar Memoria (Con Restart de Container)
Con la política RestartContainer para memoria:
kubectl patch pod app-con-resize --subresource resize --type merge -p '
{
"spec": {
"containers": [
{
"name": "app",
"resources": {
"requests": { "memory": "512Mi" },
"limits": { "memory": "512Mi" }
}
}
]
}
}'
En este caso:
restartCountincrementará- El PID cambiará
- Pero el Pod NO será recreado — volumes y networking se mantienen
Observabilidad Durante el Resize
Kubernetes 1.35 proporciona visibilidad del estado del resize via conditions:
kubectl describe pod app-con-resize
Conditions relevantes:
PodResizePending— El resize fue solicitado pero no aplicado aúnPodResizeInProgress— El kubelet está aplicando el cambio
# Ver el estado detallado
kubectl get pod app-con-resize -o jsonpath='{.status.conditions}' | jq
Esto elimina la incertidumbre. Ya no hay que adivinar si el cambio fue aplicado — el sistema lo reporta explícitamente.
Limitaciones a Considerar
El feature es potente pero tiene límites claros:
| Limitación | Descripción |
|---|---|
| QoS Class | No cambia post-creación (Guaranteed/Burstable/BestEffort) |
| Init containers | No soportan resize |
| Ephemeral containers | No soportan resize |
| Sidecars | Sí soportan resize |
| Windows Pods | No soportado |
| Memory decrease | Best-effort sin restart (la app debe liberar memoria) |
| Node constraints | Algunos CPU/Memory managers pueden bloquear cambios |
Estas limitaciones son parte de lo que hace el feature seguro. Un feature que promete todo se vuelve peligroso. Un feature que declara sus límites es operable.
Protección del Scheduler
Una preocupación válida: ¿qué pasa si el resize está pendiente pero el scheduler asume que ya se aplicó?
Kubernetes previene esto siendo conservador durante resizes incompletos. Al schedulear, considera el máximo entre:
- Lo solicitado (desired)
- Lo asignado (allocated)
- Lo aplicado (actual)
Esto evita overcommit basado en cambios que aún no se completaron.
Impacto Operacional
El cambio más significativo no es técnico — es cultural.
Antes de K8s 1.35:
- Equipos evitaban resize hasta que fuera urgente
- Ingenieros sobreaprovisionaban para evitar tocar recursos después
- “Right-sizing” era un proyecto, no un hábito
- On-call retrasaba fixes simples por miedo a la disrupción
Con K8s 1.35:
- Correcciones de CPU sin costo de restart
- Iteración más rápida sobre configuración de recursos
- Respuesta a throttling sin ventana de mantenimiento
- Resize se convierte en una operación normal, no un evento
Resumen de Comandos
# Aplicar resize de CPU
kubectl patch pod POD_NAME --subresource resize --type merge -p '
{
"spec": {
"containers": [{
"name": "CONTAINER_NAME",
"resources": {
"requests": { "cpu": "NEW_VALUE" },
"limits": { "cpu": "NEW_VALUE" }
}
}]
}
}'
# Verificar estado del resize
kubectl describe pod POD_NAME | grep -A5 Conditions
# Ver recursos actuales vs deseados
kubectl get pod POD_NAME -o jsonpath='{.status.containerStatuses[0].resources}'
# Confirmar que no hubo restart
kubectl get pod POD_NAME -o jsonpath='{.status.containerStatuses[0].restartCount}'
Conclusión
Kubernetes 1.35 resuelve un problema que nunca debió existir: la necesidad de reiniciar un proceso solo porque se ajustó un límite de recursos.
Con in-place resize GA:
- CPU puede ajustarse sin ningún restart
- Memory puede configurarse para restart de container (no de Pod)
- Observabilidad completa del estado del resize
- Protección contra overcommit durante cambios pendientes
El escalado vertical finalmente se comporta como un ajuste, no como un deployment.
Recursos
Publicado en yoDEV.dev — La comunidad de desarrolladores de Latinoamérica


