MARS 16

Sandbox Azure - Contexte et configuration Azure Active Directory

Dans le cadre de SOAT, je suis en train de mettre en place un bac à sable afin que les différents consultants qui le souhaitent puissent utiliser Azure afin de se former, et de découvrir les différentes technologies liées à la plateforme.

J’ai décidé pour cela de partir sur une page blanche dans Azure, c’est-à-dire une nouvelle souscription rattachée à un nouvel Azure Active Directory. Et bien entendu, je souhaite gérer cette souscription le moins possible pour ne pas rajouter cela à ma journée de travail, et surtout offrir la meilleure disponibilité de services aux consultants qui souhaitent se former.

Mes différentes problématiques sont donc les suivantes :

  • Gestion des comptes Azure, que ça soit de la création, de l’assignation des droits et de la suppression des consultants de l’Azure AD
  • Gestion des ressources Azure : Créations et suppressions de celles-ci après expérimentation
  • Gestion des coûts Azure
  • Peu de management pour les Admins Azure

Je suis donc parti sur les différents choix pour la gestion des services via Azure avec les types de services suivants :

  • Azure AD
  • Azure Function
  • Azure Storage
  • Azure KeyVault
  • Application Insights
  • SendGrid

Je vais tâcher de décliner la création de cette zone de bac à sable en une série d’article qui pourront vous aider par la suite, soit pour créer vous même une zone de tests, soit pour en apprendre un peu plus sur la partie management d’Azure.

Commençons donc par l’Active Directory, parce qu’il est souvent mieux de commencer par la gestion des droits.

Dans mon cas, peu de gestion de droits par groupe, puisque je compte mettre en place un système qui crée des groupes de ressources, je vais donc créer deux groupes qui sont les suivants :

  • Admins : Qui contiendra les différents admins de la souscription Azure
  • Users : Qui contient tous les autres utilisateurs, pour leur donner accès à des ressources communes à ma sandbox

Donc rien de bien complexe en terme de groupe AD, maintenant les permissions que je mets sur l’Azure AD sont les suivantes :

  • Autorisation : Les utilisateurs peuvent inscrire des applications

  • Utile pour la création de compte applicatif, notamment pour faire un peu de DevOps via VSTS ou tout autre outil, le publish depuis Visual Studio c’est mal….

  • Refus : Les membres peuvent inviter

  • Je n’ai pas envie de gérer des comptes persos sur cette plateforme, donc j’empêche les invitations aux membres.

  • Refus : Les invités peuvent inviter

  • C’est notamment s’il y a un écart un jour et qu’un admin créé un compte Guest

Les autres droits je les laisse par défaut, je n’ai aucunement besoin de les modifier dans mon cas présent.

Bien entendu, j’optimise les coûts il s’agit d’un AD gratuit.

Le prochain article parlera de comment utiliser la Graph API et des Converged Applications pour créer de nouveaux utilisateurs dans l’AD selon leur demande, donc stay tuned !

MARS 14

Utilisation des SecureString en Powershell

La gestion de vos mots de passe en powershell se fait souvent via des securestring, si toutefois la gestion de vos mots de passe se passe via un ensemble de post-it proche de votre bureau, il s’agit d’un problème de sécurité, mais ce n’est pas le sujet ici.

Pour convertir vos mots de passe en securestring, vous pouvez exécuter le code suivant :

$original  =  'myPassword'  
  
$secureString  =  ConvertTo-SecureString  $original  -AsPlainText  -Force

Cela vous donnera un objet de type System.Security.SecureString qui n’est pas très lisible comme cela, il est possible de récupérer une chaine de caractère correspondante à votre SecureString, mais cependant il ne s’agira pas de la chaine d’origine, comme on peut le voir ci-dessous :

$secureStringValue = ConvertFrom-SecureString $secureString

La valeur de notre variables secureStringValue est donc la suivante :

01000000d08c9ddf0115d1118c7a00c04fc297eb01000000e5e21feb868c94468d6fab05f535e198000000000200000000001066000000010000200000002aa496e945431d41 fe82e4e007773caf9379c1cbf563b7163689a5f752b325f5000000000e80000000020000200000007f1837b77634b506072902d0ea16276f66a6b7b05eec06979823d9271fe7 4975100000008a44ddb2f63d13dd1bf298bbc30b679240000000b94350179a432fc6ec084e2ee6ae9099963a82ee2768f8687309a59d8b371d337495240feb9efae58fba6945 9f4e018e070339798facebac15ba06ac845784dc

