Terraform countとは?実務で使える基礎知識
countパラメータの基本的な機能と特徴
Terraform countは、同一のリソースを複数作成する際に使用する機能です。countパラメータを使用することで、同じ設定のリソースを指定した数だけ簡単に作成できます。
基本的な構文:
resource "aws_instance" "web" {
  count         = 3  # 3つのインスタンスを作成
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  tags = {
    Name = "web-server-${count.index}"  # 0から始まるインデックスを使用
  }
}
countの主な特徴:
- 0から始まるインデックス(count.index)が自動的に提供される
- リソース名は配列形式で参照(web[0], web[1], web[2])
- 数値による制御が可能(条件分岐との組み合わせ)
countを使用するメリット:コード量削減とメンテナンス性向上
- コードの簡素化
- 同じリソース定義の繰り返しを防止
- DRY(Don’t Repeat Yourself)原則の実現
- 変更時の作業量削減
- 動的なリソース管理
- 環境変数による制御が容易
- スケーリングが簡単
   variable "instance_count" {
     description = "Number of instances to create"
     type        = number
     default     = 2
   }
   resource "aws_instance" "app" {
     count = var.instance_count
     # ... その他の設定
   }
- メンテナンス性の向上
- 一括変更が可能
- コードの見通しが良好
- バージョン管理がしやすい
countとfor_eachの違いと使い方
| 機能 | count | for_each | 
|---|---|---|
| データ型 | 数値 | マップまたはセット | 
| インデックス参照 | 数値インデックス | キーによる参照 | 
| リソース削除時の動作 | インデックスが詰められる | キーに基づいて個別に管理 | 
| ユースケース | 同一設定の複製 | 異なる設定の一括作成 | 
for_eachの使用例:
resource "aws_instance" "web" {
  for_each = {
    "prod" = "t2.medium"
    "dev"  = "t2.micro"
  }
  instance_type = each.value
  tags = {
    Name = "web-${each.key}"
  }
}
選択のポイント:
- 同一設定の複製 → count
- 個別の設定が必要 → for_each
- リソースの追加/削除が頻繁 → for_each
- シンプルな数値制御 → count
Terraform countの基本的な使い方:ステップバイプラクティス
count構文の基本パターンと記述方法
countの基本構文には以下のパターンがあります:
# 基本的な数値指定
resource "aws_subnet" "public" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
}
# 変数による制御
resource "aws_instance" "web" {
  count         = var.environment == "production" ? 3 : 1
  instance_type = "t2.micro"
}
# リストの長さに基づく指定
resource "aws_iam_user" "developers" {
  count = length(var.developer_names)
  name  = var.developer_names[count.index]
}
count.indexの活用方法と注意点
count.indexの高度な使用方法:
# CIDRブロックの動的生成
resource "aws_subnet" "private" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  # 10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
  tags = {
    Name = format("private-subnet-%d", count.index + 1)
    Tier = count.index < 2 ? "app" : "db"
  }
}
# インスタンスタイプの動的割り当て
resource "aws_instance" "cluster" {
  count = 3
  instance_type = count.index == 0 ? "t2.medium" : "t2.micro"  # マスターノードは大きめのインスタンス
}
注意点:
- indexは0から始まる
- リソース削除時にインデックスが再割り当て
- 参照時は必ずインデックスを指定
条件分岐とcountの組み合わせテクニック
- 環境による制御:
resource "aws_instance" "app" {
  count = var.environment == "production" ? 3 : 1
  instance_type = var.environment == "production" ? "t2.medium" : "t2.micro"
  tags = {
    Environment = var.environment
    Role        = count.index == 0 ? "primary" : "secondary"
  }
}
- リソースの条件付き作成:
# バックアップが必要な場合のみS3バケットを作成
resource "aws_s3_bucket" "backup" {
  count  = var.enable_backup ? 1 : 0
  bucket = "backup-${var.environment}-${count.index}"
}
# 開発環境では不要なリソースをスキップ
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
  count = var.environment != "development" ? 1 : 0
  alarm_name          = "high-cpu-usage"
  comparison_operator = "GreaterThanThreshold"
  threshold           = "80"
}
- 複数条件の組み合わせ:
locals {
  # 本番環境かつバックアップ有効時のみ作成
  backup_count = var.environment == "production" && var.enable_backup ? 3 : 0
}
resource "aws_ebs_volume" "backup" {
  count             = local.backup_count
  availability_zone = data.aws_availability_zones.available.names[count.index]
  size              = 100
  tags = {
    Name = "backup-volume-${count.index + 1}"
  }
}
実践的なユースケースで学ぶTerraform count活用法
複数のEC2インスタンスを効率的にデプロイ
locals {
  instance_configs = {
    small  = { count = 2, type = "t2.micro" }
    medium = { count = 3, type = "t2.medium" }
  }
}
resource "aws_instance" "app_servers" {
  count         = local.instance_configs[var.size].count
  ami           = data.aws_ami.amazon_linux_2.id
  instance_type = local.instance_configs[var.size].type
  user_data = <<-EOF
              #!/bin/bash
              echo "Server ${count.index + 1}" > /var/www/html/index.html
              EOF
  tags = {
    Name = "app-server-${count.index + 1}"
    Role = count.index == 0 ? "primary" : "secondary"
  }
}
異なるAZに同じリソースを展開
data "aws_availability_zones" "available" {
  state = "available"
}
resource "aws_subnet" "private" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "private-subnet-${data.aws_availability_zones.available.names[count.index]}"
    Tier = "application"
  }
}
resource "aws_nat_gateway" "main" {
  count         = 3
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
  depends_on = [aws_internet_gateway.main]
  tags = {
    Name = "nat-gateway-${data.aws_availability_zones.available.names[count.index]}"
  }
}
環境ごとのリソース数制御の実装
locals {
  environments = {
    dev     = { instance_count = 1, instance_type = "t2.micro" }
    staging = { instance_count = 2, instance_type = "t2.small" }
    prod    = { instance_count = 3, instance_type = "t2.medium" }
  }
  current_env = local.environments[var.environment]
}
resource "aws_instance" "application" {
  count         = local.current_env.instance_count
  ami           = data.aws_ami.amazon_linux_2.id
  instance_type = local.current_env.instance_type
  root_block_device {
    volume_size = var.environment == "prod" ? 100 : 50
  }
  tags = {
    Name        = "${var.environment}-app-${count.index + 1}"
    Environment = var.environment
  }
}
resource "aws_ebs_volume" "data" {
  count             = var.environment == "prod" ? local.current_env.instance_count : 0
  availability_zone = aws_instance.application[count.index].availability_zone
  size              = 200
  type              = "gp3"
  tags = {
    Name = "${var.environment}-data-volume-${count.index + 1}"
  }
}
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
  count               = var.environment == "prod" ? local.current_env.instance_count : 0
  alarm_name          = "${var.environment}-cpu-utilization-high-${count.index + 1}"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "300"
  statistic           = "Average"
  threshold           = "80"
  dimensions = {
    InstanceId = aws_instance.application[count.index].id
  }
}
Terraform countのベストプラクティスと注意点
countを使用する際のコーディング規約
# 1. 変数による制御
variable "instance_count" {
  description = "Number of instances to launch"
  type        = number
  default     = 2
  validation {
    condition     = var.instance_count > 0 && var.instance_count <= 10
    error_message = "Instance count must be between 1 and 10."
  }
}
# 2. ローカル変数でロジックを整理
locals {
  is_production = var.environment == "production"
  server_count  = local.is_production ? var.instance_count : 1
}
# 3. 命名規則の統一
resource "aws_instance" "web" {
  count = local.server_count
  tags = {
    Name = format("web-%s-%02d", var.environment, count.index + 1)
  }
}
よくあるエラーとトラブルシューティング
- インデックス参照エラー
# 問題のあるコード
resource "aws_eip" "lb" {
  count = var.create_eip ? 1 : 0
}
resource "aws_instance" "web" {
  # 条件が満たされない場合、エラー発生
  associate_public_ip_address = aws_eip.lb[0].id  
}
# 修正例
resource "aws_instance" "web" {
  associate_public_ip_address = var.create_eip ? aws_eip.lb[0].id : null
}
- count.indexの誤用
# 問題のあるコード
resource "aws_subnet" "private" {
  count             = 3
  cidr_block        = "10.0.${count.index}.0/24"  # 0から始まるため、意図しないCIDR
}
# 修正例
resource "aws_subnet" "private" {
  count             = 3
  cidr_block        = "10.0.${count.index + 1}.0/24"
}
メンテナンス性を考慮したモジュール設計
# モジュールの構造例
variable "instances" {
  description = "Map of instance configurations"
  type = map(object({
    count         = number
    instance_type = string
    tags         = map(string)
  }))
}
locals {
  # インスタンス設定の展開
  instance_configs = flatten([
    for name, config in var.instances : [
      for i in range(config.count) : {
        name         = name
        index        = i
        instance_type = config.instance_type
        tags         = merge(config.tags, {
          Name = format("%s-%02d", name, i + 1)
        })
      }
    ]
  ])
}
resource "aws_instance" "managed" {
  count         = length(local.instance_configs)
  instance_type = local.instance_configs[count.index].instance_type
  tags = local.instance_configs[count.index].tags
  lifecycle {
    create_before_destroy = true
  }
}
# 使用例
module "instances" {
  source = "./modules/instances"
  instances = {
    web = {
      count         = 2
      instance_type = "t2.micro"
      tags         = { Role = "web" }
    }
    app = {
      count         = 3
      instance_type = "t2.small"
      tags         = { Role = "app" }
    }
  }
}
実践演習:カウントを使用した具体的な実装例
マルチAZ構成のVPCサブネット作成
# AZの取得
data "aws_availability_zones" "available" {
  state = "available"
}
# VPC作成
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "multi-az-vpc"
  }
}
# パブリックサブネット作成
resource "aws_subnet" "public" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
  tags = {
    Name = "public-subnet-${data.aws_availability_zones.available.names[count.index]}"
    Type = "Public"
  }
}
# プライベートサブネット作成
resource "aws_subnet" "private" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 3)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "private-subnet-${data.aws_availability_zones.available.names[count.index]}"
    Type = "Private"
  }
}
# ルートテーブルの作成
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "public-rt"
  }
}
resource "aws_route_table" "private" {
  count  = 3
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "private-rt-${count.index + 1}"
  }
}
Auto Scaling用のセキュリティグループ設定
locals {
  ports = {
    http  = 80
    https = 443
    app   = 8080
  }
}
resource "aws_security_group" "asg" {
  name_prefix = "asg-sg-"
  vpc_id      = aws_vpc.main.id
  dynamic "ingress" {
    for_each = local.ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "Allow ${ingress.key} traffic"
    }
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "asg-security-group"
  }
}
resource "aws_launch_template" "app" {
  name_prefix   = "app-template"
  image_id      = data.aws_ami.amazon_linux_2.id
  instance_type = "t2.micro"
  network_interfaces {
    associate_public_ip_address = true
    security_groups            = [aws_security_group.asg.id]
  }
  user_data = base64encode(<<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              EOF
  )
}
resource "aws_autoscaling_group" "app" {
  desired_capacity    = 3
  max_size           = 6
  min_size           = 1
  target_group_arns  = [aws_lb_target_group.app.arn]
  vpc_zone_identifier = aws_subnet.private[*].id
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
}
複数環境向けIAMロールの一括作成
locals {
  environments = ["dev", "staging", "prod"]
  role_policies = {
    dev = [
      "arn:aws:iam::aws:policy/AWSCloudTrailReadOnlyAccess",
      "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
    ]
    staging = [
      "arn:aws:iam::aws:policy/AWSCloudTrailReadOnlyAccess",
      "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
      "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
    ]
    prod = [
      "arn:aws:iam::aws:policy/AWSCloudTrailReadOnlyAccess",
      "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
      "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess",
      "arn:aws:iam::aws:policy/CloudWatchFullAccess"
    ]
  }
}
resource "aws_iam_role" "environment_role" {
  count = length(local.environments)
  name  = "${local.environments[count.index]}-application-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
  tags = {
    Environment = local.environments[count.index]
  }
}
resource "aws_iam_role_policy_attachment" "environment_policy" {
  count = length(flatten([
    for env in local.environments : [
      for policy in local.role_policies[env] : {
        role = aws_iam_role.environment_role[index(local.environments, env)].name
        policy = policy
      }
    ]
  ]))
  role       = flatten([
    for env in local.environments : [
      for policy in local.role_policies[env] : {
        role = aws_iam_role.environment_role[index(local.environments, env)].name
        policy = policy
      }
    ]
  ])[count.index].role
  policy_arn = flatten([
    for env in local.environments : [
      for policy in local.role_policies[env] : {
        role = aws_iam_role.environment_role[index(local.environments, env)].name
        policy = policy
      }
    ]
  ])[count.index].policy
}