Tema representativo de entrevista

Entrevista de diseño de sistemas: ¿Cómo diseñarías el redimensionamiento in situ (in-place) de Pods en Kubernetes?

Diseño de sistemasDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Un equipo de plataforma desea modificar la CPU y la memoria sin recrear los Pods. Diseña un flujo de redimensionamiento in situ (in-place), explica cuándo debe reiniciarse un contenedor, cómo manejar la capacidad insuficiente y cómo observar y revertir los cambios.

Planteamiento y contexto

Un equipo de plataforma desea modificar la CPU y la memoria sin recrear los Pods. Diseña un flujo de redimensionamiento in situ (in-place), explica cuándo debe reiniciarse un contenedor, cómo manejar la capacidad insuficiente y cómo observar y revertir los cambios.

Kubernetes 1.33 promovió el redimensionamiento in situ de contenedores a Beta; el redimensionamiento de recursos a nivel de Pod es Beta en 1.36. Un Pod en ejecución puede recibir nuevos recursos de CPU y memoria, mientras que kubelet utiliza resizePolicy, la capacidad del nodo y el estado de los cgroups para aplicar o posponer el cambio. «Modificar la especificación» no significa «nunca reiniciar».

Qué evalúa el entrevistador

Cubre los límites a nivel de Pod frente a los recursos de los contenedores, la semántica de CPU y memoria de resizePolicy, las condiciones Pending e InProgress, las solicitudes inviables y pospuestas, las responsabilidades del scheduler frente a las de kubelet, QoS y prioridad, así como los límites de canary y reversión (rollback).

Marco de respuesta en 30 segundos

«Plantearía el redimensionamiento como una solicitud declarativa más una máquina de estados. Un controlador actualiza la especificación de recursos, kubelet comprueba la viabilidad y reporta PodResizePending o PodResizeInProgress; la CPU a menudo se aplica sin reinicio, mientras que el comportamiento de reinicio de la memoria sigue la resizePolicy de cada contenedor. Una solicitud inviable conserva su motivo y se reintenta bajo ciertos límites. La plataforma observa observedGeneration, los cgroups reales, los reinicios y los cambios de QoS, utilizando canaries pequeños y un parche inverso para la reversión».

Análisis detallado paso a paso

Paso 1: Definir el modelo de recursos

Los recursos de los contenedores establecen requests y limits para cada contenedor; las versiones compatibles también exponen límites agregados a nivel de Pod. Un límite de Pod restringe el uso agregado, pero el límite de un contenedor no puede superarlo. La API debe indicar si edita spec.containers[*].resources o spec.resources.

Paso 2: Construir un flujo de redimensionamiento declarativo

La plataforma acepta un objetivo y un motivo, escribe los campos de recursos y almacena el valor anterior, el actor y la generación. La API nunca edita directamente los cgroups del nodo. Kubelet observa la nueva generación, la valida y la aplica, y luego escribe el estado.

Paso 3: Manejar la directiva de reinicio

Un contenedor puede elegir una directiva por cada recurso:

yaml
resizePolicy:
  - resourceName: cpu
    restartPolicy: NotRequired
  - resourceName: memory
    restartPolicy: RestartContainer

NotRequired intenta una actualización en caliente (online); RestartContainer permite un reinicio para aplicar el nuevo valor. Para un Pod con restartPolicy: Never, cada recurso del contenedor debe usar NotRequired, o la solicitud no será válida.

Paso 4: Modelar la viabilidad y el estado pospuesto

Cuando el nodo no puede proporcionar el objetivo, kubelet reporta PodResizePending con motivos como Infeasible o Deferred; una solicitud procesada puede entrar en PodResizeInProgress. Los controladores deben leer el estado en lugar de limitarse a la especificación de la API y usar observedGeneration para asociar el estado con la generación solicitada.

Paso 5: Tratar la CPU y la memoria de forma diferente

La CPU generalmente puede actualizar las cuotas de cgroup sin reiniciar la aplicación. La reducción del tamaño de la memoria puede verse restringida por el working set actual, y algunos entornos de ejecución necesitan un reinicio para aplicarla de forma segura. No infieras el comportamiento de la memoria a partir de la CPU; lee la directiva por recurso y emite un evento explícito.

Paso 6: Coordinar la programación (scheduling) y la QoS

Una actualización in situ no es una reprogramación. Incrementar una solicitud puede requerir capacidad en el nodo, por lo que las solicitudes pospuestas deben reintentarse según PriorityClass, clase de QoS y tiempo de espera. Recalcula las cuotas, la QoS y las alertas tras la actualización para que un namespace o una carga de trabajo Guaranteed no queden contabilizados por debajo de lo real de forma silenciosa.

