既存のクラウド資産をTerraformの管理下に入れるとき、これまでは対話的な CLI インポートが主流でした。Terraform 1.5以降は import ブロックで宣言的に実施できます。
この記事では、宣言的インポートの基本、for_each/countやモジュール配下への取り込み、差分や生成コンフィグの扱い、試験での出題ポイントを実務レベルで整理します。
import ブロックは、既存リソースの「プロバイダ固有のインポートID」をTerraform状態に関連付ける宣言です。計画・適用のタイミングで評価され、対象のアドレスへインポートを実行します。データソースは対象外で、管理対象リソースのみがインポートできます。
従来の terraform import (CLI) が命令的手順だったのに対し、import ブロックはコード化されるため、コードレビューや再現性、CI/CDへの組み込みが容易です。適用後は import ブロックを削除しても状態は維持されますが、同じアドレスを重複インポートしようとするとエラーになります。
試験では次が問われやすいです: どのアドレスに取り込むか(count/for_eachやモジュール経路の指定)、インポートIDの形式の理解(プロバイダ依存)、差分発生時の挙動(計画での変更提案)、生成コンフィグの役割と限界、CLI と宣言的手法の使い分け。
最小構成は to(取り込み先のリソースアドレス)と id(既存リソースのインポートID)です。インポートIDはプロバイダのドキュメントに準拠します。例えば AWS の一部リソースは ARN、S3 バケットは名称、VPC は vpc-xxxx のようなIDを使います。
適用フローはシンプルで、計画時に取り込みが検出され、適用時に状態へ登録されます。その後、リソース定義と実体の差分があれば、通常の変更計画として提示されます。
宣言的インポートの流れ
既存クラウド資産 import ブロック Terraform 状態
| | |
| インポートID参照 | |
|------------------------>| to=addr / id=... |
| | (plan/apply時評価) |
| |----------------------->|
| | 状態に関連付け |
| |<-----------------------|
| | 以後は通常の差分検出 |最小例(AWS S3 バケットを取り込む)
provider "aws" {
region = var.region
}
resource "aws_s3_bucket" "example" {
bucket = "my-existing-bucket-123"
acl = "private"
}
# 宣言的インポート
import {
to = aws_s3_bucket.example
id = "my-existing-bucket-123"
}
# 実行例
# terraform init
# terraform plan
# terraform applyfor_each や count で作られた個別インスタンスを取り込む場合、アドレスにキーまたはインデックスを明示します。多数の取り込みがあるなら、import ブロック自体に for_each を用いて宣言的に一括化できます(Terraform 1.5以降)。
モジュール配下のリソースは module. 経路を含む完全修飾アドレスを指定します。階層が深い場合も同様で、module.a.module.b.resource["key"] のように書きます。
アドレス指定の具体例
# count で作られた 0 番目のインスタンスを取り込む
resource "aws_instance" "web" {
count = 2
ami = var.ami
instance_type = "t3.micro"
}
import {
to = aws_instance.web[0]
id = "i-0ab1c2d3e4f5"
}
# for_each の個別キーを取り込む
resource "aws_security_group" "sg" {
for_each = {
admin = "sg-admin"
app = "sg-app"
}
name = each.value
vpc_id = var.vpc_id
}
import {
to = aws_security_group.sg["admin"]
id = "sg-0123456789abcdef0"
}
# import ブロックに for_each を使って一括宣言
variable "buckets" {
type = map(string) # key=論理名, value=実在バケット名
}
resource "aws_s3_bucket" "b" {
for_each = var.buckets
bucket = each.value
}
import {
for_each = var.buckets
to = aws_s3_bucket.b[each.key]
id = each.value
}
# モジュール配下の取り込み
module "net" {
source = "./modules/network"
}
import {
to = module.net.aws_vpc.main
id = "vpc-0abc123def456"
}インポート直後は、既存リソースと現在のリソース定義が一致しないことが多く、計画に多数の変更が出ます。段階的移行では、まず安全に取り込むことを優先し、必要に応じて lifecycle.ignore_changes で過剰な差分を一時的に抑制し、設定の収斂後に外すのが現実的です。
Terraform 1.5以降では、対象リソースの定義がまだ無い場合に、計画時のオプションで生成コンフィグを出力できます。これは雛形生成であり、完全一致を保証するものではない点に注意してください。生成物はレビューして必要な属性のみを採用します。
一時的な差分抑制の例
resource "aws_s3_bucket" "example" {
bucket = "my-existing-bucket-123"
lifecycle {
ignore_changes = [
acl, # 現状のACLを一旦尊重
versioning, # 後で計画的に揃える
]
}
}
# 収斂が済んだら ignore_changes を削除し、改めて計画・適用小規模・単発なら CLI でも実務上は足りますが、チーム開発や再現性が重視される現場では import ブロックが有利です。レビュー可能なコードとして残せるため、監査性も高まります。一方、対話的な検証を素早く行う場面では CLI の手軽さも有効です。
生成コンフィグの自動出力や多数同時インポートの表現力は import ブロックが強みです。既存 IaC 標準や運用体制に合わせて併用を検討しましょう。
| 観点 | import ブロック(宣言的) | terraform import(CLI) | 注意点 |
|---|---|---|---|
| 宣言性・履歴 | コードに残る。PRレビュー・監査しやすい | 履歴は操作ログ頼み。手順の再現は人次第 | 組織標準のコードレビューに乗せやすいのは import ブロック |
| 一括実行 | for_each で大量インポートを宣言可能 | シェルでループなどが必要 | 人為ミスの削減に効果 |
| 生成コンフィグ | 計画時の生成オプションで雛形を得やすい | 基本は手動で設定を用意 | 雛形は要レビュー |
| モジュール階層 | 完全修飾アドレスで自然に指定 | アドレス指定は可能だが手順が分散しやすい | 深い階層ほど宣言的が有利 |
| 自動化・CI | そのままCIに組み込み可能 | 実行順制御・安全策の実装が追加で必要 | 失敗時の再実行容易性に差 |
| 学習コスト | Terraform言語に統一できる | CLIフラグや都度の説明が必要 | チームの習熟状況で選択 |
よくあるつまずきは、すでに状態に登録済みのアドレスを再度インポートしようとしてエラーになるケース、インポートIDの誤り、プロバイダや別リージョン・エイリアスの取り違えです。まず terraform state list で現状を把握し、アドレスとID、プロバイダ選択を見直します。
インポートはリソース実体を変更しません。状態への関連付けのみを行い、その後の差分調整で初めて変更が発生します。影響範囲が大きい場合は、対象ワークスペースのバックアップを取ってから段階的に進めてください。
現状確認の最小コマンド例
# 現在の状態にあるアドレス一覧
terraform state list
# 特定アドレスの詳細
terraform state show aws_s3_bucket.example
# 必要に応じてバックアップ
terraform state pull > state-backup.jsonlPro
問題 1
Terraform 1.5以降、既存の5個のS3バケットを同一モジュールで for_each により管理したい。宣言的に再現性を保ちつつ最小の手数で取り込む適切な方法はどれか。
正解: A
宣言的インポートでは import ブロック自体に for_each を指定でき、to で aws_s3_bucket.resource[each.key] のように各インスタンスアドレスへ対応付けられる。CLI の反復実行は再現性とレビュー性で劣り、moved は既存の状態間の移動に使うもので未管理→管理への取り込みではない。data ソースはインポート対象外。
インポート後は import ブロックを削除しても大丈夫ですか?
はい。import ブロックは計画・適用時に取り込みを指示する宣言で、成功後は状態に関連付けが残ります。以後は通常のリソース定義で管理されるため、import ブロックは恒久的に不要です。残しても再適用時に同一アドレスが既に管理対象ならエラーになり得るため、完了後は削除するのが一般的です。
インポートは既存リソースの設定を変えますか?停止や再作成は発生しますか?
インポート自体は状態への関連付けのみで、既存リソースを変更しません。計画で差分が検出された場合に限り、適用で変更が提案されます。停止や再作成は、定義と実体の不一致が大きい場合に発生し得るため、計画内容を必ず精査してください。
インポートIDの形式が分かりません。どうすれば良いですか?
インポートIDはプロバイダの各リソースごとに定義されています。たとえば AWS では ARN、リソース名、もしくはリソースID(vpc-... など)が求められます。対象プロバイダのリソースドキュメントを確認し、要求される形式に合わせて正確な値を指定してください。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける
Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...
Terraform Authoring & Ops Pro: 上位資格の範囲と対策
上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...
Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす
Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...
Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義
Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...
Terraform Data Source徹底理解:既存リソースの参照で壊さず足す
Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...