Déployer simplement une VM sur Azure avec Terraform

Premier article d’introduction aux concepts d’Infrastructure As Code : comment simplement se créer une VM de test sur Azure de manière automatique ?

Pour cela nous allons détailler, étape par étape, comment utiliser l’outil Terraform de Hashicorp pour construire notre petit bout d’infrastructure.

Sommaire

Attention !
La syntaxe présentée ici est compatible avec Terraform < 0.12.
Plus d’informations ici : https://www.terraform.io/upgrade-guides/0-12.html

Introduction

Que ce soit professionnellement ou sur des projets personnels, il nous est souvent arrivé de nous dire qu’il serait quand même plus simple de pouvoir disposer de bouts d’infrastructure pour tester nos développements. Ça peut être une simple machine déjà configurée ou un infrastructure un peu plus complexe, mais qui ne servirait que le temps d’un test.

L’Infrastructure As Code, en association avec les technologies cloud afin de gagner en soupless et de réduire les coûts, permet de définir sous forme de code reproductible à l’infini des infrastructures.

Je reviendrais plus en détail sur tout ce cela que peut nous apporter dans un prochain article. L’idée étant ici de décrire rapidement les premiers concepts afin de pouvoir gérer sa première VM de test.

Les pré-requis

Pour pouvoir lancer des VM sur Azure, il nous faut bien évidement un compte Microsoft Azure : https://azure.microsoft.com/fr-fr/free/

Attention !
La création du compte est gratuite mais nécessite de renseigner un moyen de paiement. En effet, toutes les ressources on un coût à l’utilisation. Cependant, Microsoft offre 170€ de crédit pendant 1 mois, largement de quoi tester la plateforme sans débourser un centime.

Une fois le compte créé, nous aurons besoin d’informations de connexion à utiliser avec Terraform :

subscription_id
client_id
client_secret
tenant_id

Je vous invite à suivre la documentation officielle de Terraform pour la connexion à Microsoft Azure afin de les générer : https://www.terraform.io/docs/providers/azurerm/auth/service_principal_client_secret.html#creating-a-service-principal-in-the-azure-portal

Enfin, pour utiliser Terraform, nous avons la possibilité d’utiliser le binaire depuis sont poste de travail (https://www.terraform.io/) ou directement depuis le Cloud Shell intégré au portail Azure (https://azure.microsoft.com/fr-fr/features/cloud-shell/).

Le scénario

Avant de se lancer, il est bon de savoir où on va. L’objectif de cet article est de pouvoir créer à l’envie des VM sur le cloud Microsoft Azure, de manière fiable grace à des scripts fiables, versionnés et idempotents.

Dans Azure, ça se traduit comme ceci :

Azure

Nous allons devoir créer plusieurs ressources :

  • Un ResourceGroup, sorte de groupement logique pour isoler toutes nos resources.
  • Un VirtualNetwork, ou réseau virtuel
  • Un Subnet défini dans le réseau virtuel précédent, afin de disposer d’un espace d’adressage réseau
  • Une IP publique dans ce subnet, pour que nous puissions accéder à notre VM mais aussi exposer des services
  • Un NetworkSecurityGroup, permettant de définir des règles d’accès au niveau réseau (~firewall)
  • Une interface réseau virtuelle, pour lier l’adresse IP publique à la VM
  • Un StorageAccount, qui nous servira à stocker nos disques virtuels et nos données en général
  • Une VM, enfin !

Mon premier fichier Terraform

Afin de savoir quelles actions effectuer, Terraform utilise des fichiers .tf définissant le résultat que nous souhaitons obtenir. Ces fichiers utilisent la syntaxe HCL propre aux outils de l’éditeur.

En cas de doute, je vous invite à jeter un oeil à la documentation officielle ici : https://www.terraform.io/docs/configuration/syntax.html

Nous allons donc commencer par créer un répertoire pour notre projet, puis un premier fichier à l’intérieur que l’on nommera par exemple main.tf.

Définition du Provider

Terraform étant un outil d’Infrastructure As Code permettant de gérer pleins de providers différents (AWS, Azure, OpenStack, Kubernetes, etc …), nous allons devoir dans un premier temps lui dire lequel utiliser.

La liste complète des providers se trouve ici : https://www.terraform.io/docs/providers/)

Dans le cas qui nous intéresse ici, nous avons besoin d’effectuer des actions sur le cloud public Microsoft Azure grace au provider azurerm. Pour cela rien de plus simple : il suffit de déclarer le provider que nous souhaitons utiliser dans notre fichier via le bloc suivant :

# PROVIDER
provider "azurerm" {
  # L'attribut `version` est optionnel mais conseillé pour contrôler la version du provider qui sera utilisé.
  # Dans le cas contraire, il sera automatiquement mis à jour par Terraform à chaque exécution.
  version = ">=1.22.0"
  
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  client_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  client_secret   = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  tenant_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

C’est ici qu’il faut renseigner les informations de connexion que nous avons obtenu dans la section Les pré-requis

Définition du ResourceGroup

Dans le cloud Azure, toute les resources dont nous avons besoin sont créées au sein d’un regroupement logique : un ResourceGroup. Cela nous permet de les identifier, ainsi que de plus simplement les manipuler via le portail Azure. Par exemple, supprimer le ResourceGroup supprimera toutes les resources qu’il contient. Pratique pour faire du ménage !

Dans notre cas nous resterons assez simple en créant un ResourceGroup pour toutes nos ressources, que nous appellerons par exemple testRG. Voici le bout de code correspondant à ajouter au fichier main.tf :

resource "azurerm_resource_group" "myFirstRG" {
    name     = "testRG"
    location = "westeurope"

    tags {
        environment = "test"
    }
}

Nous découvrons ici comment définir notre première ressource :

En premier lieu le mot-clé resource afin d’informer Terraform que nous souhaitons créer une ressource. Ensuite nous spécifions le type de resource que nous souhaitons créer. Ici un azurerm_resource_group, ainsi qu’un identifiant unique pour pouvoir y faire référence plus tard : myFirstRG

Cet identifiant est uniquement utile pour le code Terraform. Il ne sera pas visible ni utilisé par les ressources elles mêmes.

Dans le bloc entre accolades, nous allons maintenant définir les paramètres de cette ressource. Nous avons ici :

  • Le nom de la ressource dans Azure grace au mot-clé “name”. Ici “testRG”
  • Sa localisation parmi les différentes zones géographiques d’Azure grace au mot-clé “location”. Ici “westeurope” pour les Pays-Bas.
  • Des tags grace au mot-clé “tags”. Ces tags permettent de catégoriser ou d’identifier les ressource. Ces tags sont libres.

vous pouvez retrouver tous les paramètres disponibles pour cette ressource dans sa documentation : https://www.terraform.io/docs/providers/azurerm/r/resource_group.html

Définition du VirtualNetwork et du Subnet

De la même manière que le ResourceGroup précédent, le VirtualNetwork et le Subnet sont de simples ressources et se déclarent donc de la même manière. Nous pouvons donc ajouter à notre fichier main.tf :

# Creer un virtual network
resource "azurerm_virtual_network" "myFirstVNet" {
    name                = "testVNet"
    address_space       = ["10.0.0.0/16"]
    location            = "westeurope"
    resource_group_name = "${azurerm_resource_group.myFirstRG.name}"

    tags {
        environment = "test"
    }
}

# Creer un subnet
resource "azurerm_subnet" "myFirstSubnet" {
    name                 = "testSubnet"
    resource_group_name  = "${azurerm_resource_group.myFirstRG.name}"
    virtual_network_name = "${azurerm_virtual_network.myFirstVNet.name}"
    address_prefix       = "10.0.1.0/24"
}

Ça nous permet d’appréhender un nouveau concept : les références. En effet quand nous définissons la ressource VirtualNetwork, nous avons besoin de lui renseigner le paramètre resource_group_name qui détermine dans quel ResourceGroup le créer. Or nous ne le connaissons pas encore car il n’est pas créé dans Azure (nous n’avons fait que le définir dans notre fichier).

À la place, nous allons renseigner la référence qui pointe sur l’information dont nous avons besoin sous la forme :

${<type_de_ressource>.<identifiant_de_la_ressource>.<champ_contenant_la_valeur>}

avec :

  • Le type_de_ressource : ici nous utilisons le type de notre ResourceGroup créé précédement : azurerm_resource_group
  • L’identifiant_de_la_ressource : dans notre exemple nous l’avons désigné en tant que : myFirstRG
  • Le champ de la valeur de la ressource nous intéressant : ici nous devons renseigner le nom qui sera remplacé à l’exécution par la valeur du champ name de notre ressource

Dans notre exemple, resource_group_name = “${azurerm_resource_group.myFirstRG.name}” sera remplacé par la valeur du champ name de la ressource de type azurerm_resource_group qui a l’identifiant myFirstRG. Donc _resource_group_name = “testRG”

Le concept de référence est un élément central permettant à Terraform de générer lors de son exécution un “plan des dépendances” entre toutes les ressources définies dans le fichier .tf. Terraform prenant en charge de cette manière l’ordre dans lequel les actions seront effectuées sur la plateforme. Dans notre exemple, le ResourceGroup sera créé avant le VirtualNetwork qui en a besoin, et ainsi de suite.

Définition des règles de sécurité et d’accès

Une notion importante quand on travaille dans un cloud public quel qu’il soit est la gestion de la sécurité. En effet, on ne veut pas forcément que tout le monde ait accès à toutes nos ressources. Pour contrôler tout ça nous allons utiliser les NetworkSecurityGroups d’Azure :

# Créer un Network Security Group 
resource "azurerm_network_security_group" "myFirstNSG" {
    name                = "testNSG"
    location            = "westeurope"
    resource_group_name = "${azurerm_resource_group.myFirstRG.name}"

    security_rule {
        name                       = "SSH"
        priority                   = 1001
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "22"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
    }

    security_rule {
        name                       = "HTTP"
        priority                   = 1002
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "80"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
    }

    security_rule {
        name                       = "HTTPS"
        priority                   = 1003
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "443"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
    }

    tags {
        environment = "test"
    }
}

Il faut voir un NetworkSecurityGroup comme un “firewal virtuel” avec ses règles d’accès. Nous créons donc ici, toujours au sein de notre ResourceGroup, un ensemble de règles pour les connexions entrantes (avec le paramètre direction = “Inbound”) qui authorisent (avec le paramètre access = “allow”) le traffic depuis n’importe quelle source (avec les paramètres de source) pour :

  • SSH (port TCP 22)
  • HTTP (port TCP 80)
  • HTTPS (port TCP 443)

Ce n’est qu’un exemple et il est ainsi possible de définir précisément ce qui est authorisé et ce qui ne l’est pas en fonction des besoins.

Définition de la carte réseau virtuelle

Notre VM, comme toute machine, a besoin d’une carte réseau pour pouvoir communiquer avec les autres composants de notre infrastructure mais aussi pour que nous puissions communiquer avec elle. Dans certains cas il peut être intéressant de pouvoir “réutiliser” une carte réseau (pour conserver son paramètrage, son IP, etc …) en la changeant de machine. C’est pour cette raison que nous la définissons à part de notre VM avec le bloc de code suivant :

# Créer une IP publique
resource "azurerm_public_ip" "myFirstIP" {
    name                         = "testPublicIP"
    location                     = "westeurope"
    resource_group_name          = "${azurerm_resource_group.myFirstRG.name}"
    allocation_method = "Dynamic"

    tags {
        environment = "test"
    }
}


# Créer une carte réseau
resource "azurerm_network_interface" "myFirstNIC" {
    name                      = "testNIC"
    location                  = "westeurope"
    resource_group_name       = "${azurerm_resource_group.myFirstRG.name}"
    network_security_group_id = "${azurerm_network_security_group.myFirstNSG.id}"

    ip_configuration {
        name                          = "testNICConfig"
        subnet_id                     = "${azurerm_subnet.myFirstSubnet.id}"
        private_ip_address_allocation = "dynamic"
        public_ip_address_id          = "${azurerm_public_ip.myFirstIP.id}"
    }

    tags {
        environment = "test"
    }
}

Nous commençons par créer une ressource azurerm_public_ip afin d’obterir une IP publique acessible depuis internet qui nous servira à accéder à la VM depuis l’extérieur d’Azure.

Nous retrouvons ici les concepts déjà abordés jusqu’à présent : la création de ressource, ses paramètres (avec des références) et ses tags. Notons que le bloc ip_configuration est un sous ensemble de notre ressource azurerm_network_interface, ayant son propre nom et ses propres paramètres.

Définition de la VM Linux

Ça y est ! Après avoir défini toutes les ressources dont nous avons besoin, nous pouvons enfin nous occuper de notre VM (c’est pour ça qu’on est là, non ?). Le code est le suivant :

# Create virtual machine
resource "azurerm_virtual_machine" "myFirstVM" {
    name                  = "testVM"
    location              = "westeurope"
    resource_group_name   = "${azurerm_resource_group.myFirstRG.name}"
    network_interface_ids = ["${azurerm_network_interface.myFirstNIC.id}"]
    vm_size               = "Standard_B1s"

    storage_os_disk {
        name              = "myOsDisk"
        caching           = "ReadWrite"
        create_option     = "FromImage"
        managed_disk_type = "Standard_LRS"
    }

    storage_image_reference {
        publisher = "OpenLogic"
        offer     = "CentOS"
        sku       = "7.6"
        version   = "latest"
    }

    os_profile {
        computer_name  = "testVM"
        admin_username = "user"
    }

    os_profile_linux_config {
        disable_password_authentication = true
        ssh_keys {
            path     = "/home/user/.ssh/authorized_keys"
            key_data = "ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx test"
        }
    }

    tags {
        environment = "test"
    }
}

Dans un premier temps nous définissons les paramètres classiques d’une ressource : son nom, sa localisation et le ResourceGroup dans lequel on souhaites la créer.

Le fichier complet

main.tf


    # PROVIDER
    provider "azurerm" {
    # L'attribut `version` est optionnel mais conseillé pour contrôler la version du provider qui sera utilisé.
    # Dans le cas contraire, il sera automatiquement mis à jour par Terraform à chaque exécution.
        version = ">=1.22.0"
    
        subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        client_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        client_secret   = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        tenant_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }

    # Creer un ResourceGroup
    resource "azurerm_resource_group" "myFirstRG" {
        name     = "testRG"
        location = "westeurope"

        tags {
            environment = "test"
        }
    }

    # Creer un virtual network
    resource "azurerm_virtual_network" "myFirstVNet" {
        name                = "testVNet"
        address_space       = ["10.0.0.0/16"]
        location            = "westeurope"
        resource_group_name = "${azurerm_resource_group.myFirstRG.name}"

        tags {
            environment = "test"
        }
    }

    # Creer un subnet
    resource "azurerm_subnet" "myFirstSubnet" {
        name                 = "testSubnet"
        resource_group_name  = "${azurerm_resource_group.myFirstRG.name}"
        virtual_network_name = "${azurerm_virtual_network.myFirstVNet.name}"
        address_prefix       = "10.0.1.0/24"
    }

    # Créer un Network Security Group 
    resource "azurerm_network_security_group" "myFirstNSG" {
        name                = "testNSG"
        location            = "westeurope"
        resource_group_name = "${azurerm_resource_group.myFirstRG.name}"

        security_rule {
            name                       = "SSH"
            priority                   = 1001
            direction                  = "Inbound"
            access                     = "Allow"
            protocol                   = "Tcp"
            source_port_range          = "*"
            destination_port_range     = "22"
            source_address_prefix      = "*"
            destination_address_prefix = "*"
        }

        security_rule {
            name                       = "HTTP"
            priority                   = 1002
            direction                  = "Inbound"
            access                     = "Allow"
            protocol                   = "Tcp"
            source_port_range          = "*"
            destination_port_range     = "80"
            source_address_prefix      = "*"
            destination_address_prefix = "*"
        }

        security_rule {
            name                       = "HTTPS"
            priority                   = 1003
            direction                  = "Inbound"
            access                     = "Allow"
            protocol                   = "Tcp"
            source_port_range          = "*"
            destination_port_range     = "443"
            source_address_prefix      = "*"
            destination_address_prefix = "*"
        }

        tags {
            environment = "test"
        }
    }

    # Créer une IP publique
resource "azurerm_public_ip" "myFirstIP" {
    name                         = "$testPublicIP"
    location                     = "westeurope"
    resource_group_name          = "${azurerm_resource_group.myFirstRG.name}"
    allocation_method = "Dynamic"

    tags {
        environment = "test"
    }
}


# Créer une carte réseau
resource "azurerm_network_interface" "myFirstNIC" {
    name                      = "testNIC"
    location                  = "westeurope"
    resource_group_name       = "${azurerm_resource_group.myFirstRG.name}"
    network_security_group_id = "${azurerm_network_security_group.myFirstNSG.id}"

    ip_configuration {
        name                          = "testNICConfig"
        subnet_id                     = "${azurerm_subnet.myFirstSubnet.id}"
        private_ip_address_allocation = "dynamic"
        public_ip_address_id          = "${azurerm_public_ip.myFirstIP.id}"
    }

    tags {
        environment = "test"
    }
}

    # Créer une virtual machine
    resource "azurerm_virtual_machine" "myFirstVM" {
        name                  = "testVM"
        location              = "westeurope"
        resource_group_name   = "${azurerm_resource_group.myFirstRG.name}"
        network_interface_ids = ["${azurerm_network_interface.myFirstNIC.id}"]
        vm_size               = "Standard_B1s"

        storage_os_disk {
            name              = "myOsDisk"
            caching           = "ReadWrite"
            create_option     = "FromImage"
            managed_disk_type = "Standard_LRS"
        }

        storage_image_reference {
            publisher = "OpenLogic"
            offer     = "CentOS"
            sku       = "7.6"
            version   = "latest"
        }

        os_profile {
            computer_name  = "testVM"
            admin_username = "user"
        }

        os_profile_linux_config {
            disable_password_authentication = true
            ssh_keys {
                path     = "/home/user/.ssh/authorized_keys"
                key_data = "ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx test"
            }
        }

        tags {
            environment = "test"
        }
    }

Créons nos ressources avec Terraform

Initialisons notre environnement

Afin de ne pas avoir à installer Terraform, je vais utiliser celui qui est implémenté par défaut dans le CloudShell d’Azure (https://azure.microsoft.com/fr-fr/features/cloud-shell/) :

J’ai au préalable créé un dossier test ainsi qu’un fichier main.tf contenant le code que nous avons défini plus haut.

La première action à faire est d’initialiser notre environnement de travail avec la commande :

terraform init

Terraform va initialiser (et télécharger au besoin) les providers, les modules et plugins nécessaires au code contenu dans les fichier .tf où nous nous trouvons. Une fois notre environnement de travail initialisé, nous pouvons passer à l’étape suivante.

Plannifier les actions

Maintenant que notre environnement de travail est prêt, nous allons maintenant vérifier les actions qui seront effectuées avec la commande

terraform plan

Pendant l’exécution du plan, Terraform va lire les fichiers .tf là où il se trouve et va préparer le plan d’exécution des actions à faire. C’est à ce moment là que sont pris en compte les références que nous avons défini et que les dépendances sont traitées.

Les ressources sont affichées dans l’ordre alphabétique :
- en vert les resources qui seront crées
- en jaune les resources qui seront modifiées
- en rouge les resources qui seront supprimées

Aucune action n’est effectuée à cette étape. Dans notre cas, on voit bien toutes nos ressources en vert, car elles n’existent pas encore. Elles seront créées par Terraform lors de la prochaine étape.

On se lance !

Ça y est, on est enfin prêts. Nous pouvons maintenant lancer la commande pour appliquer nos changements :

terraform apply

Terraform va nous réafficher le résultat de la commande précédente en nous demandant si nous sommes bien d’accord pour effectuer les actions :

Une fois que nous avons répondu yes, Terraform va effectuer les actions prévues (ici la création des ressources) :

Terraform va stocker toutes les informations dont il a besoin dans un fichier .tfstate
IL NE FAUT JAMAIS TENTER DE LE MODIFIER SOUS PEINE DE VOIR TERRAFORM NE PLUS ARRIVER A EFFECTUER LA MOINDRE OPERATION SUR LES RESSOURCES CREEES.
Ces dernières deviendraient “orphelines” et il faudrait les supprimer manuellement via le portail.

Une fois que tout c’est bien terminé, nous pouvons vérifier dans le portail Azure que nos ressources ont bien été crées :

En ouvrant la ressource testVM correspondant à notre VM, nous accédons à ses caractéristiques. On y voit le paramètrage que nous avions défini dans notre fichier .tf comme le nom, ou les tags. On voit aussi l’adresse IP publique qui a été générée :

Nous pouvons maintenant nous connecter à notre VM grace à cette adresse IP et commencer à travailler dessus :

Détruisons nos ressources avec Terraform

Une fois que nous avons fini d’utiliser la VM, nous pouvons la détruire (et ainsi économiser le coût de son fonctionnement quand elle ne nous sert pas). Pour cela rien de plus simple, il suffit encore une fois de faire appel à la commande terraform avec le mot-clé destroy :

Terraform nous informe encore une fois des actions qu’il va effectuer, en se basant sur le contenu du fichier .tfstate. Après validation, les ressources sont supprimées, toujours en suivant le plan de dépendances que Terraform a généré au départ.

Attention, quand une VM et son StorageAccount sont supprimés, toutes les données sont définitivement perdues. Si on relance la création d’une VM, ce sera une toute nouvelle VM vierge, sans aucune donnée et avec une autre IP.

Pour aller plus loin

Les variables

Nous avons vu comment, avec un simple fichier .tf décrivant notre infrastructure, créer des ressources dans Azure avec Terraform. C’est bien mais on peut mieux faire ! Le premier point améliorable est le fait de ne plus renseigner “en dur” les informations sensibles comme les infos de connexion au compte Azure, ou la clé SSH par exemple.

Pour cela nous pouvons utiliser des variables. Commençons par créer un nouveau fichier que nous appellerons variables.tfvars dans lequel nous allons stocker nos valeurs :

subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_secret   = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
tenant_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Pour pouvoir utiliser ces variables, il nous faut les déclarer dans notre fichier main.tf :

# VARIABLES
variable "subscription_id" {}
variable "client_id" {}
variable "client_secret" {}
variable "tenant_id" {}

Nous pouvons modifier la définition de notre profider comme ceci pour utiliser ces variables :

# PROVIDER
provider "azurerm" {
  # L'attribut `version` est optionnel mais conseillé pour contrôler la version du provider qui sera utilisé.
  # Dans le cas contraire, il sera automatiquement mis à jour par Terraform à chaque exécution.
  version = ">=1.22.0"
  
  subscription_id = "${var.subscription_id}"
  client_id       = "${var.client_id}"
  client_secret   = "${var.client_secret}"
  tenant_id       = "${var.tenant_id}"
}

Il faut ensuite fournir ce nouveau fichier de variable au moment d’appeler les commandes Terraform :

terraform plan -var-file="variables.tfvars"

De la même manière, il est possible de variabiliser toutes les valeurs “redondantes” comme la localisation par exemple. On peut aussi se servir des variables pour “customiser” certaines valeurs : noms, tags, etc …

Conclusion

Grace à Terraform, il devient très simple de gérer ses infrastructures et ses ressources. L’outil se chargeant d’effectuer les actions sur les différents providers.

Terraform est évidement bien plus puissant que ce que j’ai montré ici. Je vous invite encore une fois à regarder la documentation officielle pour les fonctionnalités plus avancées.