5 de septiembre de 2026

Kubernetes 1.37: qué se rompe el día que actualizas (y por qué es tu plano de control)

Foto de Marco Orta Marco Orta | 12 min de lectura
Compartir
Un timón de barco al centro de una sala de control oscura, con la tapa de un interruptor de seguridad levantada y vacía donde antes estaba la palanca de anulación
Tabla de Contenidos

    Kubernetes v1.37 “Garhwal” salió el 26 de agosto de 2026, y su cambio con ruptura principal se está contando mal en casi todos lados. La 1.37 no introduce la regla de que los pods estáticos no pueden referenciar objetos de la API — eso aterrizó en la 1.34. Lo que la 1.37 quita es la salida de emergencia. El feature gate PreventStaticPodAPIReferences desapareció y, según la nota de versión oficial, “ya no se puede desactivar”.

    Eso invierte quién está en riesgo. Si nunca te topaste con esto, estás bien. Los clústeres que se rompen el día de la actualización son justo los que ya se toparon en la 1.34, la 1.35 o la 1.36, apagaron el gate para poder avanzar y archivaron el arreglo de verdad para después. Después es ahora, y lo que no arranca es el plano de control.

    Los tres bloqueadores, por orden

    #CambioCómo fallaUrgencia
    1Se elimina el gate PreventStaticPodAPIReferencesEl kubelet rechaza la admisión de los pods estáticos que referencian objetos de la API — incluidos los manifiestos de etcd y kube-apiserverBloquea la actualización
    2Se hace cumplir el rechazo de cgroup v1El kubelet sale al arrancar salvo que pongas failCgroupV1: falseBloquea la actualización
    3Se deprecia el modo ipvs de kube-proxySolo advertencia al arrancar. Desactivado por defecto en la 1.40, eliminado en la 1.43Planéalo, no corras

    Todo lo demás de esta versión —kube-dns depreciado en favor de CoreDNS, kubectl run --filename/-f depreciado, dieciséis funciones que pasan a estable— es limpieza. Los dos que impiden que un nodo vuelva son los dos primeros.

    1. Los pods estáticos ya no pueden referenciar objetos de la API

    Cuál es la regla de verdad

    La formulación limpia no es “nada de Secrets ni ConfigMaps”. Es la del pull request que lo implementó: los pods estáticos solo pueden usar volúmenes hostPath y emptyDir, y no pueden referenciar objetos de la API en absoluto.

    La nota de versión oficial del PR #131837, que lleva un ACTION REQUIRED explícito, nombra la superficie completa:

    Antes de actualizar, asegúrate de que los pods estáticos no estén referenciando objetos de la API como ServiceAccounts, ConfigMaps, Secrets, ResourceClaims, CSIDrivers, PersistentVolumeClaims o ClusterTrustBundles.

    Fíjate en ServiceAccounts dentro de esa lista. Casi toda la cobertura secundaria de esta versión menciona solo configMapRef y secretRef, y eso se queda corto: un pod estático con un serviceAccountName, una entrada de imagePullSecrets o un volumen respaldado por un PVC queda rechazado igual. En la práctica, los campos que hay que cazar son:

    • env.valueFrom.configMapKeyRef y env.valueFrom.secretKeyRef
    • envFrom.configMapRef y envFrom.secretRef
    • volumes[].configMap, volumes[].secret, volumes[].persistentVolumeClaim, volumes[].projected
    • serviceAccountName, imagePullSecrets, resourceClaims

    Por qué es un problema del plano de control y no de tus cargas

    Los pods estáticos son los que el kubelet corre directo del disco desde /etc/kubernetes/manifests/, sin que intervenga el API server. En un clúster de kubeadm ese directorio contiene etcd, kube-apiserver, kube-controller-manager y kube-scheduler.

    Así que la forma de fallar no es “uno de mis pods no se programó”. Es: actualizas un nodo del plano de control, el kubelet rechaza la admisión del manifiesto de etcd, y el API server nunca levanta para decirte por qué. Estás depurando desde journalctl -u kubelet en el nodo, no desde kubectl.

    El comportamiento viejo era silencioso, y por eso existe esto

    Antes de la 1.34 esto no daba error. El pod estático corría, y lo único que fallaba en reconciliar era el pod espejo — la representación de solo lectura que el kubelet crea en la API para que el pod aparezca en kubectl get pods. El contenedor estaba vivo en el nodo y era efectivamente invisible para la API. El PR #131837 cerró eso rechazando la admisión de plano, para que el contenedor nunca se cree en lugar de correr sin que nadie lo vea.

    Esa historia es la razón de que el gate existiera: los revisores pidieron que la validación saliera activada por defecto pero desactivable “durante unas cuantas versiones”. Tres ciclos de versión después, el PR #140226 lo eliminó, con milestone v1.37.

    La auditoría, antes de tocar un solo nodo

    Corre esto en todos los nodos, de plano de control y de trabajo, no en uno:

    grep -rlE 'configMapRef|secretRef|configMapKeyRef|secretKeyRef|serviceAccountName|imagePullSecrets|persistentVolumeClaim' \
      /etc/kubernetes/manifests/
    

    Cada archivo que imprima es un manifiesto que tienes que rehacer antes de actualizar ese nodo. El arreglo siempre tiene la misma forma: dejar de sacar el valor de la API y ponerlo en disco, como montaje hostPath o como literal dentro del manifiesto, ya que hostPath y emptyDir son los únicos tipos de volumen que siguen permitidos.

    Si tus manifiestos son generados —kubeadm, Cluster API, un chart de Helm que plantilla la config del nodo, un rol de Ansible— audita la plantilla, no solo la salida renderizada, o el siguiente nodo que aprovisiones vuelve a meter el problema.

    Antes de dar por bueno un manifiesto rehecho, comprueba que sigue siendo YAML válido y que no rompiste la indentación al arrancar un bloque de volúmenes:

    2. cgroup v1: el kubelet simplemente sale

    Este es corto y absoluto. El kubelet se niega a correr en un host que usa cgroup v1. No advierte y degrada: sale al arrancar con

    kubelet is configured to not run on a host using cgroup v1
    

    El valor por defecto pasó a fallar en la 1.35, y la 1.37 lo mantiene. La única anulación va en KubeletConfiguration:

    failCgroupV1: false
    

    Dónde lo pones depende de tu distribución, y el momento importa:

    • kubeadm: edita el ConfigMap kube-system/kubelet-config y añade el campo antes de actualizar. Ponerlo después de que el kubelet ya se negó a arrancar significa editar configuración en un nodo cuyo plano de control quizá esté caído.
    • RKE2 / K3s: pasa el argumento del kubelet fail-cgroupv1=false en /etc/rancher/rke2/config.yaml o /etc/rancher/k3s/config.yaml.

    Trátalo como un puente, no como un arreglo. Es exactamente la misma categoría de decisión que el gate de los pods estáticos por el que estás leyendo este post: un interruptor de apagado que upstream está retirando.

    Comprueba en qué versión de cgroup está cada nodo antes de actualizarlo:

    stat -fc %T /sys/fs/cgroup
    # cgroup2fs -> v2, vas bien
    # tmpfs     -> v1, no vas bien
    
    ls /sys/fs/cgroup/cgroup.controllers   # solo existe en v2
    

    Los nodos que tropiezan con esto son los viejos: hosts de CentOS 7 y Ubuntu 18.04 de larga vida, y cualquier cosa aprovisionada desde una AMI o imagen construida hace años y nunca reconstruida. Si corres un servicio gestionado (EKS, GKE, AKS) con imágenes de nodo actuales, casi seguro ya estás en v2 — pero “casi seguro” está a un stat de ser seguro.

    3. IPVS de kube-proxy: depreciado, no eliminado

    El mode: ipvs en KubeProxyConfiguration queda depreciado a partir de la 1.37 (KEP-5495). Hoy no se rompe nada: te salen advertencias en los logs de arranque de kube-proxy. La línea de tiempo:

    VersiónEstado
    v1.35Empiezan los logs de advertencia
    v1.37Depreciado formalmente, siguen las advertencias
    v1.40Desactivado por defecto, se reactiva solo con feature gate
    v1.43Código eliminado por completo

    Vale la pena conocer el motivo porque tumba la objeción habitual: el modo IPVS nunca fue un reemplazo completo de iptables — sigue necesitando iptables por debajo y no puede implementar los Services de Kubernetes por sí solo. El sucesor es nftables, no una vuelta a iptables.

    Comprueba qué estás corriendo:

    kubectl -n kube-system get cm kube-proxy \
      -o jsonpath='{.data.config\.conf}' | grep mode:
    

    Si dice ipvs, tienes de ahora hasta la 1.40 para pasarte a mode: nftables. Eso es más o menos un año de versiones — suficiente para hacerlo a tu ritmo, que es justo la razón para meterlo en el calendario ahora en vez de encontrártelo como la gente se está encontrando el gate de los pods estáticos este mes.

    La lista previa a actualizar

    Corre todo esto antes del primer nodo, en cada nodo:

    # 1. Referencias a la API en pods estáticos — el bloqueador
    grep -rlE 'configMapRef|secretRef|configMapKeyRef|secretKeyRef|serviceAccountName|imagePullSecrets|persistentVolumeClaim' \
      /etc/kubernetes/manifests/
    
    # 2. Versión de cgroup — el otro bloqueador
    stat -fc %T /sys/fs/cgroup
    
    # 3. Modo de kube-proxy — planear, no bloquear
    kubectl -n kube-system get cm kube-proxy \
      -o jsonpath='{.data.config\.conf}' | grep mode:
    
    # 4. ¿Sigues en kube-dns en vez de CoreDNS?
    kubectl -n kube-system get deploy | grep -E 'kube-dns|coredns'
    

    Salida vacía en el 1, cgroup2fs en el 2 y nftables o iptables en el 3 significa que la actualización va a ser aburrida. Ese es el objetivo.

    Quién tiene que actuar de verdad

    Desactivaste PreventStaticPodAPIReferences en algún momento desde la 1.34: eres el destinatario de todo este post. Ese gate ya no existe, así que lo que te llevó a desactivarlo ahora bloquea la actualización. Arregla los manifiestos primero, actualiza después.

    Corres planos de control de kubeadm que has editado a mano: corre el grep específicamente en los nodos del plano de control. Los archivos editados a mano en /etc/kubernetes/manifests/ son justo donde alguien añade un secretRef o un serviceAccountName resolviendo un problema a las 2 de la mañana.

    Tienes nodos de más de unos tres años: comprueba cgroups antes que nada. Un kubelet que sale al arrancar se ve idéntico a una actualización rota, y vas a perder una hora encontrando una respuesta de una línea.

    Estás en Kubernetes gestionado con imágenes de nodo actuales: probablemente estás limpio en el 1 y el 2. Revisa el 3 y mete la migración de IPVS en un trimestre, no en un sprint.

    Corres mode: ipvs: hoy no se rompe nada. Agenda el paso a nftables antes de la 1.40.

    Para seguir leyendo:

    Preguntas frecuentes

    ¿Qué se rompe al actualizar a Kubernetes 1.37?

    Dos cambios pueden bloquear la actualización de plano. El primero: se eliminó el feature gate PreventStaticPodAPIReferences, así que los pods estáticos que referencian objetos de la API reciben rechazo de admisión del kubelet sin forma de optar por salirse, y en clústeres de kubeadm esos pods estáticos incluyen etcd y kube-apiserver. El segundo: el kubelet se niega a arrancar en hosts que usan cgroup v1 salvo que failCgroupV1 esté puesto explícitamente en false. Un tercer cambio, la depreciación del modo IPVS de kube-proxy, solo produce advertencias en la 1.37 y todavía no rompe nada.

    ¿Por qué fallan los pods estáticos después de actualizar a Kubernetes 1.37?

    Porque se quitó la salida de emergencia, no porque la regla sea nueva. La restricción sobre pods estáticos que referencian objetos de la API salió en la 1.34 detrás del feature gate PreventStaticPodAPIReferences, que venía activado por defecto pero se podía desactivar. Kubernetes 1.37 eliminó ese gate por completo, y la nota de versión oficial dice que ya no se puede desactivar. Los clústeres que apagaron el gate para posponer el arreglo son exactamente los que se rompen al actualizar.

    ¿Qué objetos de la API no puede referenciar un pod estático en Kubernetes 1.37?

    La nota de versión oficial lista ServiceAccounts, ConfigMaps, Secrets, ResourceClaims, CSIDrivers, PersistentVolumeClaims y ClusterTrustBundles. La forma más simple de retener la regla es la del pull request que lo implementó: los pods estáticos solo pueden usar volúmenes hostPath y emptyDir. Eso significa que serviceAccountName, imagePullSecrets, envFrom.configMapRef, envFrom.secretRef, env.valueFrom.secretKeyRef, los volúmenes projected y los respaldados por PVC quedan rechazados todos, no solo las referencias a ConfigMap y Secret que mencionan la mayoría de los resúmenes.

    ¿Cómo compruebo si mi clúster está afectado antes de actualizar?

    En cada nodo de plano de control y de trabajo, corre: grep -rlE 'configMapRef|secretRef|configMapKeyRef|secretKeyRef|serviceAccountName|imagePullSecrets|persistentVolumeClaim' /etc/kubernetes/manifests/ — cualquier archivo que imprima hay que rehacerlo antes de actualizar ese nodo. Aparte, corre stat -fc %T /sys/fs/cgroup en cada nodo: cgroup2fs es seguro y tmpfs significa cgroup v1, que impide que el kubelet arranque. Si tus manifiestos los generan kubeadm, Cluster API, Ansible o Helm, audita también la plantilla o los nodos nuevos volverán a meter el problema.

    ¿Se elimina el modo IPVS de kube-proxy en Kubernetes 1.37?

    No. Queda depreciado en la 1.37 bajo el KEP-5495 y solo registra advertencias al arrancar. Se espera que quede desactivado por defecto en la 1.40, donde todavía se puede reactivar mediante feature gate, y eliminado por completo en la 1.43. El reemplazo es el modo nftables, no iptables. El razonamiento es que el modo IPVS siempre necesitó iptables por debajo y nunca pudo implementar los Services de Kubernetes por sí solo.

    ¿Qué pasaba con los pods estáticos que referenciaban Secrets antes de Kubernetes 1.34?

    Fallaba en silencio, y por eso existe la restricción. El pod estático corría en el nodo, pero el pod espejo —la representación de solo lectura en la API que hace visible el pod para kubectl— no lograba reconciliar. El contenedor estaba vivo y era efectivamente invisible para el API server. El PR #131837 cambió esto para rechazar la admisión de plano, de modo que el contenedor nunca se crea en lugar de correr sin que nadie lo observe.

    ¿Cuándo salió Kubernetes 1.37 y cómo se llama?

    Kubernetes v1.37, con nombre en clave "Garhwal", llegó a disponibilidad general el 26 de agosto de 2026. Junto con las depreciaciones graduó dieciséis funciones a estable, entre ellas el estado de dispositivo de ResourceClaim (KEP-4817), los taints y tolerations de dispositivo (KEP-5055), el estado de salud de recursos para pods (KEP-4680) y SELinuxMount con SELinuxChangePolicy (KEP-1710). También deprecó el subproyecto kube-dns en favor de CoreDNS y el flag --filename de kubectl run.

    Compartir

    Buscar

    Etiquetas

    PHP IA Migración Laravel Tutorial JavaScript Desarrollo Web Buenas Prácticas Upgrade Seguridad Laravel 13 OpenAI Backend SEO Claude