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 }