Les charges de travail Kubernetes à l'ère sans serveur: architecture, plates-formes et tendances – Serveur d’impression
Sommaire
Clés à emporter
- L'architecture de microservices a évolué pour devenir une architecture en nuage native, dans laquelle de nombreux problèmes d'infrastructure sont fournis par Kubernetes, en combinaison avec des abstractions supplémentaires fournies par des infrastructures de maillage de service et sans serveur.
- L'intérêt de Kubernetes réside dans son extensibilité pour les nouvelles charges de travail grâce à l'abstraction de Pod, qui prend également en charge l'émergence de nouveaux modèles d'application en nuage.
- Kubernetes prend en charge non seulement les applications sans état, mais également les charges de travail avec état, les singletons, les travaux par lots, les travaux cron, et même les charges de travail sans serveur et personnalisées via des CRD et des opérateurs.
- Les plates-formes sans serveur d’aujourd’hui ont des limitations importantes qui les empêchent d’être adoptées par les entreprises, qui ont de fortes contraintes d’interopérabilité et de portabilité.
- L’écosystème sans serveur évolue en explorant les formats d’emballage, d’exécution et les formats d’événement standard et ouverts. Les avancées dans ces domaines aplanit les différences entre les charges de travail natives sur le cloud et sans serveur, et poussent déjà les offres sans serveur vers des environnements ouverts, portables et interopérables.
Mon exposé sur les «modèles d'intégration dans un monde sans serveur» lors du Red Hat Summit 2019 a été inspiré par des clients qui ont demandé à quel point les charges de travail sans serveur s'inséraient dans le paysage actuel du cloud d'entreprise, dominé par Kubernetes.
Cet article résume cette discussion, mais avec moins d’intérêt pour l’intégration et une analyse plus approfondie des différentes charges de travail de Kubernetes et des limites des offres sans serveur. Si vous souhaitez assister à mon prochain webinaire sur un sujet connexe, veuillez consulter les détails à la fin de cet article.
Progressions des architectures distribuées
Avant de prédire l’orientation et l’adéquation de l’absence de serveur, nous devons analyser comment et pourquoi nous sommes arrivés ici.
Architecture monolithique
Ma carrière dans les projets d'intégration à grande échelle a commencé lorsque l'architecture orientée services (SOA) était l'architecture la plus populaire et qu'un bus de service d'entreprise (ESB) était son implémentation la plus courante.
La SOA était basée sur de bons principes, dont la plupart restaient valables: le développement selon le contrat, et des services faiblement couplés, composables et sans état, qui étaient également autonomes et réutilisables.
Les infrastructures ESB fournissent un bon ensemble de fonctionnalités, telles que la traduction de protocole, les connecteurs technologiques, les mécanismes de routage et d'orchestration, la gestion des erreurs et les primitives de haute disponibilité.
Progressions d'architectures distribuées
Le principal problème des SOA et des ESB était la centralisation, du point de vue tant architectural qu'organisationnel. Un principe important de la SOA était la réutilisation des services et des composants, ce qui a conduit à la création d'architectures de services en couches qui ont permis la réutilisation mais ont entraîné un couplage étroit des services d'architecture. Sur le plan organisationnel, les ESB appartenaient à une seule équipe, transformant ainsi l’intergiciel en un goulet d’étranglement technologique et organisationnel en termes d’évolutivité et, surtout, d’évolution rapide. Les spécifications complexes de l'industrie ont évolué lentement, définies par un petit groupe de personnes.
Microservices Architecture 1.0
Bien que la SOA fournisse une bonne base pour aborder la complexité au niveau de l’entreprise, la technologie évolue rapidement. Le modèle à source ouverte a permis une innovation plus rapide et est devenu le nouveau mécanisme de distribution et de normalisation de la technologie.
En parallèle, des pratiques de développement agiles telles que XP, Scrum et kanban ont abouti à un développement logiciel itératif, mais cette approche s'est heurtée aux architectures monolithiques existantes, incapables de gérer une conception incrémentale déployée rapidement. En conséquence, les fonctionnalités ont été développées en itérations de deux semaines mais n’ont été déployées en production qu’une ou deux fois par an.
L’architecture des microservices est apparue comme une panacée au bon moment, promettant de relever ces défis. L’architecture de Microservices a permis un changement plus rapide grâce à ses principes directeurs. Les services ont été modélisés autour d'un domaine métier, ce qui a permis de contenir le changement dans les limites du service plus efficacement que les services SOA réutilisables. Des services autonomes et pouvant être déployés indépendamment ont permis à chaque service d'évoluer et de s'adapter à son propre rythme.
Alors que ces nouveaux principes optimisaient la SOA pour l'ère des itérations rapides et de l'expérimentation, ils introduisaient également des défis en transformant chaque application en un système distribué. Par conséquent, les microservices doivent également être hautement automatisables, facilement observables, tolérants aux pannes et à tout ce qu'un composant de système distribué doit être. C’était un prix que tout le monde n’avait pas compris au début et n’étaient pas nécessairement prêts à payer.
L’architecture des microservices est née comme une réponse technologique à l’ère du développement et de l’expérimentation itératifs et rapides, mais nous avons construit la première génération de microservices en plus des outils archaïques, qui étaient difficiles à gérer. Au cours de l'ère ESB, la logique commerciale s'est infiltrée dans la plate-forme, provoquant toutes sortes de couplages. Au début de l'ère des microservices, nous avons constaté le contraire: trop de problèmes d'infrastructure se glissaient dans chaque microservice. Les premiers microservices avaient leurs propres défis; ils devaient faire leur propre recherche de service, gérer la configuration, la résilience du réseau, la distribution de journaux, etc. Avoir la capacité de créer des dizaines ou des centaines de nouveaux services ne signifiait pas forcément qu'une organisation était prête à les gérer et à les déployer en production avec leur outillage existant. Les processus et les outils associés pour la libération, la gestion et le traitement des services aux équipes opérationnelles ont dû être améliorés. Tout cela nous a poussés à l'ère Kubernetes.
Architecture native en nuage (a.k.a Microservices 2.0)
L’architecture de Microservices optimise l’architecture SOA pour une évolution rapide, mais elle y parvient en négociant la complexité du code pour la complexité opérationnelle. Ses défis ont conduit à l'adoption de conteneurs, qui ont repris l'industrie du jour au lendemain. Les conteneurs sont apparus comme un remède technique à la peine de déployer un grand nombre de microservices de manière uniforme et efficace, ce qui a permis à la pratique moderne du DevOps. Avec les conteneurs, nous pourrions empaqueter les applications et les exécuter dans un format compréhensible et utilisable par les équipes de développement et d'exploitation. Il était clair dès le début que la gestion de dizaines ou de centaines de conteneurs nécessiterait une automatisation, et Kubernetes est venu du ciel et a balayé toute la concurrence. Kubernetes a abordé les défis technologiques et les pratiques de DevOps ont traité les aspects culturels des microservices. Cet outil basé sur le cloud était si fondamental qu’il a provoqué l’émergence d’une deuxième génération de plates-formes de microservices.
Ce fut le début d'un nouveau changement, un transfert des responsabilités de l'infrastructure passant de la couche application à la couche plate-forme. Les plates-formes natives en nuage (initialement nombreuses mais avant tout Kubernetes) ont fourni des fonctionnalités telles que l'isolation et la gestion des ressources, le placement automatisé, le déploiement déclaratif, les sondes de santé et l'auto-réparation, la gestion de la configuration, la découverte de services, la mise à l'échelle automatique, etc. Ces fonctionnalités ont permis aux développeurs d'applications de se concentrer sur la logique métier et d'utiliser des fonctionnalités de plate-forme prêtes à l'emploi pour résoudre les problèmes d'infrastructure de manière uniforme.
À l'époque, j'avais comparé l'outillage populaire microservices 1.0 (Spring Cloud et Netflix OSS) avec l'outillage microservices 2.0 (Kubernetes, le standard de facto) et j'avais reçu des réactions mitigées de la part des lecteurs. Il s’agit aujourd’hui d’une transition plus largement comprise et acceptée, confirmée par la domination complète de Kubernetes en tant que plate-forme de gestion de microservices et par la dépréciation de nombreuses bibliothèques Netflix OSS de la génération précédente.
Mais tout cela fait partie de l'histoire. Explorons ce qui va suivre avec Kubernetes.
Le brillant de Kubernetes
Il existe de nombreux éléments fascinants de l’architecture de Kubernetes: les conteneurs fournissant le modèle commun d’emballage, d’exécution et d’isolation des ressources; le mécanisme de boucle de contrôle simple qui surveille l'état actuel des composants et le réconcilie avec l'état souhaité; les définitions de ressources personnalisées. Mais le véritable facilitateur pour l’extension de Kubernetes afin de prendre en charge diverses charges de travail est le concept de pod.
Un pod fournit deux ensembles de garanties. La garantie de déploiement garantit que les conteneurs d'un pod sont toujours placés sur le même noeud. Ce comportement présente des propriétés utiles, telles que permettre aux conteneurs de communiquer de manière synchrone ou asynchrone sur localhost, sur une communication interprocessus (IPC) ou à l'aide du système de fichiers local.
La garantie de cycle de vie du pod permet de s’assurer que les conteneurs d’un pod sont gérés en deux groupes: les conteneurs init et les conteneurs d’application. Les conteneurs Init sont les premiers; ils s'exécutent les uns après les autres, et uniquement si le conteneur précédent s'est terminé avec succès. Les conteneurs Init activent un comportement de pipeline séquentiel et un seul conteneur effectue chaque étape. D'autre part, les conteneurs d'applications fonctionnent en parallèle et sans aucune garantie de commande. Ce groupe de conteneurs permet au modèle de sidecar populaire d'étendre et d'améliorer la fonctionnalité de conteneurs préexistants dotés de fonctionnalités orthogonales.
Garanties de déploiement et de cycle de vie d'un pod
Le mécanisme de boucle de contrôle extensible, associé aux caractéristiques génériques du pod, permet à Kubernetes de gérer diverses charges de travail, y compris sans serveur. Examinons ces diverses charges de travail et voyons quels sont les cas d’utilisation appropriés pour chacune d’elles.
Workloads en nuage
Pour démontrer que Kubernetes est une plate-forme universelle capable de prendre en charge diverses charges de travail et cas d'utilisation, il est nécessaire d'explorer les différents types de charges de travail et leurs besoins.
Services Stateful
Commençons par une charge de travail la moins excitante, mais presque toujours présente dans les environnements d’entreprise: les services avec état. Les services avec état avec logique métier peuvent être transformés en services évolutifs sans état en déplaçant l'état dans des magasins de données externes. Une telle conception déplace les contraintes des services métier vers les sources de données, qui deviennent les composants avec état de l'architecture. Ces magasins de données sont généralement des bases de données relationnelles standard, des caches distribués, des magasins de valeurs-clés, des index de recherche, etc. La gestion de composants dynamiques distribués sur des infrastructures de cloud dynamiques requiert certaines garanties, telles que:
- Stockage persistant —État réside généralement sur un disque et les applications distribuées avec état nécessitent un stockage dédié et persistant pour que chaque instance stocke son état.
- Identifiant réseau stable – similaire à l'exigence de stockage, une application avec état distribué requiert une identité réseau stable. En plus de stocker des données spécifiques à une application dans l'espace de stockage, les applications avec état stockent également des détails de configuration, tels que le nom d'hôte et les détails de connexion de leurs homologues. Cela signifie que chaque instance doit être accessible via une adresse prévisible qui ne doit pas changer de façon dynamique, comme c'est le cas avec les adresses IP de pod dans un ReplicaSet.
- Identité stable – Comme nous pouvons le constater avec les exigences précédentes, les applications avec état en cluster dépendent fortement de chaque instance qui conserve son stockage de longue durée et son identité réseau. En effet, dans une application avec état, chaque instance est unique et connaît sa propre identité. Les principaux ingrédients de cette identité sont les coordonnées de stockage et de mise en réseau, qui ont toujours été vivaces. À cette liste, nous pourrions également ajouter l'identité / le nom de l'instance (certaines applications avec état exigent également des noms persistants uniques), qui dans Kubernetes serait le nom du pod.
- L'ordinalité – en plus d'une identité unique et durable, chaque instance d'une application avec état en cluster a également une position fixe par rapport aux autres qui comptent. Cet ordre a généralement une incidence sur la séquence d’agrandissement et de réduction des instances, mais il peut également servir de base à des algorithmes de hachage cohérents, à la distribution et à l’accès aux données, ainsi qu’au placement dans les comportements en cluster, tels que des verrous, des singletons ou des maîtres.
Une application à états distribuée sur Kubernetes
Ce sont exactement les garanties qu'un Kubernetes StatefulSet
des offres. UNE StatefulSet
fournit des primitives génériques pour la gestion des modules avec des caractéristiques avec état. Outre les déploiements typiques de ZooKeeper, Redis et Hazelcast avec un StatefulSet
D'autres cas d'utilisation incluent les courtiers de messages et même les gestionnaires de transactions.
Par exemple, le gestionnaire de transactions Narayana utilise StatefulSet
pour vous assurer qu'il ne manque aucun journal JTA lors de la réduction de services à l'aide de transactions distribuées. Apache Artemis message broker s'appuie sur StatefulSet
pour drainer les messages lors de la réduction d'un courtier de messages en cluster. le StatefulSet
est une abstraction générique puissante, utile pour les cas d'utilisation complexes avec état.
Global Singletons
Le motif singleton du Gang of Four est un concept ancien et bien compris. L'équivalent dans le monde distribué dans le cloud est le concept d'un composant singleton (tout ou partie du service) qui est un singleton global (parmi tous les services distribués) mais toujours hautement disponible. Le cas d'utilisation de ce type de charge de travail découle généralement des contraintes techniques d'autres systèmes avec lesquels nous devons interagir, par exemple, des API, des sources de données et des systèmes de fichiers n'autorisant qu'un seul client (singleton) à la fois. Un autre cas d'utilisation est lorsque l'ordre des messages doit être préservé par les services consommateurs, le limitant à un singleton. Kubernetes dispose de quelques options pour prendre en charge ce type de cas d'utilisation.
L'option la plus simple consiste à faire appel à Kubernetes pour exécuter une seule instance d'un service. Nous pouvons facilement y parvenir en utilisant un ReplicaSet
ou StatefulSet
avec des répliques = 1. La différence entre les deux alternatives est de savoir si vous avez besoin d’un singleton fortement cohérent avec «au plus une» garantie ou d’un singleton faible avec «au moins une» garantie. UNE ReplicaSet
favorise la disponibilité et donne la priorité au maintien d’une instance unique («au moins une» sémantique). Cela peut parfois entraîner l’exécution simultanée de plusieurs instances, par exemple lorsqu’un nœud est déconnecté du cluster et ReplicaSet
démarre un autre pod sur un nœud différent sans confirmer que le premier pod est arrêté. UNE StatefulSet
favorise la cohérence par rapport à la disponibilité et fournit une sémantique «au plus un». Si un noeud est déconnecté du cluster, il ne démarrera pas le pod sur un noeud sain. Cela ne peut se produire qu’après qu’un opérateur a confirmé que le nœud déconnecté ou le pod est réellement arrêté. Cela peut parfois entraîner une interruption du service, mais jamais plusieurs instances en même temps.
Il est également possible d'implémenter un singleton autogéré à partir de l'application. Alors que dans les cas d’utilisation précédents, l’application n’était pas consciente d’être gérée en tant que singleton, un singleton autogéré garantit que seul un composant est activé, quel que soit le nombre d’instances de service (pods) démarrées. Cette approche singleton nécessite une implémentation spécifique à l'exécution pour acquérir un verrou et agir en tant que singleton, mais elle présente quelques avantages. Premièrement, il n’ya pas de risque de mauvaise configuration accidentelle et, si on augmente le nombre de répliques, il n’ya toujours qu’un seul composant actif à la fois. Deuxièmement, il permet la mise à l'échelle des services tout en permettant de garantir le comportement de singleton pour une partie seulement du service, telle qu'un noeud final. Ceci est utile lorsque la seule partie d'un microservice, et non le tout, doit être un singleton, en raison de limitations techniques externes liées à une opération ou à un noeud final spécifique. Un exemple d’implémentation pour ce cas d’utilisation est la fonctionnalité singleton du connecteur Kubernetes d’Apache Camel, qui peut utiliser un Kubernetes. Carte de configuration
en tant que clé distribuée et n'active qu'un seul consommateur Camel parmi plusieurs services Camel déployés dans Kubernetes.
Charges de travail Singleton sur Kubernetes
Les singletons sont un autre type de charge de travail qui apparaît en petit nombre, mais ils sont assez communs pour être appelés. Singleton et la haute disponibilité sont deux exigences contradictoires, mais Kubernetes est suffisamment souple pour offrir les deux avec des compromis acceptables.
Travaux par lots
Le cas d'utilisation de travaux par lots convient à la gestion de charges de travail traitant des unités de travail atomiques isolées. Dans les primitives Kubernetes, il est implémenté en tant qu'abstraction de travail, qui exécute de manière fiable des pods de courte durée jusqu'à leur achèvement dans un environnement distribué.
Batch et charges de travail récurrentes sur Kubernetes
Du point de vue du cycle de vie, les charges de travail par lots présentent peu de caractéristiques similaires aux charges de travail asynchrones sans serveur, car elles sont concentrées sur une seule opération. Elles sont de courte durée et durent jusqu'à la fin de la tâche. Toutefois, bien que les charges de travail basées sur des tâches soient de nature asynchrone, elles ne prennent pas directement en compte les utilisateurs et ne sont pas directement lancées en réponse à leurs demandes. Ils savent généralement où récupérer les données d'entrée et où écrire le résultat. Si le travail a une dimension temporelle, c’est-à-dire qu’il est planifié, son exécution est déclenchée régulièrement par un événement temporel.
Workloads sans état (a.k.a 12-Factor-Apps)
Les charges de travail sans état sont le type de charge de travail le plus utilisé sur Kubernetes. Il s'agit de l'application typique à 12 facteurs ou du système basé sur microservices géré sur Kubernetes à l'aide d'un ReplicaSet. En règle générale, un ReplicaSet gérera plusieurs instances d'un tel service et utilisera différentes stratégies de mise à l'échelle automatique pour mettre à l'échelle de telles charges de travail horizontalement et verticalement.
Workloads sans état avec découverte de service sur Kubernetes
Une exigence commune pour les services gérés par un ReplicaSet
est la découverte de service et l’équilibrage de charge. Et ici, Kubernetes offre une variété d’options prêtes à l’emploi.
Mécanismes de découverte de service sur Kubernetes
Le point ici est que, même s'il existe divers mécanismes de découverte de service permettant de détecter de manière dynamique des instances de pod saines et malsaines, les différents types de service sont de nature relativement statique. La primitive Service Kubernetes n'offre pas de capacités de surveillance et de décalage du trafic dynamiques. C'est là qu'un maillage de service entre en scène.
Maille de service
L'un des défis de la mise en œuvre d'un système basé sur microservices est la création de fonctionnalités de base ne faisant pas partie de la logique métier, telles que la communication résiliente, le traçage, la surveillance, etc. Cette logique résidait autrefois dans la couche ESB centrale et doit maintenant être séparés et répétés parmi les clients intelligents de microservices. La technologie de service-maillage vise à résoudre ce problème en offrant des fonctionnalités réseau améliorées supplémentaires telles que:
- Routage du trafic – tests A / B, déploiements par étapes.
- Résilience – tentatives, disjoncteurs, limites de connexion, contrôles d'intégrité.
- Sécurité – authentification, autorisation, cryptage (mTLS).
- Observabilité – métriques, traçage.
- Test – injection de faute, mise en miroir du trafic.
- Indépendance de la plate-forme – polyglotte, permettant la configuration à l'exécution.
Si nous examinons de près ces fonctionnalités, nous noterons un chevauchement important des fonctionnalités fournies par les frameworks d’intégration.
Chevauchement des responsabilités entre services et intégration
Les opinions divergent sur la question de savoir si transférer toutes ces responsabilités en dehors des services est une bonne approche ou non. Bien que les responsabilités en matière de réseau passent clairement de la couche application à la plate-forme native en nuage commune, toutes les responsabilités en matière de réseau ne sortent toutefois pas de l'application:
- Un maillage de service convient au routage de trafic basé sur la connexion et une infrastructure d'intégration depuis un service convient au routage basé sur le contenu.
- Un maillage de service peut effectuer une traduction de protocole et un framework d'intégration peut effectuer une transformation de contenu.
- Un maillage de service peut faire un lancement à l’ombre et un framework d’intégration fait des écoutes téléphoniques.
- Un maillage de service effectue un cryptage basé sur la connexion et une infrastructure d'intégration peut effectuer un cryptage du contenu.
Certaines exigences sont mieux gérées dans le service et certaines de l'extérieur en utilisant un maillage de service. Et certains doivent encore être gérés sur les deux couches: un délai de connexion peut être configuré à partir de la couche maillage de service, mais il doit encore l'être à partir du service. Cela est vrai pour d'autres comportements tels que la récupération via des tentatives et toute autre logique de traitement des erreurs. Les outils de maillage de services, Kubernetes et autres services en nuage sont les outils actuels, mais la responsabilité ultime de la fiabilité et de l'exactitude d'une application incombe à la mise en œuvre du service et à ses équipes de développement et de conception. Cela ne change pas.
La technologie de service-maillage souligne également la principale différence entre les microservices des première et deuxième générations: le transfert de certaines responsabilités opérationnelles vers la plate-forme. Kubernetes transfère les responsabilités de déploiement vers la plate-forme et les maillages de service déplacent les responsabilités de réseau vers la plate-forme. Mais ce n'est pas l'état final. ces changements ne font que créer un monde sans serveur où le déploiement et une évolutivité instantanée basée sur le trafic sont indispensables.
Concepts sans serveur
Tout est question de perspective
Pour discuter des caractéristiques du serveur sans serveur, je vais utiliser la définition du groupe de travail sans serveur de la CNCF (Cloud Native Computing Foundation), car il s’agit de l’une des définitions les plus largement approuvées par de nombreux éditeurs de logiciels:
L'informatique sans serveur fait référence au concept de création et d'exécution d'applications ne nécessitant pas de gestion de serveur. Il décrit un modèle de déploiement à granularité plus fine dans lequel les applications, regroupées sous forme d'une ou plusieurs fonctions, sont téléchargées sur une plate-forme et exécutées, mises à l'échelle, facturées en réponse à la demande exacte requise.
Si nous considérons cette définition en tant que développeurs qui écrivent du code qui bénéficiera d'une plate-forme sans serveur, nous pourrions résumer ce dernier en tant qu'architecture qui permet «d'exécuter des fonctions plus détaillées à la demande sans gestion de serveur». Serverless est généralement considéré du point de vue du développeur, mais il existe également un autre point de vue moins discuté. Chaque plate-forme sans serveur a des fournisseurs qui gèrent la plate-forme et les serveurs: ils doivent gérer des unités de calcul à granularité grossière et leur plate-forme engage des coûts 24 h / 24, 7 j / 7, quelle que soit la demande. Les fournisseurs sont les équipes derrière AWS Lambda, Azure Functions et Google Cloud Functions ou l'équipe de votre société qui gère Apache OpenWhisk, Kubernetes avec Knative ou autre chose. Dans les deux cas, les fournisseurs permettent aux développeurs d'utiliser le calcul et le stockage en tant que type de charge de travail sans notion de serveur. Selon les facteurs organisationnels et commerciaux, les fournisseurs peuvent être une autre équipe / un même service (imaginez une équipe Amazon utilisant AWS Lambda pour ses besoins) ou une autre organisation (lorsqu'un client AWS utilise Lambda et d'autres services). Quels que soient les accords commerciaux entre fournisseurs et consommateurs, les consommateurs n’ont aucune responsabilité vis-à-vis des serveurs; les fournisseurs font.
Architecture sans serveur
La définition ci-dessus ne concerne que «l'informatique sans serveur». Mais l’architecture d’une application est composée de calculs et de données combinées. Une définition plus complète de l'architecture sans serveur est celle qui inclut à la fois les calculs sans serveur et les données sans serveur. En règle générale, ces applications intègrent des services hébergés dans le nuage pour gérer l’état et la logique générique côté serveur, tels que l’authentification, les passerelles d’API, la surveillance, les alertes, la journalisation, etc. Nous désignons généralement ces services hébergés par «back-end en tant que service». ”(BaaS) – pensez à des services tels que DynamoDB, SQS, SNS, Passerelle API, CloudWatch, etc. Rétrospectivement, le terme“ service ”plutôt que“ sans serveur ”aurait pu être une description plus précise de l'architecture résultante. Mais tout ne peut pas être remplacé par des services tiers; Si tel était le cas et qu’il existait un service pour votre logique d’entreprise, vous ne seriez pas en affaires! Pour cette raison, une architecture sans serveur comporte généralement également des «éléments en tant que service» (FaaS) qui permettent l'exécution d'un calcul personnalisé sans état, déclenché par des événements. L'exemple le plus populaire ici est AWS Lambda.
Une architecture complète sans serveur est composée de BaaS et de FaaS, sans la notion de serveur du point de vue du consommateur / développeur. Pas de serveurs à gérer ou à mettre en place signifie également une tarification basée sur la consommation (pas de capacité mise à disposition), une mise à l'échelle automatique intégrée (jusqu'à une limite), une disponibilité et une tolérance aux pannes intégrées, des correctifs intégrés et un renforcement de la sécurité (avec prise en charge intégrée) règles de sécurité), la surveillance et la journalisation (en tant que services payants supplémentaires), etc. Tout cela est consommé par les développeurs sans serveur et fourni par des fournisseurs sans serveur.
Pure Serverless
Si l'architecture sans serveur sonne si bien, pourquoi ne pas avoir une architecture sans serveur pure avec tous les composants 100% sans serveur et aucune notion de serveurs? La raison suivante peut être expliquée par la citation suivante d'Ellen Ullman: "Nous construisons nos systèmes informatiques de la même manière que nous construisons nos villes: au fil du temps, sans plan, au-dessus des ruines." Un système d'entreprise est comme une vieille ville; En général, il existe depuis plus d'une décennie et c'est de là que proviennent sa valeur et sa criticité. C'est ce qui le rend "entreprise". Imaginez Londres, une ville qui existe depuis plus de 2 000 ans, avec son réseau de tubes centenaire, ses rues étroites, ses palais, ses quartiers victoriens et ses systèmes d’alimentation; un système aussi complexe en cours d'utilisation ne pourra jamais être totalement remplacé par un nouveau, et il sera toujours en cours de restauration et de renouvellement (refactorisation, mises à niveau, migrations et reformatage en termes informatiques). Dans de tels systèmes, l'état de changement est la norme; l'état de mélange des existences anciennes et nouvelles est la norme. C'est ainsi que ces systèmes sont supposés exister.
Serverless 1.0
La technologie des conteneurs existe depuis de nombreuses années sous différentes formes, mais Docker l’a popularisée et Kubernetes en a fait la norme pour les déploiements. De même, les technologies sans serveur existent depuis de nombreuses années, mais AWS Lambda les a rendues populaires et nous n’avons pas encore annoncé qui les ferait passer au niveau supérieur.
Serverless 1.0 était essentiellement défini par AWS et représenté par AWS Lambda pour le composant FaaS et par d'autres services AWS tels que API Gateway, SQS, SNS, etc. pour les composants BaaS. AWS définissant les tendances et permettant à Google et Azure de rattraper leur retard, voici certaines des caractéristiques de la génération sans serveur de la génération actuelle qui ne sont pas idéales et qui pourraient éventuellement être améliorées.
Le modèle d'exécution non déterministe a:
- cycle de vie des conteneurs imprévisible et sémantique de réutilisation ayant des implications sur le démarrage à froid;
- les contraintes sur le modèle de programmation, qui influencent la logique d'initialisation du code, provoquent des fuites de rappel, créent des coûts d'appel récursifs additifs, etc.
- cycles de vie des ressources imprévisibles tels que le stockage de fichiers / tmp;
- limites arbitraires sur la mémoire, les délais d'attente, la charge utile, le package, le système de fichiers temporaire et les variables env; et
- un modèle d'exécution global non normalisé dans l'ensemble du secteur, avec des contraintes non normalisées qui influencent le modèle de programmation pour les fournisseurs spécifiques sans serveur.
Le support d'exécution limité signifie:
- La pile logicielle sans serveur, qui associe un système d'exploitation, une exécution linguistique et une version de bibliothèque, est limitée à une version unique d'un système d'exploitation, d'un JDK et de bibliothèques d'application (telles que AWS SDK).
- La politique de prise en charge de la plate-forme dicte souvent les conditions dans lesquelles les composants de la pile sans serveur peuvent être déconseillés et mis à jour, obligeant ainsi tous les utilisateurs sans serveur à respecter des délais rigoureux au même rythme.
- La programmation des API peut causer des problèmes. Bien que j'aie bien utilisé AWS SDK pour d'autres services, je n'aime pas le fait que toutes les fonctions doivent être fortement couplées au package com.amazonaws.services.lambda.runtime et à son modèle de programmation.
- Nous utilisons des emballages non standard. L'utilisation du répertoire .zip, uber-JAR ou lib avec un modèle de couches et de dépendances AWS personnalisé ne me permet pas de penser que le package est à l'épreuve du temps ou qu'il fonctionnera sur des plates-formes sans serveur.
- Les variables d'environnement personnalisées (commençant par AWS_) ne fonctionneraient pas sur d'autres plates-formes sans serveur.
Formats de données propriétaires
Les formats de données propriétaires sont un obstacle. Les événements constituent le principal mécanisme de connectivité pour les fonctions d'une architecture sans serveur. Ils connectent chaque fonction avec toutes les autres fonctions et BaaS. Ce sont en réalité l'API et le format de données pour les fonctions. L'utilisation d'événements définis dans le package com.amazonaws.services.lambda.runtime.events dans toutes les fonctions garantit une interoaparabilité nulle.
Pas de support Java
Bien qu'il existe un environnement d'exécution Java pour AWS Lambda, il existe une telle incompatibilité entre Java et Lambda que même AWS ne le recommande pas. Dans son exposé intitulé «Inside AWS: choix technologiques pour les applications modernes», il suggère d’utiliser Go et Python à la place. Plutôt que d’essayer de rééduquer des millions de développeurs Java et de modifier un écosystème de millions de bibliothèques Java, je m'attendais à ce que les fournisseurs sans serveur fassent mieux et améliorent leurs temps d’exécution. Java est déjà aussi léger et rapide que Go (si ce n’est encore mieux), donc construire sans serveur avec ce langage est inévitable.
Implications possibles
En résumé, la combinaison d’un modèle d’exécution non déterministe et non normalisé, d’un environnement d’exécution propriétaire, de formats de données propriétaires, d’une API propriétaire et de l’absence de prise en charge de Java signifie que tous ces problèmes s’infiltrent dans le code de l’application et ont une incidence sur la façon dont nous implémentons la logique d'entreprise. C’est la délégation de contrôle ultime d’une organisation à une autre. Parce que sans serveur tel qu'il est aujourd'hui, fournit des artefacts au moment de la construction – sous la forme de SDK et de formats d'emballage, de formats d'événement et de piles de logiciels – et fournit l'environnement d'exécution, les consommateurs sans serveur s'engagent à respecter la politique de support et les limitations propriétaires imposées par le fournisseur. Les utilisateurs s’engagent à respecter et à suivre le langage d’exécution, les kits de développement, les mises à niveau et les dépréciations du fournisseur. Ils s'engagent à respecter ces termes en écrivant des fonctions qui n'ont aucune interopérabilité sur les plates-formes sans serveur. Si nous utilisons BaaS pour tout, et que nous venons de coupler la logique commerciale de notre organisation, écrite en fonctions, au modèle d’exécution, au runtime, aux API et aux formats de données propriétaires, nous n’avons nulle part où aller. Même si nous ne voulons peut-être pas aller ailleurs, cette option est importante pour certains.
Le couplage et le verrouillage en eux-mêmes ne sont pas mauvais, mais le coût élevé de la migration l'est. L’utilisation d’AMI AWS, d’AWS RDS, de projets Open Source populaires en tant que services gérés et même de SQS sont des exemples de consommateurs qui ne craignent pas d’être bloqués, la migration vers un autre service ou fournisseur constituant une alternative viable. Ce n’est pas la même chose que de coupler notre logique métier à des technologies immatures sans serveur et aux caractéristiques du fournisseur sans serveur, en particulier. Dans ce cas, l’effort de migration consiste en une réécriture et en un test complets de la logique d’entreprise et du code collé, qui sont particulièrement coûteux compte tenu de la nature hautement distribuée de l’architecture sans serveur.
Microservices trades code complexity for operational complexity. Serverless trades control for velocity. Choose a modern architecture, but read the small print. Every architecture choice is a trade-off.
Serverless 1.5
AWS has done an amazing job of bringing serverless to where it is but it would be sad if the current state of serverless is its high point. It would also be sad if it is only AWS that is able to innovate within serverless and define its future. Considering that AWS’s background in the open-source ecosystem is relatively limited, it would be a stretch to expect AWS to standardize the serverless paradigm, affecting the whole industry on such a fundamental level. The AWS business model and market positions are good for identifying market trends and initial closed innovation, but the open-source model is better for generalization, standardization, and non-forceful industry-wide acceptance. I expect the next generation of serverless to be created using the open-source model with a wider collaboration across the industry, which will help its adoption and interoperability. That process has started, and the industry is slowly exploring interoperable and portable alternatives of the proprietary serverless offerings of today.
Let’s discuss some of the industry trends, in no particular order, that I believe will drive and influence the serverless technology of tomorrow.
Uniform Packaging and Execution Model
Containers are established as the industry standard for application packaging and runtime. A containerized application combined with a powerful orchestration engine enables a rich set of workloads, as we have seen earlier. There is no reason for serverless workloads to be an exception, as that would move us back to a mix of packaging formats and execution models. Knative is an open, joint effort from multiple vendors that is challenging the status quo by offering serverless characteristics (scaling to zero, autoscaling based on HTTP requests, subscription, delivery, binding, and management of events) to container-based workloads on Kubernetes. Container-based packaging and Kubernetes-based execution would allow an open execution model that could be standardized across multiple serverless providers. It would enable a richer set of runtimes with better support for Java, custom software stacks, limits, and customization possibilities.
Some might argue that including the language runtime and the event handler in the function package is not FaaS per the original definition of serverless but that is an implementation detail and this is a much-needed option for the serverless of tomorrow.
Industry-Accepted Event Formats
The serverless architecture is event-driven by definition, and events play a central role. The more event types there are in a serverless environment, the richer the developer experience and the more logic that can be replaced with off-the-shelf services. But that comes with a price as the business logic becomes coupled with event format and structure. While you can read AWS best practices for separating core business logic and the event-handling logic into separate methods, it is far from decoupling. This coupling of the business logic with the serverless platform’s data formats is preventing interoperability. CloudEvents is an effort to create standardized event formats that would operate across all serverless platforms. Apart from being an awesome idea, it has a huge industry interest including that of AWS, which is probably the ultimate validation of its significance and adoption potential.
Portability and Interoperability
Once there is a standard packaging format and standard events, the next level of freedom is the ability to run serverless workloads cross serverless providers on a public or private cloud, on premises, or on the edge, and mix and match all of it into a hybrid as needed. A function should be runnable on multi-cloud, hybrid cloud, any cloud, non-cloud, or mixed, and should only require a few configurations and mapping. In the same way we used to write Java applications to implement abstract interfaces and deploy them to different web containers, I want to be able to write my functions for a non-proprietary API, events, and programming model, and deploy them to any serverless platform, and for it to behave in a predictable and deterministic manner.
In addition to portability, I want to see interoperability, with functions able to consume events from any platform regardless of where the function is running. Projects such as KEDA let us run custom functions, such as Azure Functions, in response to AWS, Azure, and other event triggers. Projects such as TriggerMesh allow us to deploy AWS Lambda-compatible functions on top of Kubernetes and OpenShift. These are signs that the functions of the future will be portable and interoperable at multiple levels: packaging, execution environment, event formats, event sources, tooling, etc.
Treat Java as First-Class
While serverless workloads are suitable for many use cases, preventing the use of Java, the most popular programming language of enterprises, is a major limitation. Thanks to Substrate VM and frameworks such as Quarkus, Java is already light, fast, cloud native, and serverless friendly. And there are les indications that such Java runtimes soon will be available for serverless too, including for AWS Lambda, hopefully.
Containerized workloads with serverless characteristics, function portability and interoperability with standardized events, and ultra-light and fast Java runtimes created for cloud-native and serverless environments are all signals that serverless is about to change. I don’t yet want to label these indicators as “second-generation serverless” but it is not first-generation serverless, so 1.5 feels about right.
I remember when many thought that Cloud Foundry had won the PaaS war, but then Kubernetes happened. Many now claim that AWS Lambda has won the FaaS war. I hope Kubernetes (or something better) proves them wrong.
Serverless Workloads
We saw how microservices architecture improved the deployment cycles for monolithic applications by modeling services around business domains and encapsulating the change within the services. A naive description of serverless would be to represent this as even smaller microservices, where every operation is a function. While that is technically possible, that would be the worst of both architectures, leading to a large number of functions calling each other in a synchronous manner without gaining any benefit from the resulting architecture.
The value of a microservice comes from the fact that it can encapsulate complex business-domain logic and persistence logic behind a series of request/response-style operations with a web-based API (typically REST style). On the other hand, serverless and functions focus on events and triggers. While functions can be placed behind an API gateway and act in request/response style, the API is not intended to be the primary interface: events and triggers are. Serverless applications tend to work best when the application is asynchronous (unidirectional fire-and-forget style, rather than request/response) and connects through queues or other data and event sources. As a result, each function is intended to perform only one action and should avoid directly calling other functions directly, and write its result to an event store. Due to the execution model, functions are short-lived and are supposed to be used with other serverless data sources that are not connection oriented, as is the case with the typical RDBMS, be light in terms of deployment size, and have fast startup. All of the following use cases make serverless more suitable, as it acts as glue code that connects various event-driven systems:
- on-demand functionality such as batch processing, stream processing, and extract-transform-load (ETL);
- task scheduling for divisible work performed for a short time, such as batch jobs;
- event-driven architecture that executes logic in response to data-source changes;
- handling non-uniform traffic such as inconsistent traffic that doesn’t happen often or traffic with unpredictable load;
- general-purpose “glue” code in operations;
- continuous-integration pipelines with on-demand resources for build jobs; et
- automating operational tasks such as triggering actions or notifying a person on call when an incident happens.
I discussed some of the major innovations that are happening in the serverless world but didn’t describe what they would look like on Kubernetes. Many efforts have tried to bring serverless to Kubernetes, but the project with the widest industry support and best chance of success is Knative. The primary goal of Knative is to offer a focused API with higher-level abstractions for common serverless use cases. It is still a young project (version 0.5 as of writing) and changing quickly. Let’s explore the serverless workloads that Knative currently supports.
Request Serving
A low-friction transition approach from microservices to serverless is to use single-operation functions to handle HTTP requests. We expect a serverless platform to be able to stand up a stateless, scalable function in seconds — and this is what Knative Serving aims to achieve by providing a common toolkit and API framework for serverless workloads. A serverless workload in this context is a single-container, stateless pod, primarily driven by application-level (L7) request traffic.
The Knative Serving project provides primitives that enable:
- rapid deployment of serverless containers by providing higher-level, opinionated primitives;
- activation, scaling up and down to zero driven by requests;
- automatic routing and configuration of low-level primitives; et
- immutable snapshots of revisions (deployed code and configurations).
All of the above is achievable within certain limitations, such as a single container per pod, single port, no persistence, and several other constraints.
Eventing
The Knative Eventing project provides the building blocks for creating reliable, scalable, asynchronous-event-driven applications. It aims to create a standard experience around consumption of and creation of events using the CloudEvents standard. The high-level features of Knative Eventing include:
- extendable and pluggable architecture that allows different implementations of importers (such as GitHub, Kafka, SQS, Apache Camel, etc.) and channel implementations (such as Kafka, Google Pub/Sub, NATS, in memory, etc.);
- event registry for maintaining a catalogue of event types;
- declarative API for event orchestration by binding event sources, triggers, and services; et
- trigger capability that allows subscribing to events from a specific broker and optional filtering before routing the events to downstream Knative services.
These features are interesting, but how do they help cloud-native developers to be more productive on Kubernetes?
Let’s assume we have implemented a function, built it as a container, and tested it thoroughly. It is basically a service with a single operation that accepts CloudEvents over HTTP. Using Knative, we can deploy the container to Kubernetes as a workload with serverless characteristics. For example, using Knative Serving primitives, the container can activate only when there are HTTP requests and scale rapidly if necessary. In addition, the same pod can also be configured to accept CloudEvents from a broker by subscribing to a channel. That pod can also act as a step in a more complex event orchestration flow defined via a Knative Sequence. All of this is possible without modifying the already-built container, using only declarative Knative configurations. Knative will ensure the routing, activation, scalability, reliability, subscription, redelivery, and broker resiliency of the serverless infrastructure. It is not all there yet, but it is getting there.
And that is not all. If you have an application with very specific needs that none of the standard workload primitives provides you, Kubernetes has more options available. In such a case, a Custom Controller can add bespoke functionality to the behavior of the cluster by actively monitoring and maintaining a set of Kubernetes resources in a desired state.
At a high level, a controller is an active reconciliation process performing the “Observe -> Analyze -> Act” steps. It monitors objects of interest for the desired state and compares them to the world’s actual state. The process then sends instructions to attempt to change the world’s current state to be more like the desired state.
A more advanced approach for handling custom workloads would be to utilize another brilliant Kubernetes extension mechanism: CustomResourceDefinitions. Combing a Kubernetes Operator with CustomResourceDefinitions can encapsulate the operational knowledge for the specific application needs within an “algorithmic” form. An Operator is a Kubernetes controller that understands Kubernetes and an application domain — by combining knowledge of both areas, it can automate tasks that usually require a human operator.
Controllers and Operators are turning into the standard mechanism for extending the platform and enabling complex application lifecycles on Kubernetes. And as a result, an ecosystem of controllers for managing the full lifecycle of more sophisticated cloud-native workloads is forming out.
The Operator pattern allows us to extend the Controller pattern for more flexibility and greater expressiveness. All of the workload types discussed in this article, and other related patterns are covered in the Kubernetes Patterns book I recently co-authored. Check it out for further details on these topics.
Cloud-native Trends
The tendency of moving more and more commodity features that are not part of the business logic to the platform layer continues in the Kubernetes ecosystem:
- Deployment, placement, health checks, recovery, scaling, service discovery, and configuration management have all moved to the Kubernetes layer.
- Service meshes continue this trend by moving the network-related responsibilities such as resilient communication, tracing, monitoring, transport-level security, and traffic management to the platform.
- Knative adds specialized serverless primitives, and moves the responsibility of rapid scaling up, scaling to zero, routing, eventing infrastructure abstractions, event publishing, subscription mechanism, and workflow composition to the platform as well.
This leaves mainly business-logic concerns for the application developers to implement. The platform takes care of the rest.
Responsibilities move to the platform. More abstractions enable diverse workloads
We shift more and more commodity responsibilities from the application layer into the platform by adding higher-level abstractions to Kubernetes. For example, Istio provides higher-level networking abstractions that depend on lower-level Kubernetes primitives. Knative adds higher-level serverless abstractions that depend on the lower-level abstractions from Kubernetes and Istio (note that this is about to change, and Knative will not depend on Istio going forward, although it will need to implement a similar functionality).
These additional abstractions enable Kuberntetes to uniformly support a variety of workloads, including serverless, with the same open packaging and runtime format.
Runtimes and Application Design
With the transition from monolithic architectures to microservices and serverless, the runtimes are evolving as well; so much so that the two-decade-old Java runtime is moving away from its “write once, run anywhere” mantra to become native, light, fast, and serverless first.
Java runtime and application design trends
Over the years, Moore’s Law and increasing compute power guided Java in building one of the most advanced runtimes, with an advanced garbage collector, JIT compiler, and many other things. The end of Moore’s law led Java to introduce non-blocking primitives and libraries that benefit from multiprocessor and multi-core systems. In the same spirit, with platform trends such as cloud native and serverless, distributed system components are becoming light, fast, and optimized for a single task.
Severless
The serverless paradigm is recognized to be the next fundamental architectural evolution. But the current generation of serverless has emerged from among early adopters and disruptors, whose priority is velocity. That is not the priority of enterprise companies that have complex business and technological constraints. Among these organizations, the industry standard is container orchestration. Kubernetes is trying to link cloud native and serverless though the Knative initiative. Other projects such as CloudEvents and Substrate VM are also influencing the serverless ecosystem and pushing it to an era of openness, portability, interoperability, hybrid cloud, and global adoption.
On Thursday, October 10, 2019, I will be presenting “Designing Cloud Native Applications with Kubernetes Patterns” at a virtual event organized by Red Hat. If you found this topic and the content interesting, check the agenda, register, and listen to me live.
A propos de l'auteur
Bilgin Ibryam is a principal architect at Red Hat, and a committer to multiple Apache Software Foundation projects. He is a regular blogger, open-source evangelist, blockchain enthusiast, and speaker. He has written the books Camel Design Patterns and Kubernetes Patterns. He has over a decade of experience in designing and building highly scalable and resilient distributed systems. In his day-to-day job, Ibryam enjoys guiding teams in enterprise companies to build successful open-source solutions at scale through proven and repeatable patterns and practices. His current interests include enterprise blockchains and cloud-native and serverless paradigms. Follow him @bibryam for regular updates on these topics.
Commentaires
Laisser un commentaire