Aller au contenu
Infrastructure as Code medium

Terraform AWS, Déployer une première EC2

20 min de lecture

logo terraform

Le premier risque quand on débute sur AWS avec Terraform, ce n'est pas le code HCL, mais l'environnement autour. Si la région est mauvaise, si les credentials ne sont pas chargés, ou si vous choisissez une AMI codée en dur qui n'existe plus, votre premier terraform apply échoue avant même de vous apprendre quoi que ce soit. Ce guide sert à éliminer ces incertitudes.

Vous allez donc construire la plus petite configuration utile possible : un provider AWS, une data source pour lire l'identité courante, une autre pour retrouver automatiquement une AMI Ubuntu récente, puis une EC2 minimale. L'objectif n'est pas seulement de créer une instance, mais de comprendre l'enchaînement logique : se connecter, lire l'existant, puis créer.

  • Configurer le provider AWS avec la région
  • Authentifier Terraform automatiquement depuis vos credentials AWS
  • Lire l'identité courante (AWS account ID, ARN utilisateur)
  • Récupérer dynamiquement une AMI avec data "aws_ami"
  • Créer une première EC2 sans configuration réseau explicitée
  • Extraire les IDs essentiels dans les outputs

Sur libvirt, vous aviez surtout besoin d'un hyperviseur local et d'une image. Sur AWS, il faut d'abord prouver que Terraform sait parler au bon compte, dans la bonne région, avec des droits suffisants. C'est pour cela que ce guide commence par des lectures (aws_caller_identity, aws_ami) avant d'introduire une vraie ressource.

Si cette base est claire, les guides suivants deviennent beaucoup plus simples : vous saurez distinguer une erreur de credentials, une erreur de région et une erreur de configuration Terraform.

  • Terraform ≥ 1.11 installé
  • AWS CLI configuré avec credentials (voir préparation)
  • Compréhension basique de Terraform : variables, resources, outputs, data sources

Vous allez construire une infrastructure minimale AWS :

  1. Configurer Terraform pour accéder à AWS (provider + credentials)
  2. Lire automatiquement l'AMI Ubuntu 22.04 la plus récente
  3. Créer une EC2 t2.micro (gratuit/quasi-gratuit)
  4. Afficher ses identifiants en outputs pour vérifier la création

Durée estimée : 10 minutes Coût : Gratuit (tier gratuit AWS) ou ~$0.01

Deux vérifications suffisent avant d'écrire la moindre ligne de HCL : confirmer que vos credentials AWS sont bien reconnus, et se placer dans un répertoire de travail vide. Terraform ne gère pas l'authentification lui-même, il réutilise la même chaîne de résolution que l'AWS CLI ; si aws fonctionne, Terraform fonctionnera.

Assurez-vous que AWS CLI et vos credentials sont configurés :

Fenêtre de terminal
aws sts get-caller-identity

Sortie attendue :

{
"UserId": "AIDAXXXXXXXXXXXXXXXX",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/your-user"
}

Si vous voyez une erreur, configurez vos credentials AWS.

Terraform travaille sur tous les fichiers .tf du répertoire courant : un dossier vide évite qu'une ancienne configuration soit prise en compte par erreur.

Fenêtre de terminal
mkdir -p ~/terraform-aws-provider-ec2
cd ~/terraform-aws-provider-ec2

Étape 1, Déclarer le provider AWS et les versions

Section intitulée « Étape 1, Déclarer le provider AWS et les versions »

Le bloc terraform fixe les contraintes de version, et le bloc provider indique à quel compte et à quelle région parler. Les isoler dans un fichier versions.tf séparé est une convention répandue : elle rend la mise à jour du provider visible dans les revues de code, au lieu de la noyer au milieu des ressources. Terraform lit de toute façon l'ensemble des fichiers .tf du dossier, le découpage n'est donc qu'une question de lisibilité.

Créez versions.tf :

terraform {
required_version = ">= 1.11.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}

Ce bloc dit à Terraform :

  • Utiliser Terraform ≥ 1.11.0 (version à mineure stable)
  • Charger le provider AWS ~5.0 (version 5.x)
  • Se connecter à la région définie par var.aws_region, dont la valeur par défaut est us-east-1

Une variable Terraform est une entrée nommée, typée, avec une valeur par défaut facultative. Les trois déclarées ici couvrent les seuls paramètres susceptibles de changer d'un lancement à l'autre : la région, le nom affiché dans la console AWS et le gabarit de l'instance. Comme chacune a une valeur par défaut, la configuration reste applicable sans fournir le moindre argument.

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 = "lab01-terraform-instance"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}

