Devops
Difficulte: Intermediate
12 min de lecture

Terraform : Infrastructure as Code pour débutants

Apprenez à automatiser le déploiement de votre infrastructure cloud avec Terraform, l'outil IaC de référence créé par HashiCorp.

Retour aux tutoriels
Qu'est-ce que Terraform ?
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
Astuce tfenv
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
Important
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
}
Variables d'environnement
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
Sécurité du state
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"
    ]
  }
}
Attention
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"]
}
Bonnes pratiques secrets
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 description obligatoire et type explicite
  • 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
Précaution
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 plan avant chaque apply
Pour aller plus loin
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.
Morgann Riu

Écrit par

Morgann Riu

Expert en cybersécurité et administration Linux. Je partage mes connaissances à travers des tutoriels gratuits et des formations pour aider les administrateurs systèmes et développeurs à sécuriser leurs infrastructures.

Partager ce tutoriel

Cet article vous a plu ?

Commentaires

Checklist Sécurité Linux

30 points essentiels pour sécuriser un serveur Linux. Recevez aussi les nouveaux tutoriels par email.

Pas de spam. Désabonnement en 1 clic.