Optimizar aplicaciones de Python para Cloud Run

En esta guía se describen las optimizaciones de los servicios de Cloud Run escritos en el lenguaje de programación Python, junto con información general para ayudarte a entender las ventajas y desventajas de algunas de las optimizaciones. La información de esta página complementa los consejos de optimización generales, que también se aplican a Python.

Muchas de las prácticas recomendadas y optimizaciones de las aplicaciones web comunes de Python se centran en lo siguiente:

  • Gestionar solicitudes simultáneas (tanto basadas en hilos como de E/S no bloqueantes)
  • Reducir la latencia de respuesta mediante la agrupación de conexiones y el procesamiento por lotes de funciones no críticas, como el envío de trazas y métricas a tareas en segundo plano.

Optimizar la imagen de contenedor

Optimiza la imagen de contenedor para reducir los tiempos de carga e inicio con estos métodos:

  • Minimizar los archivos que se cargan al inicio
  • Optimizar el servidor WSGI

Minimizar los archivos que se cargan al inicio

Para optimizar el tiempo de inicio, carga solo los archivos necesarios al inicio y reduce su tamaño. Si se trata de archivos grandes, puede hacer lo siguiente:

  • Almacena archivos de gran tamaño, como modelos de IA, en tu contenedor para acceder a ellos más rápido. Te recomendamos que cargues estos archivos después del inicio o durante el tiempo de ejecución.

  • Te recomendamos que configures los montajes de volúmenes de Cloud Storage para los archivos grandes que no sean críticos al inicio, como los recursos multimedia.

  • Importa solo los submódulos necesarios de las dependencias pesadas o importa módulos cuando sea necesario en tu código, en lugar de cargarlos al iniciar la aplicación.

Optimizar el servidor WSGI

Python ha estandarizado la forma en que las aplicaciones pueden interactuar con los servidores web mediante la implementación del estándar WSGI, PEP-3333. Uno de los servidores WSGI más comunes es gunicorn, que se usa en gran parte de la documentación de ejemplo.

Optimizar gunicorn

Añade la siguiente CMD a la Dockerfile para optimizar la invocación de gunicorn:

CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

Si tienes pensado cambiar estos ajustes, modifica el número de trabajadores y de hilos por aplicación. Por ejemplo, prueba a usar un número de workers igual al número de núcleos disponibles y comprueba si hay una mejora del rendimiento. Después, ajusta el número de hilos. Si se definen demasiados procesos o subprocesos, puede haber un impacto negativo, como una latencia de inicio en frío más larga, un mayor consumo de memoria, menos solicitudes por segundo, etc.

De forma predeterminada, gunicorn genera trabajadores y escucha en el puerto especificado al iniciarse, incluso antes de evaluar el código de tu aplicación. En este caso, debes configurar sondas de inicio personalizadas para tu servicio, ya que la sonda de inicio predeterminada de Cloud Run marca inmediatamente una instancia de contenedor como correcta en cuanto empieza a escuchar en $PORT.

Si quieres cambiar este comportamiento, puedes invocar gunicorn con el ajuste --preload para evaluar el código de tu aplicación antes de escuchar. Esto puede ayudarte a:

  • Identificar errores graves del tiempo de ejecución en el momento de la implementación
  • Ahorrar recursos de memoria

Antes de añadir este atributo, debes tener en cuenta qué precarga tu aplicación.

Otros servidores WSGI

No tienes que usar gunicorn para ejecutar Python en contenedores. Puedes usar cualquier servidor web WSGI o ASGI, siempre que el contenedor escuche en el puerto HTTP $PORT, tal como se indica en el contrato de tiempo de ejecución del contenedor.

Algunas alternativas habituales son uwsgi, uvicorn y waitress.

Por ejemplo, dado un archivo llamado main.py que contiene el objeto app, las siguientes invocaciones iniciarían un servidor WSGI:

# uwsgi: pip install pyuwsgi
uwsgi --http :$PORT -s /tmp/app.sock --manage-script-name --mount /app=main:app