Ces variables vous permettront de changer de région ou de type sans modifier main.tf.

Avant de créer quoi que ce soit, on vérifie à qui Terraform est connecté. La data source aws_caller_identity répond exactement à la question posée par aws sts get-caller-identity, mais depuis Terraform, avec les credentials que le provider a réellement résolus. Elle ne prend aucun argument et se déclare donc avec des accolades vides.

Créez main.tf :

# Récupérer l'identité AWS actuelle pour afficher dans les outputs
data "aws_caller_identity" "current" {}

Cette data source lit l'identité courante : qui êtes-vous, quel account, quel ARN. Zéro effet de bord, c'est une lecture uniquement.

Une AMI (Amazon Machine Image) est le modèle de disque à partir duquel une instance démarre. Son identifiant change à chaque région et à chaque nouvelle publication de l'éditeur : coder cet identifiant en dur rend la configuration inutilisable ailleurs et périmée en quelques semaines. La data source aws_ami interroge le catalogue au moment du plan et retourne l'identifiant valide ici et maintenant.

Toujours dans main.tf, ajoutez :

# Lire l'AMI Ubuntu la plus récente
data "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"]
}
}

Cette data source cherche l'AMI la plus récente qui correspond aux critères :

  • Propriétaire : Canonical (ID 099720109477)
  • Nom : images Ubuntu 22.04 (Jammy), le * accepte les variantes (patch updates)
  • Type : HVM (hyperviseur standard)

Résultat : vous obtenez toujours l'Ubuntu 22.04 à jour sans coder l'ID d'une AMI spécifique.

C'est la première déclaration qui modifie votre compte AWS, et la seule ressource facturable de ce lab. Aucun réseau n'est précisé : AWS place alors l'instance dans le VPC par défaut de la région et lui attribue un sous-réseau automatiquement. C'est ce qui permet à cet exemple de tenir en six lignes ; la maîtrise explicite du réseau fait l'objet d'un guide dédié.

Toujours dans main.tf, complétez :

# Créer une première instance EC2
resource "aws_instance" "lab01_vm" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = var.instance_name
}
}

Cette ressource crée une EC2 :

  • ami : utilise l'ID d'AMI récupéré par la data source
  • instance_type : t2.micro (gratuit ou quasi-gratuit)
  • tags : une étiquette pour identifier l'instance dans la console AWS

Un output expose une valeur calculée après l'application. Sans lui, l'adresse IP ou l'identifiant de l'instance restent enfouis dans le fichier d'état et il faut aller les chercher à la main. Les huit sorties ci-dessous couvrent les trois objets manipulés dans ce lab : votre identité AWS, l'AMI retenue et l'instance créée.

Créez outputs.tf :

output "aws_caller_identity_account_id" {
description = "AWS Account ID"
value = data.aws_caller_identity.current.account_id
}
output "aws_caller_identity_user_arn" {
description = "ARN of the AWS user/role"
value = data.aws_caller_identity.current.arn
}
output "ubuntu_ami_id" {
description = "Ubuntu AMI ID used for the instance"
value = data.aws_ami.ubuntu.id
}
output "ubuntu_ami_name" {
description = "Ubuntu AMI name"
value = data.aws_ami.ubuntu.name
}
output "instance_id" {
description = "EC2 Instance ID"
value = aws_instance.lab01_vm.id
}
output "instance_arn" {
description = "EC2 Instance ARN"
value = aws_instance.lab01_vm.arn
}
output "instance_public_ip" {
description = "Public IP address of the instance"
value = aws_instance.lab01_vm.public_ip
}
output "instance_private_ip" {
description = "Private IP address of the instance"
value = aws_instance.lab01_vm.private_ip
}

Ces outputs afficheront les identifiants clés après apply, ce qui permet de vérifier que Terraform a bien créé ce qu'il fallait.

Le fichier terraform.tfvars est chargé automatiquement par Terraform et fournit les valeurs des variables déclarées à l'étape 2. Il reprend ici les valeurs par défaut, ce qui ne change rien au résultat mais rend le paramétrage explicite et facile à modifier. Ce fichier contient souvent des informations propres à un environnement : il ne se versionne pas.

Créez terraform.tfvars :

aws_region = "us-east-1"
instance_name = "lab01-terraform-instance"
instance_type = "t2.micro"

