この記事は、HashiCorp Japan Advent Calendar 2025 7 日目の記事です。
Terraform search とは?
- Terraform v1.14 で一般利用可能になった新機能
- Terraform で管理されていないアンマネージドなリソースの検出およびインポートを支援
- Terraform CLI と HCP Terraform どちらも利用可能
何が課題だったか?
特にクラウドへの移行の初期段階などでは、IaC を導入せずに CLI やコンソールを使って手動でリソースが作成・管理されることがあります (ClickOps と呼ばれる)。
その結果として起こるのが
- インフラの利用状況がトラッキングできないことによる無駄なクラウド支出の増加
- 誤設定や脆弱な設定によるセキュリティリスク
- 変更履歴を追えない・予期せぬ環境差分によるデプロイの失敗、インシデントの発生
- コンプライアンス、組織のポリシー、規制要件に違反するリソースの出現
などです。
どうやって解決するか?
これらの課題に対して、理想的なあるべき姿は、すべてのリソースが最初から完全に Terraform の管理下に置かれ、宣言的な定義に基づいて自動化されたワークフローによって変更管理されることですが、現実問題それは難しいでしょう。
そこで必要となるのは、管理されていないリソースを自動的に発見し、Terraform の管理下に置くようなツールです。Terraform search がまさにこれで、実際のクラウド環境をスキャンして管理されていないリソースを検出し、Terraform state にインポートするリソース定義の記述を支援してくれます。
実際に使ってみよう
前提条件
v1.14 以上の Terraform を使用してください。HCP Terraform の場合は、ワークスペースの Settings > General から変更してください。
クエリの実行
まずは、探索したいリソースのプロバイダーを定義します。これは従来の Terraform と変わりありません。また、以下では HCP Terraform の設定も記載していますが、ローカルで実行する場合はなくても OK です。
terraform {
cloud {
organization = "xxxxx"
workspaces {
name = "terraform-search-test"
}
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.26.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
次に、探索するリソースを定義するための *.tfquery.hcl という拡張子を持つファイルを作成します。最小限の定義は以下です。
list "aws_vpc" "example" {
provider = aws
}
Terraform search では、対象のリソースを list ブロックで定義します。
list "<TYPE>" "<LABEL>"
<TYPE>: 検索対象のリソース種別<LABEL>: 検出したリソースのラベル名
Terraform search をサポートしているリソース種別は、各プロバイダーのドキュメントで確認できます (例: aws_vpc)。ラベルは従来の Terraform の使い方と同じで、参照に使用するためユニークな値を指定する必要があります。
ファイルを作成したら、以下のコマンドで検索を実行します。
terraform query
結果は以下のような出力になります。この例では、1つの VPC を検出しました。
% terraform query
Running query in HCP Terraform. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the query running remotely.
Preparing the remote query run...
To view this query run in a browser, visit:
https://app.terraform.io/app/xxxxx/terraform-search-test/search/qry-DgxrByW2mbEjHYFo
Waiting for the query run to start...
Terraform v1.14.3
on linux_amd64
Initializing plugins and modules...
list.aws_vpc.example account_id=9092xxxxxxxx,id=vpc-0b67d6a8a7ec55e6c,region=ap-northeast-1 project-vpc (vpc-0b67d6a8a7ec55e6c)
検索対象のリソースは、config パラメータを指定することで細かく絞り込むことができます。次に、VPC サブネットを検索してみましょう。
list "aws_subnet" "example" {
provider = aws
config {
filter {
name = "availability-zone"
values = ["ap-northeast-1a"]
}
}
}
filter 条件で ap-norhteast-1a のサブネットのみを検索します。この VPC には合計 6 つのサブネットがありますが、1a のサブネットのみが検出されていることが以下の出力から分かります。
Waiting for the query run to start...
Terraform v1.14.3
on linux_amd64
Initializing plugins and modules...
list.aws_subnet.example account_id=9092xxxxxxxx,id=subnet-007dbf7f675e3233a,region=ap-northeast-1 project-subnet-private1-ap-northeast-1a (subnet-007dbf7f675e3233a)
list.aws_subnet.example account_id=9092xxxxxxxx,id=subnet-0a42f326856afb9d1,region=ap-northeast-1 project-subnet-public1-ap-northeast-1a (subnet-0a42f326856afb9d1)
config で指定可能なパラメータはプロバイダー/リソースごとに異なるのでドキュメントを参照してください。aws_subnet の場合は、主に filter で絞ることになります。filter で指定可能なパラメータはさらに CLI のリファレンスから確認します。
HCP Terraform を利用する場合
ワークスペースに、Search & Import というメニューが増えています。CLI から実行した terraform query の実行結果が閲覧でき、検出されたリソースの一覧が表示されています。
また、右上の + New query ボタンで UI からクエリを実行することもできます。

リソースのインポート
terraform import コマンドを使用すると、手動で作成したリソースを Terraform の管理下に置くことができます。この機能自体は以前からあるものですが、Terraform search によってリソースの検出からインポートまでのワークフローをシームレスに実現することができるようになりました。
クエリによって検出されたリソースを選択して、右上の Generate Starter Configuration ボタンをクリックすると、import ブロックとリソース定義が自動生成されます。
main.tf ファイルを作成して、Starter Configuration として生成されたコードをコピペします。
import {
to = aws_subnet.example_0
provider = aws
identity = {
account_id = "9092xxxxxxxx"
id = "subnet-007dbf7f675e3233a"
region = "ap-northeast-1"
}
}
resource "aws_subnet" "example_0" {
provider = aws
assign_ipv6_address_on_creation = false
availability_zone = "ap-northeast-1a"
availability_zone_id = "apne1-az4"
cidr_block = "10.0.128.0/20"
customer_owned_ipv4_pool = null
enable_dns64 = false
enable_lni_at_device_index = 0
enable_resource_name_dns_a_record_on_launch = false
enable_resource_name_dns_aaaa_record_on_launch = false
ipv6_cidr_block = null
ipv6_native = false
map_customer_owned_ip_on_launch = false
map_public_ip_on_launch = false
outpost_arn = null
private_dns_hostname_type_on_launch = "ip-name"
region = "ap-northeast-1"
tags = {
Name = "project-subnet-private1-ap-northeast-1a"
}
tags_all = {
Name = "project-subnet-private1-ap-northeast-1a"
}
vpc_id = "vpc-0b67d6a8a7ec55e6c"
timeouts {
create = null
delete = null
}
}
import {
to = aws_subnet.example_1
provider = aws
identity = {
account_id = "9092xxxxxxxx"
id = "subnet-0a42f326856afb9d1"
region = "ap-northeast-1"
}
}
resource "aws_subnet" "example_1" {
provider = aws
assign_ipv6_address_on_creation = false
availability_zone = "ap-northeast-1a"
availability_zone_id = "apne1-az4"
cidr_block = "10.0.0.0/20"
customer_owned_ipv4_pool = null
enable_dns64 = false
enable_lni_at_device_index = 0
enable_resource_name_dns_a_record_on_launch = false
enable_resource_name_dns_aaaa_record_on_launch = false
ipv6_cidr_block = null
ipv6_native = false
map_customer_owned_ip_on_launch = false
map_public_ip_on_launch = false
outpost_arn = null
private_dns_hostname_type_on_launch = "ip-name"
region = "ap-northeast-1"
tags = {
Name = "project-subnet-public1-ap-northeast-1a"
}
tags_all = {
Name = "project-subnet-public1-ap-northeast-1a"
}
vpc_id = "vpc-0b67d6a8a7ec55e6c"
timeouts {
create = null
delete = null
}
}
コードの中身を確認して、既存のリソース設定に合うように修正します。
ちなみにこのまま terraform plan を実行すると以下のエラーが発生しました。
│ Error: Missing required argument
│
│ with aws_subnet.example_0,
│ on main.tf line 24, in resource "aws_subnet" "example_0":
│ 24: map_customer_owned_ip_on_launch = false
│
│ "map_customer_owned_ip_on_launch": all of
│ `customer_owned_ipv4_pool,map_customer_owned_ip_on_launch,outpost_arn` must
│ be specified
╵
╷
│ Error: Conflicting configuration arguments
│
│ with aws_subnet.example_0,
│ on main.tf line 14, in resource "aws_subnet" "example_0":
│ 14: availability_zone = "ap-northeast-1a"
│
│ "availability_zone": conflicts with availability_zone_id
╵
╷
│ Error: Conflicting configuration arguments
│
│ with aws_subnet.example_0,
│ on main.tf line 15, in resource "aws_subnet" "example_0":
│ 15: availability_zone_id = "apne1-az4"
│
│ "availability_zone_id": conflicts with availability_zone
╵
╷
│ Error: enable_lni_at_device_index must not be zero, got 0
│
│ with aws_subnet.example_0,
│ on main.tf line 19, in resource "aws_subnet" "example_0":
│ 19: enable_lni_at_device_index = 0
│
╵
以下の修正を行います。
-
map_customer_owned_ip_on_launchはデフォルト false なので消します -
availability_zoneとavailability_zone_idがコンフリクトしているので片方消します -
enable_lni_at_device_indexは 0 を指定することができないのですが、これも使わないので消します
再度 terraform plan を実行すると、エラーは消えましたがリソースの差分が出てしまっています。timeouts の部分は今回いらないのでこれも削除してしまいましょう。
Terraform will perform the following actions:
# aws_subnet.example_0 will be updated in-place
# (will be imported first)
~ resource "aws_subnet" "example_0" {
arn = "arn:aws:ec2:ap-northeast-1:9092xxxxxxxx:subnet/subnet-007dbf7f675e3233a"
assign_ipv6_address_on_creation = false
availability_zone = "ap-northeast-1a"
availability_zone_id = "apne1-az4"
cidr_block = "10.0.128.0/20"
customer_owned_ipv4_pool = null
enable_dns64 = false
enable_lni_at_device_index = 0
enable_resource_name_dns_a_record_on_launch = false
enable_resource_name_dns_aaaa_record_on_launch = false
id = "subnet-007dbf7f675e3233a"
ipv6_cidr_block = null
ipv6_cidr_block_association_id = null
ipv6_native = false
map_customer_owned_ip_on_launch = false
map_public_ip_on_launch = false
outpost_arn = null
owner_id = "9092xxxxxxxx"
private_dns_hostname_type_on_launch = "ip-name"
region = "ap-northeast-1"
tags = {
"Name" = "project-subnet-private1-ap-northeast-1a"
}
tags_all = {
"Name" = "project-subnet-private1-ap-northeast-1a"
}
vpc_id = "vpc-0b67d6a8a7ec55e6c"
+ timeouts {}
}
これで再度プランを実行すると、インポートだけになりました。terraform apply を実行すると、リソースがインポートされます。
Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
この状態で再度クエリを実行すると、先ほどのリソースの IaC Status が Managed になっていることがわかります。
今回は HCP Terraform の UI からコードを生成する例を紹介しましたが、ローカルで terraform query を実行する場合は、以下のオプションを付けることでファイルに出力することもできます。
terraform query -generate-config-out=subnets.tf
まとめ
Terraform search を利用することで、レガシーな IaC 管理されていない既存のリソースを簡単に Terraform 管理下に移行することができます。これは、大量のリソースが存在し、一からリソース定義を記述するには時間がかかるような大規模ワークロードで特に効果を発揮します。
プロバイダーによってはまだ対応していないリソースもありますが、今後どんどんサポートも増えていくと思うのでぜひ HCP Terraform と合わせて活用してみてください!



