Comment construire un module Terraform personnalisé

L’auteur a choisi le Fonds pour le Logiciel Libre et Open Source pour recevoir une donation dans le cadre du programme Write for DOnations.

Introduction

Les modules Terraform vous permettent de regrouper des ressources distinctes de votre infrastructure en une seule ressource unifiée. Vous pouvez les réutiliser ultérieurement avec des personnalisations possibles, sans avoir à répéter les définitions de ressources chaque fois que vous en avez besoin, ce qui est bénéfique pour les projets de grande envergure et complexement structurés. Vous pouvez personnaliser les instances de module en utilisant des variables d’entrée que vous définissez ainsi qu’extraire des informations à partir d’elles en utilisant des sorties. Outre la création de vos propres modules personnalisés, vous pouvez également utiliser les modules prédéfinis publiés publiquement sur le Registre Terraform. Les développeurs peuvent les utiliser et les personnaliser en utilisant des entrées comme les modules que vous créez, mais leur code source est stocké dans le cloud et extrait de celui-ci.

Dans ce tutoriel, vous créerez un module Terraform qui configurera plusieurs Droplets derrière un équilibreur de charge pour la redondance. Vous utiliserez également les fonctionnalités de bouclage for_each et count du langage de configuration Hashicorp (HCL) pour déployer plusieurs instances personnalisées du module simultanément.

Prérequis

Remarque: Ce tutoriel a spécifiquement été testé avec Terraform 1.1.3.

Structure des modules et avantages

Dans cette section, vous découvrirez les avantages des modules, où ils sont généralement placés dans le projet et comment ils doivent être structurés.

Les modules Terraform personnalisés sont créés pour encapsuler des composants connectés qui sont utilisés et déployés ensemble fréquemment dans de plus grands projets. Ils sont autonomes, regroupant uniquement les ressources, les variables et les fournisseurs dont ils ont besoin.

Les modules sont généralement stockés dans un dossier central à la racine du projet, chacun dans son sous-dossier respectif en dessous. Afin de conserver une séparation claire entre les modules, assurez-vous toujours de les architecturer pour qu’ils aient un seul but et veillez à ce qu’ils ne contiennent jamais de sous-modules.

Il est utile de créer des modules à partir de vos schémas de ressources lorsque vous vous trouvez à les répéter avec des personnalisations peu fréquentes. Emballer une seule ressource en tant que module peut être superflu et élimine progressivement la simplicité de l’architecture globale.

Pour les petits projets de développement et de test, l’incorporation de modules n’est pas nécessaire car ils n’apportent pas beaucoup d’améliorations dans ces cas. Avec leur capacité de personnalisation, les modules sont l’élément de construction de projets complexement structurés. Les développeurs utilisent des modules pour les projets plus importants en raison des avantages significatifs en évitant la duplication de code. Les modules offrent également l’avantage que les définitions n’ont besoin d’être modifiées qu’à un seul endroit, ce qui sera ensuite propagé dans le reste de l’infrastructure.

Ensuite, vous définirez, utiliserez et personnaliserez des modules dans vos projets Terraform.

Création d’un module

Dans cette section, vous définirez plusieurs Droplets et un équilibreur de charge en tant que ressources Terraform et les regrouperez dans un module. Vous rendrez également le module résultant personnalisable en utilisant des entrées de module.

Vous stockerez le module dans un répertoire nommé droplet-lb, sous un répertoire appelé modules. En supposant que vous vous trouvez dans le répertoire terraform-modules que vous avez créé dans le cadre des prérequis, créez les deux en une seule fois en exécutant :

  1. mkdir -p modules/droplet-lb

L’argument -p indique à mkdir de créer tous les répertoires dans le chemin fourni.

Naviguez-y :

  1. cd modules/droplet-lb

Comme cela a été noté dans la section précédente, les modules contiennent les ressources et les variables qu’ils utilisent. À partir de Terraform 0.13, ils doivent également inclure les définitions des fournisseurs qu’ils utilisent. Les modules ne nécessitent aucune configuration spéciale pour indiquer que le code représente un module, car Terraform considère chaque répertoire contenant du code HCL comme un module, même le répertoire racine du projet.

