Terraformのdata sourceで既存AWSリソースを参照する方法|aws_ami・aws_vpc・aws_subnetsのfilter設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > Terraformのdata sourceで既存AWSリソースを参照する方法|aws_ami・aws_vpc・aws_subnetsのfilter設計
「AWSのAMI IDやVPC IDをTerraformコードに直書きして、環境ごとにコピーしていませんか?」 ハードコードされたIDはAMIの定期更新で廃止されたとき、あるいはdev・stg・prodで別々のVPCを使うときに途端に管理しきれなくなります。Terraformにはこの問題を解決する仕組みとして data source(データソース)ブロックがあります。

この記事では、aws_ami・aws_vpc・aws_subnets・aws_caller_identity を使い、IDのハードコードをゼロにするポータブルなIaC設計を実践解説します。filterブロックの書き方、よくある「No results」エラーの対処まで、実際のterraform plan出力例を交えて解説します。

動作確認環境: Terraform 1.8.5 / AWS Provider 5.50.0(ap-northeast-1リージョン)

この記事のポイント

・data sourceはリソースを作らず既存の属性値をTerraformコードへ動的に取り込む仕組み
・aws_amiでmost_recent=trueとfilterを組み合わせると常に最新AMIを自動参照できる
・aws_vpc・aws_subnetsで既存VPCのサブネットIDを一覧取得しEC2やALBへ渡せる
・「No results」エラーはfilterのName値とAWSのタグ名の不一致が主な原因


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜdata sourceを使うのか?ハードコードの3つの問題点

Terraformのresourceブロックは「リソースを作成・管理する」定義です。一方、dataブロック(data source)は「すでに存在するリソースの情報を読み取るだけ」の仕組みです。terraform apply を実行しても、data sourceが参照するリソースの作成・変更・削除は一切行いません。

ハードコードの問題を具体的に見てみましょう。

# AMI IDとサブネットIDをベタ書き(問題のある書き方) resource "aws_instance" "web" { ami = "ami-0abcdef1234567890" # 廃止されてもすぐ気づかない instance_type = "t3.micro" subnet_id = "subnet-xxxxxxxx" # 環境ごとにコードを複製する羽目になる }

このコードには3つの問題があります。

AMI廃止リスク:AWSはOSのセキュリティパッチを適用した新しいAMIを定期的にリリースし、古いものを廃止します。廃止後にterraform applyすると「AMI not found」エラーになります
リージョン非互換:AMI IDはリージョンごとに異なります。東京(ap-northeast-1)のコードを大阪(ap-northeast-3)で使うと別の値が必要になります
環境ごとのコード複製:dev・stg・prodで別々のVPCとサブネットを使う場合、コードをコピーして値を書き換える手作業が発生します

data sourceを使えば、これらを動的に解決できます。

data sourceブロックの基本構文

1. resourceとdataの書き方の違い

# resource: Terraformが作成・管理する(tfstateに記録される) resource "aws_vpc" "new_vpc" { cidr_block = "10.0.0.0/16" } # data: すでにあるリソースを読み取るだけ(作成・変更・削除しない) data "aws_vpc" "existing" { filter { name = "tag:Name" values = ["production-vpc"] } } # data sourceへの参照は data.タイプ.名前.属性名 の形式 output "existing_vpc_cidr" { value = data.aws_vpc.existing.cidr_block }

2. filterブロックの書き方

filterブロックは、AWS APIのフィールド名(name)と絞り込む値(values)でリソースを特定します。