Le déroulé complet enchaîne quatre commandes, dans un ordre qui n'est pas négociable : init télécharge le provider, validate contrôle la syntaxe sans appeler AWS, plan interroge l'API en lecture seule et annonce ce qui va changer, apply exécute. Comparez chaque sortie avec celle donnée ici ; un écart au stade du plan est toujours plus facile à corriger qu'après création.

  1. Initialiser Terraform :

    Fenêtre de terminal
    terraform init

    Sortie attendue :

    Initializing the backend...
    Initializing provider plugins...
    - Finding hashicorp/aws versions matching "~> 5.0"...
    - Installing hashicorp/aws v5.100.0...
    Terraform has successfully created the lock file .terraform.lock.hcl
    Terraform has been successfully initialized!
  2. Valider la configuration :

    Fenêtre de terminal
    terraform validate

    Sortie attendue :

    Success! The configuration is valid.
  3. Afficher le plan :

    Fenêtre de terminal
    terraform plan

    Sortie attendue (résumé) :

    data.aws_caller_identity.current: Reading...
    data.aws_ami.ubuntu: Reading...
    data.aws_caller_identity.current: Read complete after 1s [id=276757567417]
    data.aws_ami.ubuntu: Read complete after 2s [id=ami-00de3875b03809ec5]
    Terraform will perform the following actions:
    # aws_instance.lab01_vm will be created
    + resource "aws_instance" "lab01_vm" {
    + ami = "ami-00de3875b03809ec5"
    + instance_type = "t2.micro"
    + tags = {
    + "Name" = "lab01-terraform-instance"
    }
    }
    Plan: 1 to add, 0 to change, 0 to destroy.
  4. Appliquer la configuration :

    Fenêtre de terminal
    terraform apply -auto-approve

    Sortie attendue (fin) :

    aws_instance.lab01_vm: Still creating... [00m30s elapsed]
    aws_instance.lab01_vm: Creation complete after 34s [id=i-017ec14c04dd2ccd2]
    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
    Outputs:
    aws_caller_identity_account_id = "276757567417"
    aws_caller_identity_user_arn = "arn:aws:iam::276757567417:user/stephane_robert"
    instance_arn = "arn:aws:ec2:us-east-1:276757567417:instance/i-017ec14c04dd2ccd2"
    instance_id = "i-017ec14c04dd2ccd2"
    instance_private_ip = "172.31.21.186"
    instance_public_ip = "54.89.158.175"
    ubuntu_ami_id = "ami-00de3875b03809ec5"
    ubuntu_ami_name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20260320"
  5. Vérifier l'instance dans la console AWS (optionnel) :

    Fenêtre de terminal
    aws ec2 describe-instances --instance-ids i-017ec14c04dd2ccd2

Maintenant que l'instance existe, revenons sur les trois briques utilisées et sur ce qui les distingue. La confusion la plus fréquente chez les débutants porte sur la frontière entre data source et resource : les deux se déclarent presque de la même façon, mais une seule engage une modification et une facturation.

Le bloc provider "aws" dit à Terraform :

  • Où se connecter (région)
  • Comment s'authentifier (credentials automatiques)
  • Version du provider (gérée par required_providers)

Contrairement à libvirt, où vous définissez explicitement l'URI du socket KVM, le provider AWS résout seul vos credentials en parcourant la chaîne standard du SDK AWS : variables d'environnement, fichiers de configuration, puis rôle IAM de la machine.

Le tableau suivant tient en une question : est-ce que le bloc crée quelque chose ? Une data source est toujours rejouable sans risque, une resource engage un changement réel et une facturation.

AspectData sourceResource
Crée quelque chose ?❌ Non✅ Oui
UsageLire l'infrastructure existanteCréer/modifier l'infrastructure
Exempledata "aws_ami", data "aws_caller_identity"resource "aws_instance"

Dans ce lab :

  • data "aws_caller_identity" : lit votre identité
  • data "aws_ami" : trouve une AMI déjà disponible
  • resource "aws_instance" : crée une nouvelle EC2

Les outputs affichent les valeurs importantes après que apply s'exécute. C'est utile pour :

  • Vérifier les IDs créés
  • Partager l'adresse IP avec une équipe
  • Utiliser dans d'autres workflows (scripts de post-config)

Les quatre réflexes ci-dessous sont ceux qui évitent le plus de mauvaises surprises sur un compte AWS partagé. Ils traitent des trois causes classiques d'incident : une référence qui se périme, une montée de version subie, et une ressource impossible à rattacher à son propriétaire.