Si je vous dis que cette chaine de caractère ne correspond pas à l’exemple de code présent ici, vous devez uniquement me croire sur parole, car si on ne fournit pas de clé d’encryption ce qui est mon cas, la cmdlet se base sur les API Windows Data Protection, donc pour faire une conversation inverse, il faut faire celle ci sur la même machine avec le même utilisateur.

Cependant si vous voulez faire un ConvertBack, il est possible de le faire via ces différentes commandes Powershell :

$secureStringBack = $secureStringValue | ConvertTo-SecureString  
  
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureStringBack);  
$finalValue = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)

FEVR 23

Tips - Création de différents Load Balancers via des templates ARM

Voici un nouvel article sur la création de ressources Azure en ARM, ici il va s’agir du Load Balancer, tout ceux qui en ont créé un via le portail Azure, savent qu’il s’agit d’une bonne heure de clic-o-drome.

Cette fois ci, on va corser la chose, je vais vous montrer comment créer :

  • Plusieurs load balancers
  • Privé ou publique
  • Plusieurs ports ouverts

On va donc partir sur ce jeu de paramètres :

"loadBalancers": {  
    "value": [  
    {  
        "name": "front",  
        "accessType": "public",  
        "dns": "wwosfdemo",  
        "port": [ 80, 8080, 443 ]  
    },  
    {  
        "name": "middle",  
        "accessType": "private",  
        "subnetName": "MiddleSubnet",  
        "port": [ 8081 ]  
    },  
    {  
        "name": "back",  
        "accessType": "private",  
        "subnetName": "BackSubnet",  
        "port": [ 8082 ]  
    },  
    {  
        "name": "admin",  
        "accessType": "private",  
        "subnetName": "AdminSubnet",  
        "port": [ 19000, 19080 ]  
    }]  
}

Pour ceux qui l’ont reconnu, ça ressemble beaucoup à un use case complexe d’une architecture Service Fabric.

Pour créer ces différentes ressources, j’ai donc besoin de 3 types de ressources qui sont :

  • Public IP : pour pouvoir exposer mes External Load Balancer
  • Subnet (et donc Virtual Network) : pour pouvoir conserver mes Internal Load Balancers
  • Load Balancer : pour créer mes Internal et External Load Balancer

Je sépare les uses case “privés” et “publique” pour des questions de lisibilité, sachant qu’on a déjà des boucles imbriquées dans ce template, on ne va pas complexifier la chose encore une fois.

Pour la création des Virtual Network je vous renvoie à mon précédent article qui parle de ce sujet là : http://blog.woivre.fr/blog/2017/12/tips-creation-dazure-virtual-network-via-les-templates-arm #autopromo

Pour la création des adresses IP publiques, on va utiliser cette partie de template :

{  
    "apiVersion": "2017-10-01",  
    "type": "Microsoft.Network/publicIPAddresses",  
    "condition": "[equals(variables('loadBalancers')[copyIndex('publicIpLoop')].accessType, 'public')]", 
    "name": "[concat(variables('loadBalancers')[copyIndex('publicIpLoop')].name, variables('suffix').publicIPAddress)]",  
    "location": "[resourceGroup().location]",  
    "tags": {  
        "displayName": "Public IP"  
    },  
    "properties": {  
        "dnsSettings": {  
            "domainNameLabel": "[variables('loadBalancers')[copyIndex('publicIpLoop')].dns]"  
        },  
        "publicIPAllocationMethod": "Dynamic"  
    },  
    "copy": {  
        "name": "publicIpLoop",  
        "count": "[length(variables('loadBalancers'))]"  
    }  
}

On fait donc ici un mixte entre une boucle et une condition pour créer une adresse IP publique pour chacun de nos Load balancer qui en a besoin. Par ailleurs je vous conseille de nommer vos différentes boucles dans un template ARM pour des questions de lisibilité encore une fois.

Pour les Load Balancers, rappelons rapidement la structure d’un Load Balancer qui est au minima la suivante :

{  
    "apiVersion": "2017-10-01",  
    "type": "Microsoft.Network/loadBalancers",  
    "name": "LoadBalancer",  
    "location": "[resourceGroup().location]",  
    "properties": {  
        "frontendIPConfigurations": [],  
        "backendAddressPools": [],  
        "inboundNatPools": [],  
        "probes": [],  
        "loadBalancingRules": []  
    }  
}

Commençons donc par les Load Balancers publiques :

{  
    "apiVersion": "2017-10-01",  
    "type": "Microsoft.Network/loadBalancers",  
    "condition": "[equals(variables('loadBalancers')[copyIndex('loadBalancersLoop')].accessType, 'public')]",  
    "name": "[concat(variables('loadBalancers')[copyIndex('loadBalancersLoop')].name, variables('suffix').publicLoadBalancers)]",  
    "location": "[resourceGroup().location]",  
    "tags": {  
        "displayName": "External Load Balancer"  
    },  
    "dependsOn": [  
        "publicIpLoop"  
    ],  
    "properties": {  
        "frontendIPConfigurations": [{  
            "name": "FrontEndPublicIPConfiguration",  
            "properties": {  
                "publicIPAddress": {  
                    "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('loadBalancers')[copyIndex('loadBalancersLoop')].name, variables('suffix').publicIPAddress))]"  
                }  
            }  
        }],  
        "backendAddressPools": [{  
            "name": "BackEndAddressPool"  
        }],  
        "inboundNatPools": [{  
            "name": "BackEndNatPool",  
            "properties": {  
                "backendPort": 3389,  
                    "frontendIPConfiguration": {  
                        "id": "[concat(resourceId('Microsoft.Network/loadBalancers', concat(variables('loadBalancers')[copyIndex('loadBalancersLoop')].name, variables('suffix').publicLoadBalancers)), '/frontendIPConfigurations/FrontEndPublicIPConfiguration')]"  
                    },  
                "frontendPortRangeEnd": 4500,  
                "frontendPortRangeStart": 3389,  
                "protocol": "Tcp"  
            }  
        }],  
        "copy": [{  
            "name": "probes",  
            "count": "[length(variables('loadBalancers')[copyIndex('loadBalancersLoop')].port)]",  
            "input": {  
                "name": "[concat('probe-', variables('loadBalancers')[copyIndex('loadBalancersLoop')].port[copyIndex('probes')])]",  
                "properties": {  
                    "intervalInSeconds": 5,  
                    "numberOfProbes": 2,  
                    "port": "[variables('loadBalancers')[copyIndex('loadBalancersLoop')].port[copyIndex('probes')]]",  
                    "protocol": "Tcp"  
                }  
            }  
        },  
        {  
            "name": "loadBalancingRules",  
            "count": "[length(variables('loadBalancers')[copyIndex('loadBalancersLoop')].port)]",  
            "input": {  
                "name": "[concat('rule-', variables('loadBalancers')[copyIndex('loadBalancersLoop')].port[copyIndex('loadBalancingRules')])]",  
                "properties": {  
                    "backendAddressPool": {  
                        "id": "[concat(resourceId('Microsoft.Network/loadBalancers', concat(variables('loadBalancers')[copyIndex('loadBalancersLoop')].name, variables('suffix').publicLoadBalancers)), '/backendAddressPools/BackEndAddressPool')]"  
                    },  
                    "backendPort": "[variables('loadBalancers')[copyIndex('loadBalancersLoop')].port[copyIndex('loadBalancingRules')]]",  
                    "enableFloatingIP": false,  
                    "frontendIPConfiguration": {  
                        "id": "[concat(resourceId('Microsoft.Network/loadBalancers', concat(variables('loadBalancers')[copyIndex('loadBalancersLoop')].name, variables('suffix').publicLoadBalancers)), '/frontendIPConfigurations/FrontEndPublicIPConfiguration')]"  
                    },  
                    "frontendPort": "[variables('loadBalancers')[copyIndex('loadBalancersLoop')].port[copyIndex('loadBalancingRules')]]",  
                    "idleTimeoutInMinutes": 5,  
                    "probe": {  
                        "id": "[concat(resourceId('Microsoft.Network/loadBalancers', concat(variables('loadBalancers')[copyIndex('loadBalancersLoop')].name, variables('suffix').publicLoadBalancers)), '/probes/', concat('probe-', variables('loadBalancers')[copyIndex('loadBalancersLoop')].port[copyIndex('loadBalancingRules')]))]"  
                    },  
                    "protocol": "Tcp"  
                }  
            }  
        }  
    ]},  
    "copy": {  
        "name": "loadBalancersLoop",  
        "count": "[length(variables('loadBalancers'))]"  
    }  
}

