インフラ

terraform importで既存リソースをコード化する手順|importブロックとCLIの使い分け

コンソールで作ったサーバーやバケットが手元に残ったまま、Terraformの管理下に入っていない。これを解消する仕組みが取り込み(import)です。ただしコマンドを1回叩けば終わる作業ではありません。stateへの登録は自動でも、対応するHCLを書き、planの差分をゼロに寄せるまでが1セットになります。この記事では、importブロックとterraform importコマンドの違い、4段階の実装手順、差分が消えないときに疑う箇所、for_eachとidentityによる複数取り込み、失敗時の戻し方を扱います。バージョンに紐づく機能は2026年8月13日に公式ドキュメントとCHANGELOGで実測しました。

まとめ:terraform importで先に決める3点と着手の順序

先に決めるのは3点です。importブロックとCLIのどちらで進めるか、HCLを生成させるか手書きするか、失敗したときにどこまで戻すか。決めずにコマンドを打つと、stateだけが進んで設定が追いつかない状態になります。

方式は原則importブロックに寄せてください。公式も「Instead of manually importing resources, you can add the import block to your Terraform configurations」とブロック側を案内しています。取り込みが設定ファイルに残るため、レビューにかけられCIからも流せる。CLIのterraform importは1回きりの調査に絞ると、使い分けが崩れません。

着手の順序は、対象の洗い出し→リソースIDの確認→importブロック記述→設定生成→plan差分ゼロ化→applyで固定します。差分ゼロを確認するまでapplyしないという一線を守れば、取り込みで既存リソースが壊れる事故はほぼ防げます。

見送る判断もあります。対象が数本で停止でき、再作成コストが低いなら作り直しのほうが速い。条件は本文の最後に置きました。

importブロックとterraform importコマンドの違いと選び方

同じ「取り込み」でも、この2つは動くタイミングも残るものも違います。混ぜたまま作業すると、あとから経緯を追えません。

importブロックが1.5系で追加されて変わった作業の流れ

importブロックはTerraform 1.5系で追加されました。CHANGELOGには「Import is now a configuration-driven, plannable action, and is processed as part of a normal plan.」とあり、取り込みが通常のplanの一部として計画される点が従来との差です。planを走らせれば取り込み予定のリソースが他の変更と一緒に一覧で出るため、レビュープロセスに乗せられます。

書き方は、取り込み先のアドレスをtoに、クラウド側のIDをidに指定するだけです。対応するresourceブロックも必要になります。

import {
  to = aws_s3_bucket.legacy_assets
  id = "issoh-legacy-assets"
}

resource "aws_s3_bucket" "legacy_assets" {
}

1.5系の時点ではidに補間が使えず、文字列を直接書く制約がありました。その後1.7系でfor_each、1.12系でidentityが追加され、書ける範囲は広がっています。現行の引数は to(必須)、id、identity、for_each、provider の5つです。

terraform importコマンドがいまも残っている理由と使いどころ

CLIのterraform importは1.5系の時点でも変更されておらず、CHANGELOGにも「The existing terraform import CLI command has not been modified.」と明記されています。現在も動きます。

terraform import aws_s3_bucket.legacy_assets issoh-legacy-assets

ただし性質はブロックと大きく異なります。実行するとその場でstateが更新され、planによる事前確認を挟めません。設定は生成されないためHCLは別途自分で書き、1回で扱えるのは1リソースだけ。100本あれば100回叩くことになります。-state、-state-out、-backup はローカルバックエンド専用のレガシー扱いです。

使いどころは、IDのフォーマットが分からず1本だけ試したいときや、検証用ワークスペースでstateを捨てる前提で挙動を確かめるときに限られます。本番の設定リポジトリに残す作業なら、ブロック側のほうが後々の追跡が楽です。

観点 importブロック importコマンド
実行の契機 planとapply コマンド即時
事前確認 planで一覧化できる できない
設定の生成 フラグで生成可 生成しない
一度に扱う数 複数(for_each可) 1リソース
作業の記録 コードに残る 残らない

手動で作成したリソースをコード化する4段階の実装手順と確認点

取り込みの本体は、stateへの登録ではなく設定の一致作業です。段階を分けると、どこで止まったか分かります。

取り込む対象の洗い出しとリソースIDの調べ方を先に済ませておく

最初にやるのは棚卸しです。手動で作ったリソースを列挙し、依存関係の下側から並べる。ネットワーク、IAM、ストレージ、そのうえで動く計算資源、という順です。上位から取り込むと参照先がまだstateに無く、HCLの中で値をハードコードすることになります。

次にIDの形式を確認します。IDはリソース種別ごとに異なり、名前そのものだったりリージョンを含む複合文字列だったりする。プロバイダのドキュメントで各リソースのImportセクションを見るのが確実です。推測で埋めると、planの段階でエラーになります。

粒度に迷ったら、1回の取り込みを1つの論理単位に区切ってください。VPCとサブネットとルートテーブルで1回、といった単位です。数十本を一度に流すと切り分けが難しくなる。IaC自体の考え方はIaCとは?Infrastructure as Codeの仕組み・メリットと導入判断を解説で整理しています。

-generate-config-outでHCLを生成し、手で削る範囲を決める

importブロックだけ書いた状態で、生成フラグつきのplanを走らせます。resourceブロックが無いリソースのHCLが、指定ファイルへ書き出されます。

terraform plan -generate-config-out=generated.tf

出力先には既存ファイルを指定できません。公式にも「Do not supply a path to an existing file, or Terraform throws an error」とあります。生成されるHCLは現在の実体をそのまま写したもので既定値と同じ属性まで含まれるため、中身を読んで手で削ります。

この機能はv1.15系のドキュメント時点でもExperimentalの注記が付き、「Later minor versions may contain changes to the formatting of generated configuration」とあります。出力の書式はバージョン間で変わりうると考えておいてください。複雑なスキーマでは有効な設定を組み立てられない場合もあり、公式は競合例として ipv6_address_count と ipv6_addresses を挙げています。

採用するか下書き扱いにするかは、対象の数で決める。数本なら手書きのほうが読める設定になります。数十本を超えるなら生成させ、共通部分をモジュールに畳むほうが早い。命名や分割の基準はTerraformコーディング規約(スタイルガイド)|fmt・命名・構成の統一ルールに寄せておくと、生成物と手書きが混ざっても読み手が迷いません。

terraform planの差分をゼロに寄せるまで設定を合わせ込む

設定を置いたら、フラグなしのplanを繰り返します。目指すのは「取り込みは行われるが、リソースへの変更は無い」という状態です。属性の変更が1つでも出ているうちは、その差分が実体への更新として適用されます。

差分が出たら、原則としてコード側を実体に合わせます。実体を変えるのは取り込み完了後の別コミットです。取り込みと変更を同じapplyに混ぜると、事故が起きたときにどちらが原因か分かりません。

差分ゼロを確認したらapplyします。planに出た取り込み予定の件数と、applyの結果が一致するかを見ます。

terraform plan
terraform apply

applyのあとにstateとコードが一致していることを確かめる

apply後にもう一度planを走らせ、「No changes」を確認します。差分が出るなら、実体側に自動付与された属性が残っています。

取り込みが済んだらimportブロックは削除して構いません。残しても再実行はされませんが、設定ファイルに履歴として溜まると読みにくくなる。取り込み用のプルリクエストをマージした直後の別コミットが、扱いやすい削除単位です。

複数人で触るリポジトリなら、この一連をCIに載せてください。手元でplanとapplyを回す運用だと、stateのロックが効かず取り込み中に他の変更が挟まります。パイプラインの組み方はTerraformとGitHub ActionsでAWSのCI/CDを構築する手順|OIDC・S3ロック対応で扱っています。

terraform planの差分が消えないときに疑う箇所と潰し方

取り込みで時間を食うのはここです。原因は型に分かれます。

既定値・computed属性・タグの差分は何を直せば消えるのか

最も多いのは、実体に付いていてコードに書いていない属性です。コンソールで作ると自動的に入る設定や、他のツールが後から付けたタグが該当します。planの出力で値が消える形で出ていれば、コード側に書き足せば消えます。

逆に、コードに書いた値が実体と食い違って更新扱いになる場合もある。生成されたHCLに実体の現在値がそのまま入っていて、その値が別の属性から導出される種類だったケースです。該当行を削除し、プロバイダに計算させます。

組織のポリシーで自動付与されるタグは、書き足しても次回のplanでまた差分になります。付与元がTerraformの外にあるためです。この場合はlifecycleブロックのignore_changesで該当キーを除外する。除外は列挙で絞り、ブロック全体を無視対象にしないでください。何が管理外か分からなくなります。

生成HCLに残るread-only属性と参照のハードコードを整理する

生成された設定には、書き込みできない属性が混じることがあります。実体の状態を読むためだけの項目で、コードに書くとplanでエラーになるか恒常的な差分として残る。プロバイダのドキュメントでArgumentかAttributeかを確認し、Attribute側なら削除します。

もう1つは参照のハードコードです。生成された設定には他リソースのIDが文字列として直接入る。そのリソースも同じstateで管理するなら、参照式に置き換えてください。依存順序をTerraformが解決でき、後々の作り替えで効いてきます。

差分が構造的に消えない場合、プロバイダのバージョン差を疑う番です。取り込んだあとのstateそのものの扱い(保存先・ロック・移動と削除)は、Terraform stateの構造とS3バックエンド・移動削除の安全手順にまとめています。古いプロバイダで作られた実体を新しいスキーマで読むと、属性の位置が変わっている場合があります。required_providersで固定し、取り込み中は上げないでください。

複数リソースをまとめて取り込むfor_eachとidentityの書き方

1本ずつ書く方式は、10本を超えたあたりで割に合いません。展開の書き方を覚えると、棚卸しの結果をそのままコードにできます。

for_eachでimportブロックを展開する書き方と1.7系からの対応

importブロックのfor_eachは1.7系で追加されました。CHANGELOGの記述は「import: for_each can now be used to expand the import block to handle multiple resource instances」。localsに対応表を置き、キーをアドレス側、値をID側に流します。

locals {
  buckets = {
    assets = "issoh-legacy-assets"
    backup = "issoh-legacy-backup"
  }
}

import {
  for_each = local.buckets
  to       = aws_s3_bucket.this[each.key]
  id       = each.value
}

resource "aws_s3_bucket" "this" {
  for_each = local.buckets
}

toにはモジュール内のアドレスも書けます。公式のモジュール向け例は module.group[each.value.group].aws_s3_bucket.this[each.value.key] の形で、既にモジュール化された構成へ後から実体を流し込む場合はこれになる。なお1.12系では、for_each式が取り込み先そのものを参照できないよう修正が入りました。対応表はlocalsやvariableに置いてください。

identity属性でIDの文字列組み立てから離れる(1.12系で追加)

identityは1.12系で追加された引数です。CHANGELOGには「import blocks: Now support importing a resource via a new identity attribute. This is mutually exclusive with the id attribute」とある。idが単一の文字列なのに対し、identityはキーと値の組でリソースを特定します。

import {
  to = aws_s3_bucket.legacy_assets
  identity = {
    account_id = "123456789012"
    region     = "ap-northeast-1"
    bucket     = "issoh-legacy-assets"
  }
}

効いてくるのは、IDが複合キーになるリソースです。区切り文字の順序を間違えると通らない、という種類の失敗が消えます。どの範囲の実体かをコード上で読み取れる点も違いになります。

idとidentityは排他なので、両方は書けません。プロバイダが対応していればidentity、していなければidという順で試すと迷わない。実行環境が1.12系より前ならidentityは使えないため、idで組み立てます。

terraform importに失敗したときの戻し方と事前の備え

取り込みは、間違えると実体を消しうる操作です。戻し方を先に用意してから始めてください。

state rmとremovedブロックで取り込みを取り消す手順

取り込み先や対象を取り違えたときは、stateから外します。実体には触れず、管理対象から抜くだけの操作です。

terraform state rm aws_s3_bucket.legacy_assets

コードで宣言したい場合はremovedブロックを使います。1.7系で追加された記法で、CHANGELOGには「can inform Terraform whether the corresponding object should be deleted or simply removed from state」とある。destroyをfalseにすれば、実体を残したまま管理から外れます。

removed {
  from = aws_s3_bucket.legacy_assets
  lifecycle {
    destroy = false
  }
}

この宣言はレビューに残るぶん、コマンド一発より安全です。destroyの値を読み違えると実体が消えるため、この行は指差し確認の対象にしてください。

applyで既存リソースを壊さないための確認手順と権限の分離

実体が壊れる経路は3つです。差分が残ったままapplyした、同じ実体を2つのアドレスに紐づけた、削除を伴う変更に気づかなかった。

2つ目は公式が明示的に警告しています。「If you import the same object multiple times, Terraform may exhibit unwanted behavior.」とあるとおり、1つのリモートオブジェクトは1つのリソースアドレスにだけ結び付けてください。for_each展開時のキー重複は、取り込み前に確認します。

備えは3つ。作業前のstateバックアップ、取り込み専用ブランチでのplan結果レビュー、本番のapply権限を作業者と分けることです。削除を伴う操作はplanの出力に destroy の件数が出るので、この数字が0であることをapplyの承認条件にしておきます。

stateの保管場所も見直しどきです。ローカルに置いたまま複数人で取り込むとロックが効かず上書きが起きるため、リモートバックエンドかSaaS側に寄せてください。機能と料金はHCP Terraformとは?旧Terraform Cloudの料金・機能とHCPでの位置づけ|使い方も解説で整理しています。

既存リソースを取り込む場面と、作り直しに倒す場面を条件で分ける

取り込みは万能ではありません。手を動かす前に、どちらが速いか見積もってください。

既存リソースの取り込みを採用してよい条件を3つの観点で言い切る

取り込みを選んでよいのは、次の3条件が揃う場合です。第一に、対象を停止できないこと。稼働中のデータベースやストレージのように、作り直しがサービス断や移行作業を伴うなら取り込み一択になります。

第二に、実体の設定が複雑で再現に時間がかかること。手動で積み上げたセキュリティグループのルールやIAMポリシーは、目視で写すと抜けます。生成フラグで書き出させたほうが正確です。

第三に、同じ構成が今後も増えること。1回きりの箱なら管理下に置く価値は薄いものの、同型を追加する予定があるなら、取り込んでモジュール化すると以降が速くなります。3つのうち2つ以上に当てはまるなら、取り込みで進めてください。範囲の切り方はプロビジョニングとは?サーバー・ユーザー・クラウドの種類と自動化(IaC)の判断まで実装者向けに解説で扱っています。

取り込まずに作り直したほうが早く終わるケースの見分け方と目安

見送ってよい条件も明確です。対象が停止できて、再作成が数分で終わり、依存する実体が2つ以下。この3つが揃うなら、取り込みの手間より作り直しのほうが短時間で済みます。検証環境のインスタンスやテスト用のバケットが典型です。

実体の設定そのものを見直したい場合も作り直しに倒します。取り込みは現状をそのままコードにする操作で、直したい設定まで固定してしまうためです。命名規則やネットワーク構成を引き直す要求が同時にあるなら、新しく作って切り替えるほうが早く終わります。

判断が付かないときは、対象1本で取り込みを試し、差分ゼロまでの時間を測ってください。1本で30分を超えるなら、同種が10本で5時間の作業になる計算です。手動運用からコード管理へ切り替える範囲そのものを設計し直す段階なら、インフラ構築(AWS・Google Cloud・Azure)で現行構成の棚卸しから対応しています。

取り込みを始める前にツールをTerraformで固定するか決める

ライセンス面でOpenTofuを検討しているなら、作業の途中で乗り換えるより先に決めたほうが安全です。差分はOpenTofuとは?Terraformとのライセンス・機能差と移行判断を実装視点で解説にまとめました。実体の払い出しはTerraform、OS内部の設定は別ツールという切り分けにするかも、この時点の論点です。選定条件は構成管理ツールの比較と選び方|Ansible・Terraform・Chef・Puppet・OpenTofuの選定条件で比較しています。

よくある質問

terraform importコマンドは廃止されましたか?

廃止されていません。1.5系のCHANGELOGにも「The existing terraform import CLI command has not been modified.」と記載があり、v1.15系のドキュメントにも説明が残っています。ただし公式の推奨はimportブロック側です。新規の作業ではブロックを選んでください。

importブロックはどのバージョンから使えますか?

importブロック本体と -generate-config-out フラグは1.5系、for_eachによる展開は1.7系、identity引数は1.12系からです。2026年8月13日時点の最新安定版は v1.15.8(2026-07-08公開)で、v1.16.x がベータ、開発中のCHANGELOGは 1.17.0 となっています。使いたい機能に実行環境のバージョンが足りているかを先に確認してください。

生成された設定はそのまま使ってよいですか?

レビューを前提にしてください。公式も「Before applying, review the generated configuration and edit it as necessary.」としています。生成物には既定値と同じ属性や読み取り専用の項目が混じり、複雑なスキーマでは有効な設定を組み立てられない場合もある。下書きとして扱い、不要な行を削ってからplanで確認する流れが確実です。

取り込んだあとにplanの差分が消えません。どうしますか?

差分の種類で切り分けます。コードに無い属性が実体にあるなら書き足す、導出される値を書いているなら削除する、外部から自動付与されるタグならignore_changesで除外する、という順です。それでも消えないときはプロバイダのバージョン差を疑い、required_providersで固定して再確認してください。

取り込みを間違えたとき、実体は消えてしまいますか?

stateから外すだけなら実体は残ります。terraform state rm、またはdestroyをfalseにしたremovedブロックを使ってください。実体が消えるのは、差分が残ったままapplyして削除を伴う変更が適用された場合です。planの出力でdestroyの件数が0であることを確認してからapplyすれば、この経路は塞げます。

関連記事

資料請求

RELATED POSTS 関連記事