Les variables définies dans un module sont exposées en tant que ses entrées et peuvent être utilisées dans les définitions de ressources pour les personnaliser. Le module que vous créerez aura deux entrées : le nombre de Droplets à créer et le nom de leur groupe. Créez et ouvrez pour édition un fichier appelé variables.tf où vous stockerez les variables :

  1. nano variables.tf

Ajoutez les lignes suivantes :

modules/droplet-lb/variables.tf
variable "droplet_count" {}
variable "group_name" {}

Enregistrez et fermez le fichier.

Vous stockerez la définition du Droplet dans un fichier nommé droplets.tf. Créez-le et ouvrez-le pour le modifier :

  1. nano droplets.tf

Ajoutez les lignes suivantes :

modules/droplet-lb/droplets.tf
resource "digitalocean_droplet" "droplets" {
  count  = var.droplet_count
  image  = "ubuntu-20-04-x64"
  name   = "${var.group_name}-${count.index}"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

Pour le paramètre count, qui spécifie combien d’instances d’une ressource créer, vous passez la variable droplet_count. Sa valeur sera spécifiée lors de l’appel du module à partir du code principal du projet. Le nom de chaque Droplet déployé sera différent, ce que vous réalisez en ajoutant l’index du Droplet actuel au nom de groupe fourni. Le déploiement des Droplets se fera dans la région fra1 et ils exécuteront Ubuntu 20.04.

Lorsque vous avez terminé, enregistrez et fermez le fichier.

Maintenant que les Droplets sont définis, vous pouvez passer à la création du répartiteur de charge. Vous stockerez sa définition de ressource dans un fichier nommé lb.tf. Créez-le et ouvrez-le pour le modifier en exécutant :

  1. nano lb.tf

Ajoutez sa définition de ressource :

modules/droplet-lb/lb.tf
resource "digitalocean_loadbalancer" "www-lb" {
  name   = "lb-${var.group_name}"
  region = "fra1"

  forwarding_rule {
    entry_port     = 80
    entry_protocol = "http"

    target_port     = 80
    target_protocol = "http"
  }

  healthcheck {
    port     = 22
    protocol = "tcp"
  }

  droplet_ids = [
    for droplet in digitalocean_droplet.droplets:
      droplet.id
  ]
}

Vous définissez le répartiteur de charge avec le nom de groupe dans son nom pour le rendre distinguable. Vous le déployez dans la région fra1 avec les Droplets. Les deux sections suivantes spécifient les ports et protocoles cibles et de surveillance.

Le bloc droplet_ids en surbrillance prend en compte les IDs des Droplets, qui doivent être gérés par l’équilibreur de charge. Comme il y a plusieurs Droplets, et que leur nombre n’est pas connu à l’avance, vous utilisez une boucle for pour parcourir la collection de Droplets (digitalocean_droplet.droplets) et prendre leurs IDs. Vous entourez la boucle for de crochets ([]) afin que la collection résultante soit une liste.

Enregistrez et fermez le fichier.

Vous avez maintenant défini le Droplet, l’équilibreur de charge et les variables pour votre module. Vous devrez définir les exigences du fournisseur, en spécifiant quels fournisseurs le module utilise, y compris leur version et leur emplacement. Depuis Terraform 0.13, les modules doivent explicitement définir les sources des fournisseurs non maintenus par HashiCorp qu’ils utilisent ; cela est nécessaire car ils ne les héritent pas du projet parent.

Vous stockerez les exigences du fournisseur dans un fichier nommé provider.tf. Créez-le pour l’édition en exécutant :

  1. nano provider.tf

Ajoutez les lignes suivantes pour exiger le fournisseur digitalocean :

modules/droplet-lb/provider.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

Enregistrez et fermez le fichier une fois terminé. Le module droplet-lb nécessite maintenant le fournisseur digitalocean.

Les modules prennent également en charge les sorties, que vous pouvez utiliser pour extraire des informations internes sur l’état de leurs ressources. Vous définirez une sortie qui expose l’adresse IP de l’équilibreur de charge, et vous la stockerez dans un fichier nommé outputs.tf. Créez-le pour l’édition :

  1. nano outputs.tf

Ajoutez la définition suivante :

modules/droplet-lb/outputs.tf
output "lb_ip" {
  value = digitalocean_loadbalancer.www-lb.ip
}

Cette sortie récupère l’adresse IP de l’équilibreur de charge. Enregistrez et fermez le fichier.

Le module droplet-lb est maintenant fonctionnellement complet et prêt à être déployé. Vous l’appellerez à partir du code principal, que vous stockerez à la racine du projet. Tout d’abord, accédez-y en remontant dans votre répertoire de fichiers deux fois :

  1. cd ../..

Ensuite, créez et ouvrez pour l’édition un fichier appelé main.tf, dans lequel vous utiliserez le module :

  1. nano main.tf

Ajoutez les lignes suivantes :

main.tf
module "groups" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups.lb_ip
}

Dans cette déclaration, vous invoquez le module droplet-lb situé dans le répertoire spécifié en tant que source. Vous configurez les entrées qu’il fournit, droplet_count et group_name, qui est défini sur group1 afin que vous puissiez plus tard distinguer entre les instances.

Étant donné que la sortie de l’IP du Load Balancer est définie dans un module, elle ne sera pas automatiquement affichée lorsque vous appliquerez le projet. La solution consiste à créer une autre sortie récupérant sa valeur (loadbalancer_ip).

Enregistrez et fermez le fichier lorsque vous avez terminé.

Initialisez le module en exécutant :

  1. terraform init

La sortie ressemblera à ceci :

Output
Initializing modules... - groups in modules/droplet-lb Initializing the backend... Initializing provider plugins... - Finding digitalocean/digitalocean versions matching "~> 2.0"... - Installing digitalocean/digitalocean v2.19.0... - Installed digitalocean/digitalocean v2.19.0 (signed by a HashiCorp partner, key ID F82037E524B9C0E8) ... Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.

Vous pouvez essayer de planifier le projet pour voir quelles actions Terraform prendrait en exécutant :

  1. terraform plan -var "do_token=${DO_PAT}"

La sortie sera similaire à ceci :

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # Le module.groups.digitalocean_droplet.droplets[0] sera créé + resource "digitalocean_droplet" "droplets" { ... + name = "group1-0" ... } # Le module.groups.digitalocean_droplet.droplets[1] sera créé + resource "digitalocean_droplet" "droplets" { ... + name = "group1-1" ... } # Le module.groups.digitalocean_droplet.droplets[2] sera créé + resource "digitalocean_droplet" "droplets" { ... + name = "group1-2" ... } # Le module.groups.digitalocean_loadbalancer.www-lb sera créé + resource "digitalocean_loadbalancer" "www-lb" { ... + name = "lb-group1" ... } Plan: 4 to add, 0 to change, 0 to destroy. ...

Cet affichage détaille que Terraform créerait trois Droplets, nommés group1-0, group1-1, et group1-2, et créerait également un équilibreur de charge appelé group1-lb, qui gérera le trafic vers et depuis les trois Droplets.

Vous pouvez essayer d’appliquer le projet sur le cloud en exécutant :

  1. terraform apply -var "do_token=${DO_PAT}"

Entrez oui lorsqu’on vous demande. La sortie affichera toutes les actions et l’adresse IP de l’équilibreur de charge sera également affichée :

Output
module.groups.digitalocean_droplet.droplets[1]: Creating... module.groups.digitalocean_droplet.droplets[0]: Creating... module.groups.digitalocean_droplet.droplets[2]: Creating... ... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: loadbalancer-ip = ip_address

Vous avez créé un module contenant un nombre personnalisable de Droplets et un équilibreur de charge qui sera automatiquement configuré pour gérer leur trafic entrant et sortant.

Renommer les ressources déployées

Dans la section précédente, vous avez déployé le module que vous avez défini et l’avez appelé groups. Si vous souhaitez changer son nom à un moment donné, simplement renommer l’appel du module ne donnera pas les résultats escomptés. Renommer l’appel incitera Terraform à détruire et recréer des ressources, entraînant ainsi des temps d’arrêt excessifs.

Par exemple, ouvrez main.tf pour l’éditer en exécutant :

  1. nano main.tf

Renommez le module groups en groups_renamed, comme indiqué :

main.tf
module "groups_renamed" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups_renamed.lb_ip
}

Enregistrez et fermez le fichier. Ensuite, initialisez à nouveau le projet :

  1. terraform init

Vous pouvez maintenant planifier le projet :

  1. terraform plan -var "do_token=${DO_PAT}"

La sortie sera longue, mais ressemblera à ceci :

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create - destroy Terraform will perform the following actions: # module.groups.digitalocean_droplet.droplets[0] sera détruit ... # module.groups_renamed.digitalocean_droplet.droplets[0] sera créé ...

Terraform vous demandera de détruire les instances existantes et d’en créer de nouvelles. Cela est destructif et inutile, et peut entraîner des temps d’arrêt indésirables.

À la place, en utilisant le bloc moved, vous pouvez indiquer à Terraform de déplacer les anciennes ressources sous le nouveau nom. Ouvrez main.tf pour l’éditer et ajoutez les lignes suivantes à la fin du fichier :

moved {
  from = module.groups
  to   = module.groups_renamed
}

Lorsque vous avez terminé, enregistrez et fermez le fichier.

Vous pouvez maintenant planifier le projet :

  1. terraform plan -var "do_token=${DO_PAT}"

Lorsque vous planifiez avec le bloc moved présent dans main.tf, Terraform souhaite déplacer les ressources au lieu de les recréer :

Output
Terraform will perform the following actions: Le module.groups.digitalocean_droplet.droplets[0] a été déplacé vers module.groups_renamed.digitalocean_droplet.droplets[0] ... # Le module.groups.digitalocean_droplet.droplets[1] a été déplacé vers module.groups_renamed.digitalocean_droplet.droplets[1] ...

Le déplacement des ressources modifie leur emplacement dans l’état Terraform, ce qui signifie que les ressources cloud réelles ne seront pas modifiées, détruites ou recréées.

Étant donné que vous allez modifier significativement la configuration à l’étape suivante, détruisez les ressources déployées en exécutant :

  1. terraform destroy -var "do_token=${DO_PAT}"

Entrez yes lorsque vous y êtes invité. La sortie se terminera par :

Output
... Destroy complete! Resources: 4 destroyed.

Dans cette section, vous avez renommé des ressources dans votre projet Terraform sans les détruire dans le processus. Vous allez maintenant déployer plusieurs instances d’un module à partir du même code en utilisant for_each et count.

Déploiement de plusieurs instances de module

Dans cette section, vous utiliserez count et for_each pour déployer le module droplet-lb plusieurs fois avec des personnalisations.

Utilisation de count

Une façon de déployer plusieurs instances du même module à la fois est de passer le nombre dans le paramètre count, qui est automatiquement disponible pour chaque module. Ouvrez main.tf pour le modifier :

  1. nano main.tf

Modifiez-le pour qu’il ressemble à ceci, en supprimant la définition de sortie existante et le bloc moved:

main.tf
module "groups" {
  source = "./modules/droplet-lb"

  count  = 3

  droplet_count = 3
  group_name    = "group1-${count.index}"
}

En définissant count sur 3, vous indiquez à Terraform de déployer le module trois fois, chacun avec un nom de groupe différent. Lorsque vous avez terminé, enregistrez et fermez le fichier.

Planifiez le déploiement en exécutant :

  1. terraform plan -var "do_token=${DO_PAT}"

La sortie sera longue et ressemblera à ceci :

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # Le module.groups[0].digitalocean_droplet.droplets[0] sera créé ... # Le module.groups[0].digitalocean_droplet.droplets[1] sera créé ... # Le module.groups[0].digitalocean_droplet.droplets[2] sera créé ... # Le module.groups[0].digitalocean_loadbalancer.www-lb sera créé ... # Le module.groups[1].digitalocean_droplet.droplets[0] sera créé ... # Le module.groups[1].digitalocean_droplet.droplets[1] sera créé ... # Le module.groups[1].digitalocean_droplet.droplets[2] sera créé ... # Le module.groups[1].digitalocean_loadbalancer.www-lb sera créé ... # Le module.groups[2].digitalocean_droplet.droplets[0] sera créé ... # Le module.groups[2].digitalocean_droplet.droplets[1] sera créé ... # Le module.groups[2].digitalocean_droplet.droplets[2] sera créé ... # Le module.groups[2].digitalocean_loadbalancer.www-lb sera créé ... Plan: 12 to add, 0 to change, 0 to destroy. ...

Les détails de Terraform dans la sortie indiquent que chacune des trois instances de module aurait trois Droplets et un équilibreur de charge associé à chacun d’eux.

En utilisant for_each

Vous pouvez utiliser for_each pour les modules lorsque vous avez besoin d’une personnalisation plus complexe de l’instance, ou lorsque le nombre d’instances dépend de données tierces (souvent présentées sous forme de cartes) qui ne sont pas connues lors de l’écriture du code.

Vous allez maintenant définir une carte qui associe les noms de groupe aux nombres de Droplets et déployer des instances de droplet-lb selon cela. Ouvrez main.tf pour l’édition en exécutant :

  1. nano main.tf

Modifiez le fichier pour qu’il ressemble à ceci :

main.tf
variable "group_counts" {
  type    = map
  default = {
    "group1" = 1
    "group2" = 3
  }
}

module "groups" {
  source   = "./modules/droplet-lb"
  for_each = var.group_counts

  droplet_count = each.value
  group_name    = each.key
}

Vous définissez d’abord une carte appelée group_counts qui contient le nombre de Droplets qu’un groupe donné devrait avoir. Ensuite, vous invoquez le module droplet-lb, mais spécifiez que la boucle for_each doit fonctionner sur var.group_counts, la carte que vous avez définie juste avant. droplet_count prend each.value, la valeur de la paire actuelle, qui est le nombre de Droplets pour le groupe actuel. group_name reçoit le nom du groupe.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Essayez d’appliquer la configuration en exécutant :

  1. terraform plan -var "do_token=${DO_PAT}"

La sortie détaillera les actions que Terraform entreprendrait pour créer les deux groupes avec leurs Droplets et équilibreurs de charge :

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # Le droplet module.groups["groupe1"].digitalocean_droplet.droplets[0] sera créé ... # Le load balancer module.groups["groupe1"].digitalocean_loadbalancer.www-lb sera créé ... # Le droplet module.groups["groupe2"].digitalocean_droplet.droplets[0] sera créé ... # Le droplet module.groups["groupe2"].digitalocean_droplet.droplets[1] sera créé ... # Le droplet module.groups["groupe2"].digitalocean_droplet.droplets[2] sera créé ... # Le load balancer module.groups["groupe2"].digitalocean_loadbalancer.www-lb sera créé ...

Dans cette étape, vous avez utilisé count et for_each pour déployer plusieurs instances personnalisées du même module à partir du même code.

Conclusion

Dans ce tutoriel, vous avez créé et déployé des modules Terraform. Vous avez utilisé des modules pour regrouper des ressources liées de manière logique et les personnaliser afin de déployer plusieurs instances différentes à partir d’une définition de code central. Vous avez également utilisé des sorties pour afficher les attributs des ressources contenues dans le module.

Si vous souhaitez en savoir plus sur Terraform, consultez notre série Comment gérer l’infrastructure avec Terraform.

Source:
https://www.digitalocean.com/community/tutorials/how-to-build-a-custom-terraform-module