Étape 2 : déploiement des VMs

Introduction

Une fois l’architecture cible définie, il est possible de déployer les composants l’architecture.

Pour rester dans une logique d’automatisation et d’industrialisation, il est intéressant d’utiliser l’infrastructure as code pour décrire ses serveurs et les déployer.

Qu’on utilise Terraform, ou comme ici son récent fork OpenTofu, la logique décrite ici reste applicable.

Rappel des cibles à déployer

Le cluster de départ est composé de 9 VMs s’exécutant sous PhotonOS et basées sur un template préconfiguré pour être géré par Ansible.

La configuration réseau repose sur ma mini infrastructure avec des serveurs dans une zone LAN (192.168.10/24) et une zone WEB (192.168.5.0/24)

Nom Rôle Zone réseau IP vCPU Mémoire (Go) Disque (Go)
prdk8sctp501 Control Plane LAN 192.168.10.71 4 4 30
prdk8sctp502 Control Plane LAN 192.168.10.72 4 4 30
prdk8sctp503 Control Plane LAN 192.168.10.73 4 4 30
prdk8snod501 Worker LAN LAN 192.168.10.81 2 2 30
prdk8snod502 Worker LAN LAN 192.168.10.82 2 2 30
prdk8snod511 Worker DMZ WEB 192.168.5.81 2 2 30
prdk8snod512 Worker DMZ WEB 192.168.5.82 2 2 30
prdk8snlb501 Load Balancer LAN LAN 192.168.10.91 1 1 30
prdk8snlb511 Load Balancer DMZ WEB 192.168.5.91 1 1 30

Configuration OpenTofu (Terraform)

La logique décrite ici est propre à mon usage, mais j’ai essayé d’exploiter une démarche qu’on peut retrouver en entreprise avec un découpage du code en plusieurs fichiers. Il existe plusieurs manières possibles et plusieurs visions du sujet, libre à chacun d’adapter les exemples ci-dessous à son usage personnel.

Prérequis

OpenTofu (Terraform) aura besoin d’agir sur la plateforme cible hébergeant les VMs, en l’occurrence pour l’instant VMware.

C’est donc via un compte de service présent dans l’active directory que OpenTofu pourra agir sur le vCenter. Contrairement à ce que j’ai pu décrire avec Ansible, ici il faut un compte avec pouvoir, car c’est lui qui va être utilisé pour créer les VMs. Comme toujours il est conseillé d’utiliser un compte dédié et limiter au périmètre nécessaire. Dans mon cas, n’ayant qu’une infra minimaliste sous la main, le compte a tous les droits sur la plateforme.

Création d'un compte AD

Cliquez sur l'image pour l'agrandir.

Application des droits sur le vCenter

Cliquez sur l'image pour l'agrandir.

provider.tf

    
        provider "vsphere" {
            user           = var.vsphere_user
            password       = var.vsphere_password
            vsphere_server = var.vsphere_server
          
            # If you have a self-signed cert
            allow_unverified_ssl = true
          }
    

