Aller au contenu principal

Ressources

Terraform import : reprendre une infrastructure AWS existante sans la recréer

Reprendre une infrastructure AWS existante avec terraform import : inventaire des sous-objets, blocs import, premier plan lu comme contrôle et state protégé.

L'équipe CTN Solutions · publié le · 14 min de lecture

Base d'expérience : Laboratoire reproductible de CTN Solutions du 8 octobre 2026 contre un émulateur local de l'API AWS (moto 5.2.3), et documentation officielle HashiCorp et du fournisseur AWS. Aucun compte AWS réel, aucune référence client.

Faits vérifiés sur les sources primaires le

Une infrastructure AWS construite à la main, ou livrée par un prestataire, peut contenir des ressources que personne n'a déclarées dans Terraform. Reprendre le compte revient alors à décider, objet par objet, ce qui passe sous Terraform, ce qui reste hors périmètre, et comment vérifier qu'aucune ressource n'est recréée par erreur. La commande terraform import ne tranche pas cette décision à elle seule : elle rattache un objet réel à une adresse du state, sans dire si la configuration décrit fidèlement cet objet.

L'import ne migre aucune donnée et ne garantit pas que le prochain apply conservera l'existant. Nous découpons donc la reprise en trois temps : découvrir ce qui existe, importer dans le state, puis modifier la configuration seulement après acceptation du premier plan. Cet article décrit ce découpage, le contrôle à chaque étape et la protection du state. Il ne couvre ni IAM, ni politiques de bucket, ni réseau complet (routes, passerelles), ni données.

Le fil conducteur est un exemple fictif : un VPC, un sous-réseau, un groupe de sécurité et un bucket S3 créés en ligne de commande, sans aucune configuration Terraform.

Cartographier ce qui existe avant de décider qui le gère

Importer une ressource, c'est lui donner un gestionnaire unique. Avant toute configuration, la matrice doit indiquer pour chaque objet : le compte, la région, le propriétaire, le mécanisme qui le crée aujourd'hui (console, CLI, autre state, CloudFormation) et l'adresse Terraform cible.

Dans un laboratoire local du 8 octobre 2026, nous avons créé ces objets par CLI, puis les avons inventoriés. Nous avons utilisé Terraform 1.16.5 et le fournisseur hashicorp/aws en version 6.68.0, contre un émulateur local de l'API AWS, moto 5.2.3, et non contre un compte AWS réel. Tous les identifiants et noms sont fictifs. Ce que nous observons vient donc de cet émulateur : sur AWS réel, les valeurs renvoyées par l'API peuvent différer, ce qui modifie certains messages de validation.

Inventaire fictif : ressource, identifiant, mécanisme actuel et adresse Terraform cible
RessourceIdentifiant fictifMécanisme actuelAdresse Terraform cible
VPCvpc-328508c45d80b3772commande CLI create-vpcaws_vpc.principal
Sous-réseausubnet-4c14804774336e153commande CLI create-subnetaws_subnet.a
Groupe de sécuritésg-6fba0f2746cbef60dcreate-security-group, puis authorize-security-group-ingressaws_security_group.web
Bucket S3exemple-fictif-reprise-buckets3api create-bucket, put-bucket-versioning, put-bucket-taggingaws_s3_bucket.reprise

L'inventaire doit lister les sous-objets, pas seulement les ressources visibles dans la console. Dans le laboratoire, la liste de départ comptait quatre lignes, et la configuration finale ajoutait trois adresses : deux règles de groupe de sécurité (entrée HTTPS et sortie) et le versioning du bucket. Une règle de groupe de sécurité s'importe par son propre identifiant de règle, préfixé sgr-, distinct de l'identifiant du groupe. Le versioning, lui, s'importe par le nom du bucket.

Il faut aussi distinguer ce qui doit être importé de ce qui doit seulement être lu. Une source de données lit un objet du fournisseur sans le créer ni le modifier, et Terraform ne peut faire que des opérations de lecture sur elle : elle sert à référencer un objet existant, pas à le reprendre. Enfin, désignez pour la durée de la reprise la personne qui peut modifier chaque ressource en console. Une modification faite à la main pendant l'import ressortira au plan suivant comme une dérive ou une suppression.