# uvicorn: pip install uvicorn
uvicorn --port $PORT --host 0.0.0.0 main:app

# waitress: pip install waitress
waitress-serve --port $PORT main:app

Se pueden añadir como una línea CMD exec en un Dockerfile o como una entrada web: en Procfile cuando se usan buildpacks de Google Cloud.

Optimizar aplicaciones

En el código de tu servicio de Cloud Run, también puedes optimizar los tiempos de inicio y el uso de memoria.

Reducir las conversaciones

Puedes optimizar la memoria reduciendo el número de subprocesos, usando estrategias reactivas no bloqueantes y evitando las actividades en segundo plano. También debes evitar escribir en el sistema de archivos, tal como se indica en la página de consejos generales.

Si quieres admitir actividades en segundo plano en tu servicio de Cloud Run, configura tu servicio de Cloud Run para que use la facturación basada en instancias. De esta forma, podrás ejecutar actividades en segundo plano fuera de las solicitudes y seguir teniendo acceso a la CPU.

Reducir las tareas de inicio

Las aplicaciones web de Python pueden tener muchas tareas que completar durante el inicio, como precargar datos, calentar la caché y establecer grupos de conexiones. Si se ejecutan de forma secuencial, estas tareas pueden ser lentas. Sin embargo, si quieres que se ejecuten en paralelo, aumenta el número de núcleos de CPU.

Cloud Run envía una solicitud de usuario real para activar una instancia de arranque en frío. Los usuarios a los que se les haya asignado una solicitud a una instancia recién iniciada pueden experimentar retrasos prolongados.

Mejorar la seguridad con imágenes base optimizadas

Para mejorar la seguridad de tu aplicación, usa una imagen base optimizada con menos paquetes y bibliotecas.

Si decides no instalar Python desde la fuente en tus contenedores, usa una imagen base oficial de Python de Docker Hub. Estas imágenes se basan en el sistema operativo Debian.

Si usas la imagen python de Docker Hub, te recomendamos que utilices la versión slim. Estas imágenes son más pequeñas porque no incluyen varios paquetes que se usarían para crear ruedas, lo que puede que no necesites hacer en tu aplicación. La imagen python incluye el compilador GNU C, el preprocesador y las utilidades principales.

Para identificar los diez paquetes más grandes de una imagen base, ejecuta el siguiente comando:

DOCKER_IMAGE=python # or python:slim
docker run --rm ${DOCKER_IMAGE} dpkg-query -Wf '${Installed-Size}\t${Package}\t${Description}\n' | sort -n | tail -n10 | column -t -s $'\t'

Como hay menos paquetes de bajo nivel, las imágenes basadas en slim también ofrecen una superficie de ataque menor para posibles vulnerabilidades. Es posible que algunas de estas imágenes no incluyan los elementos necesarios para compilar ruedas a partir de la fuente.

Puedes volver a añadir paquetes específicos añadiendo una línea RUN apt install a tu archivo Dockerfile. Para obtener más información, consulta Usar paquetes del sistema en Cloud Run.

También hay opciones para contenedores que no estén basados en Debian. La opción python:alpine puede dar como resultado un contenedor mucho más pequeño, pero es posible que muchos paquetes de Python no tengan ruedas precompiladas que admitan sistemas basados en Alpine. La compatibilidad está mejorando (consulta la PEP-656), pero sigue variando. También puedes usar distroless base image, que no contiene ningún gestor de paquetes, shell ni ningún otro programa.

Usar la variable de entorno PYTHONUNBUFFERED para el registro

Para ver los registros sin búfer de tu aplicación Python, define la variable de entorno PYTHONUNBUFFERED. Cuando defines esta variable, los datos de stdout y stderr se muestran inmediatamente en los registros del contenedor, en lugar de almacenarse en un búfer hasta que se haya acumulado una determinada cantidad de datos o se cierre el flujo.

Siguientes pasos

Para ver más consejos, consulta