Aller au contenu principal

Projet 5 : Multi-Environnements


Contexte

Vous devez déployer la même infrastructure sur 3 environnements (dev, staging, production) avec des configurations différentes. L'objectif est de maximiser la réutilisation du code tout en permettant des variations par environnement.

Exigences

  • Code unique pour tous les environnements
  • Variables différentes par environnement
  • State séparé par environnement
  • Promotion automatisée entre environnements

Stratégies comparées


Option 1 : Terraform Workspaces

Structure

infra-workspaces/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── terraform.tfvars # Valeurs par défaut
├── envs/
│ ├── dev.tfvars
│ ├── staging.tfvars
│ └── prod.tfvars
└── modules/
├── vpc/
├── compute/
└── database/

variables.tf

variable "project_name" {
description = "Project name"
type = string
}

variable "region" {
description = "AWS Region"
type = string
default = "eu-west-1"
}

# Configuration par environnement
variable "env_config" {
description = "Environment-specific configuration"
type = object({
vpc_cidr = string
instance_type = string
min_size = number
max_size = number
desired_size = number
db_instance_class = string
multi_az = bool
enable_monitoring = bool
})
}

main.tf

locals {
environment = terraform.workspace
name_prefix = "${var.project_name}-${local.environment}"

# Configuration par défaut + override par workspace
default_config = {
dev = {
vpc_cidr = "10.1.0.0/16"
instance_type = "t3.micro"
min_size = 1
max_size = 2
desired_size = 1
db_instance_class = "db.t3.micro"
multi_az = false
enable_monitoring = false
}
staging = {
vpc_cidr = "10.2.0.0/16"
instance_type = "t3.small"
min_size = 1
max_size = 3
desired_size = 2
db_instance_class = "db.t3.small"
multi_az = false
enable_monitoring = true
}
production = {
vpc_cidr = "10.0.0.0/16"
instance_type = "t3.medium"
min_size = 2
max_size = 10
desired_size = 3
db_instance_class = "db.t3.medium"
multi_az = true
enable_monitoring = true
}
}

config = lookup(local.default_config, local.environment, local.default_config["dev"])
}

module "vpc" {
source = "./modules/vpc"

name_prefix = local.name_prefix
vpc_cidr = local.config.vpc_cidr
environment = local.environment
}

module "compute" {
source = "./modules/compute"

name_prefix = local.name_prefix
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_type = local.config.instance_type
min_size = local.config.min_size
max_size = local.config.max_size
desired_size = local.config.desired_size
environment = local.environment
}

module "database" {
source = "./modules/database"

name_prefix = local.name_prefix
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.database_subnet_ids
instance_class = local.config.db_instance_class
multi_az = local.config.multi_az
security_group_id = module.compute.app_security_group_id
environment = local.environment
}

Utilisation des Workspaces

# Créer les workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new production

# Lister les workspaces
terraform workspace list

# Sélectionner un workspace
terraform workspace select dev

# Déployer sur dev
terraform workspace select dev
terraform apply -var-file=envs/dev.tfvars

# Déployer sur staging
terraform workspace select staging
terraform apply -var-file=envs/staging.tfvars

# Déployer sur production
terraform workspace select production
terraform apply -var-file=envs/prod.tfvars

Option 2 : Structure par Répertoire

Structure

infra-directories/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── database/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── environments/
├── dev/
│ ├── main.tf
│ ├── backend.tf
│ └── terraform.tfvars
├── staging/
│ ├── main.tf
│ ├── backend.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
├── backend.tf
└── terraform.tfvars

environments/dev/main.tf

module "infrastructure" {
source = "../../modules"

project_name = "myapp"
environment = "dev"
region = "eu-west-1"

vpc_cidr = "10.1.0.0/16"
instance_type = "t3.micro"
min_size = 1
max_size = 2
desired_size = 1
db_instance_class = "db.t3.micro"
multi_az = false
}

environments/dev/backend.tf

terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "myapp/dev/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}

environments/prod/main.tf

module "infrastructure" {
source = "../../modules"

project_name = "myapp"
environment = "production"
region = "eu-west-1"

vpc_cidr = "10.0.0.0/16"
instance_type = "t3.medium"
min_size = 2
max_size = 10
desired_size = 3
db_instance_class = "db.t3.medium"
multi_az = true
}

Déploiement

# Déployer dev
cd environments/dev
terraform init
terraform apply

# Déployer production
cd ../prod
terraform init
terraform apply

Option 3 : Terragrunt (Recommandé)

Structure

