Ce guide présente les bonnes pratiques pour la conception, la mise en œuvre, le test et le déploiement d'un service Knative serving. Pour obtenir plus de conseils, consultez la page Migrer un service existant.
Écrire des services efficaces
Cette section présente les bonnes pratiques générales de conception et de mise en œuvre d'un service Knative serving.
Éviter les activités d'arrière-plan
Lorsqu'une application exécutée sur Knative serving termine le traitement d'une requête, l'accès de l'instance de conteneur au processeur est désactivé ou très limité. Par conséquent, vous ne devez pas démarrer de routines ni de threads en arrière-plan qui s'exécutent en dehors du champ d'application des gestionnaires de requêtes.
L'exécution de threads en arrière-plan peut entraîner un comportement inattendu, car toute requête ultérieure adressée à la même instance de conteneur reprend l'activité en arrière-plan suspendue.
L'activité d'arrière-plan désigne tout ce qui se produit après la transmission de votre réponse HTTP. Examinez votre code pour vous assurer que toutes les opérations asynchrones sont terminées avant de transmettre votre réponse.
Si vous pensez qu'une activité d'arrière-plan s'exécute dans votre service sans apparaître de façon évidente, recherchez dans vos journaux tous les événements consignés après l'entrée de la requête HTTP.
Supprimer les fichiers temporaires
Dans l'environnement Cloud Run, le stockage sur disque est un système de fichiers en mémoire. Les fichiers écrits sur le disque consomment de la mémoire disponible pour votre service et persistent parfois entre les appels. Si vous ne supprimez pas explicitement ces fichiers, une erreur pour mémoire insuffisante et un démarrage à froid peuvent se produire.
Optimiser les performances
Cette section décrit les bonnes pratiques relatives à l'optimisation des performances.
Démarrer les services rapidement
Les instances de conteneur étant mises à l'échelle en fonction des besoins, une méthode classique consiste à initialiser complètement l'environnement d'exécution. Ce type d'initialisation est dénommé "démarrage à froid". Si une requête de client déclenche un démarrage à froid, le démarrage de l'instance de conteneur entraîne une latence supplémentaire.
La routine de démarrage lance les actions suivantes :
- Démarrage du service
- Démarrage du conteneur
- Exécution de la commande entrypoint pour démarrer le serveur
- Vérification du port de service ouvert
L'optimisation de la vitesse de démarrage du service réduit la latence qui retarde le traitement des requêtes par une instance de conteneur.
Utiliser les dépendances à bon escient
Si vous utilisez un langage dynamique avec des bibliothèques dépendantes, telles que l'importation de modules dans Node.js, le temps de chargement de ces modules augmente la latence lors d'un démarrage à froid. Vous pouvez réduire la latence de la façon suivante :
- Réduisez le nombre et la taille des dépendances pour créer un service allégé.
- Chargez tardivement le code rarement utilisé, si votre langage le permet.
- Utilisez des optimisations de chargement de code telles que l'optimisation du chargeur automatique de l'éditeur PHP.
Utiliser des variables globales
Dans Knative serving, vous ne pouvez pas supposer que l'état du service est préservé entre les requêtes. Toutefois, Knative serving réutilise des instances de conteneur individuelles pour diffuser le trafic en cours. Vous pouvez donc déclarer une variable dans un champ d'application global pour permettre la réutilisation de sa valeur lors d'appels ultérieurs. Il n'est pas possible de savoir à l'avance si une requête individuelle peut bénéficier de cette réutilisation.
Vous pouvez également mettre en cache des objets en mémoire s’ils sont coûteux à recréer à chaque demande de service. Changer la logique de requête en appliquant le champ d'application global améliore les performances.
Node.js
Python
Go
Java
Effectuer une initialisation tardive des variables globales
L'initialisation des variables globales a toujours lieu au démarrage, ce qui augmente le temps de démarrage à froid. L'initialisation tardive des objets rarement utilisés permet de différer le coût horaire et de réduire les temps de démarrage à froid.
Node.js
Python
Go
Java
Optimiser la simultanéité
Les instances Knative serving peuvent diffuser plusieurs requêtes simultanément, jusqu'à une simultanéité maximale configurable.
Ceci diffère de Cloud Functions, qui utilise concurrency = 1
.
Vous devez conserver le paramètre de simultanéité maximale par défaut, sauf si votre code présente des exigences de simultanéité spécifiques.
Régler la simultanéité pour votre service
Le nombre de requêtes simultanées pouvant être traitées par chaque instance de conteneur peut être limité par la pile technologique et l'utilisation de ressources partagées, telles que les variables et les connexions à la base de données.
Pour optimiser votre service de façon à obtenir une simultanéité stable maximale, procédez comme suit :
- Optimisez vos performances de service.
- Définissez le degré de simultanéité compatible attendu dans votre configuration de simultanéité au niveau du code. Les piles technologiques n'exigent pas toutes ce paramètre.
- Déployez votre service.
- Définissez la simultanéité de Knative serving pour votre service sur une valeur égale ou inférieure à celle de la configuration au niveau du code. S'il n'existe pas de configuration au niveau du code, appliquez le niveau de simultanéité attendu.
- Utilisez des outils de tests de charge compatibles avec une simultanéité configurable. Vous devez tester la stabilité de votre service en fonction de la charge et de la simultanéité attendues.
- Si le service fonctionne mal, revenez à l'étape 1 pour améliorer les performances ou à l'étape 2 pour réduire la simultanéité. Si le service fonctionne correctement, revenez à l'étape 2 et augmentez la simultanéité.
Poursuivez l'itération jusqu'à trouver la simultanéité stable maximale.
Mettre en correspondance la mémoire et la simultanéité
Chaque requête traitée par votre service nécessite une certaine quantité de mémoire supplémentaire. Ainsi, lorsque vous augmentez ou diminuez la simultanéité, veillez également à ajuster votre limite de mémoire.
Éviter un état global modifiable
Si vous souhaitez tirer parti de l'état global modifiable dans un contexte de simultanéité, prenez des mesures supplémentaires au niveau du code pour garantir la sécurité de l'opération. Réduisez les conflits en limitant les variables globales à une initialisation et une réutilisation uniques, comme décrit ci-dessus dans la section relative aux Performances.
Si vous utilisez des variables globales modifiables dans un service qui traite plusieurs requêtes en même temps, veillez à appliquer des verrous ou des mutex pour éviter les conditions de concurrence.
Sécurité des conteneurs
De nombreuses pratiques générales de sécurité logicielles s’appliquent aux applications conteneurisées. Certaines pratiques sont soit spécifiques aux conteneurs, soit conformes à la philosophie et à l'architecture des conteneurs.
Pour améliorer la sécurité des conteneurs, procédez comme suit :
Utilisez des images de base activement gérées et sécurisées, telles que des images de base fournies par Google ou des images officielles de Docker Hub.
Appliquez les mises à jour de sécurité en effectuant régulièrement une recompilation des images de conteneur et un redéploiement de vos services.
Ne placez dans le conteneur que ce qui est nécessaire pour exécuter le service. Le code, les packages et les outils supplémentaires augmentent les failles de sécurité potentielles. Reportez-vous plus haut pour connaître l'impact sur les performances.
Implémentez un processus de compilation déterministe incluant des versions de logiciels et de bibliothèques spécifiques. Vous éviterez ainsi que du code non vérifié soit inclus dans votre conteneur.
Définissez votre conteneur pour qu'il s'exécute via un profil utilisateur autre que
root
à l'aide de l'instruction DockerfileUSER
. Un utilisateur spécifique peut être déjà configuré dans certaines images de conteneur.
Automatiser les analyses de sécurité
Activez l'analyse des failles pour détecter les failles de sécurité dans les images de conteneurs stockées dans Artifact Registry.
Vous pouvez également utiliser l'autorisation binaire pour vous assurer que seules les images de conteneurs sécurisées sont déployées.
Créer des images de conteneurs minimales
Les images de conteneurs volumineuses peuvent augmenter les failles de sécurité, car elles contiennent plus que ce dont le code a besoin.
Sur Knative serving, la taille de votre image de conteneur n'affecte pas le démarrage à froid ni le temps de traitement des requêtes, et n'est pas comptabilisée dans la mémoire disponible de votre conteneur.
Pour créer un conteneur minimal, envisagez de partir d'une image de base légère, telle que :
L'image de base sous Ubuntu est plus grande, mais elle est couramment utilisée avec un environnement de serveur plus complet et prêt à l'emploi.
Si votre service dispose d'un processus de compilation riche en outils, procédez plutôt par étapes pour préserver la légèreté du conteneur au moment de l'exécution.
Les ressources ci-dessous vous permettront d'en savoir davantage sur la création d'images de conteneur allégées :
- Bonnes pratiques Kubernetes : Pourquoi et comment créer des images de conteneurs réduites
- Sept bonnes pratiques en matière de création de conteneurs