Benoît Courtine



    Navigation
     » Accueil
     » A propos
     » Mentions légales
     » XML Feed

    Déploiement du template VSphere avec Terraform

    08 Dec 2020 (MAJ le 08/12/2020 à 10:49) » ops, script

    Introduction

    Rappel : dans l’article précédent, nous avons vu comment créer un template VSphere pour Ubuntu 20.04.

    Maintenant, que nous avons le template et le fichier de "post-configuration", nous aimerions automatiser son déploiement. Or, il y a un outil pour ça : Terraform. Si l’outil a initialement été conçu pour provisionner des machines dans le cloud, il supporte VSphere.

    Je renouvelle l’avertissement de mon post précédent : l’administration système (et Terraform en particulier) n’étant pas ma spécialité, il est possible que le script qui suit contienne des mauvaises pratiques. N’hésitez pas à me le signaler : je corrigerai en conséquence.
    Pour utiliser le script qui suit, il est obligatoire d’avoir assigné une adresse IP fixe au template de la machine virtuelle. C’est cette IP fixe qui permet dans la dernière partie du script de s’y connecter pour injecter le script de configuration et l’exécuter.

    Structure

    Le code Terraform peut être assez redondant. Nous allons donc mutualiser ce qui peut l’être dans un module.

    Voici la structure de notre projet :

    - main.tf
    - files
        - ubuntu20.04_config.sh
    - modules
        - ubuntu20.04
            - main.tf

    Le module

    Fichier modules/ubuntu20.04/main.tf
    # Constantes (1)
    locals {
      vm_config_file = "ubuntu20.04_config.sh"
      # Ces constantes ont été définies lors de l'installation d'Ubuntu, pour créer le template.
      vm_template_login      = "admin"
      vm_template_password   = "my_secret_password"
      vm_template_ip         = "192.168.0.10"
    }
    
    # Infrastructure VSphere.
    
    provider "vsphere" { (2)
      user = "vsphere_user"
      password = "vsphere_password"
      vsphere_server = "vsphere_server"
    }
    
    data "vsphere_datacenter" "dc" {
      name = "datacenter"
    }
    
    data "vsphere_datastore" "ds" {
      name          = var.vm_datastore
      datacenter_id = data.vsphere_datacenter.dc.id
    }
    
    data "vsphere_compute_cluster" "cluster" {
      name          = "cluster"
      datacenter_id = data.vsphere_datacenter.dc.id
    }
    
    data "vsphere_network" "network" {
      name          = "vlan1"
      datacenter_id = data.vsphere_datacenter.dc.id
    }
    
    data "vsphere_virtual_machine" "ubuntu2004_template" {
      name          = "ubuntu20.04_template"
      datacenter_id = data.vsphere_datacenter.dc.id
    }
    
    # Variables to configure VM. (3)
    
    variable "vm_hostname" {
      description = "VM hostname."
      type        = string
    }
    
    variable "vm_ip" {
      description = "VM IP."
      type        = string
    }
    
    variable "vm_num_cpus" {
      description = "VM CPUs."
      type        = number
      default     = 1
    }
    
    variable "vm_memory" {
      description = "VM memory (MB)."
      type        = number
      default     = 2048
    }
    
    variable "vm_datastore" {
      description = "VM datastore."
      type        = string
      default     = "datastore1"
    }
    
    # Déclaration des disques (4)
    variable "vm_disks" {
      description = "VM memory (MB)."
      type        = map(any)
      default     = {
        disk0 = {
          unit_number = 0
          size = 8 # Taille du template par défaut (en GB).
        }
      }
    }
    
    # Déclaration de la VM
    
    resource "vsphere_virtual_machine" "ubuntu_server_2004" {
      name             = var.vm_hostname
      resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
      datastore_id     = data.vsphere_datastore.ds.id
      guest_id         = data.vsphere_virtual_machine.ubuntu2004_template.guest_id
      scsi_type        = data.vsphere_virtual_machine.ubuntu2004_template.scsi_type
    
      num_cpus = var.vm_num_cpus
      memory   = var.vm_memory
    
      network_interface {
        network_id = data.vsphere_network.network.id
      }
    
      # Configuration des disques (4)
      dynamic "disk" {
        for_each = var.vm_disks
        content {
          label = disk.key
          size = disk.value.size
          unit_number = try(disk.value.unit_number, 0)
        }
      }
    
      clone {
        template_uuid = data.vsphere_virtual_machine.ubuntu2004_template.id
      }
    
      # Injection et exécution du script de post-configuration
    
      provisioner "file" {
        source      = "files/${local.vm_config_file}"
        destination = "/tmp/${local.vm_config_file}"
    
        connection {
          type     = "ssh"
          host     = local.vm_template_ip
          user     = var.vm_template_login
          password = var.vm_template_password
        }
      }
    
      provisioner "remote-exec" {
        inline = [
          "chmod +x /tmp/${local.vm_config_file}",
          "echo \"${var.vm_template_password}\" | sudo -S /tmp/${local.vm_config_file} ${var.vm_hostname} ${var.vm_ip}",
          "rm -f /tmp/${local.vm_config_file}",
          "echo \"${var.vm_template_password}\" | sudo -S netplan apply"
        ]
    
        connection {
          type     = "ssh"
          host     = local.vm_template_ip
          user     = var.vm_template_login
          password = var.vm_template_password
        }
      }
    }
    1 Les locals permettent d’éviter les répétitions de constantes (ou de précalculer des valeurs en combinant des variables).
    2 Pour simplifier le script, j’ai mis les identifiants en dur. Il est possible de les remplacer par des variables, pour éviter que les identifiants se retrouvent en gestion de configuration et/ou si on souhaite adresser de multiples instances VSphere.
    3 Les variables permettent d’injecter les éléments de configuration qui varient d’une machine à l’autre.
    4 L’utilisation d’une map et d’une boucle permet d’affecter plusieurs disques à notre machine virtuelle en cas de besoin.
    Ce module est assez simpliste. Il est possible d’externaliser plus de choses sous la forme de constantes/variables, pour prendre en compte une architecture VMWare plus complexes (multiples datacenters, clusters, réseaux, etc.) Il est également possible d’affecter plusieurs réseaux à une même machine, en utilisant la même technique que pour l’affectation des disques.

    La déclaration des machines

    Le module étant créé, il ne reste plus qu’à déclarer les machines dans le fichier principal main.tf.

    Fichier main.tf
    # Première VM, utilisant les valeurs par défaut du template.
    module "vm1" {
      source = "./modules/ubuntu20.04"
    
      vm_hostname = "vm1"
      vm_ip = "192.168.0.11"
    }
    
    # Utilisation des variables pour configurer les différents éléments.
    module "vm2" {
      source = "./modules/ubuntu20.04"
    
      vm_hostname = "vm2"
      vm_ip = "192.168.0.12"
      vm_num_cpus = 2
      vm_memory = 4096
      vm_datastore = "datastore2"
      vm_disks = {
        "disk0" = { size = 30 }
        "disk1" = {
          size = 40
          unit_number = 1
        }
        "disk2" = {
          size = 50
          unit_number = 2
        }
      }
    }

    Le déploiement

    Il n’y a plus qu’à exécuter et à laisser Terraform travailler :

    terraform init
    terraform plan
    terraform apply

    Conlusion

    Terraform, c’est un super-outil pour automatiser le déploiement de l’infrastructure.

    C’est aussi un outil "dangereux" : il ne sait pas gérer toutes les modifications d’infrastructure "en place", et en cas de modification d’une machine, il va parfois procéder en la supprimant/recréant. Or, tout le monde n’est pas passé à l’approche infrastructure immutable, où les machines n’hébergent que du code et où les données sont attachées depuis l’extérieur…​ Si on n’y prête pas attention, on peut donc avoir vite fait de supprimer une machine hébergeant des données de production.

    La machine ainsi mise à disposition étant "nue", Il faut ensuite installer/configurer les serveurs/applications. On pourra pour automatiser également cette partie utiliser par exemple Ansible, avec la suite de scripts DebOps pour les serveurs basés sur Debian/Ubuntu.