Choisir un import lisible et une configuration minimale

Deux chemins existent. La commande terraform import n'écrit que dans le state : il faut avoir écrit à la main le bloc resource avant, et elle ne génère aucune configuration. La documentation recommande le bloc import pour générer la configuration et pour importer plusieurs ressources. Un bloc import s'écrit dans la configuration et se planifie comme une action ordinaire, avant d'être appliqué.

Un bloc import porte deux informations : l'adresse de destination, qui doit correspondre à un bloc resource déclaré, et l'identifiant exigé par le fournisseur.

import {
  to = aws_vpc.principal
  id = "vpc-328508c45d80b3772"
}

Depuis Terraform 1.7.0, for_each permet d'étendre un bloc import à plusieurs instances. Depuis 1.12.0, l'argument identity peut remplacer id, les deux étant exclusifs. Le laboratoire n'utilise ni l'un ni l'autre.

La génération de configuration existe aussi : terraform plan -generate-config-out=PATH écrit le HCL des ressources listées dans un bloc import sans configuration. Terraform la présente comme expérimentale. Dans le laboratoire, le premier essai a affiché ce message :

Warning: Config generation is experimental. Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.

Le plan annonçait Plan: 2 to import, 0 to add, 0 to change, 0 to destroy. pour les deux ressources sans configuration, mais la validation échouait sur cinq erreurs concernant le VPC et le sous-réseau générés. Le fichier était écrit malgré l'échec. Nous avons retiré neuf arguments à la main, dont des arguments de zone et de plage IPAM, pour obtenir une validation propre. Le fichier généré présentait trois autres défauts :

  • le groupe de sécurité portait ses règles en ligne, alors que le fournisseur déconseille de combiner règles en ligne et ressources de règles séparées ;
  • aucune ressource de versioning n'était générée pour le bucket ;
  • le fichier est, selon la documentation, la meilleure estimation de Terraform, à relire avant d'être versionné.

Les erreurs de validation dépendent de valeurs renvoyées par l'API. Sur AWS réel, elles peuvent donc être différentes.

Un identifiant erroné échoue plus clairement, mais le message se lit mal. Le laboratoire a testé quatre fautes : identifiant de groupe inexistant, nom de bucket mal orthographié, nom du groupe à la place de son identifiant, identifiant de sous-réseau donné pour un groupe. Les quatre ont produit la même erreur :

Error: Cannot import non-existent remote object

Le plan affichait en plus les ressources non importées comme 1 to add, ce qui est trompeur. Il faut donc lire l'erreur avant le compte du plan.

Lire le premier plan comme un contrôle de non-régression

Le premier plan qui suit des imports ne doit annoncer que des imports. Chaque autre ligne est une décision à expliquer avant tout apply.

Quatre décisions à prendre sur les lignes du premier plan après import
Ligne du planCe qu'elle signifieConduite
Import attendu : « will be imported », sans différencel'objet existant correspond à la configurationaccepter après revue
Mise à jour inattendue : modification sur place d'un objet importé (étiquette, règle, versioning)la configuration ne décrit pas exactement l'existantexpliquer la différence ; ne pas appliquer pour faire passer le plan
Remplacement : « must be replaced » ou « forces replacement »Terraform détruirait l'objet réel puis le recréeraitarrêter le lot et comparer l'attribut en cause
Suppression : élément marqué d'un signe moins, ou valeur ramenée à nullla configuration retire un élément existantarrêter : la configuration est incomplète

Le récapitulatif ne compte pas les remplacements à part : un remplacement apparaît comme un ajout et une destruction. Dans le laboratoire, une configuration écrite rapidement, sans étiquettes ni règles, annonçait Plan: 4 to import, 1 to add, 3 to change, 1 to destroy. Le « 1 to add » et le « 1 to destroy » formaient un seul remplacement : le groupe de sécurité dont la description différait (« exemple fictif web » contre « web »), avec la mention « this will destroy the imported resource ». Les trois variantes et la configuration finale donnent le tableau suivant.