Paso 7: Observar el valor efectivo

Recolecta la especificación deseada, las condiciones de estado, observedGeneration, los cgroups reales del contenedor, reinicios, OOMs, estrangulamiento (throttling) de CPU y el working set de memoria. Si el estado indica que se completó pero los cgroups no cambiaron, trátalo como un fallo y bloquea la aplicación de otro parche en lugar de acumular cambios.

Paso 8: Despliegue canary, limitación de tasa y reversión

Redimensiona en lotes por carga de trabajo y pool de nodos, limita las operaciones concurrentes y establece tiempos de espera para Pending y topes de reintentos. En caso de fallo, restaura los recursos guardados; si la directiva de memoria reinicia un contenedor, drena el tráfico y verifica la disponibilidad (readiness) antes de expandir. Cada solicitud necesita una clave de idempotencia para evitar que los reintentos del controlador dupliquen el trabajo.

Compensaciones y límites

Continuidad sin reinicios frente a certidumbre de recursos

Los cambios in situ reducen las interrupciones, pero la capacidad y el estado pospuesto hacen que la finalización sea asíncrona. Usa pasos pequeños para servicios sensibles a la latencia; los trabajos por lotes pueden tolerar reinicios a cambio de resultados deterministas.

Límites de Pod frente a precisión de contenedor

Los recursos de Pod expresan un techo compartido mientras que los recursos de contenedor protegen los contenedores críticos. Cuando ambos existen, valida que los límites del contenedor se mantengan dentro del límite del Pod y muestra el límite efectivo en la interfaz de usuario y en los registros de auditoría.

Reintento automático frente a aprobación

Una escasez temporal de capacidad amerita un reintento acotado. Los reinicios por memoria, los cambios de QoS o los picos de producción pueden requerir aprobación o una ventana de mantenimiento; los reintentos infinitos no son seguros.

Simulacros de fallos y plan de evolución

El nodo carece de capacidad

Envía una ampliación por encima de la capacidad del nodo, confirma Infeasible o Deferred, y verifica que los reintentos no alteren el valor efectivo anterior.

Un cambio de memoria reinicia el contenedor

Establece RestartContainer para la memoria y observa el drenaje de tráfico, el reinicio, la recuperación de readiness y el orden de los eventos. Asegúrate de que el controlador no reporte el reinicio como un fallo de negocio.

Competencia entre generaciones (race conditions)

Envía dos objetivos rápidamente. La generación antigua no debe sobrescribir el nuevo objetivo, y el observedGeneration final debe coincidir con el valor del cgroup.

Errores comunes y preguntas de seguimiento

Error 1: Asumir que el redimensionamiento in situ nunca reinicia

Seguimiento: ¿Qué causa un reinicio? La resizePolicy de un contenedor puede exigirlo, especialmente para la memoria; el redimensionamiento a nivel de Pod no tiene una directiva de reinicio independiente, pero la directiva del contenedor se sigue aplicando.

Error 2: Tratar una actualización de la especificación como una finalización

Seguimiento: ¿Cómo se confirma el éxito? Comprueba las condiciones, observedGeneration, los cgroups, los eventos del contenedor y el recuento de reinicios en lugar de solo los campos deseados.

Error 3: Aplicar parches repetidamente cuando falta capacidad

Seguimiento: ¿Qué es lo correcto? Mantén el motivo de Pending, reintenta dentro de los límites de prioridad y tiempo, y ofrece migración, un objetivo más pequeño o aprobación.

Preguntas de seguimiento extendidas y respuestas modelo

¿Por qué diseñar la CPU y la memoria por separado?

Las cuotas de CPU usualmente pueden cambiar en caliente; la reducción de memoria se ve limitada por el working set y el comportamiento del entorno de ejecución, y puede requerir reinicios. Una API compartida aún requiere directivas y estados independientes por recurso.

¿Cómo evitas que un redimensionamiento duplicado sobrescriba un objetivo más reciente?

Usa la versión del recurso o la generación para actualizaciones condicionales, acepta solo el objetivo más reciente y concilia el estado y los cgroups a través de observedGeneration.

¿Cuándo deberías evitar el redimensionamiento in situ?

Usa un reemplazo progresivo (rolling replacement) cuando se requiera un aislamiento estricto, la capacidad sea inestable, la aplicación no pueda tolerar un reinicio o los cambios de recursos violen las suposiciones del entorno de ejecución. Mantén el redimensionamiento in situ como una optimización controlada.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Resolver para una respuesta de diseño de sistemas

Aclara primero los requisitos y luego avanza a través de la escala, la arquitectura, la elección de componentes y las compensaciones (trade-offs).

Ver la herramienta