Les fonctions et les conteneurs sont deux concepts clés dans le domaine du cloud computing. L’utilisation de fonctions et de conteneurs dans le cloud offre de nombreux avantages, notamment la scalabilité, l’efficacité, la portabilité et l’abstraction de l’infrastructure, ce qui permet aux développeurs de se concentrer sur le code de l’application plutôt que sur la gestion de l’infrastructure. Cependant le choix entre les fonctions et les conteneurs dépend largement des exigences spécifiques des applications.
Les fonctions, dans le contexte du cloud, font généralement référence aux Fonctions en tant que Service (FaaS). Les fonctions sont idéales pour les applications événementielles qui ont des charges de travail intermittentes et imprévisibles. Elles permettent une mise à l’échelle automatique et vous ne payez que pour le temps d’exécution de la fonction. Si les solutions FaaS possèdent un temps de démarrage très faible (moins d’une seconde), il existe aussi des limitations en ressources (CPU/RAM) et en temps d’exécution, donc elles ne sont pas idéales pour de lourds travaux applicatifs ou des applications à longue durée d’exécution. Le modèle FaaS est un service cloud où les applications sont développées, déployées et gérées à travers des fonctions isolées qui sont déclenchées par des événements spécifiques au CSP (Cloud Service Provider). Ces déclencheurs et certaines contraintes logicielles (comme les solutions nécessaires au déploiement du code) introduisent néanmoins un vendor-locking. Enfin, les équipes rencontrent au-delà d’une certaine taille critique une complexité dans la surveillance et le débogage qui peuvent être plus complexes que dans les modèles traditionnels en raison de la difficulté à corréler les événements et de la nature éphémère des fonctions. Des exemples populaires de FaaS incluent AWS Lambda, Google Cloud Functions et Azure Functions.
D’autre part, les conteneurs sont une méthode d’encapsulation et de déploiement d’applications dans un environnement isolé, connu sous le nom d’environnement de conteneur. Ils sont légers et portables, ce qui signifie qu’ils peuvent être exécutés de manière cohérente sur différentes plateformes et environnements. Cependant, ils sont souvent en permanence en exécution (nativement, le nombre d’instance ne tombe jamais à zéro) et réservent des capacités d'infrastructure qui sont alors facturées malgré leur non-utilisation effective. Les conteneurs “traditionnels” sont généralement plus lents au démarrage que les fonctions (plusieurs secondes, car ils ne sont pas développés/optimisés dans ce but), et ce délai peut même augmenter à plusieurs minutes si il est nécessaire de déployer une nouvelle machine virtuelle (“nœud”) pour exécuter de nouveaux conteneurs. De plus, les conteneurs nécessitent une gestion plus complexe et une orchestration, généralement à l’aide d’outils comme Kubernetes, pour gérer et automatiser le déploiement, le dimensionnement et la gestion des applications conteneurisées; Ces outils d’orchestration présentent une difficulté opérationnelle pour les équipes en charge de l’infrastructure, mais elles permettent d’accéder à une solution industrielle, scalable et open-source maintenant largement reconnue en production par les entreprises.
Chaque approche possède donc ses propres cas d’utilisation optimale et peut ne pas être adaptée à toutes les situations. Surtout les fonctions et les conteneurs correspondent à deux cas d'utilisations à priori opposés car les conteneurs permettent d’exécuter des charges de travail en continu et potentiellement lourdes, alors que les fonctions vont traiter des demandes intermittentes et à partir de d'événements consommés à la demande.
Dans les coulisses des CSP, il est cependant intéressant de noter que les CSP se basent sur des services conteneurisés optimisés pour déployer les fonctions : Google Cloud Functions se base sur Cloud Run, et Azure Functions propose plusieurs options d'hébergement dont Azure Container Apps.
La solution Knative propose de rendre largement accessible l’utilisation commune des conteneurs et des fonctions, en permettant à ces dernières de s’exécuter dans un environnement basé sur une architecture événementielle (non réservée aux seules fonctions) et intégré au sein des clusters Kubernetes. Dans cet article je vous propose une visite guidée de cette solution et ses apports aux spécificités propres aux fonctions et aux conteneurs. Knative est un projet CNCF open-source.
Fonctions Knative
https://knative.dev/docs/functions/
Il est nécessaire d’installer sur le cluster Kubernetes hôte les composants knative-serving qui permettront d’exécuter les fonctions. Afin de supporter le mécanisme de réveil des fonctions exécutées par Knative, l’installation de ces composants s’accompagne d’un networking layer. Kourier, développé par Knative, est celui proposé par défaut; une version allégée d’Istio et Contour sont également supportés par Knative.
Référence : Install Serving with YAML - Knative
Knative propose un CLI (Command Line Interface), func, permettant de construire des fonctions à partir de plusieurs langages (Node.js, Python, Go, Quarkus, Rust, Spring Boot & TypeScript). A partir d’un template fourni par Knative et adapté à votre langage de programmation, il vous sera possible de construire vos fonctions basée sur des événements http ou adapté au format cloudevents (certains langages comme Python se voient proposer en complément d’autres formats tels que flask).
Voici un exemple de template Node.js et basé sur des événements http :
Un premier avantage de Knative est de ne pas nécessiter l’exécution de bibliothèque spécifique pour fonctionner. Une fois votre code prêt, quelques étapes vont nous permettre de déployer notre fonction sur un cluster Kubernetes créé au préalable.
Principale étape spécifique à la création de fonctions avec Knative, la commande ci-dessous va générer les artéfacts nécessaires à la conteneurisation de votre fonction.
Les étapes suivantes correspondent au build de l’image docker et de son envoi sur un container registry distant.
Enfin, il reste à déployer la fonction sur le cluster Kubernetes. Cette étape peut être manuelle en utilisant encore le CLI fourni par Knative.
Dans un contexte d’automatisation, voici le fichier YAML équivalent permettant de déployer notre application d’exemple.
Il est possible d’ajouter de nombreuses propriétés accessibles aux Pods telles que des volumes, livenessProbe, readinessProbe, securityContext, resource requests et limit, etc.
Référence : Serving API - Knative
Pour tester votre fonction nouvellement déployée, voici quelques étapes utiles. L’exemple ci-dessous utilise le networking-layer Kourier avec un DNS par défaut (example.com).
Knative crée donc un objet KService qui représente la fonction déployée par le YAML précédent et automatiquement associé à un ingress (Ready = True).
On peut constater qu’un pod (et un sidecar nécessaire à Knative) est démarré au déploiement du KService. Comme il s’agit d’une fonction, si aucune requête entrante n’arrive, alors le nombre de pod va automatiquement tomber à zéro.
Alors que la fonction est dans un état d’attente avec zéro pod déployé, si une requête entrante arrive sur le service, alors la fonction va automatiquement démarrer pour traiter la requête.
Une fois la requête d’éveil effectuée et que le pod est Running, toute requête supplémentaire sera traitée par le même pod (voir il est possible de configurer pour augmenter le nombre de replicas en cas de forte sollicitation). Le temps d’éveil de la fonction est aussi plus court que lors de sa première création afin de traiter la requête le plus rapidement possible. Enfin, après une minute de non-sollicitation, le nombre de pod va automatiquement descendre à zéro.
Un point d’attention relevé est le temps de démarrage de la fonction/du pod en cas de forte sollicitation et si les nœuds du cluster Kubernetes sont déjà à leur maximum de capacité. Auquel cas nous explorons Karpenter pour accélérer la mise à disposition de nouveaux nœuds.
La partie Knative Serving, permettant l’exécution de fonctions sur Kubernetes, est une alternative prometteuse pour échapper au vendor-locking des CSP et elle constitue même une solution technologique intéressante des équipes déjà habituées à déployer des solutions conteneurisées. Si la performance est moins bonne que les services FaaS des CSP, capables de démarrer en moins d’une seconde, Knative est une solution viable pour la majorité des cas d’usage n’ayant pas des contraintes de temps de démarrage trop élevées.
Événements Knative
https://knative.dev/docs/eventing/
Il est nécessaire d’installer sur le cluster Kubernetes hôte les composants knative-eventing. Ces composants nécessitent les composants knative-serving présentés ci-dessus.
Référence : Installing Knative Eventing using YAML files
Knative permet de mettre en place des architectures événementielles au sein des clusters Kubernetes. Il est possible de créer des producteurs d’événements (“sources”) et des consommateurs (“sinks”), compatibles avec les composants natifs de Kubernetes et les composants spécifiques à Knative (liste complète des sources d’événements). Les événements échangés entre les composants utilisent le protocole HTTP et se conforment aux spécifications cloudevents.
Pour présenter cette fonctionnalité, voici un exemple permettant de mettre à jour les droits RBAC d’un cluster Kubernetes lors du tagging d’un namespace (ce qui nous servira d’événement) pour permettre à l’équipe associée au tag d’accéder à ce namespace en particulier. Lors de la modification du Namespace en ajoutant le tag, Knative va détecter le changement et appeler une application conteneurisée en transmettant l’événement sur le format cloudevents.
Voici la configuration Knative qui permet d’être à l’écoute d’un changement sur la ressource Namespace et qui va appeler un service qui sera décrit ci-après.
Nous devons donc créer une application qui va recevoir l’événement au format cloudevents puis appliquer du code (non détaillé dans cet article) pour faire les modifications sur le cluster Kubernetes.
Cette application est déployée et associée au Service Kubernetes sous le nom de rbac-management-sink.
A l'exécution, lors du tagging d’un namespace, voici la trace de l’événement transmis en paramètre :
Knative Eventing permet donc de construire des applications réagissant à des événements issus du cluster ou externes, mais aussi de construire des applications composées de plusieurs micro-services et sur une architecture événementielle au sein d’un cluster Kubernetes et (potentiellement) sans dépendance externe.
En conclusion de cet article, Knative ouvre la voie pour exécuter des fonctions et des architectures événementielles sans être systématiquement dépendant des solutions proposées par les CSP, en profitant de la portabilité des conteneurs et de Kubernetes. Cependant, et comme souligné par mon binôme Tanguy, Knative ne saurait remplacer les usages les plus avancés de ces fonctionnalités proposées par les CSP car elles sont plus matures et plus complètes. Cette solution est donc une nouvelle possibilité offertes au DSI pour résoudre certains cas d’usages spécifiques et souhaitant limiter/éviter le vendor-locking.