Plans annoncés par chaque variante de configuration et lecture de chacun
Configuration du laboratoirePlan annoncéLecture
Variante A : sans étiquettes ni règlesPlan: 4 to import, 1 to add, 3 to change, 1 to destroy.un remplacement du groupe de sécurité
Variante B : sans règle HTTPS ni versioning déclarésPlan: 4 to import, 0 to add, 0 to change, 0 to destroy.plan propre, mais sous-objets non décrits
Variante C : ingress = [] et egress = []Plan: 4 to import, 0 to add, 1 to change, 0 to destroy.retrait de la règle entrante et de la règle sortante
Configuration finale, règles et versioning déclarésPlan: 7 to import, 0 to add, 0 to change, 0 to destroy.après application, second plan sans changement

Le cas B est le plus trompeur. Un plan propre ne prouve rien sur ce qui n'est pas déclaré : Terraform ne compare que ce qu'il connaît. La liste des sous-objets de l'inventaire sert donc de contrôle séparé, chaque règle et chaque versioning recevant une adresse ou une exclusion écrite. Le cas C montre l'inverse : déclarer une liste vide retire ce qui existe. À la création d'un groupe de sécurité dans un VPC, le fournisseur supprime la règle de sortie « allow all » qu'AWS ajoute par défaut. Sur le groupe importé du laboratoire, cette règle n'a pas été retirée tant que egress n'était pas déclaré (variante B) ; seule la liste vide egress = [] l'a retirée (variante C).

Décider de chaque ligne du premier plan

Le premier plan après les blocs import est trié en quatre décisions, dont deux arrêtent le lot.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. Le premier plan qui suit les blocs import est lu ligne par ligne.
  2. Un import sans différence est accepté après revue.
  3. Une modification sur place est expliquée, et on n'applique pas pour faire passer le plan.
  4. Un remplacement annoncé arrête le lot.
  5. Un élément retiré arrête le lot, parce que la configuration est incomplète.
Modèle explicatif, sans donnée client, d'après les lignes du plan observées dans le laboratoire du 8 octobre 2026.

Le contrôle ne suffit pas pour les modifications faites hors Terraform après l'import. Dans le laboratoire, nous avons ajouté par CLI une seconde règle entrante (TCP 22) sur le groupe de sécurité, dont les règles étaient des ressources séparées. Le plan annonçait No changes, car la règle inconnue n'est pas vue par le plan classique. Seul terraform plan -refresh-only la montrait dans l'attribut ingress. Une étiquette ajoutée à la main, en revanche, apparaissait au plan : Plan: 0 to add, 1 to change, 0 to destroy. Un contrôle de dérive doit donc inclure un refresh-only, et les règles posées hors Terraform doivent être recherchées explicitement.

Enfin, ne mélangez pas import et modernisation dans le même lot. Un lot qui combine les deux ne permet plus de savoir quelle ligne du plan vient de quelle décision. Dans le laboratoire, la configuration corrigée annonçait Plan: 7 to import, 0 to add, 0 to change, 0 to destroy. ; l'application a importé sept ressources, et le second plan a répondu No changes. Your infrastructure matches the configuration.

Protéger le state et organiser les accès pendant la reprise

Le state relie chaque adresse Terraform à un objet réel. Le protéger, c'est empêcher deux écritures concurrentes, garder une copie récupérable et n'avoir qu'un gestionnaire par objet. Récupérer un fichier d'état et restaurer une ressource sont deux opérations distinctes.

Le backend S3 propose un verrouillage natif depuis Terraform 1.10.0. Il s'active par use_lockfile = true, dont le défaut est false, et crée à côté de l'état un fichier de verrou <clé>.tflock qui doit être couvert par les contrôles d'accès. Le verrouillage par DynamoDB est déprécié selon la documentation du backend, qui annonce son retrait dans une version mineure future. La documentation recommande fortement d'activer le versioning du bucket d'état, pour récupérer un état supprimé ou écrasé par erreur.