infra-terragrunt/
├── terragrunt.hcl # Configuration racine
├── modules/
│ └── infrastructure/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── environments/
├── terragrunt.hcl # Configuration partagée
├── dev/
│ └── terragrunt.hcl
├── staging/
│ └── terragrunt.hcl
└── prod/
└── terragrunt.hcl

terragrunt.hcl (racine)

# Configuration du backend S3 (DRY)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "mycompany-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}

# Générer le provider
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = var.region

default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terragrunt"
Project = var.project_name
}
}
}
EOF
}

environments/terragrunt.hcl

# Inclure la config racine
include "root" {
path = find_in_parent_folders()
}

# Variables communes à tous les environnements
locals {
project_name = "myapp"
region = "eu-west-1"
}

inputs = {
project_name = local.project_name
region = local.region
}

environments/dev/terragrunt.hcl

include "root" {
path = find_in_parent_folders()
}

include "env" {
path = "${dirname(find_in_parent_folders())}/environments/terragrunt.hcl"
}

terraform {
source = "${dirname(find_in_parent_folders())}/modules/infrastructure"
}

inputs = {
environment = "dev"
vpc_cidr = "10.1.0.0/16"
instance_type = "t3.micro"
min_size = 1
max_size = 2
desired_size = 1
db_instance_class = "db.t3.micro"
multi_az = false
enable_monitoring = false
}

environments/prod/terragrunt.hcl

include "root" {
path = find_in_parent_folders()
}

include "env" {
path = "${dirname(find_in_parent_folders())}/environments/terragrunt.hcl"
}

terraform {
source = "${dirname(find_in_parent_folders())}/modules/infrastructure"
}

# Dépendances (promotion depuis staging)
dependencies {
paths = ["../staging"]
}

dependency "staging" {
config_path = "../staging"

mock_outputs = {
vpc_id = "vpc-mock"
}
}

inputs = {
environment = "production"
vpc_cidr = "10.0.0.0/16"
instance_type = "t3.medium"
min_size = 2
max_size = 10
desired_size = 3
db_instance_class = "db.t3.medium"
multi_az = true
enable_monitoring = true

# Utiliser une référence de staging si nécessaire
# staging_vpc_id = dependency.staging.outputs.vpc_id
}

Commandes Terragrunt

# Déployer un environnement
cd environments/dev
terragrunt apply

# Déployer tous les environnements
cd environments
terragrunt run-all apply

# Plan sur tous les environnements
terragrunt run-all plan

# Destroy un environnement
cd environments/dev
terragrunt destroy

Variables par environnement (Détail)

Module principal - variables.tf

variable "environment" {
description = "Environment name"
type = string

validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}

# Utiliser des maps pour des valeurs par environnement
locals {
env_settings = {
dev = {
deletion_protection = false
backup_retention = 1
log_retention = 7
alarm_threshold = 90
}
staging = {
deletion_protection = false
backup_retention = 7
log_retention = 14
alarm_threshold = 85
}
production = {
deletion_protection = true
backup_retention = 30
log_retention = 90
alarm_threshold = 80
}
}

settings = local.env_settings[var.environment]
}

Comparaison des approches

CritèreWorkspacesDirectoriesTerragrunt
Simplicité⭐⭐⭐⭐⭐
Séparation state⭐⭐⭐⭐⭐⭐⭐⭐
DRY⭐⭐⭐⭐⭐⭐
CI/CD⭐⭐⭐⭐⭐⭐⭐⭐
Sécurité⭐⭐⭐⭐⭐⭐
Courbe apprentissageFacileMoyenDifficile
Recommandation
  • Petits projets : Workspaces
  • Projets moyens : Structure par répertoire
  • Projets complexes : Terragrunt

Bonnes pratiques

1. Nommage cohérent

locals {
name_prefix = "${var.project_name}-${var.environment}"

common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
CostCenter = var.cost_center
}
}

2. Validation des environnements

resource "null_resource" "validate_prod" {
count = var.environment == "production" ? 1 : 0

lifecycle {
precondition {
condition = var.multi_az == true
error_message = "Production must have multi_az enabled."
}

precondition {
condition = var.backup_retention >= 7
error_message = "Production must have at least 7 days backup retention."
}
}
}

3. Outputs par environnement

output "environment_info" {
value = {
environment = var.environment
vpc_id = module.vpc.vpc_id
api_endpoint = module.api.endpoint
db_endpoint = var.environment != "dev" ? module.database.endpoint : "N/A"
}
}

← Projet 4 | Projet 6: Pipeline CI/CD →