Aller au contenu
Infrastructure as Code medium

Terraform AWS, Launch template et autoscaling group

35 min de lecture

logo terraform

Une EC2 seule reste un bon exercice, mais ce n'est pas encore un service résilient. Dès que vous devez conserver plusieurs instances disponibles, absorber une variation de charge ou remplacer progressivement une version applicative, vous avez besoin d'un niveau d'abstraction supplémentaire. Dans AWS, ce duo s'appelle launch template plus autoscaling group.

L'idée à retenir avant même d'écrire le code est simple : le launch template décrit à quoi doit ressembler une instance, tandis que l'ASG décide combien d'instances doivent exister en permanence. Ce guide relie explicitement ces deux rôles pour éviter l'impression de manipuler deux objets AWS sans rapport.

  • Créer un launch template avec AMI, type d'instance et tags
  • Créer un ASG avec min/max/desired capacity
  • Utiliser instance_refresh pour mettre à jour les instances sans interruption
  • Utiliser random_integer pour donner un nom unique à l'ASG
  • Vérifier les instances créées dans la console AWS

Beaucoup de débutants lisent aws_launch_template puis aws_autoscaling_group comme deux chapitres séparés. En réalité, l'un fournit le modèle, l'autre orchestre les copies de ce modèle. Sans template, l'ASG ne sait pas quoi lancer. Sans ASG, le template ne crée rien à lui seul.

Ce guide vous montre donc le couple complet, avec une idée pratique en tête : garder une capacité minimale et mettre à jour progressivement les instances lorsque le modèle change.

  • Terraform ≥ 1.11
  • AWS CLI configuré
  • Compréhension de variables, resources et outputs

Le résultat visible à la fin est court à décrire : deux instances EC2 tournent en permanence, vous n'en avez déclaré aucune individuellement, et si vous en supprimez une à la main dans la console, AWS en relance une sans intervention de Terraform. C'est cette boucle de réconciliation côté AWS qui distingue un ASG d'un simple aws_instance répété avec count.

Construire une infrastructure auto-scalable :

  1. Un launch template = modèle pour les instances
  2. Un ASG qui maintient 2 instances (min=2, max=4)
  3. Observation que l'ASG crée automatiquement les instances

Durée estimée : 15-20 minutes (création d'instances) Coût : Gratuit/quasi-gratuit (t2.micro dans tier gratuit)

Cet exercice lance de vraies instances EC2 sur votre compte AWS. Travaillez dans un répertoire dédié : le terraform destroy de la fin s'appuie sur le state local qui y sera créé, et le mélanger avec un autre projet vous exposerait à détruire les mauvaises ressources.

Créez le répertoire :

Fenêtre de terminal
mkdir -p ~/terraform-aws-launch-template-asg
cd ~/terraform-aws-launch-template-asg

Créez versions.tf :

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

Notez le second provider déclaré, random. Il fournit random_integer, qui servira à suffixer le nom de l'ASG pour éviter tout conflit de nommage dans la région. Deux providers dans un même projet est la situation normale dès qu'on sort de l'exemple minimal : terraform init téléchargera les deux.

Les trois variables de dimensionnement portent des noms imposés par AWS et qu'il vaut mieux ne pas confondre. min_size et max_size sont des bornes que l'ASG ne franchira jamais. desired_capacity est la cible courante, celle qui pilote réellement le nombre d'instances lancées ; c'est aussi la seule des trois qu'une politique de scaling modifiera d'elle-même par la suite.

Créez variables.tf :

variable "aws_region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
default = "t2.micro"
}
variable "min_size" {
type = number
default = 2
}
variable "max_size" {
type = number
default = 4
}
variable "desired_capacity" {
type = number
default = 2
}

Ce fichier contient trois blocs de nature différente. Le bloc data interroge AWS pour récupérer l'identifiant d'AMI Ubuntu le plus récent, ce qui évite d'écrire en dur un ami-… qui deviendra faux dans quelques semaines et qui change d'une région à l'autre. Le numéro 099720109477 est le compte propriétaire officiel des images Ubuntu chez Canonical : le filtrer est ce qui vous protège d'une AMI homonyme publiée par un tiers. Le bloc random_integer sert uniquement à fabriquer un suffixe de nom, son rôle est expliqué plus bas. Le bloc aws_launch_template est le modèle proprement dit.