1. Utiliser les data sources plutôt que coder l'ID

Section intitulée « 1. Utiliser les data sources plutôt que coder l'ID »

Un identifiant d'AMI est valable dans une seule région et disparaît quand l'éditeur retire l'image du catalogue : la configuration échoue alors sur une erreur InvalidAMIID.NotFound.

# Hardcodé : fragile
ami = "ami-00de3875b03809ec5"
# Dynamique : toujours à jour
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter { ... }
}
ami = data.aws_ami.ubuntu.id

L'opérateur ~> autorise les correctifs et les versions mineures mais bloque le passage à la majeure suivante, celle qui casse la compatibilité. Avec >=, une nouvelle majeure publiée entre deux init peut invalider votre code sans que rien n'ait changé chez vous.

# Risqué : accepte aussi la version majeure suivante
version = ">= 5.0"
# Contrôlé : 5.x uniquement, jamais 6.0
version = "~> 5.0"

Sur un compte partagé, une ressource sans tag devient rapidement orpheline : personne n'ose la supprimer, et elle continue d'être facturée. Trois étiquettes suffisent à trancher, l'environnement, l'outil qui a créé la ressource et sa date.

tags = {
Name = "lab01-instance"
Environment = "lab"
ManagedBy = "terraform"
CreatedAt = "2026-04-01"
}

4. Utiliser un .gitignore pour les fichiers sensibles

Section intitulée « 4. Utiliser un .gitignore pour les fichiers sensibles »

Le fichier d'état terraform.tfstate contient en clair tout ce que Terraform connaît de votre infrastructure, y compris des valeurs sensibles ; les fichiers .tfvars portent le paramétrage propre à un environnement. Ni l'un ni l'autre n'a sa place dans un dépôt Git. Le répertoire .terraform/ est un cache local reconstruit par terraform init, il est donc exclu lui aussi.

Fenêtre de terminal
cat > .gitignore << EOF
.terraform/
terraform.tfstate*
*.tfvars
EOF

Le fichier .terraform.lock.hcl est le seul de la famille à devoir être versionné : il fige les versions exactes des providers retenues par init et garantit que toute l'équipe utilise les mêmes.

Les erreurs rencontrées à ce stade viennent presque toujours de l'environnement, pas du HCL. Le tableau distingue les trois familles à ne pas confondre : credentials absents, région ne contenant pas ce que vous cherchez, et droits IAM insuffisants. Le message d'erreur exact est la clé du diagnostic, lisez-le en entier avant de modifier le code.

SymptômeCause probableSolution
Error: error configuring Terraform AWS Provider: no valid credential sources for Terraform AWS Provider foundCredentials AWS manquantesExécuter aws configure ou exporter AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
Error: your query returned no results. Please change your search criteriaL'AMI n'existe pas dans votre régionChanger la région (aws_region) ou ajuster le filtre AMI
Error: AuthFailureUtilisateur AWS n'a pas les permissions ec2:RunInstancesAjouter la policy AmazonEC2FullAccess au user IAM
Instance créée mais pas visible dans la consoleCache du navigateurRafraîchir ou attendre 30 secondes

Après le lab, détruisez l'infrastructure pour ne pas être inutilement facturé :

Fenêtre de terminal
terraform destroy -auto-approve

Sortie attendue :

aws_instance.lab01_vm: Destroying... [id=i-017ec14c04dd2ccd2]
aws_instance.lab01_vm: Still destroying... [id=i-017ec14c04dd2ccd2, 00m10s elapsed]
aws_instance.lab01_vm: Destruction complete after 41s
Destroy complete! Resources: 1 destroyed.

Nettoyez les fichiers Terraform locaux :

Fenêtre de terminal
rm -rf .terraform* terraform.tfstate*
  1. Le provider AWS se configure par région et credentials, Terraform les charge automatiquement
  2. Les data sources (aws_ami, aws_caller_identity) lisent l'infrastructure existante sans la modifier
  3. Les ressources (aws_instance) créent l'infrastructure
  4. Les versions épinglées (~> X.Y) vous protègent des breaking changes
  5. Les outputs vérifient que Terraform a bien créé ce qu'il fallait
  6. Le nettoyage (destroy) est ESSENTIEL pour ne pas être facturé inutilement
  7. Les tags aident à tracer qui a créé quoi

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn