.tf, et Terraform se charge de la créer, la modifier ou la détruire automatiquement.terraform.tfstate est la source de vérité. Il mappe vos ressources déclarées aux ressources réelles dans le cloud. Sans lui, Terraform ne peut pas déterminer les changements nécessaires.terraform plan. Il compare l'état actuel (state) avec l'état désiré (code) et calcule les opérations nécessaires : create, update ou destroy.terraform apply. Il détermine l'ordre d'exécution optimal, parallélise les opérations indépendantes et sérialise les dépendantes.terraform init télécharge les providers et initialise le backend. terraform plan lit le state, interroge les APIs pour détecter les drifts, puis génère un diff. terraform apply exécute les changements en respectant le graphe de dépendances. terraform destroy inverse le graphe et supprime les ressources en ordre inverse.terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}required_providers verrouille la version du provider pour assurer la reproductibilité. Le bloc provider configure les paramètres de connexion (région, profil, credentials).resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.main.id
tags = {
Name = "WebServer"
Environment = "production"
}
}data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/*"]
}
}string, number, bool, list, map et object. Définies avec des valeurs par défaut et des validations.variable "instance_type" {
type = string
default = "t3.micro"
description = "Type d'instance EC2"
validation {
condition = contains(["t3.micro","t3.small"], var.instance_type)
error_message = "Type non autorisé."
}
}output "instance_public_ip" {
value = aws_instance.web.public_ip
description = "IP publique du serveur"
}locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
}
}module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
environment = var.environment
}terraform.tfstate, idéal pour séparer dev/staging/prod.terraform workspace new staging
terraform workspace select prod
terraform workspace listlifecycle contrôle le comportement de création et destruction. depends_on force des dépendances explicites quand Terraform ne peut pas les détecter automatiquement.resource "aws_instance" "web" {
lifecycle {
create_before_destroy = true
prevent_destroy = true
ignore_changes = [tags]
}
}
AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY en variables d'environnement. À éviter en production — risque d'exposition.aws configure. Le provider référence le profil avec profile = "mon-profil". Convenable pour le développement local.assume_role pour les déploiements cross-account et activez toujours le MFA pour les opérations sensibles.provider "aws" {
region = "eu-west-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformDeployRole"
session_name = "terraform-deploy"
external_id = "unique-external-id"
}
}resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "main-vpc" }
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "eu-west-1a"
map_public_ip_on_launch = true
tags = { Name = "public-subnet" }
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = { Name = "main-igw" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = { Name = "public-rt" }
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}resource "aws_security_group" "web_sg" {
name = "web-server-sg"
description = "Autorise HTTP et SSH"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP depuis Internet"
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"]
description = "SSH restreint"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_key_pair" "deployer" {
key_name = "deployer-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web_sg.id]
key_name = aws_key_pair.deployer.key_name
tags = { Name = "WebServer", Environment = "production" }
}
resource "aws_eip" "web_eip" {
instance = aws_instance.web.id
domain = "vpc"
}0.0.0.0/0 en production. Restreignez toujours l'accès à votre IP spécifique ou utilisez AWS Systems Manager Session Manager pour un accès sans SSH.resource "aws_s3_bucket" "data" {
bucket = "my-app-data-${var.env}"
tags = local.common_tags
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_iam_role" "s3_access" {
name = "s3-access-role"
assume_role_policy = data.aws_iam_policy_document.assume.json
}
resource "aws_iam_role_policy" "s3_policy" {
role = aws_iam_role.s3_access.id
policy = data.aws_iam_policy_document.s3.json
}resource "aws_lb" "app" {
name = "app-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
}
resource "aws_autoscaling_group" "app" {
name = "app-asg"
desired_capacity = 2
max_size = 10
min_size = 1
vpc_zone_identifier = aws_subnet.private[*].id
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
target_group_arns = [aws_lb_target_group.app.arn]
}terraform.tfstate est probablement le composant le plus critique — et le plus dangereux — de tout déploiement Terraform. C'est la source de vérité absolue : il contient le mapping complet entre vos ressources déclarées en HCL et les ressources réelles existantes dans AWS. Sans ce fichier, Terraform est aveugle.terraform apply simultanément. DynamoDB fournit un verrou distribué (lock) qui protège contre les corruptions concurrentes.terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/infrastructure/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
# Optionnel : KMS key pour chiffrement avancé
# kms_key_id = "arn:aws:kms:eu-west-1:..."
}
}
# Ressources pour créer le backend (à faire une seule fois)
resource "aws_s3_bucket" "tf_state" {
bucket = "my-terraform-state-bucket"
}
resource "aws_s3_bucket_versioning" "tf_state" {
bucket = aws_s3_bucket.tf_state.id
versioning_configuration { status = "Enabled" }
}
resource "aws_dynamodb_table" "tf_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
terraform-aws-modules/vpc/aws.version = "~> 3.0".modules/
├── vpc/
│ ├── main.tf # Ressources principales
│ ├── variables.tf # Variables d'entrée
│ ├── outputs.tf # Valeurs exportées
│ ├── versions.tf # Contraintes provider
│ └── README.md # Documentation
├── ec2/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── rds/
├── main.tf
├── variables.tf
└── outputs.tfenvironments/
├── dev/
│ ├── main.tf # module "vpc" { source = "../../modules/vpc" }
│ ├── terraform.tfvars # instance_type = "t3.micro"
│ └── backend.tf # key = "dev/terraform.tfstate"
├── staging/
│ ├── main.tf
│ ├── terraform.tfvars # instance_type = "t3.small"
│ └── backend.tf # key = "staging/terraform.tfstate"
└── prod/
├── main.tf
├── terraform.tfvars # instance_type = "t3.large"
└── backend.tf # key = "prod/terraform.tfstate"terraform plan s'exécute dans la CI et le résultat est posté en commentaire de la PRterraform apply -auto-approve s'exécute après merge sur la branche principalename: Terraform Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan
if: github.event_name == 'pull_request'
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
if: github.ref == 'refs/heads/main'terraform plan régulièrement via un cron CI pour identifier les dérives et les corriger avant qu'elles ne causent des incidents.data sources dans Terraform.assume_role et des providers aliasés.provider "aws" {
alias = "production"
region = "eu-west-1"
assume_role {
role_arn = "arn:aws:iam::PROD_ACCT:role/Deploy"
}
}
provider "aws" {
alias = "staging"
region = "eu-west-1"
assume_role {
role_arn = "arn:aws:iam::STG_ACCT:role/Deploy"
}
}
resource "aws_instance" "prod_web" {
provider = aws.production
# ...
}provider "aws" {
alias = "primary"
region = "eu-west-1"
}
provider "aws" {
alias = "secondary"
region = "us-east-1"
}
module "primary_infra" {
source = "./modules/infra"
providers = { aws = aws.primary }
}
module "dr_infra" {
source = "./modules/infra"
providers = { aws = aws.secondary }
}resource "aws_lb_listener_rule" "green" {
listener_arn = aws_lb_listener.main.arn
action {
type = "forward"
target_group_arn = var.is_green_active ?
aws_lb_target_group.green.arn :
aws_lb_target_group.blue.arn
}
}create_before_destroy = true, les health checks ALB et les rolling updates de l'ASG. L'ancienne infrastructure n'est détruite qu'après validation de la nouvelle.project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── modules/
│ ├── networking/
│ ├── compute/
│ ├── database/
│ └── security/
├── .gitignore
├── .terraform-version
└── README.md{project}-{env}-{resource}-{description}myapp-prod-ec2-webserverProjectEnvironmentManagedBy = terraformOwnerCostCenter.tf. Conséquence : Fuite de secrets si le repo est public ou compromis. Solution : Variables d'environnement, IAM roles, AWS Vault.depends_on quand Terraform ne peut pas détecter automatiquement la dépendance. Conséquence : Erreurs de déploiement aléatoires. Solution : Utiliser les références implicites ou depends_on explicite.terraform apply peut échouer si une ressource n'est pas encore propagée. Utilisez depends_on et des time_sleep si nécessaire.destroy peuvent causer des erreurs. Un ALB avec des listeners actifs ne peut pas être supprimé tant que les target groups ne sont pas vidés.apply. Faites des apply fréquents pour détecter les erreurs tôt. Un plan de 50 ressources qui échoue est impossible à debugger rapidement.developer.hashicorp.com/terraform) est la référence ultime. Complétez avec le Terraform Registry pour les exemples de modules communautaires."Infrastructure as Code n'est pas qu'une technologie — c'est une philosophie qui transforme la façon dont nous construisons et gérons le cloud."— La communauté DevOps mondiale