Notifier un déploiement Kudu sur Slack avec Azure Functions

Rémy Boyer Rémy Boyer
septembre 8, 2016
1 Commentaire

Cet article est un pas-à-pas qui vous permettra de découvrir les Azure Functions par le biais d’un cas d’usage simple mais réel.

Slack est un outil de messagerie instantanée. Il jouit d’une forte popularité dans le milieu professionnel, notamment due à sa disponibilité sur de nombreuses plateformes, mobiles et « desktop ».
Il est, selon moi, un bon outil pour les notifications de déploiement puisqu’il permet à la fois de l’instantané et de l’historique. Ceci tout en regroupant les messages dans un espace fermé et ciblé qu’est un Channel.

Kudu est une palette d’outils disponibles sur les Web Apps Azure (ou App Services). Ces outils sont disponibles à l’adresse https://{nom-de-la-web-app}.scm.azurewebsites.net/.
Kudu dispose notamment d’un processus de build et de déploiement intégré. Celui-ci permet de déployer très facilement un site web, à condition qu’il soit hébergé sur git.

Problématique

Kudu dispose déjà d’une fonctionnalité permettant d’effectuer un appel HTTP lorsqu’un déploiement se termine (avec succès ou en échec).
Pour déclencher une notification Slack, nous devons simplement effectuer un appel HTTP sur une url (appelée Hook) donnée par Slack.

Dans ce cas, pourquoi ne pas simplement envoyer la notification Kudu directement sur Slack ? Malheureusement, cela est impossible car les formats sont différents.

Voici le format de message JSON envoyé par Kudu :

{
  "id": "982843aff56d37f2bfb9f532a9c0465031f4172d",
  "status": "success",
  "statusText": "",
  "authorEmail": "someone@somewhere.com",
  "author": "Someone",
  "message": "My fix",
  "deployer": "Someone",
  "receivedTime": "2015-12-16T00:52:07.7240633Z",
  "startTime": "2016-02-27T17:44:25.8567966Z",
  "endTime": "2016-02-27T17:45:06.2016537Z",
  "lastSuccessEndTime": "2016-02-27T17:45:06.2016537Z",
  "complete": true,
  "siteName": "MySite"
}

Et celui attendu par Slack :

{
  "attachments": [
    {
      "fallback": "New open task [Urgent]: <http://url_to_task|Test out Slack message attachments>",
      "pretext": "New open task [Urgent]: <http://url_to_task|Test out Slack message attachments>",
      "color": "#D00000",
      "fields": [
        {
          "title": "Notes",
          "value": "This is much easier than I thought it would be.",
          "short": false
        }
      ]
    }
  ]
}

Nous voyons bien qu’il nous faudrait transformer le message.
Pour cela, de nombreuses possibilités s’offrent à nous. Dans cet article, nous l’effectuerons avec Azure Functions.

Cette technologie est dite « event-driven » et « serverless ». Concrètement, cela signifie que :

  • Nous n’aurons pas à nous occuper ni même à décider de l’infrastructure sur laquelle s’exécute le code.
  • Nous pouvons choisir parmi une multitude de langages pour son implémentation.
  • Nous ne serons facturés qu’à l’utilisation, c’est-à-dire lorsqu’il y aura du traitement effectif.
  • Cela nous évite de créer un site web qui devra être constamment « up and running », alors que nous n’en avons besoin qu’à des moments précis.
  • Le code source sera facilement disponible et éditable en ligne, si nécessaire.

Tous ces arguments rendent la technologie pratique pour le DevOps, comme c’est le cas ici. C’est également un bon candidat pour tout traitement asynchrone, par exemple les traitements d’événements provenant de queues.

Azure Functions est actuellement disponible en Preview. Les fonctionnalités et apis peuvent donc être amenées à changer.

Notre flux d’actions peut être représenté comme ceci :

slack-kudu-1

Nous allons mettre en œuvre toutes ses étapes en démarrant par la dernière. Cela permettra de déclencher une notification dès le premier déploiement !

Slack

Pour pouvoir envoyer une notification dans un channel Slack, nous utiliserons les Incoming Webhooks.
Ceux-ci sont très simple à mettre en place et à utiliser. Cela nous évite également de créer une application Slack à part entière.

Notre « Incoming Webhook » consiste simplement en une url sur laquelle nous pourrons poster nos requêtes HTTP. Elles seront automatiquement retranscrites en messages dans le channel.

Pour commencer, nous partons de notre channel et choisissons « Add an app or integration ».

slack-kudu-slack1

Depuis le site web, choisissons « Incoming Webhooks ».

slack-kudu-slack2

Cliquons ensuite sur « Add Incoming WebHooks Integration », le nom du channel ayant déjà été pré-sélectionné.

slack-kudu-slack3

slack-kudu-slack4

Nous arriverons ensuite sur la page de paramétrage. Ici, seule une information est nécessaire pour la suite : il s’agit de la Webhook URL. Mettons la de côté, car nous nous en resservirons plus tard.

slack-kudu-slack5

L’Azure Function

Création de la Function App

Pour créer notre première fonction, nous devons créer une nouvelle « Function App ».
La Function App correspond à un conteneur de fonctions.

slack-kudu-function1

slack-kudu-function2

Pour son bon fonctionnement, la Function App doit être liée à un Service Plan. Celui-ci peut être statique ou dynamique. Au vu de l’utilisation que nous allons en faire, nous choisirons l’approche dynamique, qui permet de « scaler » l’infrastructure au plus proche de leur utilisation. Pour plus d’informations sur le choix du Service Plan, vous pouvez vous référer ici.

Concernant la mémoire allouée, elle correspond à la mémoire totale allouée pour les fonctions qui y seront exécutées. Dans notre cas, 128 Mo suffisent.

Création de la Function

Il existe plusieurs moyens de créer des fonctions, mais les deux principaux consistent à soit utiliser les outils de ligne de commandes et ensuite à déployer la fonction, soit à utiliser uniquement l’éditeur de code en ligne Monaco.

A l’heure actuelle, l’outillage étant très limité (pas d’extension Visual Studio par exemple), l’approche en ligne nous permet d’aller au plus simple et au plus vite.

Une fois le déploiement de notre « Function App » terminé, nous pouvons créer notre première fonction. Pour cela, rendez-vous dans les « App Services », nous sélectionnons notre « Function App » et nous créons une nouvelle fonction.

slack-kudu-function3

Nous utilisons ici le template « HttpTrigger – C# », qui comme son nom l’indique, sera déclenché par un appel HTTP et sera codé en C#.

L’authentification est anonyme car nous ne pouvons pas ajouter de token d’autorisation depuis Kudu.

Si nécessaire, vous pouvez restreindre les accès aux fonctions grâce à un App Service Environnment.

Code

Nous nous retrouvons donc dans notre espace de travail. Celui-ci est composé de trois parties :

  • L’éditeur de code, avec un petit explorateur de fichier
  • Les logs d’exécutions, indiquant ce qui s’est passé récemment.
  • Une console de tests, permettant de déclencher nos functions.

Voici à quoi cela ressemble (cliquez sur l’image pour l’agrandir) :

slack-kudu-function6

Le dossier contient par défaut deux fichiers. Le premier est run.csx, qui contient notre code C# et correspond simplement au code appelé lorsque la fonction est déclenchée.
Le second est function.json et décrit notre fonction de manière très simple :

{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "res",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

On voit notamment que notre fonction doit envoyer une réponse à chaque requête.

Afin de sérialiser et désérialiser le contenu JSON des requêtes HTTP, nous utiliserons la librairie nuget Newtonsoft.Json.

Pour cela, il faut créer un fichier nommé project.json. Nous pouvons le faire à l’aide de l’IDE en ligne :

slack-kudu-function4

A l’intérieur, nous déclarerons nos dépendances nuget de cette façon :

{
  "frameworks": {
    "net46": {
      "dependencies": {
        "Newtonsoft.Json": "9.0.1"
      }
    }
  }
}

Les plus avisés remarqueront ici qu’il s’agit des fichiers de définitions de projets ASP.NET Core. C’est en effet le framework utilisé pour l’hébergement et l’invocation de notre Azure Function.

Il ne nous reste plus qu’à écrire notre code de transformation de la requête HTTP. Facile, non ? Eh bien, pas tant que ça… l’IDE ne disposant pas d’auto-complétion, n’hésitez pas, comme je l’ai fait, à passer par Visual Studio pour l’écrire. Nul doute que ces limitations disparaîtront à l’avenir.

using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");

    var content = (await req.Content.ReadAsStringAsync());
    var kuduPayload = JsonConvert.DeserializeObject<dynamic>(content);

    bool succeeded = kuduPayload["status"] == "success";

    DateTime startTime = kuduPayload["startTime"];
    DateTime endTime = kuduPayload["endTime"];

    var duration = endTime - startTime;

    string title = $"{kuduPayload["siteName"]} deployment {(succeeded ? "succeeded" : "failed")} - {kuduPayload["id"]}";
    string color = succeeded ? "good" : "danger";

    var slackPayload = new JObject(
        new JProperty("attachments",
        new JArray(
          new JObject(
            new JProperty("fallback", title),
            new JProperty("pretext", title),
            new JProperty("color", color),
            new JProperty("fields",
              new JArray(
                new JObject(
                  new JProperty("title", "Requested By"),
                  new JProperty("value", kuduPayload["author"]),
                  new JProperty("short", true)),
                new JObject(
                  new JProperty("title", "Duration"),
                  new JProperty("value", duration.ToString(@"hh\:mm\:ss")),
                  new JProperty("short", true)),
                new JObject(
                  new JProperty("title", "Changes"),
                  new JProperty("value", kuduPayload["message"]),
                  new JProperty("short", false))
        ))))));

    using (var httpClient = new HttpClient())
    {
        var requestContent = slackPayload.ToString(Formatting.None);

        log.Info($"Sending the following content to slack : {requestContent}");

        var response = await httpClient.PostAsync("https://hooks.slack.com/services/your-url-to-slack-host/",
            new StringContent(requestContent));

        response.EnsureSuccessStatusCode();
    }

    return req.CreateResponse(HttpStatusCode.OK);
}

Il n’y a rien de compliqué dans ce code. Il ne fait que construire un nouveau message à partir d’un autre.

Il ne faut pas oublier d’enregistrer nos changements grâce au bouton Save, cela entraînant la compilation.

Vous pouvez, si vous le souhaitez, déplacer l’url du hook slack dans les Application Settings et y accéder dans le code à l’aide du ConfigurationManager.

L’avantage de l’IDE en ligne est que vous pouvez également facilement tester votre fonction.

Dans la partie « Run », ajoutez ce JSON de test puis cliquez sur Run :

{
  "id": "982843aff56d37f2bfb9f532a9c0465031f4172d",
  "status": "success",
  "statusText": "",
  "authorEmail": "someone@somewhere.com",
  "author": "Someone",
  "message": "My fix",
  "deployer": "Someone",
  "receivedTime": "2015-12-16T00:52:07.7240633Z",
  "startTime": "2016-02-27T17:44:25.8567966Z",
  "endTime": "2016-02-27T17:45:06.2016537Z",
  "lastSuccessEndTime": "2016-02-27T17:45:06.2016537Z",
  "complete": true,
  "siteName": "MySite"
}

Le message devrait apparaître sur Slack !

slack-kudu-function5

Site web et Kudu

Création et configuration de l’App Service

Maintenant que notre fonction est prête, il nous faut créer un site web où nous effectuerons notre déploiement.
Je ne rentrerai pas dans les détails de la création de l’App Service, cette opération étant largement documentée ailleurs.

Une fois votre app service (vide) créé, rendez-vous dans la partie Deployment Options pour activer le déploiement par kudu.
Nous utiliserons ici la version Local Git Repository, qui nous permettra de déclencher un déploiement simplement en effectuant un push sur un repository git distant, lui-même créé et géré par l’App Service.

slack-kudu-web1

Rendez-vous ensuite dans la partie Overview afin de récupérer l’url du repository git. Mettons la de côté, elle sera nécessaire plus tard.

