
Créer une EC2 sans parler du réseau fonctionne pour un premier test, mais ce n'est pas encore une infrastructure exploitable. Très vite, vous devez répondre à trois questions concrètes : dans quel subnet l'instance doit vivre, quel security group filtre le trafic, et quels ports sont réellement ouverts. C'est cette transition entre « ça démarre » et « c'est joignable proprement » que ce guide traite.
Vous allez relire des ressources AWS déjà présentes, puis ajouter votre première couche réseau explicite. L'idée importante est la suivante : vous ne recréez pas tout. Vous branchez votre instance sur un réseau existant et vous lui attachez un pare-feu virtuel qui définit ses règles d'entrée.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Lire les ressources existantes avec les data sources (
aws_vpc,aws_subnet) - Créer un security group pour contrôler les règles réseau
- Autoriser SSH et HTTP via
aws_vpc_security_group_ingress_rule - Attacher une instance à un subnet spécifique avec des règles réseau précises
- Vérifier les sorties : subnet, security group, IP publique/privée
Pourquoi cette étape compte
Section intitulée « Pourquoi cette étape compte »Dans AWS, le réseau n'est pas un détail caché derrière l'instance. Une EC2 hérite d'un contexte réseau précis : VPC, subnet, security group. Si vous ne maîtrisez pas cet enchaînement, vous obtenez facilement une machine qui existe mais que vous ne pouvez ni joindre, ni sécuriser correctement.
Ce guide vous apprend donc une logique qui reviendra partout ensuite : lire ce qui existe, ajouter seulement la couche nécessaire, puis vérifier les identifiants réseau obtenus.
Prérequis
Section intitulée « Prérequis »- Terraform ≥ 1.11 installé
- AWS CLI configuré avec credentials valides
- Avoir lu Déployer une première EC2
Objectif
Section intitulée « Objectif »Vous allez construire une infrastructure en quatre temps, dont les deux premiers ne créent rien :
- Récupérer le VPC par défaut d'AWS
- Sélectionner un subnet existant
- Créer un security group avec ses règles d'ingress SSH et HTTP
- Lancer une EC2 dans ce subnet, protégée par ce groupe
Durée estimée : 15 minutes Coût : peut être couvert par l'offre gratuite selon la date de création du compte et le plan choisi à l'inscription, quelques centimes sinon
Préparation
Section intitulée « Préparation »Terraform lit tous les fichiers .tf du répertoire courant et les traite comme une seule configuration : l'ordre des fichiers et des blocs n'a aucune importance. Travaillez donc dans un répertoire vide et dédié, sinon une ancienne configuration oubliée sera fusionnée avec la nouvelle.
mkdir -p ~/terraform-aws-sg-subnetcd ~/terraform-aws-sg-subnetÉtape 1, Déclarer le provider AWS
Section intitulée « Étape 1, Déclarer le provider AWS »Ce fichier verrouille les versions avant tout le reste. required_version refuse d'exécuter le code avec un binaire Terraform trop ancien, et la contrainte ~> 5.0 autorise les mises à jour mineures du provider en retenant le passage à la majeure. La région n'est pas codée en dur : elle vient d'une variable déclarée à l'étape suivante.
Créez versions.tf pour configurer le provider et les versions :
terraform { required_version = ">= 1.11.0" required_providers { aws = { source = "hashicorp/aws" # Série 5.x assumée. La 6.x est la série courante, et elle ne casse # pas ce type de configuration : mesuré le 2026-09-22 sur le guide # « Rôle IAM et instance profile », dont le `terraform validate` # passe en 6.66.0. Ce guide-ci n'a pas été rejoué, et la contrainte # ne bougera qu'après un cycle complet sur un compte réel. version = "~> 5.0" } }}
provider "aws" { region = var.aws_region}Cela dit à Terraform :
- Utiliser Terraform ≥ 1.11.0
- Charger le provider AWS version ~5.0 (5.x stable)
- Se connecter à AWS dans la région définie par
var.aws_region
Étape 2, Déclarer les variables
Section intitulée « Étape 2, Déclarer les variables »Chaque variable porte une valeur par défaut, donc la configuration s'applique telle quelle sans fichier de valeurs. instance_type est celle qui touche à la facturation : t3.micro reste éligible à l'offre gratuite quelle que soit la date de création du compte, contrairement à t2.micro.
Créez variables.tf :
variable "aws_region" { description = "AWS region" type = string default = "us-east-1"}
variable "instance_name" { description = "Name tag for the EC2 instance" type = string default = "lab02-instance-with-sg"}
variable "instance_type" { description = "EC2 instance type" type = string default = "t3.micro"}Ces variables vous permettront de changer de région ou de type d'instance sans modifier le code principal.
Étape 3, Lire VPC, subnet et AMI existants
Section intitulée « Étape 3, Lire VPC, subnet et AMI existants »Ces trois blocs ne créent rien. Une data source interroge AWS et rend des valeurs que le reste de la configuration consomme, ce qui évite de coder un identifiant en dur : celui d'une AMI change à chaque région et à chaque publication d'image. most_recent retient la plus récente parmi celles que le filtre accepte, et owners restreint la recherche au compte officiel de Canonical.
Créez main.tf avec les data sources :
# Récupérer le VPC par défautdata "aws_vpc" "default" { default = true}
# Récupérer le premier subnet disponible du VPC par défautdata "aws_subnets" "default" { filter { name = "vpc-id" values = [data.aws_vpc.default.id] }}
# Lire l'AMI Ubuntu la plus récentedata "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical (Ubuntu)
filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] }
filter { name = "virtualization-type" values = ["hvm"] }}Ce bloc utilise les data sources Terraform pour interroger l'infrastructure existante d'AWS sans la recréer :
data "aws_vpc" "default": récupère le VPC par défautdata "aws_subnets" "default": récupère tous les subnets du VPCdata "aws_ami" "ubuntu": récupère la dernière Ubuntu 22.04
Étape 4, Créer le security group
Section intitulée « Étape 4, Créer le security group »Le groupe se crée vide de règles, et c'est volontaire : les règles d'entrée sont des ressources distinctes, ajoutées à l'étape suivante, ce qui permet d'en modifier une sans toucher au groupe. L'argument vpc_id le rattache au VPC lu plus haut, car un security group n'existe jamais hors d'un VPC.
Toujours dans main.tf, ajoutez :
resource "aws_security_group" "lab02_sg" { name = "lab02-sg" description = "Security group for Lab 02 - SSH and HTTP access" vpc_id = data.aws_vpc.default.id
tags = { Name = "lab02-sg" }}Un security group est un firewall virtuel pour vos instances. Vous allez lui ajouter des règles ensuite.
Étape 5, Autoriser SSH et HTTP
Section intitulée « Étape 5, Autoriser SSH et HTTP »Chaque règle est une ressource à part entière, reliée au groupe par security_group_id. La valeur 0.0.0.0/0 ouvre le port à toute adresse d'Internet : acceptable sur un lab jetable, à resserrer partout ailleurs, comme le rappellent les bonnes pratiques plus bas.
Toujours dans main.tf, ajoutez les règles d'ingress :
# Autoriser SSH (port 22)resource "aws_vpc_security_group_ingress_rule" "ssh" { security_group_id = aws_security_group.lab02_sg.id description = "Allow SSH from anywhere" from_port = 22 to_port = 22 ip_protocol = "tcp" cidr_ipv4 = "0.0.0.0/0"
tags = { Name = "allow-ssh" }}
# Autoriser HTTP (port 80)resource "aws_vpc_security_group_ingress_rule" "http" { security_group_id = aws_security_group.lab02_sg.id description = "Allow HTTP from anywhere" from_port = 80 to_port = 80 ip_protocol = "tcp" cidr_ipv4 = "0.0.0.0/0"
tags = { Name = "allow-http" }}Chaque règle dit la même chose : « autoriser le trafic TCP entrant sur ce port, depuis n'importe quelle adresse ».
Étape 6, Créer l'instance dans le subnet avec le security group
Section intitulée « Étape 6, Créer l'instance dans le subnet avec le security group »Toujours dans main.tf, complétez :
resource "aws_instance" "lab02_vm" { ami = data.aws_ami.ubuntu.id instance_type = var.instance_type subnet_id = data.aws_subnets.default.ids[0] vpc_security_group_ids = [aws_security_group.lab02_sg.id]
tags = { Name = var.instance_name }}Trois arguments portent tout le câblage de ce bloc :
subnet_idplace l'instance dans le subnet sélectionnévpc_security_group_idsassocie le security group à l'instance- la valeur
[aws_security_group.lab02_sg.id]est une liste : contrairement àsecurity_groups, réservé au VPC par défaut,vpc_security_group_idsest explicite et recommandé
Étape 7, Déclarer les outputs
Section intitulée « Étape 7, Déclarer les outputs »Ces sorties servent de vérification : elles affichent, sans ouvrir la console, le subnet réellement retenu, l'identifiant du security group et les deux adresses de l'instance. Comparer instance_private_ip et instance_public_ip montre d'un coup d'œil si la machine a bien reçu une adresse publique.
Créez outputs.tf :
output "vpc_id" { description = "VPC ID" value = data.aws_vpc.default.id}
output "subnet_id" { description = "Subnet ID where instance is running" value = aws_instance.lab02_vm.subnet_id}
output "security_group_id" { description = "Security Group ID" value = aws_security_group.lab02_sg.id}
output "instance_id" { description = "EC2 Instance ID" value = aws_instance.lab02_vm.id}
output "instance_public_ip" { description = "Public IP address of the instance" value = aws_instance.lab02_vm.public_ip}
output "instance_private_ip" { description = "Private IP address of the instance" value = aws_instance.lab02_vm.private_ip}
output "ubuntu_ami_id" { description = "Ubuntu AMI ID" value = data.aws_ami.ubuntu.id}Étape 8, Créer le fichier tfvars
Section intitulée « Étape 8, Créer le fichier tfvars »Terraform charge automatiquement terraform.tfvars s'il existe, sans option de ligne de commande. Ce fichier rassemble en un seul endroit ce que vous changerez d'un lab à l'autre, la région en tête.
Créez terraform.tfvars :
aws_region = "us-east-1"instance_name = "lab02-instance-with-sg"instance_type = "t3.micro"Vérification
Section intitulée « Vérification »Les quatre commandes ci-dessous s'enchaînent toujours dans cet ordre. terraform validate ne contacte pas AWS, il ne vérifie que la syntaxe et la cohérence des références. C'est terraform plan qui interroge réellement l'API pour résoudre les data sources, d'où les valeurs de subnet_id et ubuntu_ami_id déjà connues avant l'apply.
-
Initialiser Terraform :
Fenêtre de terminal terraform initSortie attendue :
Initializing the backend...Initializing provider plugins...- Finding hashicorp/aws versions matching "~> 5.0"...- Installing hashicorp/aws v5.100.0...Terraform has been successfully initialized! -
Valider la configuration :
Fenêtre de terminal terraform validateSortie attendue :
Success! The configuration is valid. -
Afficher le plan :
Fenêtre de terminal terraform planSortie attendue (résumé) :
Plan: 4 to add, 0 to change, 0 to destroy.Changes to Outputs:+ instance_id = (known after apply)+ instance_private_ip = (known after apply)+ instance_public_ip = (known after apply)+ security_group_id = (known after apply)+ subnet_id = "subnet-xxxxxxxxx"+ ubuntu_ami_id = "ami-xxxxxxxx"+ vpc_id = "vpc-xxxxxxxx" -
Appliquer la configuration :
Fenêtre de terminal terraform apply -auto-approveSortie attendue (fin) :
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.Outputs:instance_id = "i-0e43f24bc8817d50d"instance_private_ip = "172.31.69.130"instance_public_ip = "203.0.113.10"security_group_id = "sg-07b0de0b4eca49e47"subnet_id = "subnet-ef519de1"ubuntu_ami_id = "ami-00de3875b03809ec5"vpc_id = "vpc-801432fa" -
Vérifier l'accès SSH (optionnel, la clé par défaut n'est pas disponible) :
Fenêtre de terminal aws ec2 describe-instances --instance-ids i-0e43f24bc8817d50d
Anatomie
Section intitulée « Anatomie »Maintenant que l'infrastructure est en place, revenons sur les deux décisions structurantes : l'emploi des data sources plutôt que de nouvelles ressources, et la séparation des règles d'ingress hors du bloc aws_security_group. Deux choix qui reviendront dans toutes vos configurations AWS.
Data sources vs Resources
Section intitulée « Data sources vs Resources »La distinction se voit dans le plan : les data sources n'apparaissent jamais dans le décompte « to add », seules les resource y figurent. Ici, quatre ressources sont créées alors que sept objets sont manipulés.
| Type | Utilité | Exemple |
|---|---|---|
| Data source | Lire de l'infrastructure existante | data "aws_vpc", data "aws_ami" |
| Resource | Créer nouvelle infrastructure | resource "aws_security_group", resource "aws_instance" |
Les data sources ne créent rien, elles lisent uniquement. Cela vous permet de réutiliser ce qui existe déjà (VPC par défaut, AMI) sans duplication.
Security Group vs VPC Security Group Ingress Rule
Section intitulée « Security Group vs VPC Security Group Ingress Rule »Deux approches pour les règles :
# Approche 1 : Règles DANS la ressource (inline), dépréciéresource "aws_security_group" "example" { ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }}
# Approche 2 : Ressources SÉPARÉES (recommended) ✅resource "aws_security_group" "example" { name = "example"}
resource "aws_vpc_security_group_ingress_rule" "ssh" { security_group_id = aws_security_group.example.id from_port = 22 to_port = 22 ip_protocol = "tcp" cidr_ipv4 = "0.0.0.0/0"}Pourquoi séparer ?
- Plus granulaire : vous pouvez ajouter/modifier/supprimer une seule règle sans refactoriser tout le security group
- Plus lisible : chaque règle est explicite
- Meilleure gestion en équipe : moins de conflits lors de merges
Bonnes pratiques
Section intitulée « Bonnes pratiques »Un security group est l'un des rares objets AWS dont une erreur de configuration est directement exploitable depuis Internet. Les quatre règles ci-dessous portent sur deux axes : rendre les groupes identifiables pour pouvoir les auditer, et réduire la surface exposée.
1. Nommer clairement les security groups
Section intitulée « 1. Nommer clairement les security groups »Le champ name est celui qu'affiche la console AWS, et il est unique par VPC : un groupe nommé sg devient impossible à distinguer dès le troisième projet.
# ❌ Mauvaisresource "aws_security_group" "main" { name = "sg"}
# ✅ Bonresource "aws_security_group" "web_access" { name = "web-access-sg"}2. Restreindre 0.0.0.0/0 en production
Section intitulée « 2. Restreindre 0.0.0.0/0 en production »Un port 22 ouvert sur 0.0.0.0/0 est scanné et attaqué en quelques minutes. Remplacez-le par le préfixe de votre VPN ou de votre sortie Internet ; pour un accès nomade, préférez une solution sans port exposé comme AWS Systems Manager Session Manager.
# ❌ Risquéecidr_ipv4 = "0.0.0.0/0"
# ✅ Produitcidr_ipv4 = "203.0.113.0/24" # Votre bureau/VPN3. Utiliser des tags pour tracer qui a créé quoi
Section intitulée « 3. Utiliser des tags pour tracer qui a créé quoi »Sans tag ManagedBy, rien ne distingue dans la console une ressource pilotée par Terraform d'une ressource créée à la main : la modifier depuis la console provoque une dérive que le prochain apply écrasera sans prévenir.
tags = { Name = "lab02-sg" Environment = "lab" ManagedBy = "terraform"}4. Minimiser les ports ouverts
Section intitulée « 4. Minimiser les ports ouverts »La valeur ip_protocol = "-1" signifie tous les protocoles, et elle rend from_port et to_port sans effet : la règle ouvre alors la totalité du trafic entrant, pas seulement les ports listés.
# ❌ Trop permissiffrom_port = 0to_port = 65535ip_protocol = "-1" # Tous les protocoles
# ✅ Juste le nécessairefrom_port = 22to_port = 22ip_protocol = "tcp"Dépannage
Section intitulée « Dépannage »Les erreurs de ce lab se répartissent en deux familles : celles remontées par Terraform pendant l'apply, qui portent un message AWS explicite, et celles qui n'apparaissent qu'après coup, quand l'instance est créée mais injoignable. Pour la seconde, vérifiez toujours l'association du security group avant de soupçonner les règles.
| Symptôme | Cause probable | Solution |
|---|---|---|
Error: Invalid index sur ids[0], « the collection has no elements » | aws_subnets n'a trouvé aucun subnet dans le VPC par défaut : la data source rend une liste vide, sans erreur | Créer un VPC et un subnet, ou choisir la zone avec data "aws_availability_zones" |
Error: creating Security Group: ... InvalidParameterValue | Caractères spéciaux dans la description | Utiliser uniquement ASCII (pas d'accents, tirets longs) |
| Instance créée mais pas accessible SSH | La clé .pem n'est pas attachée | Ajouter key_name = aws_key_pair.xxx.key_name à la ressource |
| Port 22 ou 80 fermé | Security group non attaché, ou règle manquante | Vérifier vpc_security_group_ids et les ingress rules |
Nettoyage
Section intitulée « Nettoyage »Après le lab, détruisez l'infrastructure pour ne pas être facturé :
terraform destroy -auto-approveSortie attendue :
Destroy complete! Resources: 4 destroyed.Nettoyez les fichiers Terraform générés :
rm -rf .terraform* terraform.tfstate*Mettre en pratique
Section intitulée « Mettre en pratique »Vous construisez un security group dont toutes les règles sont des ressources dédiées, forme que la documentation recommande désormais à la place des blocs imbriqués. Les règles d'entrée se factorisent par for_each, et l'instance se pose dans un subnet désigné par son tag plutôt que par un identifiant écrit en dur.
À retenir
Section intitulée « À retenir »- Les data sources permettent de lire l'infrastructure existante (VPC, subnet, AMI) sans la recréer
- aws_vpc_security_group_ingress_rule est l'approche recommandée pour les règles (séparation des préoccupations)
- Les security groups sont des pare-feu virtuels, ils contrôlent qui peut accéder à vos instances
- vpc_security_group_ids (liste) est plus robuste que
security_groups(approche legacy) - Les outputs vous permettent de voir les IDs importants après l'apply
- Testez le plan avant l'apply pour voir exactement ce que Terraform créera
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Backend S3 et terraform_remote_state : Déplace le state local vers un bucket partagé, étape obligatoire dès que l'on travaille à plusieurs.
- Launch template et autoscaling group : Passe d'une instance unique à un groupe d'instances géré par AWS.
- Import, moved et drift : Reprend dans le code une ressource créée à la main, puis mesure les écarts.