On peut voir que c’est un peu verbeux puisque j’ai une boucle sur les Load Balancer, ainsi qu’une boucle sur les différents ports de chacun. Par ailleurs étant donné le fait que la partie “rules” contient uniquement des id de ressource, c’est juste un jeu de construction de template. Le tout se construit plutôt bien si vous êtes assez concentrés lors de l’écriture de celui-ci.

Pour les Load balancers privés, on repart sur le même template sauf pour les conditions, et la gestion de la FrontEndIpConfiguration qui indique un sous réseau, plutôt qu’une adresse IP Publique, comme on peut le voir ci-dessous :

"frontendIPConfigurations": [{  
    "name": "FrontEndPrivateIPConfiguration",  
    "properties": {  
        "subnet": {  
            "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetwork').name), '/subnets/', variables('loadBalancers')[copyIndex('loadBalancersLoop')].subnetName)]"  
        }  
    }  
}

Le template est présent dans cet article, je ne le mets pas de suite sur Github, je vous le mettrai avec un article prochain sur la création d’un cluster Service Fabric complexe via un template ARM.

JANV 23

Bonne résolution - Prendre soin de son cluster Service Fabric

Pour ceux qui ne connaissent pas Service Fabric, il s’agit d’un produit fourni par Microsoft qui permet de mettre en place des architectures micro-services basées soit sur des “Guest Exe”, des containers ou des “reliable service” qui sont dans ce cas présent, des applications développées avec un SDK fourni par l’éditeur.

Ceux qui ont déjà testé Service Fabric dans un cadre projet ont pu voir qu’il est assez magique et rapide de mettre en place des solutions qui fonctionnent toutes seules. Par toutes seules, je veux bien entendu parler de toutes les fonctionnalités “automagiques” qu’offre Service Fabric, comme la répartition automatique des micro-services au sein du cluster, l’auto-healing du cluster, ou encore la gestion des données des différents services stateful.

Maintenant rien n’est vraiment magique si on creuse vraiment comment cela marche, mais ce n’est pas le but de cet article, nous allons voir comment faire en sorte de bien prendre soin de son cluster pour qu’il n’arrive rien d’irrémédiable à celui-ci. Et quand je veux dire rien d’irrémédiable, je parle du moment où vous vous dîtes : “Autant créer un nouveau cluster et rattraper mes données, car l’actuel est bon pour la casse”, bon après vous me direz que cette méthode marche et elle a souvent été éprouvée, mais bon ça n’arrive jamais au bon moment.

Laissez lui de l’air pour que votre cluster s’épanouisse :

A part le fait que cette phrase marche aussi avec un adolescent, il est nécessaire de laisser de l’espace disque sur les différents nœuds de votre cluster pour qu’il puisse effectuer ses opérations sans aucun soucis. Alors grand scoop, Service Fabric a beau être présenté comme du PaaS sur Azure, derrière, vous allez monter un cluster de Virtual Machine Scale Set (moi j’appelle ça du IaaS), le côté PaaS offert par Azure sur votre cluster SF concerne surtout les updates des versions de Service Fabric, les autres fonctionnalités qui sont présentées avec le produit sont les mêmes pour un cluster on premise ou créer à la main sur des Virtual Machine Azure ou AWS ou autre … Donc ce cluster Scale Set, il faut le bichonner c’est grâce à lui que votre Service Fabric fonctionne, un des éléments à prendre en compte c’est l’espace disque disponible pour vos applications.

Bon vous me direz un disque dur ça ne se remplit pas tout seul, et bien je suis d’accord, tout dépend de votre utilisation de votre cluster. Si on exclu les erreurs courantes d’occupation d’espace disque, comme la génération de fichiers de logs sur le disque local sans mettre en place de politique de purge. Service Fabric peut utiliser l’espace disque pour stocker l’état des services stateful que ce soit vos acteurs ou vos “reliable service”, donc si vous ne supprimez jamais les données locales, il est possible de ne plus avoir d’espace disque si vous avez des acteurs qui gèrent beaucoup de données en disque.

Pour éviter ce problème, il est possible de faire les actions suivantes :

  • Supprimer vos acteurs qui ne sont plus utilisés, par exemple ceux qui font des actions temporaires dans un traitement

  • Mettre en place une politique de stockage à double niveau, à la fois basée sur le cluster, puis un déchargement sur un Blob Storage (ou autre).

  • Mettre en place des alertes sur l’espace disque utilisé afin d’éviter les mauvaises surprises le jour où cela pose problème.

Les disques à monitorer dépendent de l’installation de votre cluster, si vous êtes passé par un template ARM comme celui disponible sur le GitHub de Microsoft (https://github.com/Azure/azure-quickstart-templates/blob/master/service-fabric-secure-cluster-5-node-1-nodetype/azuredeploy.json), vous pouvez voir qu’il y a une propriété nodeDataDrive qui vous permet de positionner les données de votre cluster sur le disque OS ou sur le disque Temp. A noter qu’il est possible de mettre vos données sur le disque Temp sans risque majeur si vos applications sont bien conçues elles sont redondées sur différents nœuds du cluster.

Dans l’hypothèse ou il est trop tard, et que votre cluster est chargé de beaucoup de données, il est parfois possible de supprimer des applications via les API Service Fabric. Cependant, cette opération ne marche pas toujours si votre cluster est vraiment plein. La dernière chance est souvent celle de se connecter au cluster via RDP (ou SSH si c’est un cluster Linux) et de supprimer les dossiers contenant les données liées à la persistance de vos services. J’espère par ailleurs que vous n’arriverez pas à cette solution ultime et brutale.

Les clusters adoptent une démarche démocratique :

Si vous vous êtes intéressé un peu à la notion de cluster, notamment celui de Service Fabric, vous devez savoir qu’il y des nœuds et des composants qu’on ne peut pas supprimer sans y prendre une attention toute particulière. Au sein de Service Fabric, il y a une notion de “Seed Node” que ce soit on-premise ou sur Azure, ces nœuds particuliers hébergent entre autre les applications systèmes servant au bon fonctionnement du cluster, il est donc fortement conseillé de faire en sorte que ces nœuds soient toujours opérationnels. Vous allez me dire que vu que c’est du Service Fabric managé, et que c’est automagique, vous n’avez rien à craindre pour ces nœuds. Et bien non, ce n’est pas le cas, derrière votre cluster il y a un cluster de Virtual Machine Scale Set, et la mauvaise nouvelle c’est que vous y avez accès, donc c’est à vous de le manager et de s’assurer qu’il se porte bien. Vous pouvez voir la liste de vos “Seed Node” dans le cluster manifest, comme on peut le voir ci dessous :

image

A noter que le XML de déclaration de l’infrastructure change entre un cluster “on premise” et un cluster Azure. De même selon le niveau de durabilité de votre cluster, le nombre de vos seed nodes est différents.

Alors je parle de démocratie, puisque toute opération d’infrastructure doit être votée par l’ensemble du chorum que forme l’ensemble de ses Seed Nodes. Un cas concret, si par exemple vous avez perdu un de vos seeds node, et que vous souhaitez mettre à jour votre cluster, celle-ci ne se fera pas.

Alors ce cas là arrive uniquement quand vous faites les opérations suivantes :

  • Supprimer un nœud de votre cluster ScaleSet (oui la ressource n’est pas verrouillée par Service Fabric)

  • Scale Up / Scale down, il est possible que les nœuds du chorum soient supprimés

  • Créer votre cluster Scale Set avec le paramètre overProvision à true, dans ce cas précis lors de la création il crée plus de serveurs que nécessaire, et après il supprime ceux qu’il juge en trop, donc peut être vos nœuds de chorum

  • Maltraiter les fichiers Service Fabric situés sur votre cluster, bon là ça devient intentionnel….

Si par hasard, c’est trop tard et que vos “Seed Nodes” sont cassés, vous pouvez envoyer un mail au support Microsoft Azure qui vous dira quasiment à chaque fois qu’il faut recréer votre cluster, ce qui peut être problématique dans le cas où vous vous êtes basé sur un cluster appartenant à un Virtual Network un peu petit et ayant des contraintes d’adressage IP, comme pour un cluster privé.

Pour éviter cela, c’est très simple, il suffit soit de faire grandement attention lorsque vous touchez l’infrastructure sous jacente à votre cluster. Il est possible de totalement protéger votre cluster Scale Set Service Fabric si vous prenez la peine de créer 2 types de nœuds lors de la création de votre cluster, le premier type contiendra les services “systèmes” et le deuxième vos services applicatifs, il faudra bien entendu prendre ceci en compte lors de vos déploiements.

Via le portail Azure, lorsque vous créer votre cluster, vous pouvez faire comme cela :

image

Il est bien entendu possible de faire tout cela en ARM (car après tout qui déploie des ressources Azure depuis le portail Azure ….) Bref à la création, vous aurez 2 clusters Scale Set, à vous de mettre des droits restreints sur le scale set sysnode dans mon cas, et pour preuve :

image

Si vous creusez un peu plus le cluster, vous pourrez voir que les applications systèmes sont toutes dans le scale set “sysnode” à l’exception du DnsService qui est présent sur tous les nœuds.

Bref prenez soin de vos clusters avant que les problèmes surviennent, car ils n’arrivent jamais au bon moment et les résolutions sont soit pas simples à mettre en œuvre, soit assez destructives pour le cluster.

DECE 22

Tips - Création d’Azure Virtual Network via les templates ARM

Il existe de multiples manières de créer des ressources sur la plateforme Azure, je ne dirais pas qu’il y en a de meilleures que d’autres, elles ont chacune leurs avantages et leurs inconvénients. Pour rappel, pour créer des ressources sur Azure, vous avez la possibilité de le faire d’une des manières suivantes :

  • Via le portail Azure qui a le mérite d’être simple à utiliser et correspond très bien au besoin si vous souhaitez mettre en place une ressource Azure pour la première fois, ou pour créer une ressource à des vues de tests. Par contre, c’est peu automatisable, et je ne conseille pas de se dire “hey si je faisais ma création de ressources via un test ui automatisé”
  • Via la CLI ou du powershell qui ont le mérite de pouvoir être jouer de manière automatisée et qui permettent de créer rapidement des ressources via du code, cependant il va falloir prendre en compte les comportements lorsqu’une ressource existe déjà. Et bien entendu la méthode “je supprime si la ressource existe” puis je recrée a quelques limites.
  • Via les REST API Azure sont certes très puissantes, mais il y a du code à écrire, à maintenir, et on retrouve les mêmes désavantages qu’avec une ligne de commande.
  • Via des templates ARM qui sont en réalité une surcouche aux REST API Azure vont vous permettre de décrire votre architecture via un “simple” modèle JSON. Par ailleurs, ils gèrent nativement les ressources existantes en apportant les modifications si besoin, donc pas de soucis à se faire là dessus. Cependant ces templates sont plutôt verbeux, ce qui ne facilite pas toujours leur adoption, de plus ils ont quelques limites que nous ne verrons pas dans cet article.

Si je prends comme exemple la création d’un Virtual Network, si je veux le créer via un template ARM, soit j’exporte un template ARM depuis un VNET existant sur Azure, soit je pars d’une feuille blanche, soit je vais voir ce magnifique repo GIT de Microsoft : https://github.com/Azure/azure-quickstart-templates. Il a pour avantage de contenir toute sorte de template pour à peu près toutes les ressources Azure disponible.

Après avoir trouvé le template que je souhaite, je peux le prendre et m’en inspirer pour faire un template avec mes paramètres souhaités et divers autres changements, je vais donc avoir quelque chose de ce type :

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetName": {
            "type":"string",
            "defaultValue": "demo-vnet",
            "metadata": {
                "description" : "Virtual network name"
            }
        }, 
        "vnetAddressPrefix": {
            "type": "string",
            "defaultValue": "16.0.0.0/16",
            "metadata": {
                "description": "Address prefix"
            }
        }, 
        "subnet1Name": {
            "type": "string",
            "defaultValue": "Front",
            "metadata": {
                "description": "Subnet name"                
            }
        },
        "subnet1Prefix": {
            "type": "string", 
            "defaultValue": "16.0.1.0/24",
            "metadata": {
                "description": "subnet 1 prefix"
            }
        },
        "subnet2Name": {
            "type": "string",
            "defaultValue": "Back",
            "metadata": {
                "description": "Subnet name"                
            }
        },
        "subnet2Prefix": {
            "type": "string", 
            "defaultValue": "16.0.3.0/24",
            "metadata": {
                "description": "subnet 2 prefix"
            }
        }
    },
    "variables": {
        "vnetName":"[parameters('vnetName')]", 
        "vnetAddressPrefix": "[parameters('vnetAddressPrefix')]", 
        "subnet1Name": "[parameters('subnet1Name')]",
        "subnet1Prefix": "[parameters('subnet1Prefix')]",
        "subnet2Name": "[parameters('subnet2Name')]",
        "subnet2Prefix": "[parameters('subnet2Prefix')]"
    },
    "resources": [
        {
            "apiVersion": "2015-06-15",
            "type": "Microsoft.Network/virtualNetworks",
            "name": "[variables('vnetName')]",
            "location": "[resourceGroup().location]",
            "properties": {
              "addressSpace": {
                "addressPrefixes": [
                  "[variables('vnetAddressPrefix')]"
                ]
              },
              "subnets":[
                  {
                      "name": "[variables('subnet1Name')]", 
                      "properties":{
                          "addressPrefix": "[variables('subnet1Prefix')]"
                      }
                  },
                  {
                    "name": "[variables('subnet2Name')]", 
                    "properties":{
                        "addressPrefix": "[variables('subnet2Prefix')]"
                    }
                }
              ]
            }
        }
    ],
    "outputs": {
        
    }
}

