DECE 13

Intégrer Event Grid dans vos applications

Il existe de nombreux services de messaging sur Azure, qui ont chacun leur propre cas d’utilisation. Pour ma part j’utilise de plus en plus Event Grid pour monitorer ma plateforme Azure, car il offre les avantages suivants :

  • Evènement déclenché rapidement après l’action
  • Intégration Azure Function
  • Intégration Logic Apps
  • Possibilité d’avoir des évènements personnalisés.

Le premier point pour moi est important, car si je prends le cas d’usage alerting décrit dans la documentation, il s’agit le plus souvent d’une mise en place d’alerte suite à l’analyse de données ingérées via Log Analytics, ce qui ne me convient pas toujours en terme de délai, car on peut rapidement arriver à 15 min de latence, alors que je peux être à moins d’1 minute via Event Grid.

Nous allons voir comment mettre en place le dernier point dans une application C#. Pour cela dans le portail, nous allons créer un objet de type Event Grid Topic

Outre les informations typiques, il vous est demandé de choisir entre Event Grid Schema et Cloud Event Schema

Event Grid est un produit made by Microsoft, avec un schéma spécifique, alors qu’un Cloud Event doit respecter une spécification qu’on peut retrouver ici : https://github.com/cloudevents/spec/blob/master/json-format.md

Voici le schéma pour Event Grid :

[
  {
    "id": string,
    "eventType": string,
    "subject": string,
    "eventTime": string-in-date-time-format,
    "data":{
      object-unique-to-each-publisher
    },
    "dataVersion": string
  }
]

Ici, nous allons utiliser le schéma Event Grid en créant un object C# qui correspond à un item de ce schéma :

public class GridEvent<T> where T : class
{
	public string Id { get; set; }
	public string EventType { get; set; }
	public string Subject { get; set; }
	public DateTime EventTime { get; set; }
	public T Data { get; set; }
	public string DataVersion { get; set; }
}

Sous Linqpad, j’utilise le script suivant pour envoyer un message sur mon topic Event Grid :

private const string Key = "GnXsbgmdlfklqzrjz/ddsfj="; 
private const string Endpoint = "https://demo-eg.westeurope-1.eventgrid.azure.net/api/events";

async Task Main()
{
	HttpClient client = new HttpClient();
	client.DefaultRequestHeaders.Add("aeg-sas-key", Key);
	
	var events = new [] {
		new GridEvent<object>() {
			Id = Guid.NewGuid().ToString(),
			EventType = "CustomEventGrid.Demo",
			Subject = "Test me !",
			EventTime = DateTime.UtcNow,
			Data = null,
			DataVersion = "0.1"
		}
	};
	
	string jsonData = JsonConvert.SerializeObject(events);

	HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Endpoint)
	{
		Content = new StringContent(jsonData, Encoding.UTF8, "application/json")
	};

	HttpResponseMessage response = await client.SendAsync(request);
}

J’ai mis en place une Azure Function qui lira mes messages postés sur mon topic Event Grid assez simplement comme on peut le voir ci-dessous :

#r "Microsoft.Azure.EventGrid"
#r "Newtonsoft.Json"

using Newtonsoft.Json;
using Microsoft.Azure.EventGrid.Models;

public static void Run(EventGridEvent eventGridEvent, ILogger log)
{
    var evt = JsonConvert.SerializeObject(eventGridEvent);

    log.LogInformation(evt);
}

Voici un log de démo :

2018-10-29T15:59:04.485 [Information] {"id":"3a6cd1ed-a4ea-4eb5-8f4c-05a4388842df","topic":"/subscriptions/e7bd1bb5-e9af-49c7-b5aa-ac09992fdfeb/resourceGroups/eventgrid-test/providers/Microsoft.EventGrid/topics/demo-eventgrid","subject":"Test me !","data":null,"eventType":"CustomEventGrid.Demo","eventTime":"2018-10-29T15:59:06.0856579Z","metadataVersion":"1","dataVersion":"0.1"}

On peut donc voir ici qu’entre l’envoi du message et la lecture de celui-ci, l’opération est de moins de 2 secondes. Ce qui peut être utile quand vous souhaitez avoir un système extrêmement réactif.


DECE 07

ARM - Etendre vos templates grâce à Azure Function

Pour construire une infrastructure sur Azure, il y a plusieurs moyens qui s’offrent à vous, notamment les suivants :

  • Le portail Azure et votre souris (ou votre trackpad)
  • Les REST API pour les courageux
  • Azure CLI
  • Azure Powershell
  • Terraform
  • Template ARM

