1. 概要
この記事では、以下の内容を解説します。
countメタ引数の基本構文とcount.indexの使い方countで作成したリソースの参照方法(スプラット式)countが安全に使えるケース・使ってはいけないケースcountを使った条件付きリソース作成パターン(0/1制御)for_eachとの違いと使い分けの基準- よくあるエラーと対処法
countは、同じリソースを指定した個数分作成するメタ引数です。シンプルな構文が特徴ですが、中間要素を削除したときにインデックスがずれて全リソースが再作成されるという重大な問題があります。本番環境での複数リソース作成には原則for_eachを使い、countは「リソースの有無を0/1で制御するだけ」のケースに限定することを推奨します。
2. countとは
countはリソース・モジュールブロック内で使えるメタ引数です。number型の値を渡すと、その数だけリソースが作成されます。
resource "aws_iam_user" "deploy" {
count = 3 # → aws_iam_user.deploy[0] / [1] / [2] が作成される
name = "deploy-user-${count.index}" # count.indexは 0, 1, 2
}
作成されたリソースはインデックス(0始まりの整数)で管理されます。これがcount最大の特徴であり、問題点でもあります。
3. 基本構文とcount.index
resource "<リソースタイプ>" "<リソース名>" {
count = <number> # 作成する個数
# count.index でループの現在のインデックスを参照(0始まり)
<引数> = "prefix-${count.index}"
}
count.indexの使い方
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
variable "server_names" {
description = "作成するサーバー名のリスト"
type = list(string)
default = ["web", "app", "db"]
}
resource "aws_instance" "servers" {
count = length(var.server_names) # リストの要素数 = 3
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
tags = {
Name = var.server_names[count.index] # [0]→"web" / [1]→"app" / [2]→"db"
Environment = "dev"
ManagedBy = "terraform"
}
}
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
Terraformは内部で以下のようにリソースを管理します。
aws_instance.servers[0] → Name="web"
aws_instance.servers[1] → Name="app"
aws_instance.servers[2] → Name="db"
4. countで作ったリソースの参照方法
特定インデックスのリソースを参照する
# [インデックス番号] で特定のリソースを参照
output "first_instance_id" {
description = "最初のEC2インスタンスのID"
value = aws_instance.servers[0].id
}
スプラット式([*])で全要素をリスト化する
# [*].属性名 で全リソースの属性をリストとして取得
output "all_instance_ids" {
description = "全EC2インスタンスのIDリスト"
value = aws_instance.servers[*].id
# → ["i-abc123", "i-def456", "i-ghi789"]
}
output "all_private_ips" {
description = "全EC2インスタンスのプライベートIPリスト"
value = aws_instance.servers[*].private_ip
}
5. countの問題点:中間要素削除でインデックスがずれる
countの最大の問題は、リストの中間要素を削除するとインデックスがずれて、関係のないリソースまで再作成されることです。
問題の再現
# 変更前: ["web", "app", "db"] の3台
variable "server_names" {
default = ["web", "app", "db"]
}
# Stateの状態
aws_instance.servers[0] → "web"
aws_instance.servers[1] → "app"
aws_instance.servers[2] → "db"
ここで "app" を削除します。
# 変更後: "app"を削除 → ["web", "db"]
variable "server_names" {
default = ["web", "db"]
}
# terraform planの出力(期待と実際が異なる)
~ aws_instance.servers[1] # "app" → "db" に変更(タグが書き換えられる)
- aws_instance.servers[2] # "db" が削除される ← 消したくないのに!
"app"だけを削除したかったのに、"db"まで影響を受けます。 インデックス[1]が"app"→"db"に変わり、[2]だった"db"は削除されるためです。
⚠️ このリスクがある限り、本番環境の複数リソース管理に
countは使わないことを強く推奨します。 代わりにfor_eachを使ってください。
6. countが安全に使えるケース
countが安全なのは、中間要素の削除が発生しないケース、つまり「リソースを作るか作らないか(0か1か)だけを制御する」場合です。
パターン1: 条件付きリソース作成(最頻出パターン)
variable "enable_bastion" {
description = "踏み台サーバーを作成するか"
type = bool
default = false
}
resource "aws_instance" "bastion" {
count = var.enable_bastion ? 1 : 0 # trueなら1台、falseなら0台
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
tags = {
Name = "bastion"
Environment = var.environment
ManagedBy = "terraform"
}
}
# 作成されたbastionのIPを出力(countが0のときはnull)
output "bastion_ip" {
description = "踏み台サーバーのパブリックIP(作成していない場合はnull)"
value = var.enable_bastion ? aws_instance.bastion[0].public_ip : null
}
パターン2: 環境による有無の切り替え
variable "environment" {
type = string
}
# 本番環境のみCloudWatchアラームを作成
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
count = var.environment == "prd" ? 1 : 0
alarm_name = "prd-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 80
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
# ✅ 本番環境のみWAFを有効化
resource "aws_wafv2_web_acl_association" "main" {
count = var.environment == "prd" ? 1 : 0
resource_arn = aws_lb.main.arn
web_acl_arn = aws_wafv2_web_acl.main[0].arn
}
7. countとfor_eachの選び方
| 判断基準 | 使うべきもの |
|---|---|
| リソースを「作る or 作らない」だけ制御 | count |
| 複数リソースを作成・要素の追加削除がある | for_each |
| 意味のある識別子(名前・環境名など)で管理したい | for_each |
| 設定値(インスタンスタイプなど)を各リソースで変えたい | for_each |
判断フローチャート
複数のリソースを作りたい
│
▼
「0か1かだけ制御(条件付きON/OFF)?」
│
Yes ─┤ No
│ │
▼ ▼
count = 0 or 1 for_each
(条件式との組み合わせ) (必須)
詳細な比較は「count vs for_each — 違いと使い分けの完全ガイド」を参照してください。
8. よくあるエラー
count.indexに意味のある名前をつけていない
# ❌ よくある問題: count.indexをそのままタグに使うと意味が不明になる
resource "aws_instance" "web" {
count = 3
tags = {
Name = "web-${count.index}" # → "web-0", "web-1", "web-2" — 何を意味するか不明
}
}
# ✅ 改善: 変数のリストから名前を取得する(または for_each を使う)
variable "roles" {
default = ["frontend", "backend", "batch"]
}
resource "aws_instance" "web" {
count = length(var.roles)
tags = {
Name = "web-${var.roles[count.index]}" # → "web-frontend" など意味が明確
}
}
countとfor_eachを同じリソースに両方書いた
│ Error: Invalid combination of "count" and "for_each"
│
│ The arguments "count" and "for_each" are mutually exclusive.
countとfor_eachは同じブロック内では同時に使えません。 どちらか一方だけ使います。
count = 0のリソースを[0]で参照した
│ Error: Invalid index
│
│ The given key does not identify an element in this collection value: the
│ collection has no elements.
# ❌ count = 0のとき[0]を参照するとエラー
output "bastion_ip" {
value = aws_instance.bastion[0].public_ip # countが0ならエラー
}
# ✅ 条件演算子で安全に参照する
output "bastion_ip" {
value = var.enable_bastion ? aws_instance.bastion[0].public_ip : null
}
9. 関連記事
- for_each — map/setで動的にリソースを作成するメタ引数
- count vs for_each — 違いと使い分けの完全ガイド
- lifecycleブロックの使い方 —
prevent_destroyでcountリソースの誤削除を防ぐ - for式の使い方 — countと組み合わせるリスト操作
- resourceブロック — 基本構文と使い方
- 条件式(三項演算子)— 使い方
10. まとめ
countはnumberを渡すと、その個数分リソースを作成するメタ引数- 各リソースは
[0],[1],[2]…のインデックスで管理される count.indexでループ内の現在のインデックス(0始まり)を参照できる- 最大の問題点: リストの中間要素を削除するとインデックスがずれ、無関係なリソースまで再作成される
- 安全なユースケースは「0か1かの制御のみ」(
var.enable_xxx ? 1 : 0パターン) - 複数リソースの作成・管理には原則
for_eachを使うこと countとfor_eachは同一ブロック内で同時使用不可
動作確認バージョン: Terraform >= 1.9 / AWS Provider ~> 5.0 対象リージョン: ap-northeast-1(東京) 公式ドキュメント: https://developer.hashicorp.com/terraform/language/meta-arguments/count