Bon, comme on peut le voir, c’est plutôt verbeux… Mais pour moi là n’est pas le problème, c’est que pour rajouter un subnet à notre VNET, il faut soit passer par un autre template ARM qui ajoute des subnets, ce qui est plutôt compliqué pour les rejouer si on a ajouté 3/4 subnets entre le temps de création du VNET et la nouvelle exécution de ce template sur une autre souscription. Pour parer cela, il est possible d’utiliser des objets complexes, et des fonctions intégrées aux templates ARM, tel que la copy. Cela nous donnerait donc ce script si je l’applique sur le même principe :

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetName": {
            "type":"string",
            "defaultValue": "demo-vnet",
            "metadata": {
                "description" : "Virtual network name"
            }
        }, 
        "vnetAddressPrefix": {
            "type": "string",
            "defaultValue": "16.0.0.0/16",
            "metadata": {
                "description": "Address prefix"
            }
        }, 
        "subnets":{
            "type": "array",
            "defaultValue": [
                {
                    "Name": "Front",
                    "Prefix": "16.0.1.0/24"
                },
                {
                    "Name": "Middle",
                    "Prefix": "16.0.2.0/24"
                },
                {
                    "Name": "Back",
                    "Prefix": "16.0.3.0/24"
                }
            ],
            "metadata": {
                "description" : "List of subnets to create"
            }
        }
    },
    "variables": {
        "vnetName":"[parameters('vnetName')]", 
        "vnetAddressPrefix": "[parameters('vnetAddressPrefix')]", 
        "subnets": "[parameters('subnets')]"
    },
    "resources": [
        {
            "apiVersion": "2015-06-15",
            "type": "Microsoft.Network/virtualNetworks",
            "name": "[variables('vnetName')]",
            "location": "[resourceGroup().location]",
            "properties": {
              "addressSpace": {
                "addressPrefixes": [
                  "[variables('vnetAddressPrefix')]"
                ]
              },
              "copy": [
                  {
                      "name": "subnets",
                      "count": "[length(variables('subnets'))]",
                      "input": {
                          "name": "[variables('subnets')[copyIndex('subnets')].Name]",
                          "properties": {
                              "addressPrefix": "[variables('subnets')[copyIndex('subnets')].Prefix]"
                          }
                      }
                  }
              ]
            }
        }
    ],
    "outputs": {
        
    }
}

Alors, effectivement en terme de verbosité on ne gagne pas tant de ligne que cela, mais on a l’avantage de garder toujours le même template pour modifier notre VNET et y ajouter différents subnets.

Et sans oublier le lien des templates ARM, si vous le voulez pas copier coller  : https://github.com/wilfriedwoivre/demo-blog/tree/master/ARM/Tips%20VNET%20creation