Ici, on va plutôt parler du dernier, car c’est celui que je préfère, je n’ai pas encore été rattrapé par la hype qui touche Terraform.

Bien que l’on puisse faire beaucoup de choses avec les templates ARM, je trouve qu’on est limité en terme de fonction built in.

Dans ma liste de souhait pour Noël, j’aimerais entre autres les fonctions suivantes :

  • Calcul de dates : Date du jour, date dans 1 an, dans 1 heure …
  • Calcul de timespan : Utile dans les templates ARM créant des secrets dans des KeyVaults par exemple.

J’ai trouvé une solution pour m’offrir ces fonctionnalités dans mes templates ARM qui se base sur Azure Function et le compilateur Roslyn.

J’ai donc créé une Azure Function qui référence le package Nuget suivant : Microsoft.CodeAnalysis.Scripting en version 2.3.0

Cette fonction basée sur un trigger HTTP exécute le code suivant :

string script = req.Query["script"];
string result = await Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync<string>(script);

StringBuilder template = new StringBuilder();  
template.AppendLine("{");
template.AppendLine("    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",");
template.AppendLine("    \"contentVersion\": \"1.0.0.0\",");
template.AppendLine("    \"parameters\": {},");
template.AppendLine("    \"variables\": {},");
template.AppendLine("    \"resources\": [],");
template.AppendLine("    \"outputs\": {");
template.AppendLine("        \"eval\": {");
template.AppendLine("           \"type\": \"string\",");
template.AppendLine("           \"value\": \""+ result +"\"");
template.AppendLine("        }");
template.AppendLine("    }");
template.AppendLine("}");

return (ActionResult)new OkObjectResult(template.ToString());

Je récupère via ma querystring une chaine de caractère que j’évalue grâce à Roslyn pour ensuite la passer dans l’output d’un template ARM que je contruis dans mon code.

Il est possible d’appeler cette fonction via un template ARM, grâce à la méthode des linkedTemplate comme on peut le voir ci dessous :

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {
        "functionUrl": "https://myfunc.azurewebsites.net/api/executecsharp",
        "script": "System.DateTime.UtcNow.AddYears(10).ToString()"
    },
    "resources": [
        {
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2015-01-01",
            "name": "arm-dep",
            "properties": {
                "mode": "Incremental",
                "templateLink": {
                    "uri": "[concat(variables('functionUrl'), '?script=', variables('script'))]",
                    "contentVersion": "1.0.0.0"
                }
            }
        }
    ],
    "outputs": {
        "fromLinked": {
           "type": "string",
           "value": "[reference('arm-dep').outputs.eval.value]"
        }
    }
}

Grâce à cette méthode j’arrive à récupérer dans ce cas la date du jour dans 10 ans, il est bien entendu possible de réutiliser la sortie de mon template dans d’autres ressources comme c’est le cas pour des templates linkés.

On peut utiliser ce trick dans plusieurs scénarios plus ou moins legit. Cependant bien que cela soit possible, je ne vous conseille pas cette astuce en premier choix d’implémentation.


DECE 03

Créez vos Logic Apps via des templates ARM

Si vous avez déjà créé des workflows Logic Apps depuis le portail Azure, vous avez pu voir qu’il est possible de faire énormément de choses avec. Pour ma part, je l’utilise souvent lié à EventGrid afin d’envoyer des mails pour faire ce que j’appelle du Reactive Monitoring. Bien qu’il soit très simple de configurer notre Logic Apps depuis le portail Azure, la mise en place de ces workflows en ARM n’est pas chose aisée.

Si je prends mon cas d’usage, mes workflows Logic Apps utilisent entre autre les étapes suivantes :

  • Trigger Event Grid
  • Parsing JSON
  • Conditions
  • Appel Azure Function
  • Appel Azure Automation
  • Appel Azure AD
  • Envoi de mail

Voyons maintenant comment construire notre template ARM. Nous allons commencer par le début, en définissant la structure de notre template ARM :

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Logic/workflows",
            "apiVersion": "2016-06-01",
            "name": "mylogic",
            "location": "[resourceGroup().location]",
            "tags": {
               "displayName": "LogicApp"
            },
            "properties": {
               "state": "Disabled",
               "definition": {
                  "$schema": "https://schema.management.azure.com/schemas/2016-06-01/Microsoft.Logic.json",
                  "contentVersion": "1.0.0.0",
                  "parameters": {},
                  "triggers": {},
                  "actions": {},
                  "outputs": {}
               },
               "parameters": {}
            }
         }
    ],
    "outputs": {}
}

