Optimiser les applications Python pour Cloud Run

Ce guide décrit des optimisations pour les services Cloud Run écrits dans le langage de programmation Python. Il présente également des informations générales pour vous aider à comprendre les compromis requis par certaines des optimisations. Les informations contenues sur cette page viennent en complément des conseils généraux d'optimisation, qui s'appliquent également à Python.

Un grand nombre de bonnes pratiques et d'optimisations de ces applications Web Python traditionnelles reposent sur les aspects suivants :

  • La gestion des requêtes simultanées (E/S non bloquantes et basées sur un thread)
  • La réduction de la latence de réponse à l'aide de fonctions non critiques de pooling des connexions et de traitement par lots, par exemple l'envoi de traces et de métriques à des tâches en arrière-plan

Optimiser l'image de conteneur

Optimiser l'image du conteneur peut vous aider à réduire les temps de chargement et de démarrage. Différentes actions vous permettent d'optimiser l'image :

  • Ne placer dans votre conteneur que les choses dont votre application a besoin au moment de l'exécution
  • Optimiser le serveur WSGI

Ne placer dans votre conteneur que ce dont votre application a besoin au moment de l'exécution

Déterminez les composants inclus dans le conteneur et s'ils sont nécessaires à l'exécution du service. Il existe plusieurs façons de réduire l'image de conteneur :

  • Utiliser une image de base moins volumineuse
  • Déplacer des fichiers volumineux en dehors du conteneur

Utiliser une image de base moins volumineuse

Docker Hub fournit un certain nombre d'images de base officielles Python que vous pouvez utiliser si vous choisissez de ne pas installer Python à partir de la source dans vos conteneurs. Elles sont basées sur le système d'exploitation Debian.

Si vous utilisez l'image python de Docker Hub, envisagez d'utiliser la version slim. Ces images sont moins volumineuses, car elles ne sont pas fournies avec un certain nombre de packages qui serviraient à construire des roues (par exemple), ce qui n'est pas nécessaire pour votre application. Par exemple, l'image Python est fournie avec le compilateur, le préprocesseur et les utilitaires de base GNU C.

Pour identifier les dix packages les plus volumineux d'une image de base, vous pouvez exécuter la commande suivante :

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'

Comme il y a moins de ces packages de bas niveau, les images basées sur slim offrent également moins de surface d'attaque pour les failles potentielles. Notez que ces images peuvent ne pas inclure les éléments requis pour créer des roues à partir de la source.

Vous pouvez rajouter des packages spécifiques en ajoutant une ligne RUN apt install à votre fichier Dockerfile. En savoir plus sur l'utilisation des packages système dans Cloud Run

Il existe également des options pour les conteneurs non-Debian. L'option python:alpine peut générer un conteneur beaucoup moins volumineux, mais de nombreux packages Python peuvent ne pas comporter de roues précompilées compatibles avec les systèmes Alpine. La compatibilité s'améliore (voir PEP-656), mais reste variable. Vous pouvez également envisager d'utiliser le fichier distroless base image, qui ne contient aucun gestionnaire de packages, shell ni aucun autre programme.

Déplacer des fichiers volumineux en dehors du conteneur

Les fichiers volumineux tels que les éléments multimédias, etc… n'ont pas besoin d'être inclus dans le conteneur de base.

Google Cloud propose plusieurs options d'hébergement, telles que Cloud Storage, pour stocker ces éléments volumineux. Déplacez les éléments volumineux vers ces services, puis référencez-les depuis votre application au moment de l'exécution.

Optimiser le serveur WSGI

Python a standardisé la manière dont les applications peuvent interagir avec les serveurs Web grâce à la mise en œuvre de la norme WSGI PEP-3333. L'un des serveurs WSGI les plus courants est gunicorn. Il est utilisé dans la plupart des exemples de documentation.

Optimiser gunicorn

Ajoutez le CMD suivant au Dockerfile pour optimiser l'appel de gunicorn :

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

Si vous envisagez de modifier ces paramètres, ajustez le nombre de nœuds de calcul et de threads individuellement pour chaque application. Par exemple, essayez d'utiliser un nombre de nœuds de calcul égal aux cœurs disponibles et assurez-vous que les performances s'améliorent, puis ajustez le nombre de threads. La définition d'un trop grand nombre de nœuds de calcul ou de threads peut avoir un impact négatif, par exemple une latence de démarrage à froid plus longue, une consommation de mémoire plus importante, un nombre plus faible de requêtes par seconde, etc…

L'ajout du paramètre --preload peut aider à :

  • Identifier les bugs d'exécution graves au moment du déploiement
  • Économiser la mémoire

Vous devez prendre en compte le préchargement de votre application avant d'envisager l'ajout de ce paramètre.

Autres serveurs WSGI

Vous n'êtes pas restreint à l'utilisation de gunicorn pour exécuter Python dans des conteneurs. Vous pouvez utiliser n'importe quel serveur Web WSGI ou ASGI tant que le conteneur écoute sur le port HTTP $PORT, conformément au contrat d'exécution du conteneur.

Les alternatives courantes incluent uwsgi, uvicorn et waitress.

Par exemple, avec un fichier nommé main.py contenant l'objet app, les appels suivants démarrent un serveur 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

Ceux-ci peuvent être ajoutés en tant que ligne CMD exec dans un objet Dockerfile ou en tant qu'entrée web: dans un objet Procfile lorsque vous utilisez les packs de création Google Cloud.

Optimiser les applications

Dans votre code de service Cloud Run, vous pouvez également optimiser le délai de démarrage et l'utilisation de la mémoire.

Réduire le nombre de threads

Vous pouvez optimiser la mémoire en réduisant le nombre de threads, en faisant appel à des stratégies réactives non bloquantes et en évitant les activités d'arrière-plan. Évitez également d'écrire dans le système de fichiers, comme indiqué sur la page Conseils généraux.

Si vous souhaitez prendre en charge les activités d'arrière-plan dans votre service Cloud Run, configurez le processeur de votre service Cloud Run pour qu'il soit toujours alloué afin de pouvoir exécuter des activités d'arrière-plan en dehors des requêtes tout en conservant l'accès au processeur.

Limiter les tâches de démarrage

Les applications Web Python peuvent avoir de nombreuses tâches à exécuter au démarrage (préchargement des données, préchauffage du cache, établissement de pools de connexions, etc.). Lorsqu'elles sont exécutées de manière séquentielle, ces tâches peuvent se révéler lentes. Toutefois, si vous souhaitez qu'elles s'exécutent en parallèle, vous devez augmenter le nombre de cœurs de processeur.

Actuellement, Cloud Run envoie une requête utilisateur réelle pour déclencher le démarrage à froid d'une instance. Les utilisateurs dont la requête est attribuée à une instance tout juste démarrée peuvent être confrontés à des délais importants. À l'heure actuelle, Cloud Run ne dispose pas d'une "vérification d'aptitude" qui permettrait d'éviter l'envoi de requêtes à des applications qui ne sont pas prêtes.

Étape suivante

Pour accéder à d'autres conseils, consultez les pages suivantes :