Ressources Terraform
Objectifs du chapitre
- Comprendre le cycle de vie des ressources
- Créer différents types de ressources
- Utiliser les data sources
- Gérer les dépendances
1 - Anatomie d'une ressource
Structure de base
resource "type" "name" {
argument1 = "value1"
argument2 = "value2"
nested_block {
nested_argument = "value"
}
}
Exemple concret
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "WebServer"
}
}
# Accès aux attributs
output "instance_id" {
value = aws_instance.web.id
}
output "public_ip" {
value = aws_instance.web.public_ip
}
2 - Cycle de vie des ressources
Commandes du cycle de vie
États d'une ressource
| État | Description |
|---|---|
| Create | Nouvelle ressource à créer |
| Update | Ressource existante modifiée |
| Destroy | Ressource à supprimer |
| Replace | Détruire puis recréer |
| No-op | Aucun changement |
3 - Meta-arguments
count
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "web-${count.index}"
}
}
# Accès: aws_instance.web[0], aws_instance.web[1], etc.
output "instance_ids" {
value = aws_instance.web[*].id
}
for_each
resource "aws_instance" "servers" {
for_each = {
web = { type = "t2.micro", ami = "ami-web" }
api = { type = "t2.small", ami = "ami-api" }
db = { type = "t2.medium", ami = "ami-db" }
}
ami = each.value.ami
instance_type = each.value.type
tags = {
Name = each.key
}
}
# Accès: aws_instance.servers["web"], aws_instance.servers["api"]
depends_on
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# Dépendance explicite
depends_on = [
aws_iam_role_policy.example
]
}
lifecycle
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
lifecycle {
# Créer le nouveau avant de détruire l'ancien
create_before_destroy = true
# Empêcher la destruction
prevent_destroy = true
# Ignorer certains changements
ignore_changes = [
tags,
user_data,
]
# Conditions de remplacement
replace_triggered_by = [
null_resource.trigger.id
]
}
}
provider
resource "aws_instance" "us_server" {
provider = aws.us_east
ami = "ami-87654321"
instance_type = "t2.micro"
}
4 - Ressources AWS courantes
EC2 Instance
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
key_name = aws_key_pair.deployer.key_name
vpc_security_group_ids = [aws_security_group.web.id]
subnet_id = aws_subnet.public.id
root_block_device {
volume_size = 20
volume_type = "gp3"
encrypted = true
}
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
EOF
tags = {
Name = "WebServer"
}
}
S3 Bucket
resource "aws_s3_bucket" "data" {
bucket = "my-unique-bucket-name"
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = 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" "main" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
}
Security Group
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
5 - Data Sources
Concept
Les data sources permettent de lire des données existantes.
Exemples
# Trouver la dernière AMI Ubuntu
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# Utilisation
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
# ...
}
# Lire les zones de disponibilité
data "aws_availability_zones" "available" {
state = "available"
}
# Utilisation
resource "aws_subnet" "private" {
count = length(data.aws_availability_zones.available.names)
availability_zone = data.aws_availability_zones.available.names[count.index]
# ...
}
# Lire le compte AWS actuel
data "aws_caller_identity" "current" {}
output "account_id" {
value = data.aws_caller_identity.current.account_id
}
6 - Dépendances
Dépendances implicites
# Terraform détecte automatiquement la dépendance
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # Dépendance implicite
cidr_block = "10.0.1.0/24"
}
Dépendances explicites
resource "aws_iam_role" "example" {
name = "example-role"
# ...
}
resource "aws_iam_role_policy" "example" {
role = aws_iam_role.example.name
# ...
}
resource "aws_instance" "web" {
# ...
# Dépendance explicite nécessaire
depends_on = [
aws_iam_role_policy.example
]
}
7 - Provisioners
Avertissement
attention
Les provisioners sont considérés comme un dernier recours. Préférez les alternatives cloud-native (user_data, cloud-init).
Types de provisioners
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
# Exécute sur la machine locale
provisioner "local-exec" {
command = "echo ${self.public_ip} >> inventory.txt"
}
# Exécute sur la ressource créée
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
# Copie des fichiers
provisioner "file" {
source = "config/"
destination = "/tmp/config"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
}
8 - Import de ressources existantes
Workflow
Commande import
# Syntaxe
terraform import <resource_type>.<name> <id>
# Exemples
terraform import aws_instance.web i-1234567890abcdef0
terraform import aws_s3_bucket.data my-bucket-name
terraform import aws_vpc.main vpc-12345678
Générer la configuration
# Terraform 1.5+
terraform plan -generate-config-out=generated.tf
9 - Moved blocks
Renommer des ressources
# Avant: resource "aws_instance" "web" {}
# Après: resource "aws_instance" "web_server" {}
moved {
from = aws_instance.web
to = aws_instance.web_server
}
Déplacer dans un module
moved {
from = aws_instance.web
to = module.compute.aws_instance.web
}
10 - Bonnes pratiques
Nommage
# ✅ Noms descriptifs
resource "aws_instance" "web_server_production" {}
resource "aws_security_group" "web_inbound" {}
# ❌ Noms génériques
resource "aws_instance" "instance1" {}
resource "aws_security_group" "sg" {}
Organisation
# ✅ Grouper les ressources liées
# networking.tf - VPC, subnets, routes
# compute.tf - EC2, ASG, ALB
# database.tf - RDS, ElastiCache
# security.tf - Security groups, IAM
Résumé
Points clés
- Les ressources sont l'élément central de Terraform
- count et for_each pour créer plusieurs instances
- depends_on pour les dépendances explicites
- lifecycle pour contrôler le comportement
- Data sources pour lire sans créer
Exercices pratiques
- Créez une instance EC2 avec security group
- Utilisez for_each pour créer plusieurs subnets
- Importez une ressource existante
- Utilisez un data source pour trouver une AMI