On peut voir que les propriétés de notre Logic Apps sont en fait représentées par un JSON qui s’imbrique dans notre template ARM.

Une fois la structure définie, il faut commencer par notre trigger Event Grid. Pour cela il faut créer un objet de type Microsoft.Web/connections

{
    "name": "[variables('eventGridConnexion')]",
    "type": "Microsoft.Web/connections",
    "apiVersion": "2016-06-01",
    "location": "[resourceGroup().location]",
    "properties": {
        "displayName": "[variables('eventGridConnexion')]",
        "api": {
            "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azureeventgrid')]"
        }
    }
}

Cet objet connexion ne contient pas les informations nécessaires pour initier la connexion dans Logic Apps, elles seront à renseigner plus tard via le portail Azure, ou via ce script Powershell : https://github.com/logicappsio/LogicAppConnectionAuth

Pour les autres managedApis que j’utilise, j’utilise les id suivants :

  • Azure Event Grid : azureeventgrid
  • Office 365 : office365
  • Azure Active Directory : azuread

On rajoute par la suite cette connexion en dépendance requise pour notre Logic Apps.

"dependsOn": [
    "[resourceId('Microsoft.Web/connections', variables('eventGridConnexion'))]",
    "[resourceId('Microsoft.Web/connections', variables('AADConnexion'))]",
    "[resourceId('Microsoft.Web/connections', variables('O365Connexion'))]"
]

Ensuite, on passe en paramètre de notre Logic Apps les informations nécessaires pour notre connexion, ici je passe par un objet $connexions, le même qui est généré par l’export ARM depuis le portail Azure.

