Comment automatiser le déploiement des ressources Azure avec Terraform à l'aide des Pipelines Azure DevOps.

TERRAFORM

Terraform est un outil open-source développé par HashiCorp, et utilisé pour provisionner et gérer des infrastructures IT dans le Cloud. Écrit en Go et fonctionnant en mode Infrastructure as a Code (IAC), il permet d’administrer une infrastructure grâce à du code au lieu de procéder à un ensemble d’opérations manuelles.

La force de Terraform est de reposer sur un langage de description d’infrastructure simple et lisible, on parle ici de HCL. L’approche IAC de Terraform permet de gérer le versioning d’une infrastructure en lui ajoutant ou retirant des composants.

Objectif

Dans cet article, nous allons voir :

  1. Comment définir une stack Terraform simple
  2. Comment utiliser conjointement Terraform et Azure DevOps dans l’optique de déployer l’infrastructure Azure, de manière automatique et continue.

Initialisation du projet

Voici la structure de fichier que nous allons adopter :

|– src\
|– terraform\
|– README.md

|– src\ will contain the sources of the Ansible Config,
|– terraform\ will contain all the deployment files.

1- Azure service principal

Rendons-nous dans Azure Active Directory et allons dans la partie App registrations.

  • Connectez-vous à votre compte Azure sur https://portal.azure.com
  • Cliquez sur le bouton Cloud Shell pour lancer le Cloud Shell.
  • La commande ci-dessous créera un principal de service avec le nom « SPName ». Remplacez la valeur et exécutez la commande dans le cloud shell.
az ad sp create-for-rbac --name SPName
  • À l’issu de cette commande, Azure CLI retournera un block JSON contenant les informations nécessaires a l’authentification du SP (client-id, client-secret)
abd###@Azure:~$ az ad sp create-for-rbac --name SPName
Changing "SPName" to a valid URI of "http://SPName", which is the required format used for service principal names
Creating a role assignment under the scope of "/subscriptions/##########-c1b3-####-8b7a-####9578ebf0"
  Retrying role assignment creation: 1/36
  Retrying role assignment creation: 2/36
  Retrying role assignment creation: 3/36
{
  "appId": "########-4611-4c09-9728-6ec9284314de",
  "displayName": "SPName",
  "name": "http://SPName",
  "password": "########",
  "tenant": "########-d76d-45cf-a7d2-ae98f73067ee"
}
  • Complétez le formulaire. Cliquez sur Vérifier la connexion pour vous assurer que les valeurs fonctionnent comme prévu. Cliquez sur OK une fois vérifié. Vous pourrez désormais référencer cette connexion à partir des tasks du pipeline.

2- TERRAFORM / INFRASTRUCTURE

|– src\
|– terraform\
____|– main.tf
____|– outputs.tf
____|– provider.tf
____|– variables.tf
____|– variables.tfvars
|– README.md

MAIN.tf file

data "azurerm_client_config" "current" {
}

locals {
  resource_group_name = "rg-${var.env}"

  tags = {
    env = "${var.env}"
  }
}

# ======================================================================================
# Resource Group
# ======================================================================================

resource "azurerm_resource_group" "resource_group" {
  location = "${var.location}"
  name     = "${var.env}-rg"
  tags     = "${local.tags}"
}

# ======================================================================================
# KeyVault
# ======================================================================================

resource "azurerm_key_vault" "key_vault" {
  name                        = "${var.env}-keyvault"
  location                    = "${azurerm_resource_group.app_resource_group.location}"
  resource_group_name         = "${azurerm_resource_group.app_resource_group.name}"
  tenant_id                   = "${data.azurerm_client_config.current.tenant_id}"
  enabled_for_disk_encryption = true

  sku {
    name = "standard"
  }

  access_policy {
    tenant_id = "${data.azurerm_client_config.current.tenant_id}"
    object_id = "${data.azurerm_client_config.current.object_id}"

    secret_permissions = [
      "get",
      "list",
      "set",
      "delete"
    ]
  }

  tags = "${local.tags}"
}

# ======================================================================================
# Network
# ======================================================================================

resource "azurerm_virtual_network" "vnet" {
  name                = "${var.vnet-name}"
  address_space       = "${var.address_space}"
  dns_servers         = [
           "192.168.0.1",
           "168.168.25.1",
        ]
  location            = "${azurerm_resource_group.resource_group.location}"
  resource_group_name = "${azurerm_resource_group.resource_group.name}"

  tags     = "${local.tags}"
}

resource "azurerm_subnet" "subnet" {
  name                 = "${var.subnet-name}"
  resource_group_name  = "${azurerm_resource_group.resource_group.name}"
  virtual_network_name = "${azurerm_virtual_network.vnet.name}"
  address_prefix       = "${var.address_prefix}"
  tags     = "${local.tags}"
}

resource "azurerm_network_interface" "vm-network-interface" {
  name                = "${var.vm-name}"
  location            = "${azurerm_resource_group.resource_group.location}"
  resource_group_name = "${azurerm_resource_group.resource_group.name}"
  
  ip_configuration {
    name                          = "vm-ipconfig"
    subnet_id                     = "${azurerm_subnet.subnet.id}"
    private_ip_address_allocation = "Dynamic"
  }
}
# ======================================================================================
# VM
# ======================================================================================

resource "azurerm_virtual_machine" "virtual-machine" {
  name                  = "${var.vm-name}"
  location              = "${azurerm_resource_group.resource_group.location}"
  resource_group_name   = "${azurerm_resource_group.resource_group.name}"
  network_interface_ids = ["${azurerm_network_interface.vm-network-interface.id}"]

  vm_size               = "DS3V2"

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2016-Datacenter"
    version   = "latest"
  }
  storage_os_disk {
    name              = "vm-os-disk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
    
  }

  os_profile {
    computer_name  = "${var.computer-name}"
    admin_username = "${var.AdminUsername}"
    admin_password = "${var.AdminPassword}"
  }

}

PROVIDER.TF file

provider "azurerm" {
  version = "1.44.0"
}

terraform {
  required_version = "0.11.21"
  backend "azurerm" {
    storage_account_name = "shared$$_application_$$tfsa"
    container_name       = "terraform"
    key                  = "terraform-$$_environment_$$.tfstate"
    access_key           = "$$_tf_storage_account_key_$$"
  }
}

VARIABLES.TF file

data "azurerm_client_config" "current" {
}

provider "azurerm" {
    version = "=1.44.0"
    subscription_id  = "${data.azurerm_client_config.current.subscription_id}"               
    client_id        = "${data.azurerm_client_config.current.client_id}"                  
    tenant_id        = "${data.azurerm_client_config.current.tenant_id}"
    client_secret    = "$$_client_secret_$$"                 
    }

variable "env" {
  type = "string"
}
variable "location" {
  type = "string"
}
variable "address_space" {
  type = "string"
}
variable "subnet-name" {
  type = "string"
}
variable "address_prefix" {
  type = "string"
}

VARIABLES.TFVARS file

env = "$$_env_$$"
location = "$$_location_$$"
address_space = "$$_0.0.0.0/0_$$"
subnet-name = "$$_subnet-name_$$"
address_prefix = "$$_0.0.0.0/24_$$"

3- PIPELINE

Prérequis

Un compte GitHub, où vous pouvez gérer vos repository. Si vous n’en avez pas, vous pouvez créer un gratuitement.

Une organisation Azure DevOps. Si vous n’en avez pas, vous pouvez en créer une gratuitement. (Une organisation Azure DevOps est différente de votre organisation GitHub. Par best-practices, donnez-leur le même nom)

Commençons par créer notre premier pipeline !

1- Azure DevOps – BUILD

Créons un nouveau Build Pipeline, puis cliquer sur Use the visual designer pour avoir le mode visuel.

Pour ce lab, nous utiliserons la branche master.

commencer par un Empty job

Ensuite, ajoutez Publish Artifact. Cette étape est utilisée pour incorporer les fichiers Terraform dans l’artifact.

Ensuite, spécifiez le dossier ou le chemin du fichier à publier. Il peut s’agir d’un full path ou d’un chemin relatif à la racine du repository.

Enfin, procédons au lancement de notre première build. Si tout se passe bien, nous obtenons l’artifact suivant :

2- Azure DevOps – RELEASE

Créons maintenant un nouveau Release Pipeline, pour ce faire, nous allons commencer à partir d’un modèle de travail vide Empty job template.

Renommons la première étape DEV.

Pour utiliser Terraform, nous avons besoin de :

  1. créer son backend sur Azure Storage (Blob),
  2. récupérer la Key de ce Storage et de l’injecter dans les variables pour pouvoir écrire/lire dans le Blob.

Première étape, ajoutons une étape de type Azure CLI pour la création du backend :

az group create --location $(location) --name "rg-$(env)-tfstate" 

az storage account create --name "sa$(env)tfstate$(location)" --resource-group "rg-$(env)-tfstate" --location $(location) --sku Standard_LRS  --tags 

az storage container create --name "terraform" --account-name "sa$(env)tfstate$(location)"

Next, add a second job Azure Powershell to get the storage account access_key

$key = (Get-AzStorageAccountKey -ResourceGroup "rg-$(env)-tfstate" -Name "sa$(env)tfstate$(location)").Value[0]

Write-Host "##vso[task.setvariable variable=tf_storage_account_key]$key"

The following two steps allow you to replace the tokens ($$_value_$$) present in the Terraform files.

Ensuite, ajoutons les tasks terraform:

  • install terrafrom ()
  • init
  • validate
  • plan
  • apply

Add Pipiline variables

Link a KeyVault to your DevOps pipeline project

prochains articles :

  1. Configurer une ressource (AD serveur) a l’aide d’Ansible-DevOps en one shot
  2. Configurer une ressource (AD serveur) a l’aide du DSC-DevOps en one shot

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s