Cómo controlar el Graceful Shutdown en Python sin problemas

Cuando una aplicación se detiene, es crucial controlar el graceful period para cerrar conexiones, finalizar tareas en curso, o liberar recursos. En este post, veremos cómo controlar este proceso en Python usando el paquete signals.

¿Qué es el Graceful Shutdown y por qué es importante?

El graceful shutdown es el proceso de detener una aplicación de forma ordenada, permitiéndole completar tareas pendientes antes de finalizar. Esto es especialmente útil en servidores y sistemas en producción, donde una terminación abrupta puede causar pérdida de datos, conexiones interrumpidas o estados inconsistentes.

En mi día a día es habitual, ya que todas mis aplicaciones están desplegadas en Kubernetes, tener que controlar estos escenarios donde la aplicación puede rotar en cualquier momento. Por tanto es importante estar preparado para ccuando esto ocurra y que no sea un problema.

Al implementar un cierre controlado, podemos asegurarnos de que la aplicación libere recursos correctamente, cierre conexiones a bases de datos o tenga tiempo a terminar eventos en ejecución, mejorando la estabilidad y confiabilidad del sistema.

Graceful Shutdown custom en cualquier aplicacion Python

Implementar un graceful shutdown personalizado en Python nos permite adaptar el proceso de cierre a las necesidades específicas de nuestra aplicación.

Por suerte para nosotros implementar esto es bastante sencillo, ya que basta con hacer uso del paquete signal para definir una función a ejecutar cuando ocurran ciertas señales de cierre en nuestra aplicación. Veamos un ejemplo:

from signal import signal, SIGTERM
from time import sleep
from typing import FrameType

from src.logger import Logger


logger = Logger()

def attach_sigterm_handler(_signal: int, _frame: FrameType | None) -> None:
    logger.info("Graceful shutdown started....")
    # Do whatever you need

    sleep(5) # Sleep 5 seconds
    events_processor.stop()

    # Do whatever you need
    logger.info("Graceful shutdown finished....")


signal(SIGTERM, attach_sigterm_handler)

events_processor = RabbitMQEventsProcessor(logger)
events_processor.start() # blocking process

En este código de ejemplo:

  • Definimos el handler attach_sigterm_handler donde:
    • Emitimos un mensaje de inicio en el log.
    • Aquí podemos ejecutar cualquier cosa que necesitemos antes del sleep.
    • Pausamos la ejecución por 5 segundos.
    • Aquí podemos ejecutar cualquier cosa que necesitemos antes de acabar el graceful shutdown.
    • Detenemos el proceso bloqueante del procesador eventos llamado events_processor.
  • Usamos la función signal() para capturar la señal SIGTERM y ejecutar attach_sigterm_handler, que se encarga de realizar las acciones necesarias antes de finalizar la aplicación.
  • Arrancamos el RabbitMQEventsProcessor con el metodo start que es bloqueante y se queda corriendo hasta que en algún momento sea parado por el handler.

Este enfoque permite asegurarnos de que los procesos en ejecución, como la gestión de eventos de RabbitMQ, se detengan correctamente antes de que la aplicación termine, evitando pérdidas de mensajes o estados inconsistentes.

¡Espero que os haya gustado!