Infrastructure as Code dans Azure : Utiliser des conditions dans les templates ARM
L’utilisation des templates ARM est maintenant une pratique courante pour la mise en place d’infrastructures Azure. J’avais déjà blogué sur l’utilité de l’infrastructure as code il y a maintenant plusieurs mois !
Lorsque l’on crée une infrastructure cloud à partir d’un template ARM, il est parfois utile de modifier cette infrastructure selon l’environnement cible : souvent dans un souci d’économie pour ne pas payer des ressources Azure inutilement.
Il est maintenant possible d’utiliser des conditions dans nos template ARM, ces dernières permettant de déployer ou non certaines ressources. C’est ce que nous allons découvrir dans cet article.
Réflexion, calcul du coût et mise en place de l’infrastructure
Une template ARM permet de déployer un ensemble de ressources Azure en les définissant dans un fichier json. Ce dernier est couplé à un ou plusieurs fichiers de paramètres.
Une bonne pratique consiste à définir son architecture cloud dans un template ARM et de créer plusieurs fichiers de paramètres : un par environnement (par exemple « dev », « staging », prod »).
Exemple :
On définit un template ARM principal nommé deploy.json qui définit l’architecture cloud suivante :
- Un site web nommé « back »
- Un plan de service nommé « back-sp » qui héberge le site web « back »
- Un site web nommé « front »
- Un plan de service nommé « front-sp » qui héberge le site web « front »
Cette architecture doit pouvoir être déployée pour 3 environnements « dev », « staging » et « prod ».
On définit donc ensuite trois fichiers de paramètres, un par environnement. Ces fichiers définissent le niveau de service (« pricing tiers ») des plans de services « back-sp » et « front-sp » :
parameters.dev.json : Définit un niveau service “Basic” (B1) pour les deux plans de services
parameters.staging.json : Définit un niveau service “Standard” (S0) pour les deux plans de services
parameters.prod.json : Définit un niveau service “Standard” (S2) pour les deux plans de services
Pour ne pas engorger cet article avec des templates .json trop verbeux, le template deploy.json et les fichiers de paramètres sont disponible sur mon github.
Détails du template ARM :
Dans les paramètres du template deploy.json on définit un paramètre « env » qui va être automatiquement renseigné par les fichiers de paramètre avec les valeurs « dev », « staging » ou « prod ». La valeur de ce paramètre sera automatiquement ajoutée au nom de ressources Azure.
Le paramètre « préfix » permet de spécifier une chaine de caractères qui sera définie comme préfixe des noms de ressource Azure selon le modèle suivant : {prefix}-nomDeLaRessource-{env} :
"parameters": { "sku": { "type": "string", "defaultValue": "F1", "allowedValues": [ "F1", "D1", "B1", "B2", "B3", "S1", "S2", "S3", "P1", "P2", "P3", "P4" ] }, "env": { "type": "string", "allowedValues": [ "dev", "staging", "prod" ] } }, "variables": { "prefix": "tra", "webSiteBackName": "[Concat(variables('prefix'), '-back-', parameters('env'))]", "webSiteFrontName": "[Concat(variables('prefix'), '-front-', parameters('env'))]", "spBackName": "[Concat(variables('prefix'), '-back-sp-', parameters('env'))]", "spFrontName": "[Concat(variables('prefix'), '-front-sp-', parameters('env'))]" }
Une fois le template deploy.json déployé avec le fichier de paramètre parameters.dev.json, les ressources sont déployées avec succès dans Azure :
Coût de l’infrastructure :
Selon l’environnement, la puissance des serveurs utilisés par les applications web doit être différente. Il n’est en effet pas nécessaire que les environnements de développement et de staging utilisent des machines très puissantes, de plus cela a un impact non négligeable sur les coûts :
- Un plan de service Standard Basic (B1) = 47 € / mois
- Un plan de service Standard (S0) = 74 € / mois
- Un plan de service Standard (S2) = 150 € / mois
Puisque notre architecture utilise un plan de service pour chaque application web, voici le coût de notre infrastructure par environnement :
- dev = 2 x B1 = 94 € / mois
- staging = 2 x S0 = 154 / mois
- premium = 2 x S2 = 300 € / mois
Soit un total de 548 € / mois.
Optimisation du coût
L’infrastructure de prod ne doit pas être impactée par la volonté de faire des économies car c’est l’infrastructure utilisée par les clients. Elle doit par conséquent être dimensionnée de façon à offrir un niveau de service optimal, c’est la raison pour laquelle chaque application web possède son propre plan de service.
Cependant pour les environnements de développement et de staging, certaines concessions peuvent être effectuées car des performances moindres n’ont pas d’impact business. Par exemple, il serait possible de placer les applications web sur un seul plan de service de façon à payer un seul serveur au lieu de deux.
En termes de coût, ça devient tout de suite plus intéressant :
- dev = 1 x B1 = 47 € / mois
- staging = 1 x S0 = 74€ / mois
- premium = 2 x S2 = 300 € / mois
Soit un total de 421 € / mois
Pour réaliser cette économie, il faut être capable de modifier l’infrastructure Azure déployée, selon l’environnement cible.
Pour cela nous allons modifier le template de déploiement en trois étapes :
- Ajout d’un nouveau plan de service
- Création d’un catalogue de ressource par environnement
- Utilisation du mot clef « condition »
1. Ajout du nouveau plan de service
Dans un premier temps, il est nécessaire d’ajouter un nouveau plan de service dans le template de déploiement. Ce dernier sera créé uniquement pour les environnements de « dev » et de « staging » et utilisé par les deux sites web « back » et « front ». Une variable nommée spSharedName est également ajoutée pour nommer ce nouveau plan de service avec la bonne nomenclature :
"variables": { "prefix": "tra", "webSiteBackName": "[Concat(variables('prefix'), '-back-', parameters('env'))]", "webSiteFrontName": "[Concat(variables('prefix'), '-front-', parameters('env'))]", "spBackName": "[Concat(variables('prefix'), '-back-sp-', parameters('env'))]", "spFrontName": "[Concat(variables('prefix'), '-front-sp-', parameters('env'))]", "spSharedName": "[Concat(variables('prefix'), '-shared-sp-', parameters('env'))]", }, "resources": [ { "apiVersion": "2015-08-01", "name": "[variables('spSharedName')]", "type": "Microsoft.Web/serverfarms", "location": "[resourceGroup().location]", "tags": { "displayName": "sp shared" }, "sku": { "name": "[parameters('sku')]" }, "properties": { "name": "[variables('spSharedName')]" } }
Pour l’instant aucune logique ne nous permet d’utiliser ce plan de service.
2. Catalogue de ressources par environnement
Dans un template ARM, une variable peut être un objet complexe. Nous allons donc créer une variable nommée spConfig qui va définir les plans de services à utiliser pour chaque environnement. Cette variable va nous permettre de gérer de manière plus souple l’attribution du plan de service de chaque application web :
"variables": { "prefix": "tra", "webSiteBackName": "[Concat(variables('prefix'), '-back-', parameters('env'))]", "webSiteFrontName": "[Concat(variables('prefix'), '-front-', parameters('env'))]", "spBackName": "[Concat(variables('prefix'), '-back-sp-', parameters('env'))]", "spFrontName": "[Concat(variables('prefix'), '-front-sp-', parameters('env'))]", "spSharedName": "[Concat(variables('prefix'), '-shared-sp-', parameters('env'))]", "spConfig": { "dev": { "spBack": "[variables('spSharedName')]", "spFront": "[variables('spSharedName')]" }, "staging": { "spBack": "[variables('spSharedName')]", "spFront": "[variables('spSharedName')]" }, "prod": { "spBack": "[variables('spBackName')]", "spFront": "[variables('spFrontName')]" } }, "currentEnvConfig": "[variables('spConfig')[parameters('env')]]" }
La variable spConfig fait ici office de catalogue de ressource. La variable currentEventConfig quant à elle, charge la bonne configuration selon l’environnement courant. Cette dernière est utilisée par les applications web « back » et « front » pour définir leur plan de service cible :
{ "apiVersion": "2015-08-01", "name": "[variables('webSiteBackName')]", "type": "Microsoft.Web/sites", "location": "[resourceGroup().location]", "tags": { "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', variables('currentEnvConfig').spBack)]": "Resource", "displayName": "back" }, "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms/', variables('currentEnvConfig').spBack)]" ], "properties": { "name": "[variables('webSiteBackName')]", "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('currentEnvConfig').spBack)]" } }, { "apiVersion": "2015-08-01", "name": "[variables('webSiteFrontName')]", "type": "Microsoft.Web/sites", "location": "[resourceGroup().location]", "tags": { "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', variables('currentEnvConfig').spFront)]": "Resource", "displayName": "front" }, "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms/', variables('currentEnvConfig').spFront)]" ], "properties": { "name": "[variables('webSiteFrontName')]", "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('currentEnvConfig').spFront)]" } }
3. Le mot clef « condition »
Dans notre catalogue de ressources, on remarque que pour les environnements de développement et de staging, le plan de service cible est le même ("spSharedName") pour les deux applications web ! Dans ce cas, il n’est pas nécessaire de créer dans Azure les autres plans de services (back et front) qui sont également définis dans le template.
L’idée est d’implémenter la logique suivante au sein du template :
- Si je déploie un environnement de « dev » ou « staging », je déploie uniquement dans Azure le plan de service de mutualisé (« shared ») et on y place les deux applications web
- Si je déploie un environnement de « prod », je déploie dans Azure un plan de service pour chaque application web
Pour mettre en place ces règles, nous allons placer une condition dans la définition de chaque plan de service. Ces conditions vont évaluer si le plan de service doit être créé ou non dans Azure (selon l’environnement courant).
Les plans de services « spBack » et « spFront » seront créés uniquement si la condition suivante renvoie « true » :
"condition": "[equals(parameters('env'),'prod')]"
De même, le plan de service « spShared », sera créé uniquement si la condition suivante renvoie « true » :
"condition": "[not(equals(parameters('env'),prod))]"
L’élément « condition » s’utilise directement dans les nœuds ressources du template :
{ "condition": "[not(equals(parameters('env'),'prod'))]", "apiVersion": "2015-08-01", "name": "[variables('spSharedName')]", "type": "Microsoft.Web/serverfarms", "location": "[resourceGroup().location]", "tags": { "displayName": "sp shared" }, "sku": { "name": "[parameters('sku')]" }, "properties": { "name": "[variables('spSharedName')]" } }, { "condition": "[equals(parameters('env'),'prod')]", "apiVersion": "2015-08-01", "name": "[variables('spBackName')]", "type": "Microsoft.Web/serverfarms", "location": "[resourceGroup().location]", "tags": { "displayName": "sp back" }, "sku": { "name": "[parameters('sku')]" }, "properties": { "name": "[variables('spBackName')]" } }, { "condition": "[equals(parameters('env'),'prod')]", "apiVersion": "2015-08-01", "name": "[variables('spFrontName')]", "type": "Microsoft.Web/serverfarms", "location": "[resourceGroup().location]", "tags": { "displayName": "sp front" }, "sku": { "name": "[parameters('sku')]" }, "properties": { "name": "[variables('spFrontName')]" } }
Une fois les 3 étapes précédentes mise en place, l’utilisation des fichiers de paramètres parameters.dev.json et parameters.staging.json permettra de déployer dans Azure un environnement plus léger et moins coûteux en mutualisant l’utilisation d'un plan de service.
Exemple des ressources déployées dans Azure avec le template ARM optimisé et le fichier de paramètre parameters.dev.json :
Bons déploiements ! :)
Commentaires