Créez main.tf :

# Lire l'AMI Ubuntu la plus récente
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# Suffixe aléatoire pour garantir un nom d'ASG unique dans la région
resource "random_integer" "launch_template_version" {
min = 1
max = 1000
}
# Créer le launch template
resource "aws_launch_template" "lab05_template" {
name_prefix = "lab05-template-"
image_id = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tag_specifications {
resource_type = "instance"
tags = {
Name = "lab05-asg-instance"
}
}
lifecycle {
create_before_destroy = true
}
}

Points clés :

  • name_prefix : génère un nom unique (ex : lab05-template-abc123)
  • tag_specifications : les instances héritent des tags
  • create_before_destroy : crée le nouveau template avant de supprimer l'ancien

L'ASG a besoin de savoir lancer les instances : c'est le rôle du bloc data "aws_subnets", qui récupère les sous-réseaux par défaut de la région et les passe à vpc_zone_identifier. Sur un compte AWS dont le VPC par défaut a été supprimé, ce data source renvoie une liste vide et l'ASG sera créé sans jamais démarrer d'instance.

Un point mérite votre attention dans le bloc launch_template ci-dessous. L'alias "$Latest" est souvent recommandé, mais le provider AWS documente explicitement qu'un instance refresh ne démarre pas quand version = "$Latest" est configuré : l'attribut ne change pas aux yeux de Terraform, donc aucune mise à jour n'est déclenchée. Référencer latest_version du template produit au contraire un numéro qui change à chaque nouvelle version, ce qui déclenche bien le remplacement progressif.

Toujours dans main.tf, ajoutez :

# Lire les subnets par défaut
data "aws_subnets" "default" {
filter {
name = "default-for-az"
values = ["true"]
}
}
# Créer l'ASG
resource "aws_autoscaling_group" "lab05_asg" {
name = "lab05-asg-${random_integer.launch_template_version.result}"
min_size = var.min_size
max_size = var.max_size
desired_capacity = var.desired_capacity
vpc_zone_identifier = data.aws_subnets.default.ids
launch_template {
id = aws_launch_template.lab05_template.id
# Numéro de version résolu par Terraform, et non l'alias "$Latest" :
# sans cela, l'instance refresh ne se déclencherait jamais.
version = aws_launch_template.lab05_template.latest_version
}
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 50
}
}
lifecycle {
create_before_destroy = true
}
tag {
key = "Name"
value = "lab05-asg-instance"
propagate_at_launch = true
}
}

Explications :

ParamètreRôle
min_sizeMinimum d'instances (2)
max_sizeMaximum d'instances (4)
desired_capacityCible actuelle (2) = créer 2 instances
vpc_zone_identifierAZs où lancer les instances
launch_template { version = ...latest_version }Numéro de la dernière version du template, résolu par Terraform
instance_refresh { strategy = "Rolling" }Remplacer les instances progressivement
min_healthy_percentage = 50Garder au moins 50% des instances saines pendant l'update

Ces sorties ne sont pas décoratives : elles vous donnent les valeurs à réutiliser dans les commandes de vérification. asg_name contient le nom généré, que vous ne pouvez pas deviner à l'avance puisqu'il embarque un tirage aléatoire. asg_availability_zones est calculé par AWS et non par vous : il révèle dans quelles zones de disponibilité l'ASG répartit réellement ses instances, ce qui permet de contrôler que vpc_zone_identifier a bien récupéré plusieurs sous-réseaux.

Créez outputs.tf :

output "launch_template_id" {
value = aws_launch_template.lab05_template.id
}
output "launch_template_name_prefix" {
value = aws_launch_template.lab05_template.name_prefix
}
output "asg_name" {
value = aws_autoscaling_group.lab05_asg.name
}
output "asg_min_size" {
value = aws_autoscaling_group.lab05_asg.min_size
}
output "asg_max_size" {
value = aws_autoscaling_group.lab05_asg.max_size
}
output "asg_desired_capacity" {
value = aws_autoscaling_group.lab05_asg.desired_capacity
}
output "asg_availability_zones" {
value = aws_autoscaling_group.lab05_asg.availability_zones
}