Si le verrou ne peut pas être obtenu, Terraform ne poursuit pas l'opération. L'option -lock-timeout réessaie pendant une durée donnée ; la documentation ne fixe pas de valeur par défaut, et le laboratoire a utilisé cinq secondes. Dans le laboratoire, un apply restait en attente de confirmation et tenait le verrou : un fichier terraform.tfstate.tflock apparaissait à côté de l'état. Un second plan avec -lock-timeout=5s a échoué après cinq secondes :

Error: Error acquiring the state lock

Le détail de la réponse S3 (une précondition refusée, code 412) vient de l'émulateur et peut différer sur AWS réel. Le fichier de verrou disparaissait à la fin de l'opération détentrice. L'option -lock=false passe sans verrou ; la documentation la juge dangereuse si d'autres commandes peuvent s'exécuter sur le même espace de travail. Elle ne doit pas servir à contourner une attente.

force-unlock sert à lever son propre verrou lorsque le déverrouillage automatique a échoué, mais la documentation avertit que déverrouiller pendant qu'un autre opérateur tient le verrou peut créer plusieurs écrivains. Ce n'est pas une réponse standard à un plan bloqué : il faut d'abord identifier le détenteur du verrou, indiqué dans le bloc de diagnostic, et vérifier que son opération est terminée.

Deux états ne doivent jamais gérer le même objet. Dans le laboratoire, un second dossier, avec un état local distinct, a importé le même groupe de sécurité sous une autre adresse. Terraform n'a émis aucun avertissement. Chaque état a ensuite voulu défaire la modification de l'autre : l'étiquette Team ajoutée par un état était retirée par l'autre, et chaque apply de l'un défaisait le précédent apply de l'autre. Le plan de destruction du second état annonçait la suppression d'un groupe utilisé par le premier. L'inventaire doit donc porter une colonne « gestionnaire », avec un seul nom par ligne.

La commande historique terraform import ADRESSE ID écrit aussitôt dans le state. Dans le laboratoire, elle a répondu Import successful! sans aperçu, et la différence, une étiquette absente, n'est apparue qu'au plan suivant. Le bloc import, au contraire, est planifié avant d'être appliqué.

Pour sortir un objet du state sans le détruire, le bloc removed avec destroy = false est disponible depuis Terraform 1.7.0. Il faut savoir que prevent_destroy fait échouer les plans qui détruiraient l'objet, mais ne protège pas si le bloc de configuration est retiré.

Transmettre un dossier d'exploitation utilisable

Une reprise n'est terminée que lorsque la personne qui intervient ensuite peut rejouer le plan sans aide. Le dossier de transmission contient donc les commandes reproductibles, les versions épinglées, la liste des ressources adoptées avec leur adresse et leur identifiant (sans secret), les exclusions écrites, le prochain plan attendu et le propriétaire de la pipeline et des accès au state.

Épinglez la version de Terraform sur une 1.16.x précise, et le fournisseur AWS sur une version exacte : le laboratoire a utilisé 1.16.5 et = 6.68.0. Le changelog de Terraform 1.16.1 corrige un bug où des blocs import étaient ignorés quand plusieurs blocs visaient des instances différentes d'une ressource utilisant for_each ou count. Une version exacte permet de savoir quelle correction est en vigueur.

La reprise terminée n'est pas le refactoring. Découper la configuration en modules, changer de backend ou réorganiser les adresses sont autant de lots distincts, chacun avec son propre premier plan. Pour le périmètre et l'ordre des chantiers, l'article Platform engineering pour une scale-up : par où commencer sans sur-ingénierie pose la question en amont.

Une fois la reprise faite, la question change : qui exploite, qui intervient, selon quel contrat. Externaliser l'exploitation cloud et Kubernetes : que confier, à qui et avec quel contrat traite ce point. Lorsque la reprise doit être cadrée avec nous, la page reprise d'une infrastructure cloud existante décrit la démarche proposée.

Le tableau suivant résume la décision à prendre à chaque étape.

