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.
La plupart des bonnes pratiques et des optimisations courantes dans les applications web Python s'articulent autour de :
- 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
Optimisez l'image de conteneur pour réduire la charge et les temps de démarrage, en utilisant les méthodes suivantes :
- Réduisez le nombre de fichiers chargés au démarrage.
- Optimiser le serveur WSGI
Réduisez le nombre de fichiers chargés au démarrage.
Pour optimiser le temps de démarrage, chargez uniquement les fichiers nécessaires au démarrage et réduisez leur taille. Pour les fichiers volumineux, considérez les options suivantes :
Stockez les fichiers volumineux, tels que les modèles d'IA, dans votre conteneur pour un accès plus rapide. Envisagez de charger ces fichiers après le démarrage ou pendant l'exécution.
Envisagez de configurer des points de montage de volume Cloud Storage pour les fichiers volumineux qui ne sont pas critiques au démarrage, tels que les ressources multimédias.
N'importez que les sous-modules nécessaires des dépendances importantes, ou importez les modules uniquement lorsque votre code en a besoin, au lieu de les charger au démarrage de l'application.
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…
Par défaut, gunicorn génère des nœuds de calcul et écoute le port spécifié au démarrage, même avant d'évaluer le code de votre application. Dans ce cas, vous devez configurer des sondes de démarrage personnalisées pour votre service, car la sonde de démarrage par défaut de Cloud Run marque immédiatement une instance de conteneur comme saine dès qu'elle commence à écouter sur $PORT.
Si vous souhaitez modifier ce comportement, vous pouvez invoquer gunicorn avec le paramètre --preload pour évaluer le code de votre application avant l'écoute. Vous pouvez ainsi :
- 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 en arrière-plan dans votre service Cloud Run, configurez votre service Cloud Run sur facturation basée sur l'instance afin que vous puissiez exécuter des activités en arrière-plan en dehors des requêtes et avoir toujours accès au processeur.
Limiter les tâches de démarrage
Les applications web Python peuvent avoir de nombreuses tâches à accomplir au démarrage, telles que le préchargement des données, l'initialisation du cache et l'établissement de pools de connexions. Exécutées séquentiellement, ces tâches peuvent être lentes. Toutefois, si vous souhaitez qu'elles s'exécutent en parallèle, augmentez le nombre de cœurs du processeur.
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 associée à une instance nouvellement démarrée peuvent subir de longs délais.
Améliorer la sécurité avec des images de base minimalistes
Pour améliorer la sécurité de votre application, utilisez une image de base allégée avec moins de packages et de bibliothèques.
Si vous choisissez de ne pas installer Python à partir de la source dans vos conteneurs, utilisez une image de base Python officielle de Docker Hub. Ces images 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 plus petites car elles n'incluent pas un certain nombre de paquets nécessaires à la construction des roues, ce dont vous n'aurez peut-être pas besoin pour votre application. L'image python est fournie avec le compilateur, le préprocesseur et les utilitaires de base GNU C.
Pour identifier les dix plus gros paquets dans une image de base, exécutez 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. Certaines de ces images peuvent ne pas inclure les éléments nécessaires à la fabrication de roues à partir des sources.
Vous pouvez rajouter des packages spécifiques en ajoutant une ligne RUN apt install à votre fichier Dockerfile. Pour plus d'informations, consultez Utilisation des packages système dans Cloud Run.
Il existe également des options pour les conteneurs non-Debian. L'option python:alpine pourrait donner lieu à un conteneur beaucoup plus petit, mais de nombreux packages Python pourraient ne pas avoir de roues précompilées qui prennent en charge les systèmes basés sur Alpine. Le soutien s’améliore (voir PEP-656), mais continue de varier.
Envisagez également d'utiliser distroless base image, qui ne contient aucun gestionnaire de paquets, shell ou autre programme.
Utilisez la variable d'environnement PYTHONUNBUFFERED pour la journalisation
Pour afficher les journaux non mis en mémoire tampon de votre application Python, définissez la variable d'environnement PYTHONUNBUFFERED. Lorsque vous définissez cette variable, les données stdout et stderr sont immédiatement visibles dans les journaux du conteneur, au lieu d'être conservées dans une mémoire tampon jusqu'à ce qu'une certaine quantité de données se soit accumulée ou que le flux soit fermé.
Étapes suivantes
Pour accéder à d'autres conseils, consultez les pages suivantes :