状況
深夜の作業中、新しいモジュールのVPC IDやARNを別のリソースに渡すよう設定して terraform plan を実行したところ、こんなエラーが出た:
Error: Reference to undeclared output value
on main.tf line 14, in resource "aws_instance" "app":
14: subnet_id = module.network.subnet_id
An output value with the name "subnet_id" has not been declared in module.network.
モジュールは存在する。リソースも存在する。それでもTerraformは動かない — 参照しようとしているoutputが見つからないのだ。
なぜこのエラーが起きるのか
Terraformのモジュールはブラックボックスだ。外部に公開できる値は、モジュール内で明示的に output ブロックとして宣言されたものだけだ。 module.network.subnet_id を参照すると、TerraformはNetworkモジュールのディレクトリ内で output "subnet_id" ブロックを探す。ブロックがなければ値もなく、このエラーだけが残る。
主な原因はいくつかある:
- 呼び出し元モジュールに参照を追加したが、子モジュールに
outputブロックを宣言し忘れた。 - outputブロックの名前を変更したが、それを参照しているすべての箇所を更新しなかった。
- 別の場所からモジュールをコピーしたが、このバージョンには期待していたoutputが存在しない。
- 名前が微妙に違う — たとえば
subnet_ids(複数形)とsubnet_id(単数形)のような違い。 terraform_remote_stateでワークスペースをまたいでoutputを取得しようとしているが、そのoutputがソーススタックで宣言されていない。
ステップ1 — モジュールが実際に宣言しているoutputを確認する
まずモジュールの場所を特定する。ローカルモジュールの場合、エラーメッセージにヒントがある — module.network なら modules/network/ を確認する:
ls modules/network/
grep -r 'output' modules/network/
レジストリやGitから取得したモジュールの場合、キャッシュされたダウンロード先を確認する:
find .terraform/modules -name '*.tf' | xargs grep '^output'
モジュールが実際に持つすべての output ブロックが表示される。そのリストと参照している名前を比較すれば、不一致がすぐにわかるはずだ。
不足しているoutputブロックを追加する
モジュールを自分で管理している場合、修正は簡単だ。モジュールディレクトリ内にoutputを追加する — 通常は modules/network/outputs.tf :
output "subnet_id" {
description = "The ID of the primary subnet"
value = aws_subnet.main.id
}
再度 terraform plan を実行すれば、エラーは消える。
複数のサブネットを公開したい場合は、リスト形式のoutputを使う:
output "subnet_ids" {
description = "List of all subnet IDs"
value = aws_subnet.main[*].id
}
呼び出し元の設定からインデックスで参照する:
subnet_id = module.network.subnet_ids[0]
outputは存在するが名前が違う場合
何かを編集する前に、モジュールが実際に公開している値を確認しよう。Terraformコンソールが便利だ:
terraform console
> module.network
モジュールの利用可能なoutputがすべて表示される。ほとんどの場合、名前の不一致がすぐに見つかる — あとは参照を修正するだけだ:
# 誤り
subnet_id = module.network.subnet_id
# 正しい — outputブロックは "private_subnet_id" という名前
subnet_id = module.network.private_subnet_id
リモートステート:ソーススタックにoutputが存在しない場合
terraform_remote_state を使って別のワークスペースからoutputを読み込もうとしている場合、そのoutputはソーススタックで宣言され、かつapply済みである必要がある。
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-tf-state"
key = "vpc/terraform.tfstate"
region = "us-east-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id
}
VPCスタックの outputs.tf にこれが必要だ:
output "subnet_id" {
value = aws_subnet.main.id
}
まずVPCスタックをデプロイすること。Terraformはステートファイルから読み込むため、outputがapplyされていなければ存在しない。 .tf ファイルに記述するだけでは不十分だ。
パブリックレジストリのモジュール:そのバージョンにoutputが存在しない場合
古いバージョンのモジュールを固定している場合、目的のoutputがまだ存在しないことがある。 terraform-aws-modules でよくある問題で、バージョン3.xと5.xでは公開されているoutputが異なる。
module "network" {
source = "terraform-aws-modules/vpc/aws"
version = "3.0.0" # 必要なoutputが存在しないバージョン
}
モジュールのchangelogを確認するか、GitHubで該当タグの outputs.tf を参照しよう。必要なoutputが新しいバージョンに存在するなら、バージョンを上げる:
module "network" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
}
そして更新を取得する:
terraform init -upgrade
修正を確認する
以下の3つのコマンドを順番に実行する:
# まず構文エラーを確認
terraform validate
# エラーなしで完了するはず
terraform plan
# apply後にルートレベルのoutputをすべて確認
terraform output
applyする前に特定のモジュール参照を確認したい場合は、コンソールを使う:
terraform console
> module.network.subnet_id
値(または既知のプレースホルダー)が返ってくれば、参照は正しく接続されている。
クイックリファレンス
- outputが別のファイルに宣言されている — モジュールディレクトリ内のどの
.tfファイルにoutputブロックがあっても問題ない。モジュールディレクトリ内のどこかに存在していればよい。 - センシティブなoutput —
sensitive = trueを付けてもoutputの参照は壊れない。Terraformはplanの出力で値を非表示にするだけだ。 - countまたはfor_eachを使うモジュール — outputの形式が変わる。
module.network.subnet_idの代わりにmodule.network[0].subnet_idとして参照すること。

