Un despliegue puede ejecutarse sin incidencias, todos los pods arrancan, todos informan Ready, no hay reinicios, y aun así unos cientos de peticiones se pierden por el camino. En los logs de la aplicación no aparece ninguna, porque esas peticiones nunca llegaron a la aplicación. Detrás no hay ningún error de configuración. Al terminar un pod, la baja de la red no ocurre antes del apagado, ocurre al lado. Este artículo muestra el fallo en un servicio Spring Boot detrás de un ingress NGINX, después la corrección con un hook preStop y graceful shutdown, y al final una prueba de carga que demuestra que realmente ha desaparecido.
Contenido
- Una release a las dos y diez
- Qué ocurre en realidad cuando se termina un pod
- Por qué el log de la aplicación se queda vacío
- Por qué el fallo parece esporádico y golpea a las escrituras
- El reflejo que no ayuda: afinar la sonda de readiness
- El hook preStop que no hace nada
- La cuenta que tiene que salir
- La aplicación tiene que colaborar: graceful shutdown en Spring Boot
- El manifiesto completo
- Lo que el hook preStop no resuelve
- Provocar el fallo antes de que llegue solo
- Los patrones de un vistazo
- Cuándo no necesitas un hook preStop
- FAQ
- Conclusión
- Fuentes
Una release a las dos y diez
Un servicio Spring Boot checkout en el namespace shop-prod, tres réplicas, delante un ingress con el controlador NGINX. Estrategia de despliegue RollingUpdate, la que sale de cualquier chart de Helm. Una release no es más que una nueva etiqueta de imagen.
A las 02:10 se ejecuta exactamente una de esas releases. Kubernetes arranca un pod nuevo, espera a que informe Ready y termina uno viejo a cambio. Tres veces seguidas. En menos de dos minutos la nueva versión está en todas partes, todos los pods funcionan, ningún contenedor se ha reiniciado, ningún evento parece sospechoso.
Seis horas después hay once avisos de pedidos interrumpidos en el buzón de soporte, todos de la ventana entre las 02:10 y las 02:12.
La primera búsqueda va a los logs de la aplicación checkout, y están limpios. Ninguna excepción, ningún error, ninguna transacción abortada. Desde el punto de vista de la aplicación, esos once pedidos nunca existieron.
Están en el log del controlador de ingress, con estado 502 y la anotación connect() failed (111: Connection refused) while connecting to upstream. Las peticiones sí llegaron, solo que no adonde se estaba mirando.
Qué ocurre en realidad cuando se termina un pod
La idea extendida es una secuencia: Kubernetes da de baja el pod, espera a que deje de llegar tráfico y después lo apaga. Sería lo lógico. No es lo que pasa.
Cuando se borra un pod, el API server fija una marca de tiempo de borrado, y a partir de ahí arrancan dos procesos a la vez:
Momento 0: el pod se marca para borrado
│
├─ Vía A (en el nodo)
│ kubelet ejecuta el hook preStop
│ después: SIGTERM al PID 1 del contenedor
│ la aplicación se apaga
│
└─ Vía B (en el clúster)
el controlador de EndpointSlice quita la IP del pod
el API server distribuye el cambio
kube-proxy reescribe sus reglas en cada nodo
el controlador de ingress reescribe su lista de upstreamsLa documentación de Kubernetes es clara en este punto: la eliminación de los EndpointSlices ocurre de forma asíncrona al apagado. Quien confía en una secuencia está confiando en una carrera.
Y esa carrera la suele ganar la vía A. Un SIGTERM está disponible en el nodo de inmediato. La vía B tiene que pasar por el API server, después a cada nodo y después por la recarga de configuración en el controlador de ingress. Según el tamaño del clúster eso son desde unos cientos de milisegundos hasta varios segundos.
Importante para entenderlo: son dos consumidores distintos que dependen ambos de la vía B, pero que envían tráfico por caminos diferentes.
- kube-proxy se ocupa del tráfico dentro del clúster, es decir, de las llamadas que pasan por la ClusterIP de un service. Reescribe reglas de iptables o IPVS.
- El controlador de ingress se ocupa del tráfico desde fuera. El controlador NGINX evita por completo la ClusterIP y mantiene su propia lista de IPs de pods como upstreams. kube-proxy no participa en absoluto en esta ruta.
Los dos toman su verdad de los mismos EndpointSlices y los dos necesitan su propio tiempo para aplicarla. Por eso el problema afecta tanto a las llamadas desde fuera como a las llamadas entre dos servicios dentro del clúster.
Por qué el log de la aplicación se queda vacío
En la ventana en la que la aplicación ya está cerrando y el upstream sigue registrado, la petición llega a un puerto que ya no acepta. El sistema operativo responde con un reset TCP y el controlador de ingress lo traduce en un 502 para el cliente.
La aplicación no se ha enterado de nada. No hubo manejador de peticiones, ni filtro, ni interceptor que pudiera registrar algo. El establecimiento de la conexión falló antes de que ocurriera nada a nivel HTTP.
Eso explica la segunda mitad del problema. No solo se produce el fallo, sino que además es invisible justo donde uno mira primero:
| Dónde | Qué hay |
|---|---|
| Log de la aplicación | nada |
Métricas de la aplicación (http_server_requests) |
nada, el contador solo sube con peticiones servidas |
| Trazas | nada, el span empieza dentro del servidor |
| Log del ingress | 502 y connect() failed (111: Connection refused) |
| Eventos de Kubernetes | nada, el pod se fue según lo previsto |
Quien solo vigila el lado de la aplicación no tiene ni un solo indicador para este fallo. La tasa de error en Grafana se queda en cero porque las peticiones nunca se contaron. Es el mismo punto ciego que con las series temporales ausentes: un valor que nadie mide se ve igual que un valor que está bien.
Por qué el fallo parece esporádico y golpea a las escrituras
Aquí llega la objeción que aparece en cualquier discusión: NGINX prueba el siguiente upstream cuando falla una conexión. Es cierto, y es la razón por la que el problema pasa años sin detectarse.
El controlador de ingress NGINX pone proxy_next_upstream en error timeout por defecto. Una conexión rechazada es un error, así que la petición pasa al siguiente pod y el cliente no nota nada.
Solo que eso no vale para todas las peticiones. NGINX no repite por su cuenta las peticiones no idempotentes, es decir, ni POST, ni PATCH, ni LOCK. El motivo es razonable: en una petición que modifica algo, NGINX no puede saber si el servidor ya la procesó antes de que se cortara la conexión. Un reintento podría duplicar un pedido. Quien lo quiera de todos modos tiene que escribir non_idempotent explícitamente en la directiva.
De ahí sale el cuadro de fallo que llega al buzón:
- Las peticiones GET se repiten en silencio y acaban en un pod sano. Nadie se queja.
- Las peticiones POST pasan de largo y llegan al cliente como un 502.
Por eso el problema parece esporádico, por eso no se reproduce recargando la página, y por eso golpea precisamente a las llamadas que mueven dinero. Un pedido, un pago, un registro: todo POST.
Si desglosas los 502 del log del ingress por método, lo ves de inmediato. La proporción de peticiones de escritura queda muy por encima de su proporción en el tráfico total.
El reflejo que no ayuda: afinar la sonda de readiness
La primera idea, una vez entendido el cuadro, suele ser: entonces la sonda de readiness tiene que darse cuenta antes de que el pod se va.
Eso pasa por alto el asunto, y por dos motivos.
Primero, la sonda de readiness responde a otra pregunta. Decide si un pod en marcha debe recibir tráfico ahora mismo, por ejemplo mientras arranca o mientras una dependencia no está accesible un momento. Al terminar, Kubernetes trata al pod como no listo de todas formas, en cuanto queda marcado para borrado. Nadie espera al resultado de la siguiente comprobación.
Segundo, ni siquiera así bastaría. Supongamos que la sonda se ejecutara cada segundo y saltara de inmediato: el camino desde ese hallazgo hasta la lista de upstreams reescrita en el controlador de ingress es exactamente el mismo que antes. Habrías acelerado el disparador, no la distribución.
Aun así la sonda de readiness no es inútil, solo resuelve la otra mitad del problema. Al arrancar se encarga de que un pod solo reciba tráfico cuando puede responderlo. Al apagar hace falta otro mecanismo.
El hook preStop que no hace nada
El mecanismo es tan simple que en la primera lectura parece equivocado: el pod espera antes de empezar a apagarse. Durante ese tiempo no hace nada especial, sencillamente sigue plenamente operativo.
lifecycle:
preStop:
sleep:
seconds: 10Lo que ocurre es justo lo deseado: la vía A se frena artificialmente mientras la vía B sigue a toda velocidad. El kubelet ejecuta el hook y solo envía el SIGTERM después. Durante esos diez segundos el pod sigue siendo un servidor plenamente funcional. Acepta conexiones, responde peticiones y ni siquiera sabe que debe marcharse.
En paralelo la baja recorre el clúster, kube-proxy reescribe sus reglas, el controlador de ingress recarga su lista de upstreams. Cuando por fin llega el SIGTERM, ya nadie envía nada a esa IP de pod.
Sobre la disponibilidad de la acción sleep, porque esto se confunde a menudo:
| Versión de Kubernetes | Estado |
|---|---|
| 1.29 | alfa, hay que activar el feature gate |
| 1.30 a 1.33 | beta, activa por defecto |
| 1.34 en adelante | estable |
En clústeres anteriores a 1.30 queda la vía clásica:
lifecycle:
preStop:
exec:
command: ["sleep", "10"]Funciona igual, pero exige un binario sleep en la imagen. Las imágenes distroless o scratch no lo tienen, y el hook falla en silencio. Justo para eso existe la acción sleep nativa.
Sobre la duración: diez segundos es un valor de partida útil para un clúster normal. El valor correcto es el tiempo que necesita un cambio de endpoint en tu caso hasta llegar a todas partes. En un clúster pequeño bastan cinco, con muchos nodos o con un balanceador externo que tiene su propio intervalo de comprobación pueden ser treinta. Un balanceador en la nube que comprueba cada diez segundos y necesita dos fallos suma ya veinte segundos de retraso por sí solo.
La cuenta que tiene que salir
Aquí está la trampa que vuelve a romper toda la corrección. El contador de terminationGracePeriodSeconds no arranca después del hook preStop, arranca junto con él.
La documentación de Kubernetes lo dice sin rodeos: el periodo de gracia cubre el tiempo total del hook preStop más el apagado normal del contenedor.
De donde sale:
espera preStop + tiempo de vaciado < terminationGracePeriodSecondsEl valor por defecto de terminationGracePeriodSeconds es de 30 segundos. Quien añade un hook preStop de 10 segundos y lo deja así solo tiene 20 segundos para el apagado propiamente dicho.
Se pone incómodo en combinación con Spring Boot. Allí spring.lifecycle.timeout-per-shutdown-phase también está en 30 segundos por defecto. Si lo sumas, llegas a 40 y te sales del periodo de gracia:
10s preStop + 30s de vaciado = 40s > 30s de periodo de graciaLo que pasa entonces es especialmente molesto: a los 30 segundos el kubelet envía un SIGKILL. No se puede capturar, el proceso desaparece al instante, y las peticiones que aún se estaban sirviendo se cortan en mitad de la respuesta. Es decir, has eliminado los 502 del establecimiento de conexión y a cambio te has traído respuestas truncadas en las peticiones largas.
Por eso la cuenta hay que fijarla de forma explícita:
terminationGracePeriodSeconds: 60Con 10 segundos de preStop y 30 de vaciado quedan 20 segundos de margen. El margen es generoso a propósito, porque la secuencia conoce otros retrasos, por ejemplo la terminación de los sidecars.
La aplicación tiene que colaborar: graceful shutdown en Spring Boot
El hook preStop protege las peticiones que todavía no han llegado. No protege las que se están procesando. Para eso la aplicación tiene que reaccionar correctamente al SIGTERM, y no lo hace por sí sola.
Spring Boot se termina de inmediato por defecto. Las peticiones en curso se cortan. El interruptor para ello:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30sCon graceful, Spring Boot cierra primero el punto de aceptación al recibir el SIGTERM, es decir, deja de aceptar conexiones nuevas, y permite que las peticiones en curso terminen. Solo después cierra el contexto. timeout-per-shutdown-phase es el límite superior para eso.
Un detalle que se cae con facilidad: el SIGTERM tiene que llegar de verdad al proceso Java. Va al PID 1 del contenedor. Quien arranca su imagen con un envoltorio de shell tiene la shell como PID 1, y una shell no reenvía las señales automáticamente.
# Mal: la shell es PID 1 y se traga el SIGTERM
ENTRYPOINT java -jar /app.jar
# Bien: el proceso Java es PID 1
ENTRYPOINT ["java", "-jar", "/app.jar"]La primera forma es la forma shell, Docker la arranca a través de /bin/sh -c. La segunda es la forma exec. Si de verdad necesitas un script de arranque, termínalo con exec java -jar /app.jar para que el proceso Java sustituya al script y asuma su PID.
Se comprueba en una línea:
kubectl exec -it deploy/checkout -- ps -o pid,commSi en el PID 1 hay algo distinto de java, la señal nunca llega y toda la configuración de graceful shutdown no sirve de nada.
El manifiesto completo
Todas las piezas juntas, en un deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
namespace: shop-prod
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
spec:
terminationGracePeriodSeconds: 60
containers:
- name: checkout
image: registry.example.com/checkout:1.4.2
ports:
- containerPort: 8080
lifecycle:
preStop:
sleep:
seconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 2
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
failureThreshold: 6
startupProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 30Tres ajustes de ahí merecen una justificación.
maxUnavailable: 0 garantiza que durante el despliegue nunca haya menos pods disponibles de los previstos. Junto con maxSurge: 1, Kubernetes arranca primero un pod nuevo y solo después termina uno viejo. Sin eso, el propio despliegue quita capacidad y los pods restantes reciben un pico de carga además del cambio.
Los endpoints separados para readiness y liveness vienen de Spring Boot Actuator y hay que activarlos:
management.endpoint.health.probes.enabled=trueCon eso quedan disponibles /actuator/health/readiness y /actuator/health/liveness. La diferencia es esencial: el endpoint de readiness incluye dependencias como la base de datos, el de liveness no. Quien usa la misma ruta para ambos y comprueba allí la base de datos se construye una tormenta de reinicios: si la base de datos cae, Kubernetes reinicia todos los pods aunque ninguno esté roto.
La startupProbe cubre el arranque. Con failureThreshold: 30 y periodSeconds: 5, la aplicación puede tardar 150 segundos en levantarse sin que la sonda de liveness interfiera. Para una JVM con un contexto de Spring grande es un margen realista.
Lo que el hook preStop no resuelve
Cuatro límites que se notan en la práctica.
Las conexiones keep-alive existentes quedan fuera. El hook preStop protege los establecimientos de conexión nuevos. Una conexión keep-alive ya abierta entre el ingress y el pod se mantiene y se sigue usando, incluso después de que el pod haya desaparecido de los upstreams. Si la aplicación la cierra de golpe al apagarse, la petición que vaya por ahí todavía puede fallar. El graceful shutdown de Spring Boot trata este caso correctamente al dejar terminar las peticiones en curso.
Las peticiones de larga duración desbordan cualquier margen. Una exportación que calcula durante tres minutos no sobrevive a un periodo de gracia de 60 segundos. Ese trabajo no pertenece a una petición HTTP que deba sobrevivir a un despliegue, sino a un job o a una cola.
Las conexiones permanentes como WebSockets y SSE se cortan inevitablemente al terminar. Aquí no ayuda ningún ajuste de tiempos, solo un cliente que reconecte.
Los balanceadores externos siguen su propio ritmo. Si delante del clúster hay un balanceador en la nube que comprueba los nodos por su cuenta, su cadencia se suma. Un intervalo de comprobación de diez segundos con dos fallos necesarios da veinte segundos durante los cuales se sigue enviando tráfico. La espera del preStop tiene que superar eso.
Un caso límite más, porque aparece a menudo al buscar: Kubernetes tiene las condiciones serving y terminating en los EndpointSlices. Un pod que se está terminando es terminating, pero puede seguir siendo serving. Algunos proxies lo aprovechan para enviar tráfico a pods en terminación como último recurso cuando no queda ninguno más. Eso es un salvavidas contra la caída total, no un sustituto de unos tiempos bien puestos.
Provocar el fallo antes de que llegue solo
El punto donde se decide todo: un despliegue sin carga no demuestra absolutamente nada. Casi todos los despliegues se prueban en un entorno donde nadie usa la aplicación, y allí el fallo naturalmente no aparece.
La prueba consiste en dos terminales. En el primero corre carga continua, y con POST, porque un GET se repetiría:
hey -z 120s -c 20 -m POST
-H "Content-Type: application/json"
-d '{"sku":"A-1","qty":1}'
https://shop.example.com/api/cartEn el segundo se lanza un despliegue mientras tanto:
kubectl -n shop-prod rollout restart deploy/checkout
kubectl -n shop-prod rollout status deploy/checkoutDespués solo cuenta la distribución de estados en la salida de hey. Sin hook preStop ahí hay 502, y su número escala con el número de réplicas y con la carga. Con el hook bien puesto y graceful shutdown ahí no hay más que 200.
Contraprueba desde el log del ingress, si los números no son claros:
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --since=5m
| grep ' 502 ' | wc -lEl valor antes y después del cambio es el verdadero resultado de este artículo. Todo lo anterior es teoría.
Un apunte sobre el montaje: esta prueba pertenece a un entorno de staging con el mismo número de réplicas y la misma configuración de ingress que producción. Un clúster con una sola réplica da otra imagen, porque allí durante el despliegue no hay destino disponible ni un momento.
Los patrones de un vistazo
| Síntoma | Causa | Medida |
|---|---|---|
| 502 durante el despliegue, log de la aplicación vacío | la baja corre en paralelo al apagado | preStop con sleep, de 5 a 30 segundos |
| Solo afecta a POST, GET sin novedad | NGINX no repite peticiones no idempotentes | trabajar los tiempos, no poner non_idempotent |
| Las respuestas se cortan a mitad | SIGKILL tras agotarse el periodo de gracia | poner terminationGracePeriodSeconds por encima de preStop más vaciado |
| El SIGTERM no surte efecto | el PID 1 es una shell | forma exec en ENTRYPOINT o exec en el script de arranque |
| Las peticiones en curso se cortan | falta graceful shutdown | server.shutdown=graceful |
| Tormenta de reinicios al caer la base de datos | liveness comprueba dependencias | endpoints de actuator separados para liveness y readiness |
| Hueco de capacidad durante el despliegue | estrategia por defecto | maxUnavailable: 0, maxSurge: 1 |
| El hook no se ejecuta y no hay mensaje de error | falta el binario sleep en la imagen distroless |
acción sleep nativa en lugar de exec |
| El balanceador externo sigue enviando | su propia cadencia de comprobación | poner la espera del preStop por encima del intervalo de comprobación |
Cuándo no necesitas un hook preStop
No todas las cargas de trabajo lo necesitan, y un hook puesto en todas partes por norma alarga cada apagado por su tiempo de espera.
En un Job o un CronJob no hay service ni lista de upstreams. Nadie envía tráfico, no hay nada que dar de baja.
En un consumidor de una cola, por ejemplo un consumidor de Kafka, el tráfico no pasa por un service. El pod se busca su propio trabajo. Lo que necesita es salir limpiamente del grupo de consumidores al recibir el SIGTERM, o sea graceful shutdown, pero ninguna espera previa.
En una aplicación que solo tiene una réplica por nodo y se alcanza vía hostNetwork, el mecanismo tampoco aplica.
Comprueba simplemente si la IP del pod está en una lista que mantiene otro. Si la respuesta es sí, actualizar esa lista lleva tiempo y el pod tiene que esperarlo. Si es no, basta con graceful shutdown.
FAQ
¿Por qué Kubernetes no da de baja el pod primero y lo termina después?
Porque los dos procesos los ejecutan componentes distintos que no se coordinan entre sí. El kubelet del nodo termina el pod, el controlador de EndpointSlice mantiene los endpoints. Sincronizarlos significaría que el kubelet espera hasta que cada kube-proxy y cada controlador de ingress del clúster hayan confirmado. Ese canal de vuelta no existe, y con varios miles de nodos tampoco sería practicable.
¿Cuánto tiene que esperar el hook preStop?
Lo que tarde un cambio de endpoint en tu clúster en llegar al último consumidor. Cinco segundos bastan en clústeres pequeños, diez es un buen valor de partida, y con un balanceador externo que tiene su propio intervalo de comprobación tiene que ser más que ese intervalo multiplicado por el número de fallos necesarios. Medir en vez de adivinar: despliegue bajo carga, contar los 502, ajustar el valor.
¿Sirve con subir simplemente terminationGracePeriodSeconds?
No, por sí solo no. El periodo de gracia es un límite superior, no un mecanismo de espera. Sin hook preStop el SIGTERM sale de inmediato, la aplicación se apaga de inmediato y el periodo de gracia se queda sin usar. Aun así tiene que ser suficientemente alto, si no el SIGKILL corta el vaciado.
¿Por qué solo afecta a las peticiones POST?
Porque NGINX prueba el siguiente upstream cuando falla una conexión, pero por seguridad no lo hace con métodos no idempotentes. Un POST repetido podría lanzar un pedido por duplicado. Las peticiones GET se repiten en silencio y por eso no llaman la atención.
¿Puedo activar non_idempotent en su lugar?
Técnicamente sí, con sentido rara vez. Con eso permites que NGINX repita también las peticiones POST y te arriesgas a un procesamiento duplicado si el servidor ya había aceptado la primera. El hook preStop corrige la causa, non_idempotent la tapa y crea un riesgo nuevo.
¿Vale esto también para Traefik, HAProxy o Envoy?
Sí, el mecanismo es el mismo porque vive en Kubernetes y no en el proxy. Solo cambian el tiempo de reacción y el comportamiento ante reintentos. Envoy dentro de un service mesh suele reaccionar más rápido porque obtiene los endpoints de un plano de control propio, pero tampoco allí la distribución es instantánea.
¿Lo necesito también en llamadas entre dos servicios dentro del clúster?
Sí. Allí el camino pasa por kube-proxy en lugar del controlador de ingress, pero la carrera es idéntica. El servicio que llama recibe entonces una conexión rechazada directamente en su cliente HTTP en vez de un 502, lo que acaba en una excepción o en un reintento según cómo esté configurado el cliente.
¿Basta con poner el hook preStop solo en el servicio de producción?
Si es donde está el tráfico, sí. Tiene más sentido ponerlo en la plantilla común de la que salen los deployments, por ejemplo en el chart base. Si no, lo tendrá el servicio en el que alguien pensó y el resto no.
Conclusión
La causa es una secuencia que no lo es: la baja y el apagado corren en paralelo, y la aplicación termina antes de que el clúster consiga propagar la información.
Tres ajustes lo corrigen, y ninguno es costoso: un hook preStop que espera, un periodo de gracia por encima de espera más vaciado, y una aplicación que se vacía limpiamente al recibir el SIGTERM en vez de parar en seco. A eso se suma comprobar que el SIGTERM llega siquiera al PID 1.
Sin la cuarta, eso sí, el resto se viene abajo: lanzar un despliegue bajo carga y contar los códigos de estado. Sin ese paso solo sabes que los pods nuevos arrancan, y esa nunca fue la pregunta.
Empieza por el servicio donde más duele una petición perdida. Lanza carga POST contra él, arranca un despliegue y cuenta los 502. Ese número es tu medición de partida.
Fuentes
- Kubernetes, ciclo de vida y terminación de pods: kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle
- Kubernetes, container lifecycle hooks: kubernetes.io/docs/concepts/containers/container-lifecycle-hooks
- Kubernetes, KEP-3960 acción sleep para el hook preStop: github.com/kubernetes/enhancements
- Kubernetes v1.33, novedades del ciclo de vida del contenedor: kubernetes.io/blog/2025/05/14
- Spring Boot, graceful shutdown: docs.spring.io/spring-boot/reference/web/graceful-shutdown.html
- Spring Boot, sondas de Kubernetes en el actuator: docs.spring.io/spring-boot/reference/actuator/endpoints.html
- NGINX,
proxy_next_upstream: nginx.org/en/docs/http/ngx_http_proxy_module.html - Ingress NGINX, 502 al escalar pods hacia abajo: github.com/kubernetes/ingress-nginx/issues/3639
Todos los manifiestos, configuraciones y fragmentos de código de este artículo son propios.