countメタ引数 — 整数でリソースを複数作成する

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.

countfor_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. 関連記事


10. まとめ

  • countnumberを渡すと、その個数分リソースを作成するメタ引数
  • 各リソースは[0], [1], [2]…のインデックスで管理される
  • count.indexでループ内の現在のインデックス(0始まり)を参照できる
  • 最大の問題点: リストの中間要素を削除するとインデックスがずれ、無関係なリソースまで再作成される
  • 安全なユースケースは「0か1かの制御のみ」var.enable_xxx ? 1 : 0パターン)
  • 複数リソースの作成・管理には原則for_eachを使うこと
  • countfor_eachは同一ブロック内で同時使用不可

動作確認バージョン: Terraform >= 1.9 / AWS Provider ~> 5.0 対象リージョン: ap-northeast-1(東京) 公式ドキュメント: https://developer.hashicorp.com/terraform/language/meta-arguments/count