Sortie attendue et condition d'acceptation de chaque étape de la reprise
ÉtapeSortie attendueCondition d'acceptation
Inventairematrice par ressource et par sous-objet, avec propriétaire et gestionnaire uniquechaque ligne a une adresse ou une exclusion écrite
Importun bloc import par adresse, identifiant vérifié contre la matricetoute erreur « Cannot import non-existent remote object » arrête le lot
Premier planquatre lignes classéesseuls des imports attendus ou des différences expliquées ; remplacement et suppression non prévus arrêtent le lot
Second planNo changes après applicationpreuve de stabilité pour les objets décrits seulement ; les sous-objets non déclarés se vérifient à part

Sources officielles et limites de lecture

Faits datés

Les pages officielles citées ont été relues le . Les résultats de laboratoire valent pour Terraform 1.16.5 et le fournisseur hashicorp/aws 6.68.0, face à l'émulateur moto 5.2.3.

Vérifié le

  • L'émulateur n'est pas AWS. Valeurs par défaut, attributs calculés et textes d'erreur peuvent différer sur AWS réel ; les erreurs de validation du HCL généré et le détail du verrou S3 sont à reconfirmer sur un compte réel.
  • Le laboratoire ne couvre ni IAM, ni politiques de bucket, ni instances, ni bases de données, ni routes. Il utilise un seul compte fictif et une seule région, sans concurrence réelle entre opérateurs.
  • La génération de configuration est expérimentale selon Terraform : le HCL produit doit être relu et corrigé.
  • Le comportement des règles de groupe de sécurité dépend du fournisseur en version 6.68.0 et peut évoluer.
  • Les versions de Terraform et du fournisseur changent : relisez la documentation de la version effectivement utilisée.
  • Aucune expérience client n'est décrite ici. Les résultats présentés viennent du laboratoire, et non d'une mission.

Sources

  1. https://developer.hashicorp.com/terraform/language/block/import
  2. https://developer.hashicorp.com/terraform/language/import
  3. https://developer.hashicorp.com/terraform/language/import/generating-configuration
  4. https://developer.hashicorp.com/terraform/cli/import
  5. https://developer.hashicorp.com/terraform/cli/commands/plan
  6. https://developer.hashicorp.com/terraform/language/state/locking
  7. https://developer.hashicorp.com/terraform/language/backend/s3
  8. https://developer.hashicorp.com/terraform/language/block/removed
  9. https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle
  10. https://developer.hashicorp.com/terraform/language/data-sources
  11. https://raw.githubusercontent.com/hashicorp/terraform/v1.5.0/CHANGELOG.md
  12. https://raw.githubusercontent.com/hashicorp/terraform/v1.7.0/CHANGELOG.md
  13. https://raw.githubusercontent.com/hashicorp/terraform/v1.10.0/CHANGELOG.md
  14. https://raw.githubusercontent.com/hashicorp/terraform/v1.12.0/CHANGELOG.md
  15. https://raw.githubusercontent.com/hashicorp/terraform/v1.16.5/CHANGELOG.md
  16. https://raw.githubusercontent.com/hashicorp/terraform-provider-aws/v6.68.0/website/docs/r/security_group.html.markdown
  17. https://raw.githubusercontent.com/hashicorp/terraform-provider-aws/v6.68.0/website/docs/r/s3_bucket.html.markdown
  18. https://raw.githubusercontent.com/hashicorp/terraform-provider-aws/v6.68.0/website/docs/r/vpc.html.markdown
  19. https://raw.githubusercontent.com/hashicorp/terraform-provider-aws/v6.68.0/website/docs/r/subnet.html.markdown
  20. https://raw.githubusercontent.com/hashicorp/terraform-provider-aws/v6.68.0/website/docs/r/vpc_security_group_ingress_rule.html.markdown
  21. https://raw.githubusercontent.com/hashicorp/terraform-provider-aws/v6.68.0/website/docs/r/s3_bucket_versioning.html.markdown

Parlons de votre contexte.

Cet article expose une pratique générale ; votre situation a ses propres contraintes. Décrivez-la, nous répondons avec un périmètre.

Ouvrir le formulaire