Terraform est un outil open source d'Infrastructure as Code (IaC) développé par HashiCorp. Il vous permet de définir votre infrastructure cloud dans des fichiers de configuration déclaratifs, puis de la déployer et la gérer de manière reproductible. Terraform supporte plus de 3000 providers (AWS, Azure, GCP, OVH, Docker, Kubernetes...) et constitue aujourd'hui la référence en matière d'IaC.
Prérequis
- Système d'exploitation : Linux, macOS ou Windows (ce guide se concentre sur Linux et macOS)
- Connaissances de base : Ligne de commande, notions de cloud computing
- Compte cloud : Un compte AWS, Azure, GCP ou autre provider (les exemples utilisent AWS)
- Éditeur de texte : VS Code avec l'extension HashiCorp Terraform recommandée
- Git : Pour versionner vos fichiers de configuration
Installation
Installation sur Linux (Debian/Ubuntu)
Ajoutez le dépôt officiel HashiCorp et installez Terraform :
# Ajout de la clé GPG HashiCorp
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
# Ajout du dépôt
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
# Installation
sudo apt update && sudo apt install -y terraform
# Vérification
terraform --version
Installation sur macOS
# Via Homebrew
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# Vérification
terraform --version
Installation avec tfenv (recommandé)
tfenv est un gestionnaire de versions pour Terraform, similaire à nvm pour Node.js. Il permet de jongler facilement entre les versions :
# Installation de tfenv
git clone https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Installer une version spécifique
tfenv install 1.9.0
tfenv use 1.9.0
# Lister les versions disponibles
tfenv list-remote
Créez un fichier
.terraform-version à la racine de chaque projet pour fixer la version. Cela garantit que toute l'équipe utilise la même version de Terraform.
Concepts fondamentaux
Providers
Les providers sont des plugins qui permettent à Terraform d'interagir avec des APIs de services cloud, SaaS ou autres. Chaque provider expose des ressources et des data sources.
# Configuration du provider AWS
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-3" # Paris
}
Resources
Les ressources sont les blocs de construction principaux. Chaque ressource décrit un élément d'infrastructure à créer :
# Créer une instance EC2
resource "aws_instance" "web_server" {
ami = "ami-0a21d1c76ac56fee7"
instance_type = "t3.micro"
tags = {
Name = "web-server"
Environment = "production"
}
}
State (état)
Le state est un fichier JSON (terraform.tfstate) qui enregistre la correspondance entre vos fichiers de configuration et les ressources réelles dans le cloud. C'est le mécanisme central qui permet à Terraform de savoir ce qui existe déjà et ce qui doit être modifié.
Plan et Apply
Terraform fonctionne en deux étapes clés :
- Plan : Compare votre configuration avec le state actuel et affiche les modifications à appliquer (création, modification, suppression)
- Apply : Exécute réellement les changements planifiés sur votre infrastructure
Vérifiez toujours le plan avant d'appliquer les changements. Un
terraform plan minutieux évite les suppressions accidentelles de ressources en production.
Premier projet
Créons un projet Terraform complet qui déploie une instance EC2 avec un groupe de sécurité.
Structure du projet
mkdir mon-premier-projet && cd mon-premier-projet
# Structure recommandée
touch main.tf variables.tf outputs.tf terraform.tfvars
Fichier main.tf
# main.tf - Configuration principale
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Groupe de sécurité
resource "aws_security_group" "web_sg" {
name = "web-sg"
description = "Autoriser HTTP et SSH"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.admin_ip]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Instance EC2
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web_sg.id]
tags = {
Name = "mon-serveur-web"
}
}
Commandes de déploiement
# Initialiser le projet (télécharge les providers)
terraform init
# Vérifier la syntaxe
terraform validate
# Prévisualiser les changements
terraform plan
# Appliquer les changements
terraform apply
# Appliquer sans confirmation interactive (CI/CD)
terraform apply -auto-approve
# Détruire toute l'infrastructure
terraform destroy
Variables et Outputs
Définir des variables (variables.tf)
# variables.tf
variable "aws_region" {
description = "Région AWS pour le déploiement"
type = string
default = "eu-west-3"
}
variable "instance_type" {
description = "Type d'instance EC2"
type = string
default = "t3.micro"
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "Le type d'instance doit être t3.micro, t3.small ou t3.medium."
}
}
variable "ami_id" {
description = "AMI de l'instance EC2"
type = string
}
variable "admin_ip" {
description = "IP de l'administrateur pour SSH (format CIDR)"
type = string
sensitive = false
}
Fichier de valeurs (terraform.tfvars)
# terraform.tfvars - Valeurs des variables
aws_region = "eu-west-3"
instance_type = "t3.micro"
ami_id = "ami-0a21d1c76ac56fee7"
admin_ip = "203.0.113.50/32"
Outputs (outputs.tf)
# outputs.tf - Valeurs de sortie
output "instance_id" {
description = "ID de l'instance EC2"
value = aws_instance.web.id
}
output "public_ip" {
description = "IP publique du serveur"
value = aws_instance.web.public_ip
}
output "security_group_id" {
description = "ID du groupe de sécurité"
value = aws_security_group.web_sg.id
}
Vous pouvez aussi passer des variables via l'environnement en les préfixant avec
TF_VAR_. Par exemple : export TF_VAR_aws_region="eu-west-3". C'est la méthode recommandée en CI/CD.
State management
State local vs remote
Par défaut, Terraform stocke le state localement dans terraform.tfstate. En équipe, il faut impérativement utiliser un backend distant pour éviter les conflits et les pertes de données.
Backend S3 avec DynamoDB (recommandé pour AWS)
# backend.tf - Configuration du backend distant
terraform {
backend "s3" {
bucket = "mon-projet-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-3"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
Créer les ressources du backend
# Créer le bucket S3 pour le state
aws s3api create-bucket \
--bucket mon-projet-terraform-state \
--region eu-west-3 \
--create-bucket-configuration LocationConstraint=eu-west-3
# Activer le versioning sur le bucket
aws s3api put-bucket-versioning \
--bucket mon-projet-terraform-state \
--versioning-configuration Status=Enabled
# Créer la table DynamoDB pour le state locking
aws dynamodb create-table \
--table-name terraform-state-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
State locking
Le state locking empêche deux utilisateurs d'exécuter terraform apply simultanément. DynamoDB gère ce verrou automatiquement. Si un lock reste bloqué :
# Forcer le déverrouillage (utiliser avec précaution)
terraform force-unlock LOCK_ID
Le fichier
terraform.tfstate peut contenir des mots de passe, clés API et autres secrets en clair. Ne le commitez jamais dans Git. Ajoutez-le à votre .gitignore et utilisez un backend chiffré.
Modules
Les modules sont des conteneurs réutilisables pour des groupes de ressources. Ils permettent d'organiser votre code, d'éviter la duplication et de standardiser les déploiements.
Créer un module
# Structure d'un module
modules/
ec2-instance/
main.tf
variables.tf
outputs.tf
# modules/ec2-instance/main.tf
resource "aws_instance" "this" {
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = var.instance_name
Environment = var.environment
}
}
# modules/ec2-instance/variables.tf
variable "ami_id" {
type = string
}
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "instance_name" {
type = string
}
variable "environment" {
type = string
default = "dev"
}
# modules/ec2-instance/outputs.tf
output "instance_id" {
value = aws_instance.this.id
}
output "public_ip" {
value = aws_instance.this.public_ip
}
Utiliser un module
# main.tf - Appel du module
module "web_server" {
source = "./modules/ec2-instance"
ami_id = "ami-0a21d1c76ac56fee7"
instance_type = "t3.small"
instance_name = "web-production"
environment = "prod"
}
module "api_server" {
source = "./modules/ec2-instance"
ami_id = "ami-0a21d1c76ac56fee7"
instance_type = "t3.medium"
instance_name = "api-production"
environment = "prod"
}
# Accéder aux outputs du module
output "web_ip" {
value = module.web_server.public_ip
}
Modules du Registry
Le Terraform Registry propose des modules communautaires et officiels prêts à l'emploi :
# Utiliser un module du registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "mon-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-3a", "eu-west-3b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
}
Provisioners
Les provisioners exécutent des scripts ou commandes après la création d'une ressource. Ils sont à utiliser en dernier recours, préférez les outils dédiés (Ansible, cloud-init) quand c'est possible.
local-exec
Exécute une commande sur la machine qui lance Terraform :
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
provisioner "local-exec" {
command = "echo ${self.public_ip} >> inventory.txt"
}
}
remote-exec
Exécute des commandes directement sur la ressource créée :
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
key_name = "ma-cle-ssh"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
provisioner "remote-exec" {
inline = [
"sudo apt update",
"sudo apt install -y nginx",
"sudo systemctl enable nginx"
]
}
}
Les provisioners sont un dernier recours. HashiCorp recommandé d'utiliser
cloud-init (via user_data) pour le bootstrapping des instances et Ansible ou Chef pour la configuration. Les provisioners ne sont pas suivis dans le state et rendent le code moins prévisible.
Workspaces
Les workspaces permettent de gérer plusieurs environnements (dev, staging, prod) avec la même configuration en isolant le state de chacun.
# Lister les workspaces
terraform workspace list
# Créer un workspace
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Changer de workspace
terraform workspace select prod
# Afficher le workspace actif
terraform workspace show
Utiliser le workspace dans la configuration
# Adapter la configuration selon l'environnement
locals {
instance_types = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.medium"
}
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = local.instance_types[terraform.workspace]
tags = {
Name = "web-${terraform.workspace}"
Environment = terraform.workspace
}
}
Gestion des secrets
Variables sensibles
# Marquer une variable comme sensible
variable "db_password" {
description = "Mot de passe de la base de données"
type = string
sensitive = true
}
resource "aws_db_instance" "main" {
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
username = "admin"
password = var.db_password
}
Variables d'environnement
# Passer des secrets via l'environnement
export TF_VAR_db_password="mon_mot_de_passe_sécurisé"
terraform apply
Intégration avec HashiCorp Vault
# Récupérer un secret depuis Vault
provider "vault" {
address = "https://vault.example.com:8200"
}
data "vault_generic_secret" "db_creds" {
path = "secret/data/database"
}
resource "aws_db_instance" "main" {
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
username = data.vault_generic_secret.db_creds.data["username"]
password = data.vault_generic_secret.db_creds.data["password"]
}
Ajoutez systématiquement
*.tfvars et terraform.tfstate* à votre .gitignore. En CI/CD, utilisez les secret managers natifs de votre plateforme (GitHub Secrets, GitLab CI Variables, AWS Secrets Manager).
Bonnes pratiques
Structure de projet recommandée
projet-terraform/
environments/
dev/
main.tf
backend.tf
terraform.tfvars
staging/
main.tf
backend.tf
terraform.tfvars
prod/
main.tf
backend.tf
terraform.tfvars
modules/
vpc/
ec2/
rds/
.gitignore
README.md
Convention de nommage
- Fichiers : Utilisez des noms descriptifs (
main.tf,variables.tf,outputs.tf,backend.tf) - Ressources : Nommage en snake_case, préfixez par le type logique (
web_server,api_gateway) - Variables : Noms descriptifs avec
descriptionobligatoire ettypeexplicite - Tags : Standardisez les tags (Name, Environment, Team, Project, Cost-Center)
Versioning des modules
# Toujours épingler la version des providers et modules
terraform {
required_version = ">= 1.5.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0" # Version exacte en production
}
.gitignore pour Terraform
# .gitignore
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
!example.tfvars
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
Troubleshooting
Validation et formatage
# Valider la syntaxe de la configuration
terraform validate
# Formater les fichiers selon le style standard
terraform fmt
# Formater récursivement
terraform fmt -recursive
# Vérifier le formatage (CI/CD)
terraform fmt -check
Mode debug
# Activer les logs de debug
export TF_LOG=DEBUG
terraform plan
# Niveaux disponibles : TRACE, DEBUG, INFO, WARN, ERROR
# Rediriger les logs dans un fichier
export TF_LOG_PATH="terraform-debug.log"
terraform apply
State surgery (opérations avancées)
# Lister les ressources dans le state
terraform state list
# Afficher les détails d'une ressource
terraform state show aws_instance.web
# Déplacer une ressource (renommage)
terraform state mv aws_instance.web aws_instance.web_server
# Retirer une ressource du state (sans la détruire)
terraform state rm aws_instance.web
# Importer une ressource existante dans le state
terraform import aws_instance.web i-0abc123def456
# Rafraîchir le state depuis l'infrastructure réelle
terraform refresh
Les commandes
terraform state modifient directement le state. Créez toujours une sauvegarde avant toute opération : cp terraform.tfstate terraform.tfstate.backup. En backend distant, le versioning S3 fait office de filet de sécurité.
Conclusion
Terraform est un outil puissant qui transforme la gestion d'infrastructure en la rendant déclarative, reproductible et versionnable. En résumé, voici les points clés à retenir :
- Déclarez votre infrastructure dans des fichiers HCL versionnés avec Git
- Utilisez des modules pour créer des composants réutilisables et standardisés
- Sécurisez le state avec un backend distant chiffré et le state locking
- Isolez les environnements avec des workspaces ou des répertoires séparés
- Protégez les secrets avec des variables sensibles et un gestionnaire de secrets
- Validez systématiquement avec
terraform planavant chaqueapply
Explorez Terraform Cloud pour la collaboration en équipe, les modules avancés avec
for_each et dynamic, et l'intégration dans vos pipelines CI/CD (GitHub Actions, GitLab CI). La documentation officielle HashiCorp est excellente et constamment mise à jour.
Commentaires