TP 7 : Workspaces multi-environnements
Objectif
Section intitulée « Objectif »Utiliser les workspaces Terraform pour gérer plusieurs environnements à partir du même code :
devtestprod
Chaque workspace déploie un conteneur Docker différent, avec un nom et un port adaptés à l’environnement.
Prérequis
Section intitulée « Prérequis »- Terraform installé
- Docker installé et lancé
- Un terminal
- Un éditeur de code
Vérifier :
terraform versiondocker versionArborescence
Section intitulée « Arborescence »tp-terraform-workspaces/├── main.tf├── variables.tf├── outputs.tf└── .gitignoreFichier .gitignore
Section intitulée « Fichier .gitignore ».terraform/*.tfstate*.tfstate.*.terraform.lock.hcl*.tfvarsFichier variables.tf
Section intitulée « Fichier variables.tf »variable "image_name" { description = "Image Docker utilisée" type = string default = "nginx:latest"}
variable "environment_ports" { description = "Ports utilisés selon le workspace" type = map(number)
default = { default = 8080 dev = 8081 test = 8082 prod = 8083 }}Fichier main.tf
Section intitulée « Fichier main.tf »terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "~> 3.0" } }}
provider "docker" {}
locals { environment = terraform.workspace port = lookup(var.environment_ports, local.environment, 8080)}
resource "docker_image" "nginx" { name = var.image_name}
resource "docker_container" "web" { name = "web-${local.environment}" image = docker_image.nginx.image_id
ports { internal = 80 external = local.port }}
lookup(map, clé, valeur_par_défaut)retourne la valeur associée à la clé dans la map, ou la valeur par défaut si la clé est absente. Cela évite une erreur si un workspace est créé sans entrée correspondante dans la map des ports.
terraform.workspaceretourne le nom du workspace actif sous forme de chaîne. Dans le workspacedefault, la valeur est"default".
Fichier outputs.tf
Section intitulée « Fichier outputs.tf »output "workspace" { description = "Workspace Terraform utilisé" value = terraform.workspace}
output "container_name" { description = "Nom du conteneur créé" value = docker_container.web.name}
output "application_url" { description = "URL locale de l'application" value = "http://localhost:${local.port}"}Étape 1 : initialiser le projet
Section intitulée « Étape 1 : initialiser le projet »terraform initÉtape 2 : afficher le workspace courant
Section intitulée « Étape 2 : afficher le workspace courant »terraform workspace showPar défaut, Terraform utilise le workspace default.
Étape 3 : créer le premier environnement
Section intitulée « Étape 3 : créer le premier environnement »Créer le workspace dev :
terraform workspace new devVérifier le workspace actif :
terraform workspace showPuis appliquer :
terraform planterraform applyTester : http://localhost:8081
Étape 4 : créer un deuxième environnement
Section intitulée « Étape 4 : créer un deuxième environnement »Créer le workspace test :
terraform workspace new testterraform planterraform applyTester : http://localhost:8082
Étape 5 : créer un troisième environnement
Section intitulée « Étape 5 : créer un troisième environnement »Créer le workspace prod :
terraform workspace new prodterraform planterraform applyTester : http://localhost:8083
Étape 6 : lister les workspaces
Section intitulée « Étape 6 : lister les workspaces »terraform workspace listExemple de résultat :
default dev test* prodL’étoile indique le workspace actif.
Étape 7 : passer d’un environnement à l’autre
Section intitulée « Étape 7 : passer d’un environnement à l’autre »Revenir sur dev :
terraform workspace select devterraform outputPasser sur test :
terraform workspace select testterraform outputChaque workspace possède son propre state. C’est pour cela que les conteneurs web-dev, web-test et web-prod peuvent exister simultanément.
Étape 8 : modifier le port d’un environnement
Section intitulée « Étape 8 : modifier le port d’un environnement »Sélectionner dev :
terraform workspace select devAppliquer un port différent pour ce seul workspace avec -var :
terraform plan -var='environment_ports={"default":8080,"dev":8091,"test":8082,"prod":8083}'terraform apply -var='environment_ports={"default":8080,"dev":8091,"test":8082,"prod":8083}'Tester : http://localhost:8091
Attention :
environment_portsest défini dansvariables.tfet partagé par tous les workspaces. Modifier la valeurdefaultdansvariables.tfchangerait le port pour tous les environnements à leur prochainapply. Pour modifier un seul environnement, utiliser-varcomme ci-dessus ou un fichier.tfvarsdédié.
Étape 9 : observer les states
Section intitulée « Étape 9 : observer les states »Lister les ressources du workspace actif :
terraform state listChanger de workspace et comparer :
terraform workspace select testterraform state listChaque workspace maintient un state isolé : les ressources de dev et test sont indépendantes.
Étape 10 : supprimer un environnement
Section intitulée « Étape 10 : supprimer un environnement »terraform workspace select devterraform destroyterraform workspace select defaultterraform workspace delete devUn workspace ne peut être supprimé que s’il ne contient plus de ressources dans son state.
terraform destroydoit être exécuté avantterraform workspace delete.
Exercice 1 : ajouter un environnement staging
Section intitulée « Exercice 1 : ajouter un environnement staging »Ajouter un port dans variables.tf :
default = { default = 8080 dev = 8081 test = 8082 staging = 8084 prod = 8083}Créer le workspace et appliquer :
terraform workspace new stagingterraform planterraform applyTester : http://localhost:8084
Exercice 2 : changer l’image selon l’environnement
Section intitulée « Exercice 2 : changer l’image selon l’environnement »Attention : cet exercice renomme la ressource
docker_image.nginxendocker_image.web. Terraform va détruire l’ancienne ressource et en créer une nouvelle : ce qui entraîne aussi la recréation du conteneur. C’est un comportement normal lors d’un renommage de ressource dans le code.
Ajouter dans variables.tf :
variable "environment_images" { description = "Images Docker utilisées selon le workspace" type = map(string)
default = { default = "nginx:latest" dev = "nginx:alpine" test = "httpd:latest" staging = "caddy:latest" prod = "nginx:latest" }}Remplacer le contenu de main.tf par :
terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "~> 3.0" } }}
provider "docker" {}
locals { environment = terraform.workspace port = lookup(var.environment_ports, local.environment, 8080) image = lookup(var.environment_images, local.environment, "nginx:latest")}
resource "docker_image" "web" { name = local.image}
resource "docker_container" "web" { name = "web-${local.environment}" image = docker_image.web.image_id
ports { internal = 80 external = local.port }}Tester dans chaque workspace :
terraform workspace select devterraform plan && terraform apply
terraform workspace select testterraform plan && terraform apply
terraform workspace select stagingterraform plan && terraform applyExercice 3 : comprendre lookup() et la valeur par défaut
Section intitulée « Exercice 3 : comprendre lookup() et la valeur par défaut »Le main.tf utilise déjà lookup() depuis le début du TP. Cet exercice explique pourquoi.
port = lookup(var.environment_ports, local.environment, 8080)image = lookup(var.environment_images, local.environment, "nginx:latest")lookup(map, clé, valeur_par_défaut) fonctionne ainsi :
- si la clé existe dans la map, la valeur associée est retournée
- si la clé est absente, la valeur par défaut (troisième argument) est retournée sans erreur
Sans lookup, un accès direct comme var.environment_ports[local.environment] provoquerait une erreur si le workspace ne figure pas dans la map.
Tester ce comportement :
terraform workspace new hotfixterraform planAvec lookup, Terraform utilise le port 8080 et l’image nginx:latest par défaut. Sans lookup, l’exécution échoue.
Exercice 4 : comprendre les limites des workspaces
Section intitulée « Exercice 4 : comprendre les limites des workspaces »Les workspaces sont utiles pour séparer plusieurs states avec le même code.
Ils ont cependant des limites importantes :
- le code est identique pour tous les environnements : il est difficile d’avoir des configurations très différentes entre
devetprod - les fichiers
variables.tfetmain.tfsont partagés : une modification affecte potentiellement tous les workspaces au prochainapply - en équipe, il est facile d’oublier dans quel workspace on se trouve avant d’exécuter un
applyou undestroy
Pour un environnement critique comme prod, une séparation par dossiers peut être préférable :
environments/├── dev/│ ├── main.tf│ └── terraform.tfvars├── test/│ ├── main.tf│ └── terraform.tfvars└── prod/ ├── main.tf └── terraform.tfvarsCette approche donne un contrôle plus strict sur chaque environnement, au prix d’une duplication de code réduite par l’usage de modules.
Nettoyage complet
Section intitulée « Nettoyage complet »Supprimer les ressources dans chaque workspace utilisé :
terraform workspace select dev && terraform destroyterraform workspace select test && terraform destroyterraform workspace select staging && terraform destroyterraform workspace select prod && terraform destroyRevenir sur default et supprimer les workspaces vides :
terraform workspace select default
terraform workspace delete devterraform workspace delete testterraform workspace delete stagingterraform workspace delete prodAdapter la liste selon les workspaces effectivement créés pendant le TP.