Cuando trabajamos con aplicaciones desplegadas en Kubernetes
, uno de los aspectos más críticos
es cómo gestionamos el apagado de los pods para evitar interrupciones en el servicio.
Si además trabajamos aplicando CI/CD con decenas de despliegues al día, esto se vuelve más relevante si cabe.
Es bastante habitual encontrarse con la perdida de requests en un servicio que se quedan sin responder sin
motivo aparente (been there, done that). Esto se debe a que out-of-the-box
Kubernetes no se preocupa por el
estado de nuestros pods y simplemente termina unos para arrancar nuevas versiones de los mismos lo más rápido posible.
El graceful shutdown nos permite controlar
adecuadamente el proceso de terminación de un Pod
, dándole el tiempo necesario para finalizar sus tareas pendientes
antes de ser eliminado. De esta manera, garantizamos que no haya pérdida de datos, requests sin contestar ni afectaciones
a la experiencia del usuario.
En este post, exploraremos cómo funciona el graceful shutdown en Kubernetes
, las señales que intervienen
en este proceso y cómo podemos configurar nuestras aplicaciones para realizar un cierre controlado. Además, veremos
algunas buenas prácticas para asegurar que los despliegues sean más robustos y estén preparados para escalar sin problemas.
¿Qué es el Graceful Shutdown?
Cuando un Pod
en Kubernetes
es marcado para su eliminación, el proceso de graceful shutdown se activa para garantizar
que la aplicación pueda cerrar de manera ordenada y sin interrupciones abruptas.
El proceso se basa en una secuencia de señales enviadas al contenedor:
SIGTERM
: Al recibir esta señal, el contenedor inicia el cierre de la aplicación.- Aquí es donde debe realizar tareas críticas como completar solicitudes en curso, cerrar conexiones abiertas y liberar recursos.
- Kubernetes otorga un tiempo de espera configurable (grace period) para que esto ocurra.
Grace period
: Kubernetes espera un tiempo determinado (por defecto 30 segundos) antes de forzar la terminación delPod
.- Durante este tiempo, el
Pod
sigue funcionando para finalizar sus operaciones pendientes.
- Durante este tiempo, el
SIGKILL
: Si elPod
no ha terminado después del periodo de gracia, Kubernetes envía la señalSIGKILL
, que fuerza la finalización inmediata del contenedor.- El objetivo del graceful shutdown es asegurar que el proceso de apagado sea suave y que no haya pérdida de datos ni interrupciones en la disponibilidad del servicio.
Implementando nuestro propio Graceful Shutdown
Deployment en Kubernetes
La forma más común de implementar un graceful shutdown en Kubernetes es directamente en el manifiesto del Deployment
.
El hook preStop
es un mecanismo en Kubernetes que nos permite ejecutar comandos o scripts personalizados justo antes de que un
Pod
reciba la señal de SIGTERM
en el proceso de graceful shutdown. Este hook es útil cuando necesitamos realizar acciones
adicionales antes de iniciar el apagado del contenedor, como notificar a otros servicios, cerrar conexiones externas, o
hacer un flush de registros.
Cuando se define un hook preStop
, Kubernetes ejecuta la tarea especificada y espera a que termine antes de enviar la
señal SIGTERM
al contenedor. Esto nos da un control adicional para asegurarnos de que todas las tareas previas al apagado
se realicen correctamente.
En resumen, el hook preStop
complementa el proceso de graceful shutdown al permitir que ejecutemos tareas previas al
cierre del contenedor, mejorando así la robustez y el control durante el apagado.
Veamos un ejemplo de como definir un hook preStop
en un Deployment
de Kubernetes:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: my-service
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
Servidor FastAPI
Si no podemos/queremos tocar manifiestos de Kubernetes algunos frameworks tienen su propia solución, veamos
cómo implementar un graceful shutdown en una aplicación FastAPI
desplegada en Kubernetes.
FastAPI proporciona un evento de ciclo de vida (lifespan
) que nos permite definir
acciones específicas, como por ejemplo esperar 5 segundos antes de matar el pod, que se ejecutan cuando la aplicación se
inicia y cuando se cierra, lo cual es ideal para gestionar el graceful shutdown.
Podemos, por tanto, utilizar este lifespan
para asegurarnos de que nuestra aplicación realiza las tareas necesarias antes de
apagarse, como cerrar conexiones a bases de datos, liberar recursos o finalizar trabajos en segundo plano.
Para usar el lifespan
en FastAPI, simplemente tenemos que definir una función que maneje el ciclo de vida de la aplicación y
asignársela a la aplicación al iniciarla:
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from time import sleep
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncGenerator:
logger.info("Starting FastAPI server...")
yield
# Graceful shutdown
sleep(5) # wait for the app to finish processing requests
logger.info("FastAPI server finished!")
app = FastAPI(lifespan=lifespan)
En el bloque de shutdown (parte posterior al yield
), podemos realizar cualquier tarea necesaria antes de que la
aplicación se apague, lo cual complementa el proceso de graceful shutdown cuando Kubernetes envía la
señal SIGTERM
. De esta manera, FastAPI puede manejar el apagado de manera controlada, asegurando que todas
las conexiones y recursos se cierren adecuadamente.
Conclusiones
En resumen, implementar un graceful shutdown en Kubernetes es esencial para garantizar que nuestras aplicaciones
se terminen de manera ordenada y sin afectar la experiencia del usuario. Utilizando herramientas como el hook preStop
o
el ciclo de vida (lifespan
) de FastAPI, podemos controlar de forma precisa cómo se cierran los pods y asegurarnos
de que todas las tareas pendientes se completen antes del apagado.
Al aplicar estas prácticas, no solo mejoramos la resiliencia de nuestras aplicaciones, sino que también aseguramos una mayor estabilidad y escalabilidad en nuestros entornos de producción.
!Espero que este post te haya sido de utilidad!