Il est également nécessaire de définir un mot de passe de déploiement pour la publication git. Nous pouvons le faire dans la section Deployment credentials :

slack-kudu-web2

Nous allons maintenant activer le webhook dans git à l’adresse suivante : http://{nom-de-la-web-app}.scm.azurewebsites.net/

Naviguons dans la partie Web hooks puis créons-en un nouveau.

slack-kudu-web3

slack-kudu-web4

L’url respectera ce format : https://{nom-function-app}.azurewebsites.net/api/{nom-function}

C’est terminé pour la configuration de Kudu !

Création et déploiement du site web

Pour déclencher un déploiement, nous allons simplement publier un site web asp.net core fraîchement créé par les outils.

Tout d’abord, installons les dépendances npm :

npm install -g yo
npm install -g generator-aspnet

Créons ensuite notre site web :

C:\VS> yo aspnet

     _-----_     ╭──────────────────────────╮
    |       |    │      Welcome to the      │
    |--(o)--|    │  marvellous ASP.NET Core │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

? What type of application do you want to create? Empty Web Application
? What's the name of your ASP.NET application? kudu-slack-website
   create kudu-slack-website\.gitignore
   create kudu-slack-website\Program.cs
   create kudu-slack-website\Startup.cs
   create kudu-slack-website\project.json
   create kudu-slack-website\web.config
   create kudu-slack-website\Dockerfile
   create kudu-slack-website\Properties\launchSettings.json
   create kudu-slack-website\README.md


Your project is now created, you can use the following commands to get going
    cd "kudu-slack-website"
    dotnet restore
    dotnet build (optional, build will also happen when it's run)
    dotnet run

Nous allons maintenant publier le site web sur le repository git précédemment créé.

C:\VS> cd .\kudu-slack-website\
C:\VS\kudu-slack-website> git init
C:\VS\kudu-slack-website> git remote add origin https://[votre-url-vers-le-repo-git].git
C:\VS\kudu-slack-website> git add .
C:\VS\kudu-slack-website> git commit -m "First commit!"
C:\VS\kudu-slack-website> git push -u origin master

La dernière commande vous invitera à taper les identifiants précédemment renseignés.

Counting objects: 11, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (11/11), 5.02 KiB | 0 bytes/s, done.
Total 11 (delta 0), reused 0 (delta 0)
remote: Updating branch 'master'.
…
remote: Finished successfully.
remote: Running post deployment command(s)...
remote: Deployment successful.
To https://cellenzaftp1@[...].git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

Le premier déploiement est un peu long, car il est nécessaire de télécharger toutes les dépendances nuget, qui seront mises en cache pour les déploiements suivants.
Au bout d’une minute, le push devrait être terminé et vous devriez recevoir dans la foulée la notification slack :

slack-kudu-web5

Conclusion

Cet article nous a permis d’aborder trois technologies : les Incoming Webhooks de Slack, les déploiements Kudu et les Azure Functions.

Nous n’avons qu’entraperçu les possibilités des fonctions. En plus des appels HTTP, les fonctions peuvent être déclenchées par un minuteur, par un message dans une queue, l’ajout d’un fichier dans un Blob Storage, de nombreux événements sur des solutions SaaS, et bien d’autres.
Il y a également une console de monitoring permettant de visualiser toutes les exécutions avec leurs logs associés et des APIs de gestion.

Avec les Azure Functions nous avons rapidement pu mettre en œuvre un service capable de transformer un message JSON. Bien que cela ait pu sembler un peu fastidieux pour une si petite tache, une fois l’environnement de base mis en place (l’Azure Function App) et une fois l’utilisateur rompu à son utilisation, cela devient d’une grande facilité et a pour avantage de ne demander aucune maintenance : pas de machine virtuelle à mettre à jour, pas de site web à déployer, rien à changer si la charge augmente.
Cette approche conviendra très bien à de nombreuses taches non critiques, mais pourra également être adaptée, en y ajoutant un peu plus de rigueur (contrôleur de code source, déploiement, authentification), aux applications de production.

Un commentaire. Laisser un commentaire

Super article, ça a changé ma vie !

Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *