2-04 Qu'est ce que larchitecture en modules de Terraform
Objectifsâ
- Comprendre les modules dans Terraform
- Savoir créer ses propres modules
Les diffĂ©rentes formes de structuration du codeâ
Avant d'aborder les modules, voyons quels sont les structures utilisées pour organiser le code.
La structure "monolithique"â
Tout le code est dans un seul fichier, sans séparation entre responsabilités.
prod
ââ outputs.tf
ââ main.tf
ââ variables.tf
Rapidement elle peut devenir insuffisante.
La structure "flat"â
Tout le code est dans un seul dossier, avec chaque partie "logique" de l'infrastructure dans un fichier distinct.
prod
ââ database.tf
ââ load-balancer.tf
ââ outputs.tf
ââ variables.tf
ââ webserver.tf
Elle résulte d'une analyse des composants logiques de la recette.
La structure en recettes sĂ©parĂ©esâ
Les différents composants sont séparés dans des dossiers différents.
âââ prod
âââ webserver
â âââ main.tf
â âââ outputs.tf
â âââ variables.tf
âââ database
âââ main.tf
âââ outputs.tf
âââ variables.tf
Elle permet de séparer les problÚmes, mais elle implique une duplication des ressources avec par exemple des dossiers .terraform distincts.
La structure "liens symboliques"â
On déploie le code dans plusieurs dossiers, avec les fichiers communs définis comme des liens symboliques.
âââ dev
â âââ infra_override.tf
â âââ infra.tf -> ../common/infra.tf
â âââ outputs.tf -> ../common/outputs.tf
â âââ state.tf
â âââ terraform.tfvars
â âââ variables.tf -> ../common/variables.tf
âââ common
âââ infra.tf
âââ outputs.tf
âââ variables.tf
Cette structure fonctionne en chargeant les variables locales et en surchargeant des parties de la recette avec des fichiers via _override.tf
à noter également, le systÚme de fichier de Windows ne supporte pas historiquement les liens symboliques, et il faut ajouter le mode developpeur pour y accéder.
Les modules dans Terraformâ
Documentation:
Les modules sont des packages de code autonomes qui vous permettent de créer des composants réutilisables en regroupant des ressources associées.
C'est un compossant essentielle de toute architecture d'infrastructure logicielle Terraform mature, car les modules offrent de nombreux avantages
- Lisibilité
- Réusabilité
- Testabilité
Vous n'avez pas besoin de savoir comment fonctionne un module pour pouvoir l'utiliser ; il suffit de savoir paramétrer les entrées et les sorties. Les modules sont des outils utiles pour promouvoir l'abstraction logicielle et la réutilisation du code.
On va utiliser un exemple standard d'infrastructure modulaire.
TP2.05-modules
Dans le premier main.tf, notez l'usage de
module "<NAME>" {
source = "<SOURCE>"
[CONFIG ...]
}
On observe que ce module est principalement composé d'autres modules.
Ce modÚle est connu sous le nom de composition logicielle : la pratique consistant à décomposer un code volumineux et complexe en sous-systÚmes plus petits.
HashiCorp recommande fortement que chaque module suive certaines conventions de code connues sous le nom de structure de module standard.
Au minimum, cela signifie avoir trois configurations Terraform fichiers de figuration par module.
- main.tfâle point d'entrĂ©e principal
- outputs.tfâdĂ©clarations pour toutes les valeurs de sortie
- variables.tfâdĂ©clarations pour toutes les variables d'entrĂ©e
Le module racine est le module de niveau supérieur.
C'est là que les variables d'entrée fournies par l'utilisateur sont configurées et que les commandes Terraform telles que terraform init et terraform apply sont exécutées.
Le module racine ne fait pas grand-chose à part déclarer des modules de composants et leur permettre de transmettre des données entre eux.
Du point de vue du module racine, les modules sont des fonctions avec des effets secondaires (c'est-à -dire des fonctions non pures), qui sont les ressources provisionnées à la suite de l'application de terraform.
Nous pouvons voir que les sorties de chaque module "s'écoulent" via le module racine qui fait appel à leur outputs.
module.<MODULE_NAME>.<OUTPUT_NAME>
Le module avec le moins de dépendances est appelé en premier, et ses sorties sont transmises au module avec le plus de dépendances (l'équilibreur de charge) par le module racine.
Bonnes pratiques pour le module racineâ
Il est conseillé d'intégrer certains fichiers sont dans le module racine :
- versions.tf
terraform {
required_version = ">= 0.14"
required_providers {
aws = "= 3.28"
}
}
- providers.tf
provider "google" {
project = var.project_id
region = var.region
}
- README.md
Ces fichiers définissent la documentation et configuration de base de votre recette.
Cependant rien n'interdit un module d'avoir sa propre documentation ou de surcharger un provider (par exemple pour utiliser une version différente).
Sources des modulesâ
On peut utiliser les modules disponibles sur le registre Terraform, créer les siennes et utiliser un mélange des deux.
Les modules du Registryâ
Il existe de trĂšs nombreux modules qui couvrent Ă peu prĂšs tous les usages sur le Registry.
On les appelle en mentionnant une source "absolue" et terraform les télécharge avec les providers quand on initie le projet.
module "alb" {
source = "terraform-aws-modules/alb/aws"
...
}
De la mĂȘme maniĂšre qu'avec les providers, on peut bloquer les versions des modules.
Souvent, les modules du Registry utiliseront eux-mĂȘmes des modules : c'est ce qu'on appelle des modules imbriquĂ©s (nested).
CrĂ©er ses propres modulesâ
La plupart du temps on crée ses modules au sein de son projet, la syntaxe dans ce cas fait appel à un dossier relatif.
module "webservers" {
source = "./modules/webservers"
}
On peut ensuite ajouter ce module dans le Registry Terraform, et en réalité on peut aussi charger des modules depuis Github, des buckets S3 et encore d'autres.
Créer un module Terraform est trÚs simple : tout ensemble de fichiers de configuration Terraform dans un dossier est un module.
Toutes les configurations que vous avez écrites jusqu'à présent étaient techniquement des modules, bien que pas particuliÚrement intéressants, puisque vous les avez déployés directement : si vous exécutez apply directement sur un module, il est appelé module racine.
Pour voir de quoi les modules sont rĂ©ellement capables, vous devez crĂ©er un module rĂ©utilisable, c'est-Ă -dire un module destinĂ© Ă ĂȘtre utilisĂ© dans d'autres modules.
Structurer le moduleâ
Comme on l'a vu la norme est de mettre
- La génération de ressources dans le fichier
main.tf - Les inputs dans le fichier
variables.tf - Les outputs dans le fichier
outputs.tf
Rendre son module customisableâ
La bonne gestion des variables du module est essentielle, car la capacitĂ© Ă pouvoir ĂȘtre rĂ©utilisable dĂ©pend d'elle.
Il faut donc essayer de multiplier intelligemment les variables, avec des défauts intelligents.
Ăviter les piĂšgesâ
Le premier piĂšge apparaĂźt quand on fait appel Ă des fonctions utilisant les fichiers locaux, comme template.
Terraform utilise le dossier courant comme référence par défaut des chemins relatifs, mais ça ne fonctionnera plus quand vous serez dans un module.
On utilise donc des variables spéciales path.* qui permettent à Terraform de charger les fichiers correctement.
user_data = templatefile("${path.module}/user-data.sh", {
server_port = var.server_port
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
})
Le deuxiÚme piÚge survient avec les déclarations de bloc dans les ressources.
Avec la déclaration suivante dans un module, il est impossible de surcharger le ingress déclaré dans le module, car le bloc est déterminé.
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
...
}
On utillise donc des ressources séparées, qui seront aggrégées au moment de l'exécution.
C'est pourquoi on trouve dans les listes d'objets mis à disposition par les providers une myriade de sous-éléments.
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
}
resource "aws_security_group_rule" "allow_http_inbound" {
type = "ingress"
security_group_id = aws_security_group.alb.id
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
En exportant le Security Group...
output "alb_security_group_id" {
value = aws_security_group.alb.id
description = "The ID of the Security Group attached to the load balancer"
}
On peut le réutiliser pour ajouter de nouvelles rÚgles hors module
module "webservers" {
source = "./modules/webservers"
...
}
resource "aws_security_group_rule" "allow_testing_inbound" {
type = "ingress"
security_group_id = module.webservers.alb_security_group_id
from_port = 12345
to_port = 12345
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
Rappel des objectifsâ
- Comprendre les modules dans Terraform
- Savoir créer ses propres modules