Le fichier variables.tf déclare les variables et leurs valeurs par défaut ; terraform.tfvars fournit les valeurs effectives de ce déploiement. Ici les deux coïncident, ce qui est volontaire : vous obtenez un fichier unique à éditer pour changer la taille du groupe, sans toucher au code. Ce fichier contient souvent des valeurs propres à un compte ou à un environnement, ajoutez-le à votre .gitignore avant le premier commit.

Créez terraform.tfvars :

aws_region = "us-east-1"
instance_type = "t2.micro"
min_size = 2
max_size = 4
desired_capacity = 2

Quatre commandes, dont deux pour confirmer que le résultat existe côté AWS et pas seulement dans le state Terraform. Le point de contrôle le plus parlant est la troisième : terraform apply annonce 3 ressources créées, alors que describe-instances en retourne deux. Rien d'anormal, les instances ne sont pas des ressources Terraform, elles sont lancées par l'ASG. Remplacez lab05-asg-121 par la valeur réelle de la sortie asg_name avant d'exécuter les deux dernières commandes.

  1. Initialiser :

    Fenêtre de terminal
    terraform init
  2. Appliquer (⚠️ cela prend 3-4 minutes, création de 2 instances) :

    Fenêtre de terminal
    terraform apply -auto-approve

    Sortie attendue (fin) :

    Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
    Outputs:
    asg_availability_zones = toset([
    "us-east-1a",
    "us-east-1b",
    "us-east-1c",
    ...
    ])
    asg_desired_capacity = 2
    asg_max_size = 4
    asg_min_size = 2
    asg_name = "lab05-asg-121"
    launch_template_id = "lt-06738d00c2e67dafb"
    launch_template_name_prefix = "lab05-template-"
  3. Vérifier les instances dans la console AWS :

    Fenêtre de terminal
    aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=lab05-asg-instance" \
    --query 'Reservations[*].Instances[*].[InstanceId,State.Name,InstanceType,SubnetId]' \
    --output table

    Sortie attendue :

    | i-0abc123... | running | t2.micro | subnet-xx... |
    | i-0def456... | running | t2.micro | subnet-yy... |
  4. Voir l'ASG :

    Fenêtre de terminal
    aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names lab05-asg-121 \
    --query 'AutoScalingGroups[0].[MinSize,MaxSize,DesiredCapacity,Instances]' \
    --output table

La ligne à retenir est la deuxième. Un launch template appliqué seul ne produit rien : c'est une définition stockée chez AWS, versionnée, que plusieurs ASG peuvent référencer en même temps. Cette séparation explique le comportement observé pendant la mise à jour : modifier le template crée une nouvelle version sans perturber les instances existantes, et c'est l'ASG, via son instance_refresh, qui décide du moment où les instances seront remplacées.

AspectLaunch TemplateASG
QuoiModèle pour les instancesGestionnaire d'instances
Crée des instances ?❌ Non✅ Oui
Instances réelles❌ Non✅ Oui (plusieurs)
Mise à jourNouvelle versionUtilise la nouvelle version

Quand le template change, l'ASG remplace les instances progressivement. Le provider AWS n'accepte qu'une seule valeur pour strategy : Rolling. Toute autre valeur, y compris BlueGreen, est rejetée à la validation. Le réglage se fait donc entièrement dans preferences, et le curseur principal est min_healthy_percentage :

instance_refresh {
strategy = "Rolling" # Seule valeur acceptée par le provider
preferences {
min_healthy_percentage = 50 # Par défaut : 90
instance_warmup = 300 # Secondes avant de compter l'instance saine
}
}

Avec min_healthy_percentage = 50 et une capacité désirée de 2, AWS s'autorise à retirer une instance à la fois. Monté à 90, la valeur par défaut, le remplacement exige d'abord de lancer la nouvelle instance : plus prudent, mais la capacité dépasse temporairement la cible et il faut que max_size le permette. instance_warmup retarde le moment où une instance fraîchement lancée est comptée comme saine, le temps que l'application démarre réellement.

