Azure Functions : les différentes étapes

Publié le 27 avril, 2016

Ce billet de blog a été rédigé par Mathew Charles, ingénieur logiciel principal, Microsoft.​

Notre équipe était ravie de publier récemment une préversion du nouveau service Azure Functions lors de la conférence //build. Nous avons déjà publié des billets de blog au sujet du service (par exemple un billet sur la présentation d’Azure Functions). Cependant, dans cet article, nous souhaitons vous inviter dans les coulisses et discuter du lancement du projet et du chemin que nous avons parcouru pour arriver là où nous en sommes aujourd’hui. Nous aborderons le runtime d’Azure Functions, la couche Calcul dynamique (« serverless ») ainsi que le portail Functions. Nous vous présenterons également de façon générale l’évolution de ces éléments et leur regroupement pour former un produit cohérent. L’équipe s’est bien amusée et cela ne fait que commencer

L’évolution de ce projet est un excellent exemple de l’identification de synergies entre un ensemble de composants de plateforme existants et de leur intégration dans une nouvelle offre de produits. Dans Azure App Service, nous avions déjà mis en place de nombreux éléments constitutifs pour nous permettre d’exécuter assez rapidement la vision Azure Functions. En tirant parti de ces atouts existants et en apportant de nouvelles innovations et fonctionnalités, nous avons pu rapidement concrétiser le projet.

SDK WebJobs

Pendant l’événement //build, lors de sa conférence de présentation d’Azure Functions, Chris a expliqué en quoi Azure Functions est basé sur le SDK Azure WebJobs. Le SDK WebJobs existe depuis quelques années déjà et de nombreux clients l’utilisent avec joie pour créer des travaux de traitement backend qui déclenchent une grande variété de sources d’événements. Le SDK WebJobs possède un modèle de programmation déclaratif simple qui permet d’écrire très facilement des fonctions de travail sophistiquées avec un minimum de code. En voici un exemple :

public static void ProcessOrder(
    [QueueTrigger(“orders”)] Order order,
    [Blob(“processed/{Id}”)] out string receipt,
TraceWriter log)
{
log.Verbose(string.Format(“Processing Order {0}”, order.Id));

    // business logic

    receipt = “<some value>”;
}

Lorsqu’elle est hébergée par le SDK WebJobs JobHost dans une application console .NET Vanilla, cette fonction est automatiquement déclenchée chaque fois qu’un nouveau message de file d’attente est ajouté aux « commandes » de la file d’attente Azure et que la charge utile de la file d’attente est désérialisée en un OCT de commande. La fonction se lie également automatiquement à un blob de sortie à l’aide de la propriété « Id » du message entrant dans le cadre du chemin de blob. Avec ce modèle de programmation, votre fonction de travail se concentre uniquement sur sa logique métier et ne doit s’occuper d’aucune opération de stockage. C’est fantastique !

Le modèle d’hébergement de telles fonctions utilisant le SDK WebJobs consiste à les déployer en tant que Azure WebJobs. Cela fonctionne très bien, offre beaucoup de flexibilité et reste une fonctionnalité très populaire d’Azure App Service.

Runtime d’Azure Functions

Vers le milieu de l’année dernière, nous avons commencé à discuter de ce qu’il était nécessaire d’effectuer pour appliquer ce modèle de programmation simple à d’autres langages. Nous avons également demandé leur avis à nos clients. Tout le monde n’est pas un programmeur .NET C#, et pourtant beaucoup souhaitent utiliser ces modèles de SDK WebJobs. Nous avons donc entrepris des travaux de prototypage sur ce sujet et avons mis au point un modèle qui nous permettait de tirer parti de l’exécution éprouvée du SDK WebJobs .NET existant, en superposant un nouveau modèle de description JSON pour les métadonnées. Le résultat est que vous pouvez écrire la même fonction que ci-dessus en Node.js (ou d’autres langages) :

module.exports = function (context, order) {
context.log(‘Processing order’, order.id);

    // business logic

context.bindings.receipt = “<some value>”;
context.done();
}

Notez que cette fonction est structurellement identique à la fonction C# ci-dessus. C’est parce qu’elle est mappée à la même implémentation de runtime. Les attributs de code déclaratif ne sont qu’un moyen de spécifier des métadonnées. Nous avons réalisé que nous pouvions capturer les mêmes informations dans un simple fichier de description JSON. Voici le fichier de métadonnées correspondant décrivant les liaisons pour cette fonction (c’est-à-dire tous les bits contenus dans les attributs déclaratifs de l’exemple C#) :

{
  “bindings”: [
    {
      “type”: “queueTrigger”,
      “name”: “order”,
      “direction”: “in”,
      “queueName”: “orders”
    },
    {
      “type”: “blob”,
      “name”: “receipt”,
      “direction”: “out”,
      “path”: “processed/{id}”
    }
  ]
}

