Conseils et astuces
Ce document décrit les bonnes pratiques relatives à la conception, à la mise en œuvre, au test et au déploiement de fonctions Cloud Run.
Exactitude
Cette section décrit les bonnes pratiques générales relatives à la conception et à la mise en œuvre de fonctions Cloud Run.
Écrire des fonctions idempotentes
Vos fonctions devraient produire le même résultat même si elles sont appelées plusieurs fois. Vous pouvez ainsi relancer un appel si l'appel précédent a échoué au milieu de votre code. Pour en savoir plus, consultez la section Effectuer de nouvelles tentatives d'exécution des fonctions basées sur des événements.
Vérifier que les fonctions HTTP envoient une réponse HTTP
Si votre fonction est déclenchée par HTTP, n'oubliez pas d'envoyer une réponse HTTP, comme indiqué ci-dessous. À défaut, votre fonction peut s'exécuter jusqu'au délai d'inactivité. Dans ce cas, vous serez facturé pendant toute la durée définie. Les délais d'inactivité peuvent également provoquer un comportement imprévisible ou un démarrage à froid lors d'appels ultérieurs, ce qui entraîne une latence supplémentaire.
Node.js
Python
Go
Java
C#
Ruby
PHP
Ne pas démarrer les activités d'arrière-plan
L'activité d'arrière-plan désigne tout ce qui se produit après l'arrêt de votre fonction.
Un appel de fonction se termine une fois que la fonction renvoie ou signale la fin, par exemple en appelant l'argument callback
dans les fonctions basées sur des événements Node.js. Tout code exécuté après un arrêt concerté ne peut pas accéder au processeur et ne progresse pas.
Par ailleurs, lorsqu'un appel ultérieur est exécuté dans le même environnement, votre activité d'arrière-plan reprend, ce qui interfère avec le nouvel appel et peut entraîner un comportement inattendu et des erreurs difficiles à analyser. L'accès au réseau après l'arrêt d'une fonction entraîne généralement la réinitialisation des connexions (code d'erreur ECONNRESET
).
L'activité d'arrière-plan peut souvent être détectée dans les journaux d'appels individuels, en recherchant tout ce qui est enregistré à la suite de la ligne indiquant que l'appel est terminé. Elle peut parfois être enfouie plus profondément dans le code, notamment en présence d'opérations asynchrones telles que des rappels ou des timers. Examinez votre code pour vous assurer que toutes les opérations asynchrones se terminent avant d'arrêter la fonction.
Toujours supprimer les fichiers temporaires
Le stockage sur disque local dans le répertoire temporaire est un système de fichiers en mémoire. Les fichiers que vous écrivez consomment de la mémoire disponible pour votre fonction et persistent parfois entre les appels. Si vous ne supprimez pas explicitement ces fichiers, cela risque d'entraîner une erreur de mémoire insuffisante et un démarrage à froid ultérieur.
Pour voir la mémoire utilisée par une fonction individuelle, sélectionnez cette dernière dans la liste des fonctions de la console Google Cloud et choisissez le tracé d'utilisation de la mémoire.
Si vous avez besoin d'accéder à un stockage à long terme, envisagez d'utiliser des montages de volumes Cloud Run avec Cloud Storage ou des volumes NFS.
Vous pouvez réduire les besoins en mémoire lors du traitement de fichiers plus volumineux à l'aide du pipeline. Par exemple, pour traiter un fichier sur Cloud Storage, créez un flux de lecture, transmettez-le via un processus basé sur le flux et écrivez le flux de sortie directement dans Cloud Storage.
Framework des fonctions
Pour vous assurer que les mêmes dépendances sont installées de manière cohérente dans différents environnements, nous vous recommandons d'inclure la bibliothèque du framework des fonctions dans votre gestionnaire de paquets et d'épingler la dépendance à une version spécifique du framework des fonctions.
Pour ce faire, spécifiez la version que vous préférez dans le fichier de verrouillage approprié (par exemple, package-lock.json
pour Node.js ou requirements.txt
pour Python).
Si le framework des fonctions n'est pas explicitement listé en tant que dépendance, il sera automatiquement ajouté lors du processus de compilation à l'aide de la dernière version disponible.
Outils
Cette section fournit des instructions sur l'utilisation des outils pour la mise en œuvre, le test et l'interaction avec les fonctions Cloud Run.
Développement local
Le déploiement des fonctions prend un peu de temps. Il est donc souvent plus rapide de tester le code de votre fonction localement.
Création de rapports d'erreur
Dans les langages qui utilisent le traitement des exceptions, ne lancez pas d'exceptions non interceptées, car elles forcent les démarrages à froid lors d'appels ultérieurs. Consultez le guide Error Reporting pour savoir comment signaler correctement les erreurs.
Ne pas fermer manuellement
La fermeture manuelle peut entraîner un comportement inattendu. Veuillez plutôt utiliser les idiomes spécifiques aux langages suivants :
Node.js
N'utilisez pas process.exit()
. Les fonctions HTTP doivent envoyer une réponse avec res.status(200).send(message)
, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
Python
N'utilisez pas sys.exit()
. Les fonctions HTTP doivent explicitement renvoyer une réponse sous forme de chaîne, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
Go
N'utilisez pas os.Exit()
. Les fonctions HTTP doivent explicitement renvoyer une réponse sous forme de chaîne, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
Java
N'utilisez pas System.exit()
. Les fonctions HTTP doivent envoyer une réponse avec response.getWriter().write(message)
, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
C#
N'utilisez pas System.Environment.Exit()
. Les fonctions HTTP doivent envoyer une réponse avec context.Response.WriteAsync(message)
, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
Ruby
N'utilisez ni exit()
, ni abort()
. Les fonctions HTTP doivent explicitement renvoyer une réponse sous forme de chaîne, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
PHP
N'utilisez ni exit()
, ni die()
. Les fonctions HTTP doivent explicitement renvoyer une réponse sous forme de chaîne, et les fonctions basées sur des événements se ferment une fois qu'elles renvoient une valeur (implicitement ou explicitement).
Utiliser Sendgrid pour envoyer des e-mails
Cloud Run Functions n'autorise pas les connexions sortantes sur le port 25. Vous ne pouvez donc pas établir de connexions non sécurisées avec un serveur SMTP. La méthode recommandée pour envoyer des e-mails consiste à utiliser un service tiers tel que SendGrid. Vous pouvez trouver d'autres options d'envoi d'e-mails dans le tutoriel Envoyer des e-mails depuis une instance pour Google Compute Engine.
Performances
Cette section décrit les bonnes pratiques relatives à l'optimisation des performances.
Éviter la faible concurrence
Étant donné que les démarrages à froid sont coûteux, la possibilité de réutiliser des instances lancées récemment lors d'un pic est une excellente optimisation pour gérer la charge. Limiter la simultanéité limite l'utilisation des instances existantes, ce qui entraîne davantage de démarrages à froid.
Augmenter la simultanéité permet de différer plusieurs requêtes par instance, ce qui facilite la gestion des pics de charge. Remarque: La simultanéité des fonctions de 1re génération est limitée à 1. Nous vous recommandons de migrer vers les fonctions Cloud Run.Utiliser les dépendances à bon escient
Les fonctions étant sans état, l'environnement d'exécution est souvent initialisé à partir de zéro (lors d'un démarrage à froid). En cas de démarrage à froid, le contexte global de la fonction est évalué.
Si vos fonctions importent des modules, le temps de chargement de ces modules peut augmenter la latence d'appel lors d'un démarrage à froid. Pour réduire cette latence, ainsi que le temps nécessaire pour déployer votre fonction, chargez correctement les dépendances et ne chargez pas celles que votre fonction n'utilise pas.
Utiliser des variables globales pour réutiliser des objets lors de futurs appels
Il n'est pas garanti que l'état d'une fonction de Cloud Run Functions soit préservé pour les futurs appels. Cloud Run Functions recycle néanmoins souvent l'environnement d'exécution d'un appel précédent. Si vous déclarez une variable dans le champ d'application global, sa valeur peut être réutilisée dans les appels suivants sans avoir à être recalculée.
De cette façon, vous pouvez mettre en cache des objets qui peuvent être coûteux à recréer à chaque appel de fonction. Le déplacement de ces objets du corps de la fonction vers le champ d'application global peut entraîner des améliorations significatives des performances. Dans l'exemple suivant, un objet lourd est créé une seule fois par instance de fonction et partagé à travers tous les appels de fonction atteignant l'instance donnée :
Node.js
Python
Go
Java
C#
Ruby
PHP
Il est particulièrement important de mettre en cache les connexions réseau, les références de bibliothèque et les objets client API dans un champ d'application global. Consultez la section Optimiser la mise en réseau pour obtenir des exemples.
Réduisez les démarrages à froid en définissant un nombre minimal d'instances
Par défaut, Cloud Run Functions adapte le nombre d'instances en fonction du nombre de requêtes entrantes. Vous pouvez modifier ce comportement par défaut en définissant un nombre minimal d'instances que Cloud Run Functions doit garder prêtes à diffuser les requêtes. La définition d'un nombre minimal d'instances réduit les démarrages à froid de votre application. Nous vous recommandons de définir un nombre minimal d'instances et d'effectuer l'initialisation au moment du chargement si votre application est sensible à la latence.
Pour savoir comment définir un nombre minimal d'instances, consultez la section Utiliser un nombre minimal d'instances.
Remarques sur le démarrage à froid et l'initialisation
L'initialisation globale se produit au moment du chargement. Sans cela, la première requête devrait terminer l'initialisation et charger les modules, ce qui entraînerait une latence plus élevée.
Toutefois, l'initialisation globale a également un impact sur les démarrages à froid. Pour minimiser cet impact, n'initialisez que ce qui est nécessaire pour la première requête afin de réduire la latence de la première requête au maximum.
Cela est particulièrement important si vous avez configuré des instances minimales comme décrit ci-dessus pour une fonction sensible à la latence. Dans ce scénario, l'initialisation au moment du chargement et la mise en cache des données utiles permettent de s'assurer que la première requête n'a pas besoin de le faire et qu'elle est diffusée avec une faible latence.
Si vous initialisez des variables dans un champ d'application global, selon le langage, de longs temps d'initialisation peuvent entraîner deux comportements : - pour certaines combinaisons de langages et de bibliothèques asynchrones, le framework de fonctions peut s'exécuter de manière asynchrone et renvoyer immédiatement, ce qui entraîne la poursuite de l'exécution du code en arrière-plan, ce qui peut entraîner des problèmes tels que l'impossibilité d'accéder au processeur. Pour éviter cela, vous devez bloquer l'initialisation du module, comme décrit ci-dessous. Cela garantit également que les requêtes ne sont pas traitées tant que l'initialisation n'est pas terminée. - D'autre part, si l'initialisation est synchrone, le long temps d'initialisation entraînera des démarrages à froid plus longs, ce qui peut poser problème, en particulier avec les fonctions à faible concurrence lors des pics de charge.
Exemple de préchauffage d'une bibliothèque Node.js asynchrone
Node.js avec Firestore est un exemple de bibliothèque Node.js asynchrone. Pour profiter de min_instances, le code suivant termine le chargement et l'initialisation au moment du chargement, en bloquant le chargement du module.
TLA est utilisé, ce qui signifie qu'ES6 est obligatoire, à l'aide d'une extension .mjs
pour le code node.js ou en ajoutant type: module
au fichier package.json.
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
Node.js
import Firestore from '@google-cloud/firestore'; import * as functions from '@google-cloud/functions-framework'; const firestore = new Firestore({preferRest: true}); // Pre-warm firestore connection pool, and preload our global config // document in cache. In order to ensure no other request comes in, // block the module loading with a synchronous global request: const config = await firestore.collection('collection').doc('config').get(); functions.http('fetch', (req, res) => { // Do something with config and firestore client, which are now preloaded // and will execute at lower latency. });
Exemples d'initialisation globale
Node.js
Python
Go
Java
C#
Ruby
PHP
Les fonctions PHP ne peuvent pas conserver les variables entre les requêtes. L'exemple de champs d'application ci-dessus utilise le chargement différé pour mettre en cache les valeurs des variables globales dans un fichier.
Cette recommandation est d'autant plus importante si vous définissez plusieurs fonctions dans un seul fichier et que chaque fonction utilise des variables différentes. Si vous n'utilisez pas l'initialisation paresseuse, vous risquez de gaspiller des ressources sur des variables initialisées mais jamais utilisées.
Autres ressources
Pour en savoir plus sur l'optimisation des performances, regardez la vidéo "Google Cloud Performance Atlas" Temps de démarrage à froid de Cloud Run Functions.