aws_launch_configuration est l'ancêtre du launch template, et la voie est fermée : AWS ne prend plus en charge les nouveaux types d'instances dans les launch configurations depuis janvier 2023, et les comptes créés à partir du 1er octobre 2024 ne peuvent plus en créer, quelle que soit la méthode. Un launch configuration est de surcroît immuable : pour le changer, il faut en créer un autre puis repointer l'ASG. Le launch template apporte le versionnement, indispensable au mécanisme d'instance refresh vu plus haut.

# ❌ Ancien style (déprécié)
resource "aws_autoscaling_group" "bad" {
launch_configuration = aws_launch_configuration.example.id
}
# ✅ Nouveau style
resource "aws_autoscaling_group" "good" {
launch_template {
id = aws_launch_template.example.id
version = aws_launch_template.example.latest_version
}
}

Sans ce bloc, Terraform détruit la ressource avant de recréer la nouvelle, et le service passe par une fenêtre à zéro instance. Sur un ASG dont le name change, la différence se compte en minutes d'indisponibilité. Le prix à payer est un pic de capacité transitoire, puisque les deux générations coexistent le temps de la bascule.

lifecycle {
create_before_destroy = true
}

Cela évite les gaps de 0 instances pendant les updates.

L'argument s'appelle instance_warmup, sans suffixe _seconds : c'est une erreur de frappe classique, et le provider la rejette à terraform validate. Sans cette valeur, AWS reprend le health check grace period de l'ASG, qui est souvent trop court pour une application qui met une minute à démarrer.

instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 90
instance_warmup = 300
}
}

Cela maintient le service disponible pendant les mises à jour.

Passer desired_capacity par une variable documentée plutôt que par une valeur écrite en dur laisse une trace du dimensionnement dans le dépôt Git. Attention toutefois : si vous ajoutez plus tard une politique de scaling automatique, c'est AWS qui pilotera desired_capacity et Terraform verra une dérive à chaque plan. Dans ce cas, retirez l'argument de votre configuration ou ajoutez lifecycle { ignore_changes = [desired_capacity] }.

variable "desired_capacity" {
description = "Desired number of instances"
type = number
default = 2
}
# Changez `terraform.tfvars` pour scaler
# desired_capacity = 5 # ASG créera 3 instances supplémentaires

Le piège commun à ces quatre situations est que terraform apply réussit malgré tout : l'ASG est bien créé, le state est cohérent, et pourtant aucune instance ne tourne. Le diagnostic ne se fait donc pas dans Terraform mais dans l'historique d'activité de l'ASG, consultable avec aws autoscaling describe-scaling-activities --auto-scaling-group-name <nom> : c'est là qu'AWS écrit la raison d'un lancement refusé.

SymptômeCauseSolution
ASG créé mais 0 instancePas de subnets disponiblesVérifier vpc_zone_identifier
Les instances sont correctes mais ASG unhealthyHealth check failedVérifier les security groups et ports
Plan montre "replacement" de l'ASGLe name de l'ASG a changé, il force un remplacementVérifier ce qui fait varier le nom ; random_integer n'en est pas la cause, sa valeur est figée dans le state
Impossible de SSH aux instancesPas de key pair ni security groupAjouter key_name au template et SSH ingress

Ne sautez pas cette étape : les instances continuent d'être facturées tant qu'elles tournent, et un ASG relance automatiquement toute instance que vous arrêteriez à la main. La seule façon propre d'arrêter la facturation est de détruire le groupe lui-même. Vérifiez ensuite dans la console EC2 qu'aucune instance lab05-asg-instance ne subsiste.

Important : la destruction prend 3 à 4 minutes (arrêt des instances) :

Fenêtre de terminal
terraform destroy -auto-approve
rm -rf .terraform* terraform.tfstate*
  1. Launch template = modèle réutilisable pour les instances
  2. ASG = gestionnaire automatique d'instances (min/max/desired)
  3. Instance refresh = mise à jour progressive des instances
  4. latest_version plutôt que l'alias "$Latest" : ce dernier bloque le déclenchement de l'instance refresh
  5. create_before_destroy = éviter les gaps de 0 instances
  6. random_integer = suffixe de nom unique, tiré une seule fois et conservé dans le state
  7. Scaling = changez desired_capacity pour ajouter/retirer des instances

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