L’idée de base est que nous pouvons utiliser ces métadonnées pour générer un adaptateur en mémoire entre différents langages et le runtime du SDK WebJobs .NET. Nous générons effectivement la fonction C# que vous voyez ci-dessus, et le corps de la méthode de cette fonction délègue simplement la fonction utilisateur réelle (c’est-à-dire la fonction Node.js que vous avez écrite). Une fonction Azure peut alors être un simple fichier de métadonnées function.json décrivant les liaisons de fonction, ainsi qu’une collection d’un ou plusieurs fichiers de script implémentant la fonction. Voici le même exemple que ci-dessus, utilisant le même fichier de métadonnées, avec la fonction écrite en tant que fichier BAT Windows :

SET /p order=<%order%
echo Processing order ‘%order%’
echo ‘<some value>’ > %receipt%

Ce même fichier de métadonnées peut être utilisé pour décrire une fonction dans l’un des 7 langages que nous prenons en charge. Bien sûr, chaque langage a ses propres bizarreries et capacités, et certaines sont plus adaptées que d’autres, selon la tâche à effectuer. Le point essentiel ici est que nous pouvons avoir le même runtime de déclenchement/liaison pour tous ces langages, ce qui permet à chaque langage de se mapper à ce modèle à sa manière. Les fichiers BAT sont quelque peu limités, mais via les variables d’environnement et les flux de fichiers, ils peuvent à la fois recevoir des entrées et écrire des sorties, que le runtime d’Azure Functions mappe automatiquement aux artefacts Stockage Azure sous-jacents.

Si Azure Functions repose sur le SDK WebJobs principal, nous n’avons pas besoin d’écrire ni de gérer différentes versions du SDK WebJobs par langage, ce qui représente une victoire considérable en termes d’ingénierie. Nous disposons d’un runtime principal unique qui gère toute notre logique de liaison/déclenchement et notre investissement dans ce runtime principal apporte des avantages aux fonctions, ainsi qu’à tous nos clients du SDK WebJobs. Cela signifie également que toutes les extensions de déclencheur/liaison que les personnes écrivent pour le SDK principal peuvent également être utilisées dans Functions. Nous continuerons de travailler de façon intensive sur le SDK principal et les extensions WebJobs, tant pour nos clients traditionnels que pour Azure Functions.

Prise en charge des webhooks

Les webhooks sont un autre domaine important sur lequel nous avons commencé à nous concentrer. La possibilité de déclencher des fonctions sur des événements Stockage Azure est excellente, mais des clients WebJobs nous ont également demandé de pouvoir déclencher leurs fonctions via des requêtes WebHook. Nous avions déjà expérimenté cela l’année dernière en écrivant une extension qui fonctionnait bien, mais qui présentait un gros inconvénient du fait que WebJobs est exécuté sur le site Kudu SCM, ce qui signifie que des informations d’identification de base sont nécessaires pour effectuer des demandes. C’est un facteur décisif pour la plupart des scénarios d’intégration de webhook, car vous voulez distribuer une URL avec un simple code d’authentification limité à l’accès uniquement à ce point de terminaison.

Pour résoudre ce problème, nous avons décidé de packager le runtime d’Azure Functions sous la forme d’une extension de site s’exécutant à la racine d’une application web. Cela signifie qu’il n’est PAS situé derrière le point de terminaison SCM, ce qui nous permet d’obtenir les modèles d’authentification requis. Cela nous a permis d’exposer un simple ensemble de points de terminaison authentifiés pour les fonctions de webhook. Nous avons également intégré à cela la bibliothèque de webhooks ASP.NET, ce qui nous permet de tirer parti du grand nombre de fournisseurs de webhooks pris en charge par cette bibliothèque. Et cela nous offre une assistance de premier ordre pour des fournisseurs tels que GitHub, Slack, DropBox, Instagram, etc.

Nous avions donc à ce stade un runtime d’Azure Functions flexible qui prenait en charge l’ensemble du modèle de déclenchement/liaison du SDK WebJobs pour 7 langages (Node.js, C#, F#, Bash, BAT, Python, PHP), ainsi qu’une tête HTTP prenant en charge un large éventail de scénarios d’intégration de webhooks.

Calcul dynamique

Parallèlement au travail ci-dessus concernant le runtime, nous avons également eu des discussions sur l’informatique Serverless et sur ce que nous voulions faire dans ce domaine. Nous avons réalisé que le travail que nous étions en train d’effectuer pour WebJobs était très synergique. Nous développions un runtime Azure Functions flexible et multilingue qui pouvait exécuter du code utilisateur dans un environnement en mode bac à sable à grande échelle. Cependant, le modèle WebJobs traditionnel nécessite que les utilisateurs créent et gèrent l’hôte WebApp sur lequel ces WebJobs sont exécutés. Et si nous pouvions en faire abstraction afin que les utilisateurs n’aient plus qu’à écrire les fonctions eux-mêmes, en nous laissant gérer toutes les préoccupations relatives au déploiement et à la mise à l’échelle ? En clair, nous proposerions un SDK WebJobs en tant que service. Eurêka !

Nous avons formé une équipe pour étudier cette partie du programme : le « calcul dynamique ». À cette étape du projet, notre équipe est passée d’une poignée de personnes à une équipe beaucoup plus grande. Nos réunions Scrum comptaient chaque jour deux à trois personnes en plus. A priori, notre couche de calcul dynamique était responsable de la mise à l’échelle automatique des fonctions lorsque la charge augmentait et de la diminution de leur ampleur lorsque la charge diminuait. Il en résulte que l’utilisateur final n’a pas du tout à se soucier de cela et qu’il n’est facturé que pour le temps de calcul réellement utilisé. Le domaine lié au calcul dynamique du projet est vaste et inclut également d’autres aspects de service tels que la surveillance et les diagnostics, la télémétrie, etc. Ce domaine mérite son propre billet de blog à l’avenir.

Portail Functions

Nous nous sommes ensuite concentrés sur une expérience de portail facilitant la création et la gestion de ces fonctions. Dans le modèle de SDK WebJobs traditionnel, vous compilez et déployez une application console .NET (JobHost) contenant toutes vos fonctions de travail précompilées. Pour Azure Functions, le modèle de déploiement est beaucoup plus simple. Le runtime d’Azure Functions a été conçu pour avoir une structure de système de fichiers très simple. Cela permet d’avoir une interface utilisateur de portail simple qui fonctionne sur ces fichiers via les API Kudu. Nous pouvions avoir un éditeur de portail simple qui vous permettrait de créer/éditer ces fichiers et de les insérer dans le conteneur de fonctions (WebApp exécutant les fonctions). Le modèle de système de fichiers simple permet également de déployer Azure Functions via des modèles ARM. Cela est effectivement possible aujourd’hui, mais n’est pas encore bien documenté.

L’équipe a pu mettre en place un portail assez rapidement et nous étions tous très heureux de pouvoir commencer à l’utiliser pour nous familiariser avec le produit naissant. Une fois le portail en place, les choses ont vraiment commencé à prendre forme ! L’ensemble de l’équipe a pu commencer à se familiariser avec le produit, ce qui a généré de nombreuses discussions et améliorations sur la convivialité et nous a également permis de résoudre les bogues.  Lorsque le travail sur le portail a commencé, tout comme pour le runtime d’Azure Functions, une ou deux personnes y travaillaient, mais à mesure que les travaux initiaux prenaient de l’ampleur et que notre champ d’action/nos plans augmentaient, d’autres personnes ont intégré le projet. Les réunions Scrum se sont encore agrandies

Modèles

Le simple modèle de système de fichiers pour les fonctions nous a également permis de développer le modèle de modèle impressionnant que vous voyez aujourd’hui dans le portail des fonctions. Nous avons commencé à créer des modèles de métadonnées/scripts simples pour des scénarios courants dans les différents langages : “QueueTrigger – Node”, “GitHub WebHook C#”, etc. L’idée est de disposer de simples « recettes » ou points de départ permettant à vos fonctions d’être opérationnelles immédiatement, et vous pouvez ensuite les personnaliser et les enrichir en fonction de vos besoins. À l’avenir, nous espérons permettre à la communauté de créer également de tels modèles pour générer un écosystème.

Extensibilité

Un autre domaine sur lequel nous nous sommes beaucoup concentrés avant l’annonce d’Azure Functions lors de la conférence build// était un nouvel ensemble d’extensions du SDK WebJobs que nous avons rendues disponibles dans Functions. L’automne dernier, nous avons publié le modèle d’extensibilité du SDK WebJobs, qui a ouvert le modèle de programmation à de nouvelles sources de déclencheur/liaison. Notre équipe avait déjà fourni à la communauté de nouvelles extensions utiles (par exemple, TimerTrigger, FileTrigger, la liaison SendGrid, etc.) dans le dépôt d’extensions du SDK WebJobs. Des membres de la communauté ont également commencé à créer leurs propres extensions. Comme Functions est basé sur le SDK, toutes ces extensions peuvent également être mises à disposition dans Azure Functions. Nous avons comme projet d’écrire de nombreuses extensions, mais nous n’avions pas le temps. Lorsque notre équipe s’est agrandie, nous avons eu les ressources pour commencer à en produire quelques-unes. Au cours des deux derniers mois, nous avons ajouté les extensions supplémentaires suivantes et les avons placées en première classe dans Functions : EventHub, DocumentDb, NotificationHub, MobileApps et ApiHub. Ce n’est que le début, car de nombreuses extensions supplémentaires sont prévues, et nous nous attendons à ce que la communauté en crée davantage. Nous travaillons également sur un modèle simple permettant aux tiers d’intégrer leurs extensions dans Functions. Restez attentif pour être au courant des prochaines informations à ce sujet.

La bonne nouvelle, c’est que nous avons décidé très tôt d’effectuer tout notre travail en open source, comme c’est le cas avec les principaux dépôts SDK WebJobs et Extensions du SDK WebJobs. Nous avons donc créé le Script du SDK WebJobs qui contient le runtime d’Azure Functions. De même, le portail Functions est également open source : AzureFunctionsPortal.

En conclusion, tout ce qui précède constitue une vue d’ensemble de haut niveau des différentes parties du projet et de la manière dont elles ont été réunies : le runtime d’Azure Functions, le portail Functions et le Calcul dynamique. Dans les prochains billets, nous approfondirons davantage les détails de ces différents domaines.