data "aws_ami" "example" { most_recent = true owners = ["amazon"] # filterは複数書けばAND条件で絞り込まれる filter { name = "name" # AMIの名前フィールド(ワイルドカード可) values = ["al2023-ami-*"] } filter { name = "architecture" values = ["x86_64"] # valuesの中はOR条件 } }

filterのname(小文字)には、AWSコンソールや aws ec2 describe-images コマンドで確認できるフィールド名を使います。タグで絞る場合は `tag:タグキー名` の形式にします(例:`tag:Environment`)。複数のfilterブロックはAND条件で評価されます。

aws_amiで最新AMIを動的取得する

1. Amazon Linux 2023の最新AMIを自動参照する

# main.tf data "aws_ami" "amzn2023" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-2023*-x86_64"] } filter { name = "virtualization-type" values = ["hvm"] } } resource "aws_instance" "web" { ami = data.aws_ami.amzn2023.id # data sourceで動的参照 instance_type = "t3.micro" tags = { Name = "web-server" } }

terraform planを実行すると、data sourceの読み取り結果が最初に表示されます。

# terraform plan の実行例(ap-northeast-1リージョン) $ terraform plan data.aws_ami.amzn2023: Reading... data.aws_ami.amzn2023: Read complete after 1s [id=ami-0b5c74e235407b044] Terraform will perform the following actions: # aws_instance.web will be created + resource "aws_instance" "web" { + ami = "ami-0b5c74e235407b044" + instance_type = "t3.micro" + tags = { + "Name" = "web-server" } ... } Plan: 1 to add, 0 to change, 0 to destroy.

data sourceはplan時に実行され、取得したAMI IDがamiフィールドに自動でセットされます。次回以降のterraform planでAMIが更新されていれば、そのplan実行のタイミングで自動的に最新のIDに切り替わります。

2. Ubuntu 24.04 LTSの最新AMIを取得する

Ubuntuの場合はownersにCanonicalのAWSアカウントIDを指定します。

data "aws_ami" "ubuntu_2404" { most_recent = true owners = ["099720109477"] # CanonicalのAWSアカウントID(固定値) filter { name = "name" values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] } filter { name = "virtualization-type" values = ["hvm"] } }

既存VPCとサブネットをdata sourceで参照する

1. aws_vpcで既存VPCをタグ名で参照する

Terraform管理外の既存VPC(マネジメントコンソールや別のTerraformプロジェクトで作成したVPC)を参照する場合はaws_vpcを使います。

# Nameタグで既存VPCを参照 data "aws_vpc" "production" { filter { name = "tag:Name" values = ["production-vpc"] } } # 取得した属性値の参照例 output "vpc_id" { value = data.aws_vpc.production.id } output "vpc_cidr" { value = data.aws_vpc.production.cidr_block }

2. aws_subnetsで複数サブネットIDを一括取得する

ECSやALBのマルチAZ構成では複数のサブネットIDが必要です。aws_subnets(複数形)を使うと、条件に一致するすべてのサブネットIDをリストで取得できます。

# VPC内のプライベートサブネットIDをすべて取得 data "aws_subnets" "private" { filter { name = "vpc-id" values = [data.aws_vpc.production.id] } filter { name = "tag:Type" values = ["private"] } } # ALBにサブネットIDリストをそのまま渡す resource "aws_lb" "app" { name = "app-alb" internal = true load_balancer_type = "application" subnets = data.aws_subnets.private.ids # IDリストを直接渡せる }

3. aws_vpc・aws_subnets・aws_amiを組み合わせてIDハードコードゼロにする

3つを組み合わせると、すべてのIDをdynamic参照にできます。

# 既存VPCをタグで参照 data "aws_vpc" "main" { filter { name = "tag:Name" values = ["main-vpc"] } } # 最新Amazon Linux 2023 AMIを動的取得 data "aws_ami" "amzn2023" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-2023*-x86_64"] } } # パブリックサブネットを取得 data "aws_subnets" "public" { filter { name = "vpc-id" values = [data.aws_vpc.main.id] } filter { name = "tag:Type" values = ["public"] } } # EC2インスタンス(IDハードコードなし) resource "aws_instance" "web" { ami = data.aws_ami.amzn2023.id instance_type = "t3.micro" subnet_id = data.aws_subnets.public.ids[0] tags = { Name = "web-server" } }

このコードはリージョンを変えるだけで別環境に適用できます。AMIが更新されても次のterraform planで自動的に最新IDを参照します。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、data sourceを活用したポータブルなIaC設計から環境分離・モジュール設計まで、ハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

便利なdata source一覧(aws_caller_identity・aws_region・aws_availability_zones)

AMIやVPC以外にも頻繁に使う便利なdata sourceがあります。

1. aws_caller_identityでAWSアカウントIDを取得する

IAMポリシーやS3バケットポリシーで自分のAWSアカウントIDが必要な場合に使います。

data "aws_caller_identity" "current" {} # S3バケットポリシーでアカウントIDをハードコードしない resource "aws_s3_bucket_policy" "example" { bucket = aws_s3_bucket.example.id policy = jsonencode({ Statement = [{ Effect = "Allow" Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" } Action = "s3:GetObject" Resource = "${aws_s3_bucket.example.arn}/*" }] }) }

2. aws_regionとaws_availability_zonesでリージョン・AZ情報を取得する

# 現在のリージョン名を取得(マルチリージョン展開で使う) data "aws_region" "current" {} # 利用可能なAZを全取得(stateでフィルタリング可能) data "aws_availability_zones" "available" { state = "available" } # 先頭2つのAZにサブネットを作成(AZ名のハードコードなし) resource "aws_subnet" "public_a" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = data.aws_availability_zones.available.names[0] } resource "aws_subnet" "public_c" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" availability_zone = data.aws_availability_zones.available.names[1] }

これでAZ名をハードコードせずに、どのリージョンでも同じコードが動くようになります。

トラブルシュート・エラー対処

「Your query returned no results」が出た時の対処法

filterの条件に一致するリソースが1件もない場合に発生します。

$ terraform plan # エラー出力例 Error: Your query returned no results. Please change your search criteria and try again. with data.aws_ami.amzn2023, on main.tf line 1, in data "aws_ami" "amzn2023": 1: data "aws_ami" "amzn2023" {

調査手順は以下です。

AWS CLIで同じ条件を検証する:aws ec2 describe-images --owners amazon --filters "Name=name,Values=al2023-ami-2023*-x86_64" を実行して実際に返ってくる値を確認する
filter.name値を見直す:タグフィルターは大文字小文字を区別する。`tag:Environment` のEnvironmentは実際のタグキーと完全一致が必要
ワイルドカードの確認:values内の `*` はAWS APIのパターンマッチング構文で正規表現ではない

注意: filterのname(小文字)とvaluesの大文字小文字を混同するミスが本番環境でも頻繁に起きます。AWSコンソールで実際のタグ名を確認してから書くのが確実です。

「Your query returned more than one result」が出た時の対処法

複数のリソースが条件に一致し、1件に絞れない場合に発生します。aws_ami以外のdata sourceで多いエラーです。

$ terraform plan # エラー出力例 Error: Your query returned more than one result. Please try a more specific search criteria. with data.aws_vpc.main, on main.tf line 10, in data "aws_vpc" "main": 10: data "aws_vpc" "main" {

対処法は条件を絞り込むことです。

・filterを追加して候補を1件に絞る(Nameタグに加えてEnvironmentタグも絞り込む等)
・VPC IDが固定であれば id = "vpc-xxxxx" で直接指定する方が確実
・複数件が想定される場合はaws_vpcs(複数形のdata source)を使いids属性でIDリストを取得する

data sourceが古いキャッシュを返している疑いがある時

Terraformはplan実行時にdata sourceを再読み込みしますが、AWSリソースを外部で変更した直後に反映されないケースがあります。その場合は `-refresh-only` で強制的に最新状態に同期します。

# stateを最新のAWS状態に同期する(リソースの変更はしない) $ terraform plan -refresh-only $ terraform apply -refresh-only

本記事のまとめ

Terraformのdata sourceを使ったハードコード排除の設計ポイントをまとめます。
やりたいこと 使うdata source
最新AMIを動的取得する data "aws_ami" { most_recent = true }
既存VPCをタグで参照する data "aws_vpc" { filter { name = "tag:Name" } }
複数サブネットIDを一括取得する data "aws_subnets" { filter { name = "vpc-id" } }
AWSアカウントIDを取得する data "aws_caller_identity" "current" {}
現在のリージョン名を取得する data "aws_region" "current" {}
利用可能なAZを全取得する data "aws_availability_zones" { state = "available" }
data sourceを使うと、AMIの更新・VPCの再構築・マルチリージョン展開に対してコードを変更せず対応できます。resourceブロックが「何を作るか」の定義であるのに対し、data sourceは「何がすでにあるか」をTerraformコードに取り込む仕組みです。この2つを使い分けることが、長期間メンテナンスできるIaC設計の基本です。

TerraformのHCL変数設計|variable・locals・output・data sourceで構成を整理する方法
Terraformのmoduleとfor_each・countで構成を再利用する設計パターン
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Terraformのdata source活用・モジュール分離・マルチ環境管理まで、実機を使ったハンズオン形式のセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。