Passons aux choses sérieuses

Passons aux choses sérieuses

Après avoir essayé pas mal d'options différentes, je commence à avoir une idée précise de ce que je veux mettre en place. Pour le choix des technos, j'ai essayé de faire au plus simple. Même si j'ai conscience de certaines limites pour la première version de mon projet, je cherche avant tout à faire quelques choses qui fonctionne.

Je vais donc utiliser Terraform pour toute la partie IaC (Infrastructure as Code) et Docker / Docker-Compose pour pouvoir lancer et gérer mes applications facilement.

Nous allons détailler ensemble la partie Terraform dans cet article et nous verrons dans le prochain la partie Docker.

Terraform

Sans trop rentrer dans les détails, Terraform est un outil d'IaC qui permet de créer, changer et versionner de l'infrastructure (virtuelle, physique, réseau, ...) via un langage déclaratif le HCL (HashiCorp Configuration Language).

Terraform a beaucoup de qualités mais pour moi son plus gros avantage c'est qu'il est facile à prendre en main avec son excellente documentation. Attention, ça ne veut pas dire que vous serez un expert Terraform en quelques heures mais par contre, après quelques essais vous devriez pouvoir monter une infrastructure simple sans trop de soucis.

Ça plutôt tombe bien car l’infrastructure que je veux mettre en place ici est très simple. Voila en un petit schéma d'archi qui résume ce que je veux déployer :

J'ai découpé mon code Terraform en 3 partie :

  • Network : gère la couche réseau de mon infrastructure. Comme je n'avais pas forcement envi de recréer cette partie là à chaque déploiement, j'ai préféré la gérer dans une partie spécifique.
  • Storage : gère mon disque de données. A manipuler avec précaution pour éviter de perdre des données ! Je pense qu'il est prudent de gérer son stockage persistant à l'écart de la partie compute pour éviter de tout perdre lors d'un destroy...
  • Compute : gère ma VM et les différents composants attachés à cette dernière (IP publique, Network Security Group, ...). C'est cette partie la que je veux être capable de redéployer facilement et régulièrement.

Une fois que vous avez écrit votre code Terraform il y a quelques commandes de base à connaitre :

  • terraform init : cette commande permet d'initialiser votre répertoire de travail. Elle permet entre autre de télécharger les modules et providers nécessaires pour pouvoir exécuter votre code.
  • terraform plan : cette commande permet de générer un plan d’exécution de votre code. Cette commande n'effectue aucune modification, elle permet de lister ce qui va être réalisé par Terraform. Elle se déroule en 3 étapes :
      - Vérification de l'état des ressources déjà créées par Terraform
      - Comparaison entre l'état des ressources et le code
      - Proposition des actions (création, suppression ou modification) à effectuer afin que l'état des ressources correspondent à ce qui est décrit dans le code
  • terraform apply : cette commande permet d’exécuter les action proposées lors du terraform plan. C'est donc cette commande qui va donner vie à votre code mais attention elle peut aussi supprimer ou modifier des ressources.
  • terraform destroy : cette commande permet de supprimer toutes les ressources gérées par terraform dans le répertoire de travail où elle est exécutée. Attention car cette commande va forcement impacter vos ressources mais elle peut être utile si vous avez créé une infrastructure temporaire pour réaliser des tests par exemple.
  • terraform fmt : petite commande bonus qui permet de formater votre code afin qu'il respecte les conventions de style Terraform.

Network

Au départ, je gérais les parties réseau et compute dans la même stack Terraform. Le problème était que je devais redéployer le réseau à chaque fois que je redéployé ma VM. Premièrement ça n'était pas très pratique et deuxièmement cela signifie que ce réseau devait être complétement dédié à ma VM... pour éviter cela, j'ai sorti les ressources réseaux dans une stack ce qui me permet de la gérer de manière autonome.

Concrètement, je gère uniquement 3 ressources ici : un ressource group, un vnet et un subnet :

variable "location" {
  type    = string
  default = "North Europe
}    

variable "dc" {
  default = {
    "North Europe" = "neu"
    "West Europe"  = "weu"
  }
}

variable "id" {
  type    = string
  default = "001"
}

variable "tags" {
  type = map
  default = {
    Environment = "Terraform"
    Resource    = "Network"
    Application = "Stack"
  }
}

resource "azurerm_resource_group" "network-rg" {
  name     = format("rg-network-%s-%s", lookup(var.dc, var.location), var.id)
  tags     = var.tags
  location = var.location
}

resource "azurerm_virtual_network" "network-vnet" {
  name                = format("vnet-stack-%s-%s", lookup(var.dc, var.location), var.id)
  tags                = var.tags
  address_space       = ["192.168.0.0/24"]
  location            = azurerm_resource_group.network-rg.location
  resource_group_name = azurerm_resource_group.network-rg.name
}

resource "azurerm_subnet" "network-snet" {
  name                 = format("snet-stack-%s-%s", lookup(var.dc, var.location), var.id)
  resource_group_name  = azurerm_resource_group.network-rg.name
  virtual_network_name = azurerm_virtual_network.network-vnet.name
  address_prefixes     = ["192.168.0.0/25"]
}

Voila le résultat sur le portail Azure :

Storage

Ici je gère simplement mon disque qui contient les données que je veux pouvoir conserver après avoir redéployé ma VM. Il faut toujours considérer la VM et son disque OS comme des ressources éphémères, il est donc indispensable d'avoir un disque additionnel qui va contenir toutes les données persistantes.

Cette stack va gérer uniquement 2 ressources : un resource group et un disque.

resource "azurerm_resource_group" "storage-rg" {
  name     = format("rg-storage-%s-%s", lookup(var.dc, var.location), var.id)
  tags     = var.tags
  location = var.location
}

resource "azurerm_managed_disk" "storage-disk" {
  name                    = format("data-%s-%s", lookup(var.dc, var.location), var.id)
  tags                    = var.tags
  location                = azurerm_resource_group.storage-rg.location
  resource_group_name     = azurerm_resource_group.storage-rg.name

  storage_account_type    = "StandardSSD_LRS"
  create_option           = "Empty"
  disk_size_gb            = "64"
}

Voila le résultat sur le portail Azure :

Compute

C'est ici que l'on va créer la VM et toutes les ressources qui lui sont liées :

  • Un resource group
  • Une IP publique
  • Une interface réseau
  • Un Network Security Group (NSG)
  • Une VM
resource "azurerm_resource_group" "compute-rg" {
  name     = format("rg-compute-%s-%s", lookup(var.dc, var.location), var.id)
  tags     = var.tags
  location = var.location
}

resource "azurerm_public_ip" "pip-cloudstack" {
  name                = "pip-01-vmcloudstack001"
  tags                = var.tags
  location            = azurerm_resource_group.compute-rg.location
  resource_group_name = azurerm_resource_group.compute-rg.name
  domain_name_label   = "vmcloudstack001"
  allocation_method   = "Static"
}

resource "azurerm_network_interface" "nic-cloudstack" {
  name                = "nic-01-vmcloudstack001"
  tags                = var.tags
  location            = azurerm_resource_group.compute-rg.location
  resource_group_name = azurerm_resource_group.compute-rg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = data.azurerm_subnet.network-snet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.pip-cloudstack.id
  }
}

resource "azurerm_linux_virtual_machine" "vmcloudstack" {
  name                = "vmcloudstack001"
  tags                = var.tags
  location            = azurerm_resource_group.compute-rg.location
  resource_group_name = azurerm_resource_group.compute-rg.name
  size                = "Standard_B2s"
  admin_username      = "vincent"
  network_interface_ids = [
    azurerm_network_interface.nic-cloudstack.id,
  ]

  custom_data         = filebase64("cloud-init.yaml")

  admin_ssh_key {
    username   = "vincent"
    public_key = var.DEFAULT_SSHKEY
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "StandardSSD_LRS"
    name                 = "osd-vmcloudstack001"
    disk_size_gb         = "30"
  }

  source_image_reference {
    publisher = "canonical"
    offer     = "0001-com-ubuntu-server-groovy"
    sku       = "20_10"
    version   = "latest"
  }
}

resource "azurerm_virtual_machine_data_disk_attachment" "attachment-data" {
  managed_disk_id    = data.azurerm_managed_disk.data-disk.id
  virtual_machine_id = azurerm_linux_virtual_machine.vmcloudstack.id
  lun                = "0"
  caching            = "ReadWrite"
}

resource "azurerm_network_security_group" "nsg-cloudstack" {
  name                = "nsg-cloudstack"
  location            = azurerm_resource_group.compute-rg.location
  resource_group_name = azurerm_resource_group.compute-rg.name
}

resource "azurerm_network_interface_security_group_association" "association-nsg-cloudstack" {
  network_interface_id      = azurerm_network_interface.nic-cloudstack.id
  network_security_group_id = azurerm_network_security_group.nsg-cloudstack.id
}

resource "azurerm_network_security_rule" "nsg-cloudstack-IN-TCP-443" {
  name                        = "IN-TCP-443"
  priority                    = 200
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "TCP"
  source_address_prefix       = "*"
  source_port_range           = "*"
  destination_address_prefix  = "*"
  destination_port_range      = "443"
  resource_group_name         = azurerm_resource_group.compute-rg.name
  network_security_group_name = azurerm_network_security_group.nsg-cloudstack.name
}

Comme je l'ai expliqué dans la présentation de mon projet, je veux redéployer toutes les semaines une nouvelle VM. Avec Terraform rien de plus simple :

  • cd terraform/compute
  • terraform init
  • terraform destroy
  • terraform apply

Tout le code source de la partie Terraform que je viens de vous présenter est disponible ici : https://gitlab.com/VincentMisson/cloud-stack/-/tree/master/terraform

Maintenant que nous avons vu la partie infrastructure, nous verrons dans le prochain article comment je déploie les différentes applications.

N'hésitez pas à me contacter sur Twitter @vincentmisson si vous avez des questions.