"$connections": {
    "value": {
        "azureeventgrid": {
            "connectionId": "[resourceId('Microsoft.Web/connections', variables('eventGridConnexion'))]",
            "connectionName": "[variables('eventGridConnexion')]",
            "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('location'), '/managedApis/azureeventgrid')]"
        },
        "azuread": {
            "connectionId": "[resourceId('Microsoft.Web/connections', variables('AADConnexion'))]",
            "connectionName": "[variables('AADConnexion')]",
            "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('location'), '/managedApis/azuread')]"
        },
        "office365": {
            "connectionId": "[resourceId('Microsoft.Web/connections', variables('O365Connexion'))]",
            "connectionName": "[variables('O365Connexion')]",
            "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('location'), '/managedApis/office365')]"
        }
}

Et je peux ainsi définir mon trigger de la sorte :

"triggers": {
    "Event_Grid_Trigger": {
        "inputs": {
            "body": {
                "properties": {
                    "destination": {
                        "endpointType": "webhook",
                        "properties": {
                            "endpointUrl": "@{listCallbackUrl()}"
                        }
                    },
                    "filter": {
                        "includedEventTypes": [
                            "Microsoft.Resources.ResourceWriteSuccess"
                        ]
                    },
                    "topic": "[subscription().id]"
                }
            },
            "host": {
                "connection": {
                    "name": "@parameters('$connections')['azureeventgrid']['connectionId']"
                }
            },
            "path": "[concat('/subscriptions/', uriComponent(subscription().subscriptionId), '/providers/', uriComponent('Microsoft.Resources.Subscriptions'), '/resource/eventSubscriptions')]",
            "queries": {
                "subscriptionName": "[variables('eventGridConnexion')]",
                "x-ms-api-version": "2017-06-15-preview"
            }
        },
        "splitOn": "@triggerBody()",
        "type": "ApiConnectionWebhook"
    }
}

Il est possible de nommer votre trigger comme vous le souhaitez, par défaut les underscores sont remplacés par des espaces dans le designer Logic Apps.

Ensuite, il faut lire le contenu de notre trigger en ajoutant une action qui nous permettra de lire notre JSON et de valider le schéma d’entrée. Pour cela, on va utiliser cette action :

"Parse_EventGrid_JSON": {
    "runAfter": {},
    "type": "ParseJson",
    "inputs": {
        "content": "@triggerBody()?['data']",
        "schema": {
            /* Schéma JSON */
        }
    }
}

Pour cette première étape, il n’est pas nécessaire d’indiquer qu’elle s’exécute après notre trigger, cependant pour les autres il s’agit d’un prérequis, le service de management Azure ne prendra pas en compte l’ordre des élément dans votre template.

Afin de ne pas faire 50 pages de templates ARM ici même, voici quelques astuces pour construire votre template Logic Apps


NOVE 19

Sandbox Azure pour tout le monde

Il y a quelques articles de cela, j’ai écrit une série d’articles pour vous montrer comment j’ai créé une sandbox Azure à SOAT.

Il faut savoir que j’ai aussi sur mon compte Azure une sandbox basée sur les mêmes services à savoir :

  • Azure Functions
  • Managed Service Identity

Cette sandbox est donc moins avancée que celle disponible pour SOAT, mais elle me sert grandement pour tout ce qui est démo pour mes clients ou pour les différents meetups / conférences auxquels je participe.

Elle gère donc uniquement la création et la suppression de mes ResourceGroups, ce qui me suffit amplement, car seul moi ai accès à toutes les ressources de mes souscriptions Azure.

Bien qu’il existe à ce jour la sandbox by Microsoft, j’ai tout de même décidé de vous fournir la mienne, elle est disponible sur le site : https://www.serverlesslibrary.net/, il s’agit du projet Azure Sandbox Utils

Via le site Serverless Library, vous pouvez aller sur le repository de ma sandbox et appuyer sur le bouton déployer afin de mettre cette fameuse sandbox sur votre compte Azure.

Par la suite, il vous suffira d’effectuer les étapes suivantes:

  • Mettre le MSI en tant que contributeur ou ayant les droits Read / write / delete sur les ressources group
  • Ajouter un application insight, car les logs c’est important
  • Appeler l’url de création de ressource group avec les paramètres suivants
{ 
     name: "nameValue", 
     expirationDate: "2018-09-25",
     location: "West Europe" 
}

Et voilà fini les resourcegroups inutilisés dans votre souscription. N’hésitez pas à me faire des retours en cas de bug ou de nouvelles fonctionnalités à implémenter.

J’utilise cette sandbox à la place de celle de Microsoft pour les raisons suivantes :

  • Disponible sur mon tenant Azure (j’ai des clients qui bloquent tous les tenants sauf les leurs)
  • Temps de disponibilité des ressources modifiables
  • Partageable entre plusieurs utilisateurs

OCTO 26

Réaliser une copie de vos bases SQL Database

Lorsque vous voulez créer des environnements identiques à un autre, par exemple pour réaliser une copie de votre PROD vers une PPROD, ou mettre en place un environnement de PRA. Le problème se situe au plus souvent de vos données, puisque si je souhaite utiliser un backup et le restore sur une autre base, cela peut prendre beaucoup de temps entre la réalisation du backup et sa restauration.

Sur SQL Database, il est possible de faire une copie de votre base de données vers un autre SQL Database de manière très simple et très rapide en utiliant la commande suivante:

CREATE DATABASE newDestinationDB AS COPY OF sourcedemo.sourcedb;

Cette commande magique a tout de même quelques contraintes : si vous utilisez uniquement des comptes SQL, il faut que le compte que vous utilisez sur les deux servers ait un login et un mot de passe identique. Si vous utilisez un compte provenant d’un Azure Active Directory, vous n’aurez pas de problème car il s’agira du même compte.

Ayant eu à mettre en place ces comptes récemment et n’ayant pas trouvé la doc Azure très limpide à ce sujet, j’ai décidé d’en écrire un article.

Commençons donc par se connecter sur le SQL Server contenant la base de données que l’on souhaite copier.

Créons notre compte de la manière suivante :

CREATE LOGIN backup_sa WITH PASSWORD = 'topsecure42!'

Puis sur la base de données que l’on souhaite copier

CREATE USER backup_sa FROM LOGIN backup_sa
GO
-- role owner sur la base de données que l'on souhaite copier
sp_addrolemember db_owner, backup_sa

En ce qui concerne votre base de données source, vous avez fini votre setup. Maintenant passons à votre serveur cible, celui où vous voulez copier votre base de données.

Créons notre compte de la même manière que précédemment :

CREATE LOGIN backup_sa WITH PASSWORD = 'topsecure42!'

Puis sur la base de données master il faut ajouter notre utilisateur dans le groupe db_manager

CREATE USER backup_sa FROM LOGIN backup_sa
GO
-- role dbmanager sur la base de données que l'on souhaite copier
sp_addrolemember dbmanager, backup_sa

Il est ensuite possible de réaliser votre copie de base de données via la commande suivante :

CREATE DATABASE destinationdb AS COPY OF sourcedemo.sourcedb;