Ce fichier va permettre d’indiquer les providers à utiliser pour piloter la plateforme cible. Dans ce cas VMware, il n’y a donc qu’un seul provider à déclarer, en lui indiquant les paramètres nécessaires à son fonctionnement.

  • L’URL du vCenter
  • Le login de connexion
  • Le password de connexion
  • Ne pas vérifier le certificat du vCenter (auto générée par défaut)

  • En l’état, il est inutile d’indiquer les vraies valeurs, pour cela nous allons passer par un fichier de variables, d’où l’usage du préfixe var.nom_de_la_variable

    variables.tf

    Ce fichier permet de regrouper par section l’intégralité des variables à utiliser. Cela permet de simplifier les changements de configuration, puisqu’en théorie et sauf exception, seul ce fichier sera à modifier si par la suite on était amené à modifier la configuration des VMs.

    Le nommage des variables et libre, et même si ce n’est pas parfait ici, je vous invite à retenir une logique spécifique simplifiant la lecture.

    Arbitrairement le fichier contient une première section propre aux variables VMware. L’URL du vCenter, le datacenter, le cluster… On n’y trouve également les tags à positionner sur chaque VM et les catégories associés. Cela permettra par la suite d’exploiter ces informations comme groupe d’inventaire lors de l’exécution des playbook Ansible.

    On y indique aussi le nom du template source à utiliser et dont terraform se servira pour cloner ce dernier et créer les nouvelles VMs.

    Pour éviter de laisser apparaitre un mot de passe associé au compte de connexion au vCenter dans le fichier, la variable n’est pas explicitement remplie, mais elle sera demandée à l’exécution de OpenTofu (terraform) et non affichée à l’écran (sensitive = true)

        
            #######################Variables vsphere
    
            variable "vsphere_user" {
              default = "svc_prd_terraform@coolcorp.priv"
            }
            
            variable "vsphere_password" {
                type = string
                sensitive = true
            }
            
            variable "vsphere_server" {
              default = "vcenter.coolcorp.priv"
            }
            
            variable "vsphere_datacenter" {
              default = "Paris"
            }
            
            variable "vsphere_cluster" {
              default = "COOLCLUSTER"
            }
            
            variable "vsphere_template" {
              default = "prdtplk8s501"
            }
            
            variable "tag_primary_category" {
              default = "CAT-COOLCORP-APPS"
            }
            
            variable "tag_apps" {
              default = "TAG-COOLCORP-APPS-K8S"
            }
            
            variable "tag_role_category" {
              default = "CAT-COOLCORP-ROLE"
            }
            
            variable "tag_role_master" {
              default = "TAG-COOLCORP-ROLE-MASTER"
            }
            
            variable "tag_role_worker" {
              default = "TAG-COOLCORP-ROLE-WORKER"
            }
            
            variable "tag_role_haproxy" {
              default = "TAG-COOLCORP-ROLE-HAPROXY"
            }
            
            variable "tag_network_category" {
              default = "CAT-COOLCORP-NETWORK"
            }
            
            variable "tag_network_lan" {
              default = "TAG-COOLCORP-NETWORK-LAN"
            }
            
            variable "tag_network_web" {
              default = "TAG-COOLCORP-NETWORK-WEB"
            }  
        
    

    L’autre section concerne les variables communes associées à la configuration réseau. Les gateway, les dns, le nom des VLAN…

        
            ################################### Variables Network
    
            variable "vlan_lan" {
              default = "LAN"
            }
            
            variable "vlan_dmz" {
              default = "VLAN_WEB"
            }
            
            variable "gateway_lan" {
              default = "192.168.10.253"
            }
            
            variable "gateway_dmz" {
              default = "192.168.5.253"
            }
            
            variable "default_dns" {
              default = "192.168.10.35"
            }
            
            variable "default_dns_domain" {
              default = "coolcorp.priv"
            }        
        
    

    On arrive ensuite à la section des serveurs, avec des sous-sections par serveur. Le motif se répète pour chaque VM.

  • Nom du serveur
  • Datastore où sera hébergé le disque (VMDK) de la VM
  • Nombre de CPU
  • Taille de la mémoire
  • Adresse IP et masque

  • Il n’y a pas d’informations sur la taille du disque, car même s’il était possible de l’indiquer, on peut laisser la taille du disque par défaut du template.

        
            #################################### Variables serveur
    
    ####Detail Master 1
    
    variable "master_01_name" {
      default = "prdk8sctp501"
    }
    
    variable "master_01_dts" {
      default = "prddtsvol007"
    }
    
    variable "master_01_cpu_number" {
      default = "4"
    }
    
    variable "master_01_memory_size" {
      default = "4096"
    }
    
    variable "master_01_ip_address" {
      default = "192.168.10.71"
    }
    
    variable "master_01_ip_netmask" {
      default = "24"
    }
    
    
    ####Detail Master 2
    
    variable "master_02_name" {
      default = "prdk8sctp502"
    }
    
    variable "master_02_dts" {
      default = "prddtsvol004"
    }
    
    variable "master_02_cpu_number" {
      default = "4"
    }
    
    variable "master_02_memory_size" {
      default = "4096"
    }
    
    variable "master_02_ip_address" {
      default = "192.168.10.72"
    }
    
    variable "master_02_ip_netmask" {
      default = "24"
    }
    
    ####Detail Master 3
    
    variable "master_03_name" {
      default = "prdk8sctp503"
    }
    
    variable "master_03_dts" {
      default = "prddtsvol001"
    }
    
    variable "master_03_cpu_number" {
      default = "4"
    }
    
    variable "master_03_memory_size" {
      default = "4096"
    }
    
    variable "master_03_ip_address" {
      default = "192.168.10.73"
    }
    
    variable "master_03_ip_netmask" {
      default = "24"
    }
    
    ####Detail Worker 1 (LAN)
    
    variable "worker_lan_01_name" {
      default = "prdk8snod501"
    }
    
    variable "worker_lan_01_dts" {
      default = "prddtsvol005"
    }
    
    variable "worker_lan_01_cpu_number" {
      default = "2"
    }
    
    variable "worker_lan_01_memory_size" {
      default = "2048"
    }
    
    variable "worker_lan_01_ip_address" {
      default = "192.168.10.81"
    }
    
    variable "worker_lan_01_ip_netmask" {
      default = "24"
    }
    
    ####Detail Worker 2 (LAN)
    
    variable "worker_lan_02_name" {
      default = "prdk8snod502"
    }
    
    variable "worker_lan_02_dts" {
      default = "prddtsvol002"
    }
    
    variable "worker_lan_02_cpu_number" {
      default = "2"
    }
    
    variable "worker_lan_02_memory_size" {
      default = "2048"
    }
    
    variable "worker_lan_02_ip_address" {
      default = "192.168.10.82"
    }
    
    variable "worker_lan_02_ip_netmask" {
      default = "24"
    }
    
    ####Detail Worker 1 (DMZ)
    
    variable "worker_dmz_01_name" {
      default = "prdk8snod511"
    }
    
    variable "worker_dmz_01_dts" {
      default = "prddtsvol006"
    }
    
    variable "worker_dmz_01_cpu_number" {
      default = "2"
    }
    
    variable "worker_dmz_01_memory_size" {
      default = "2048"
    }
    
    variable "worker_dmz_01_ip_address" {
      default = "192.168.5.81"
    }
    
    variable "worker_dmz_01_ip_netmask" {
      default = "24"
    }
    
    ####Detail Worker 2 (DMZ)
    
    variable "worker_dmz_02_name" {
      default = "prdk8snod512"
    }
    
    variable "worker_dmz_02_dts" {
      default = "prddtsvol007"
    }
    
    variable "worker_dmz_02_cpu_number" {
      default = "2"
    }
    
    variable "worker_dmz_02_memory_size" {
      default = "2048"
    }
    
    variable "worker_dmz_02_ip_address" {
      default = "192.168.5.82"
    }
    
    variable "worker_dmz_02_ip_netmask" {
      default = "24"
    }
    
    ####Detail HA Proxy LAN
    variable "haproxy_lan_01_name" {
      default = "prdk8snlb501"
    }
    
    variable "haproxy_lan_01_dts" {
      default = "prddtsvol004"
    }
    
    variable "haproxy_lan_01_cpu_number" {
      default = "1"
    }
    
    variable "haproxy_lan_01_memory_size" {
      default = "1024"
    }
    
    variable "haproxy_lan_01_ip_address" {
      default = "192.168.10.91"
    }
    
    variable "haproxy_lan_01_ip_netmask" {
      default = "24"
    }
    
    ####Detail HA Proxy DMZ
    
    variable "haproxy_dmz_01_name" {
      default = "prdk8snlb511"
    }
    
    variable "haproxy_dmz_01_dts" {
      default = "prddtsvol001"
    }
    
    variable "haproxy_dmz_01_cpu_number" {
      default = "1"
    }
    
    variable "haproxy_dmz_01_memory_size" {
      default = "1024"
    }
    
    variable "haproxy_dmz_01_ip_address" {
      default = "192.168.5.91"
    }
    
    variable "haproxy_dmz_01_ip_netmask" {
      default = "24"
    }
        
    

    vsphere.tf

    Il s’agit du premier fichier contenant du code IAC. Il permet de décrire la partie vSphere, les objets décrits pourront être réutilisés dans les autres fichiers

        
            data "vsphere_datacenter" "dc" {
                name = "${var.vsphere_datacenter}"
              }
              
              data "vsphere_compute_cluster" "cluster" {
                name          = "${var.vsphere_cluster}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
              
              data "vsphere_network" "network_lan" {
                name          = "${var.vlan_lan}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
              
              data "vsphere_network" "network_dmz" {
                name          = "${var.vlan_dmz}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
              
              data "vsphere_virtual_machine" "template" {
                name          = "${var.vsphere_template}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
        
    

    server_xx.tf

    Ces fichiers décrivent chaque serveur, ils vont s’appuyer sur les variables décrites précédemment et également sur les objets vSphere.

    La logique de description est globalement toujours la même.

    On initie un objet propre au provider retenu, on lui donne un nom puis on y développe ses propriétés. Comme on dispose d’un fichier de variable, c’est ces dernières qu’on utilise pour fixer les valeurs des propriétés.

    À noter qu’un objet peut devenir une propriété d’un autre objet.

    Par exemple pour le premier fichier master_01.tf associé au premier control plane, l’objet vsphere_datastore nommé ici datastore_vmaster_01, va s’appuyer sur la variable master_01_dts du fichier variable.tf, mais reprendre également l’objet vsphere_datacenter nommé dc et lui déclaré dans vsphere.tf.

    Ce même objet vsphere_datastore sera quand lui utilisé dans les propriétés de l’objet vsphere_virtual_machine nommé vm_master_01.

    Chaque objet est décrit dans la documentation du provider et si vous commencez à vous perdre, un outil comme ChatGPT peut vous aider. Ce n’est pas magique, mais pour générer du IAC, les IA sont très pratiques…du moment que vous comprenez un minimum ce que vous faite et les réponses données.

        
            data "vsphere_datastore" "datastore_master_01" {
                name          = "${var.master_01_dts}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
              
              
              resource "vsphere_virtual_machine" "vm_master_01" {
                name             = "${var.master_01_name}"
                resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
                datastore_id     = "${data.vsphere_datastore.datastore_master_01.id}"
              
                num_cpus = "${var.master_01_cpu_number}"
                memory   = "${var.master_01_memory_size}"
                guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
                tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_master.id}","${vsphere_tag.tag_net_lan.id}"]
              
                scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
              
                network_interface {
                  network_id   = "${data.vsphere_network.network_lan.id}"
                  adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
                }
              
                disk {
                  label            = "disk0"
                  size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
                  eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
                  thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
                }
              
                extra_config = {
                  "disk.EnableUUID" = "TRUE"
              }
              
                clone {
                  template_uuid = "${data.vsphere_virtual_machine.template.id}"
                   customize {
                    linux_options {
                      host_name = "${var.master_01_name}"
                      domain    = "coolcorp.priv"
                    }
                      network_interface {
                        ipv4_address = "${var.master_01_ip_address}"
                        ipv4_netmask = "${var.master_01_ip_netmask}"
                    }
                    ipv4_gateway = "${var.gateway_lan}"
                    dns_suffix_list = ["${var.default_dns_domain}"]
                    dns_server_list = ["${var.default_dns}"]
                 }
                }
              }
        
    

    Petite spécificité Kubernetes, pour être pleinement compatible avec le driver de storage vSphere et pouvoir monter des Persistent Volumes facilement, il faut que les VMs aient une option avancée disk.EnableUUID à true.

    Pour ceux qui n'exploiteraient pas un déploiement via de l'infrastructure as code, cela correspond à ce parametre dans la vue avancée des VMs:

    Options avancées VM

    Cliquez sur l'image pour l'agrandir.

    La logique est exactement la même pour le reste des VM, on l’on va retrouver un fichier par serveur. Seuls les noms des variables seront différents.

    master_02.tf
        
            data "vsphere_datastore" "datastore_master_02" {
                name          = "${var.master_02_dts}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
              
              
              resource "vsphere_virtual_machine" "vm_master_02" {
                name             = "${var.master_02_name}"
                resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
                datastore_id     = "${data.vsphere_datastore.datastore_master_02.id}"
              
                num_cpus = "${var.master_02_cpu_number}"
                memory   = "${var.master_02_memory_size}"
                guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
                tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_master.id}","${vsphere_tag.tag_net_lan.id}"]
              
                scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
              
                network_interface {
                  network_id   = "${data.vsphere_network.network_lan.id}"
                  adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
                }
              
                disk {
                  label            = "disk0"
                  size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
                  eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
                  thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
                }
              
                  extra_config = {
                  "disk.EnableUUID" = "TRUE"
              }
              
                clone {
                  template_uuid = "${data.vsphere_virtual_machine.template.id}"
                   customize {
                    linux_options {
                      host_name = "${var.master_02_name}"
                      domain    = "coolcorp.priv"
                    }
                      network_interface {
                        ipv4_address = "${var.master_02_ip_address}"
                        ipv4_netmask = "${var.master_02_ip_netmask}"
                    }
                    ipv4_gateway = "${var.gateway_lan}"
                    dns_suffix_list = ["${var.default_dns_domain}"]
                    dns_server_list = ["${var.default_dns}"]
                 }
                }
              }
        
    
    master_03.tf
        
            data "vsphere_datastore" "datastore_master_03" {
                name          = "${var.master_03_dts}"
                datacenter_id = "${data.vsphere_datacenter.dc.id}"
              }
              
              
              resource "vsphere_virtual_machine" "vm_master_03" {
                name             = "${var.master_03_name}"
                resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
                datastore_id     = "${data.vsphere_datastore.datastore_master_03.id}"
              
                num_cpus = "${var.master_03_cpu_number}"
                memory   = "${var.master_03_memory_size}"
                guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
                tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_master.id}","${vsphere_tag.tag_net_lan.id}"]
              
                scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
              
                network_interface {
                  network_id   = "${data.vsphere_network.network_lan.id}"
                  adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
                }
              
                disk {
                  label            = "disk0"
                  size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
                  eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
                  thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
                }
              
                  extra_config = {
                  "disk.EnableUUID" = "TRUE"
              }
              
               clone {
                  template_uuid = "${data.vsphere_virtual_machine.template.id}"
                   customize {
                    linux_options {
                      host_name = "${var.master_03_name}"
                      domain    = "coolcorp.priv"
                    }
                      network_interface {
                        ipv4_address = "${var.master_03_ip_address}"
                        ipv4_netmask = "${var.master_03_ip_netmask}"
                    }
                    ipv4_gateway = "${var.gateway_lan}"
                    dns_suffix_list = ["${var.default_dns_domain}"]
                    dns_server_list = ["${var.default_dns}"]
                 }
                }
              }
        
    
    worker_lan_01.tf
        
           
    data "vsphere_datastore" "datastore_lan_worker_01" {
        name          = "${var.worker_lan_01_dts}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
      }
      
      resource "vsphere_virtual_machine" "vm_lan_worker_01" {
        name             = "${var.worker_lan_01_name}"
        resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
        datastore_id     = "${data.vsphere_datastore.datastore_lan_worker_01.id}"
      
        num_cpus = "${var.worker_lan_01_cpu_number}"
        memory   = "${var.worker_lan_01_memory_size}"
        guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
        tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_worker.id}","${vsphere_tag.tag_net_lan.id}"]
      
        scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
      
        network_interface {
          network_id   = "${data.vsphere_network.network_lan.id}"
          adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
        }
      
        disk {
          label            = "disk0"
          size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
          eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
          thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
        }
      
            extra_config = {
          "disk.EnableUUID" = "TRUE"
      }
      
       clone {
          template_uuid = "${data.vsphere_virtual_machine.template.id}"
           customize {
            linux_options {
              host_name = "${var.worker_lan_01_name}"
              domain    = "coolcorp.priv"
            }
              network_interface {
                ipv4_address = "${var.worker_lan_01_ip_address}"
                ipv4_netmask = "${var.worker_lan_01_ip_netmask}"
            }
            ipv4_gateway = "${var.gateway_lan}"
            dns_suffix_list = ["${var.default_dns_domain}"]
            dns_server_list = ["${var.default_dns}"]
         }
        }
      }
        
    
    worker_lan_02.tf
        
            
    data "vsphere_datastore" "datastore_lan_worker_02" {
        name          = "${var.worker_lan_02_dts}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
      }
      
      resource "vsphere_virtual_machine" "vm_lan_worker_02" {
        name             = "${var.worker_lan_02_name}"
        resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
        datastore_id     = "${data.vsphere_datastore.datastore_lan_worker_02.id}"
      
        num_cpus = "${var.worker_lan_02_cpu_number}"
        memory   = "${var.worker_lan_02_memory_size}"
        guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
        tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_worker.id}","${vsphere_tag.tag_net_lan.id}"]
      
        scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
      
        network_interface {
          network_id   = "${data.vsphere_network.network_lan.id}"
          adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
        }
      
        disk {
          label            = "disk0"
          size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
          eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
          thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
        }
        
          extra_config = {
          "disk.EnableUUID" = "TRUE"
      }
      
       clone {
          template_uuid = "${data.vsphere_virtual_machine.template.id}"
           customize {
            linux_options {
              host_name = "${var.worker_lan_02_name}"
              domain    = "coolcorp.priv"
            }
              network_interface {
                ipv4_address = "${var.worker_lan_02_ip_address}"
                ipv4_netmask = "${var.worker_lan_02_ip_netmask}"
            }
            ipv4_gateway = "${var.gateway_lan}"
            dns_suffix_list = ["${var.default_dns_domain}"]
            dns_server_list = ["${var.default_dns}"]
         }
        }
      }
        
    
    worker_dmz_01.tf
        
           
    data "vsphere_datastore" "datastore_dmz_worker_01" {
        name          = "${var.worker_dmz_01_dts}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
      }
      
      resource "vsphere_virtual_machine" "vm_worker_01_dmz" {
        name             = "${var.worker_dmz_01_name}"
        resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
        datastore_id     = "${data.vsphere_datastore.datastore_dmz_worker_01.id}"
      
        num_cpus = "${var.worker_dmz_01_cpu_number}"
        memory   = "${var.worker_dmz_01_memory_size}"
        guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
        tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_worker.id}","${vsphere_tag.tag_net_web.id}"]
      
        scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
      
        network_interface {
          network_id   = "${data.vsphere_network.network_dmz.id}"
          adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
        }
      
        disk {
          label            = "disk0"
          size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
          eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
          thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
        }
      
          extra_config = {
          "disk.EnableUUID" = "TRUE"
      }
      
       clone {
          template_uuid = "${data.vsphere_virtual_machine.template.id}"
           customize {
            linux_options {
              host_name = "${var.worker_dmz_01_name}"
              domain    = "coolcorp.priv"
            }
              network_interface {
                ipv4_address = "${var.worker_dmz_01_ip_address}"
                ipv4_netmask = "${var.worker_dmz_01_ip_netmask}"
            }
            ipv4_gateway = "${var.gateway_dmz}"
            dns_suffix_list = ["${var.default_dns_domain}"]
            dns_server_list = ["${var.default_dns}"]
         }
        }
      }
        
    
    worker_dmz_02.tf
        
           
    data "vsphere_datastore" "datastore_dmz_worker_02" {
        name          = "${var.worker_dmz_02_dts}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
      }
      
      resource "vsphere_virtual_machine" "vm_worker_02_dmz" {
        name             = "${var.worker_dmz_02_name}"
        resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
        datastore_id     = "${data.vsphere_datastore.datastore_dmz_worker_02.id}"
      
        num_cpus = "${var.worker_dmz_02_cpu_number}"
        memory   = "${var.worker_dmz_02_memory_size}"
        guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
        tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_worker.id}","${vsphere_tag.tag_net_web.id}"]
      
        scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
      
        network_interface {
          network_id   = "${data.vsphere_network.network_dmz.id}"
          adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
        }
      
        disk {
          label            = "disk0"
          size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
          eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
          thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
        }
      
          extra_config = {
          "disk.EnableUUID" = "TRUE"
      }
      
       clone {
          template_uuid = "${data.vsphere_virtual_machine.template.id}"
           customize {
            linux_options {
              host_name = "${var.worker_dmz_02_name}"
              domain    = "coolcorp.priv"
            }
              network_interface {
                ipv4_address = "${var.worker_dmz_02_ip_address}"
                ipv4_netmask = "${var.worker_dmz_02_ip_netmask}"
            }
            ipv4_gateway = "${var.gateway_dmz}"
            dns_suffix_list = ["${var.default_dns_domain}"]
            dns_server_list = ["${var.default_dns}"]
         }
        } 
      }
        
    
    haproxy_lan_01.tf
        
            
    data "vsphere_datastore" "datastore_lan_haproxy_01" {
        name          = "${var.haproxy_lan_01_dts}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
      }
      
      resource "vsphere_virtual_machine" "vm_lan_haproxy_01" {
        name             = "${var.haproxy_lan_01_name}"
        resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
        datastore_id     = "${data.vsphere_datastore.datastore_lan_haproxy_01.id}"
      
        num_cpus = "${var.haproxy_lan_01_cpu_number}"
        memory   = "${var.haproxy_lan_01_memory_size}"
        guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
        tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_haproxy.id}","${vsphere_tag.tag_net_lan.id}"]
      
        scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
      
        network_interface {
          network_id   = "${data.vsphere_network.network_lan.id}"
          adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
        }
      
        disk {
          label            = "disk0"
          size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
          eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
          thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
        }
      
       clone {
          template_uuid = "${data.vsphere_virtual_machine.template.id}"
           customize {
            linux_options {
              host_name = "${var.haproxy_lan_01_name}"
              domain    = "coolcorp.priv"
            }
              network_interface {
                ipv4_address = "${var.haproxy_lan_01_ip_address}"
                ipv4_netmask = "${var.haproxy_lan_01_ip_netmask}"
            }
            ipv4_gateway = "${var.gateway_lan}"
            dns_suffix_list = ["${var.default_dns_domain}"]
            dns_server_list = ["${var.default_dns}"]
         }
        }
      }
        
    
    haproxy_dmz_01.tf
        
            
    data "vsphere_datastore" "datastore_dmz_ha_proxy_01" {
        name          = "${var.haproxy_dmz_01_dts}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
      }
      
      resource "vsphere_virtual_machine" "vm_haproxy_01_dmz" {
        name             = "${var.haproxy_dmz_01_name}"
        resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
        datastore_id     = "${data.vsphere_datastore.datastore_dmz_ha_proxy_01.id}"
      
        num_cpus = "${var.haproxy_dmz_01_cpu_number}"
        memory   = "${var.haproxy_dmz_01_memory_size}"
        guest_id = "${data.vsphere_virtual_machine.template.guest_id}"
        tags = ["${vsphere_tag.tag_apps.id}","${vsphere_tag.tag_apps_haproxy.id}","${vsphere_tag.tag_net_web.id}"]
      
        scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}"
      
        network_interface {
          network_id   = "${data.vsphere_network.network_dmz.id}"
          adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}"
        }
      
        disk {
          label            = "disk0"
          size             = "${data.vsphere_virtual_machine.template.disks.0.size}"
          eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
          thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
        }
      
       clone {
          template_uuid = "${data.vsphere_virtual_machine.template.id}"
           customize {
            linux_options {
              host_name = "${var.haproxy_dmz_01_name}"
              domain    = "coolcorp.priv"
            }
              network_interface {
                ipv4_address = "${var.haproxy_dmz_01_ip_address}"
                ipv4_netmask = "${var.haproxy_dmz_01_ip_netmask}"
            }
            ipv4_gateway = "${var.gateway_dmz}"
            dns_suffix_list = ["${var.default_dns_domain}"]
            dns_server_list = ["${var.default_dns}"]
         }
        }
      }
        
    

    backend.tf (optionnel)

    Comme décrit dans l’article sur le IAC ou est présenté le cloud Terraform, il est possible de tracer ses actions et d’héberger une copie des tfstates dans une solution spécialisée.

    Pour rappel, OpenTofu (Terrafom) est basé sur le principe d’état, ou l’outil sauvegarde toutes les actions qu’il réalise dans un référentiel. À chaque lancement, OpenTofu (Terrafom) compare ce qu’il l’a réalisé précédemment à ce qui décrit dans le code. S’il y voit une différence, alors il procédera aux modifications.

    À aucun moment il ne se préoccupe de ce qui est réellement déployé… Si vous perdez votre référentiel (tfstates), ou que vous initiez des modifications manuellement alors pour OpenTofu (Terraform) vous n’êtes plus cohérents avec son état. Cela implique qu’il essayera de recréer des objets déjà présents ou qu’il n’arrivera simplement pas à exécuter ses actions.

    D’où l’intérêt de passer sur des plateformes comme terraform cloud pour non seulement sécuriser son référentiel, mais également le partager avec ses collègues et autoriser des exécutions depuis différents postes.

    Pour cela vous pouvez utiliser un fichier « backend.tf » qui reprendra l’URL de votre « repo IAC ». L’exemple ci-dessous est pour le cloud terraform, mais d’autres solutions existent.

    Si vous ne le faites pas, OpenTofu (Terraform) considèrera votre dossier local comme source de référentiel. N’hésitez pas faire un tour sur l’article dédié pour mieux comprend ce point.

        
            terraform {
                cloud {
                  organization = "COOLCORP-IAC"
                   hostname = "app.terraform.io"
                  workspaces {
                    name = "kub.coolcorp.priv"
                  }
                }
              }
        
        

    Lancement

    Il est temps maintenant de lancer la création des VMs. Pour cela il suffit de se placer dans le répertoire qui contient les fichiers et de lancer le binaire tofu (terraform) avec les bons paramètres.

    On démarre d’abord avec l’option init, celle-ci va permettre d’initialiser le référentiel (ici dans le répertoire local) et de récupérer les providers nécessaires (ici celui de VMware)

    Init OpenTofu/Terraform

    Cliquez sur l'image pour l'agrandir.

    Ensuite, on poursuit avec le paramètre plan. Celui-ci va faire une lecture du code, s’assurer qu’il est cohérent et va afficher les opérations à réaliser : création, modification ou suppression d’objets.

    Plan OpenTofu/Terraform

    Cliquez sur l'image pour l'agrandir.

    Plan OpenTofu/Terraform

    Cliquez sur l'image pour l'agrandir.

    Si on n’est d’accord avec le résultat affiché alors on peut lancer la commande finale apply en n’oubliant pas de valider pour réellement lancer le déploiement.

    Apply OpenTofu/Terraform

    Cliquez sur l'image pour l'agrandir.

    Apply OpenTofu/Terraform

    Cliquez sur l'image pour l'agrandir.

    À noter que pour le paramètre plan et apply, le mot de passe du compte d’accès au vCenter est demandé puisqu’il n’a pas été indiqué explicitement dans le fichier variables.tf pour des raisons de sécurité (comme expliqué dans l’article sur terraform, d’autres méthodes sont possibles pour sécuriser les variables présentant des risques )

    Après seulement quelques minutes, les 9 VMs sont déployées et opérationnelles. Le seul point pouvant rester est la création des entrées DNS. Disposant dans le LAB d’un DNS Microsoft, il n’y a pas de création automatique. Il existe des providers pour automatiser la chose, mais ne les ayant pas testés, je ne peux pas en parler.

    Résultat du déploiement

    Cliquez sur l'image pour l'agrandir.

    Le test final consiste à lancer un premier playbook Ansible basé sur l’article dédié que je vous invite à lire si vous ne l’avez pas fait.

    Toutes les VMs étant créées à partir d’un template disposant de la configuration minimale pour être accessible par une instance Ansible sous Windows grâce à WSL, il est possible de configurer l’OS de chaque VM et de préparer la configuration Kubernetes.

    Les VMs disposant de tags et l’instance Ansible configurée pour utiliser comme source d’inventaire le vCenter, le passage sur chaque VM est simplifié. (Plus d’info dans l'article dédié)

    Validation avec Ansible

    Cliquez sur l'image pour l'agrandir.

    Validation avec Ansible

    Cliquez sur l'image pour l'agrandir.

    Conclusion

    Cet article n’a pas vocation a être un tutoriel avancé pour OpenTofu/Terraform, mais j’espère qu’il vous aura fourni quelques informations utiles et démontre bien l’intérêt du IAC, notamment dans le déploiement d’un cluster Kubernetes qui nécessite plusieurs nodes pour être opérationnel (hors cluster de test/demo). Encore une fois, d’autres méthodes sont possibles et une autre organisation des fichiers OpenTofu (Terraform) sont imaginable, l’important est de retenir une logique qui vous parle et qui puisse être commune à vos autres déploiements.


    Pour poursuivre le cookbook avec l'étape suivante c'est par ici