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
}