【完全ガイド】Terraformの条件分岐7つの出現パターン | 現役インフラエンジニアが解説

Terraformで条件分岐が必要になるケース

インフラのコード化(IaC)においてTerraformを使用する際、環境や状況に応じて異なるリソース設定を適用する必要が頻繁に発生します。このセクションでは、Terraformで条件分岐が必要となる代表的なケースとその実装アプローチについて解説します。

本番環境と開発環境で設定を分けたい場合の実装方法

多くの組織では、本番環境(Production)と開発環境(Development)で異なるインフラ設定を適用する必要があります。以下のような状況で条件分岐が必要となります:

  1. インスタンスタイプの変更
  • 本番環境:高性能で冗長性のある構成(例:t3.medium以上)
  • 開発環境:コスト最適化された構成(例:t3.micro)
  1. バックアップ設定の制御
  • 本番環境:自動バックアップを有効化
  • 開発環境:必要に応じてバックアップを手動実行
  1. セキュリティ設定の調整
  • 本番環境:厳格なセキュリティグループ設定
  • 開発環境:開発作業用の柔軟なアクセス設定

実装例:

# 環境変数の定義
variable "environment" {
  type    = string
  default = "dev"
}

# 環境に応じたインスタンスタイプの選択
locals {
  instance_type = var.environment == "prod" ? "t3.medium" : "t3.micro"
}

# EC2インスタンスの定義
resource "aws_instance" "example" {
  instance_type = local.instance_type
  # その他の設定...
}

リソースの設定を環境で制御するメソッド

環境による設定制御は、以下のような方法で実現できます:

  1. 変数による制御
   # 環境別の設定をマップで定義
   variable "environment_configs" {
     type = map(object({
       instance_type = string
       backup_retention = number
     }))
     default = {
       prod = {
         instance_type = "t3.medium"
         backup_retention = 7
       }
       dev = {
         instance_type = "t3.micro"
         backup_retention = 1
       }
     }
   }
  1. 条件付きリソース作成
   # 本番環境のみバックアップを有効化
   resource "aws_backup_plan" "example" {
     count = var.environment == "prod" ? 1 : 0
     name  = "production-backup-plan"
     # バックアップ設定...
   }
  1. 動的ブロック生成
   # セキュリティグループルールの動的生成
   dynamic "ingress" {
     for_each = var.environment == "prod" ? local.prod_rules : local.dev_rules
     content {
       from_port   = ingress.value.port
       to_port     = ingress.value.port
       protocol    = "tcp"
       cidr_blocks = ingress.value.cidrs
     }
   }

これらの実装方法を適切に組み合わせることで、環境ごとに最適化された設定を維持しながら、コードの再利用性と保守性を高めることができます。ただし、過度に複雑な条件分岐は避け、可読性とメンテナンス性のバランスを考慮することが重要です。

次のセクションでは、これらの条件分岐をさらに詳しく実装する具体的なパターンについて解説します。

Terraformでの条件分岐の実装パターン

count条件付きリソース作成の基本的な使い方

countパラメータを使用した条件分岐は、リソースの作成有無を制御する最も基本的な方法です。

# 環境変数の定義
variable "create_backup" {
  type    = bool
  default = false
}

# バックアップボールトの条件付き作成
resource "aws_backup_vault" "example" {
  count = var.create_backup ? 1 : 0
  name  = "example-backup-vault"
  tags  = {
    Environment = var.environment
  }
}

for_eachを使った動的なリソース生成メソッド

for_eachは複数リソースを動的に生成する際に使用します。

# 環境別のEC2設定
variable "ec2_instances" {
  type = map(object({
    instance_type = string
    volume_size   = number
  }))
  default = {
    web = {
      instance_type = "t3.micro"
      volume_size   = 20
    }
    app = {
      instance_type = "t3.small"
      volume_size   = 30
    }
  }
}

# 動的なEC2インスタンス生成
resource "aws_instance" "servers" {
  for_each = var.environment == "prod" ? var.ec2_instances : {}

  instance_type = each.value.instance_type
  ami           = data.aws_ami.amazon_linux_2.id

  root_block_device {
    volume_size = each.value.volume_size
  }

  tags = {
    Name = "server-${each.key}"
  }
}

条件演算子を使用した値の動的な切り替え方

三項演算子を使用して、条件に基づいて値を切り替えます。

locals {
  # 環境に応じたインスタンスタイプの選択
  instance_type = var.environment == "prod" ? "t3.medium" : "t3.micro"

  # 複数条件の組み合わせ
  backup_retention = (
    var.environment == "prod" ? 30 :
    var.environment == "stg"  ? 7  :
    1  # dev環境のデフォルト値
  )
}

locals変数を活用した条件分岐の実装例

localsブロックで複雑な条件分岐をまとめることで、コードの可読性が向上します。

locals {
  # 環境別の基本設定
  env_configs = {
    prod = {
      instance_count = 2
      multi_az       = true
      backup_enabled = true
    }
    dev = {
      instance_count = 1
      multi_az       = false
      backup_enabled = false
    }
  }

  # 現在の環境の設定を取得
  current_config = local.env_configs[var.environment]

  # タグの動的生成
  common_tags = {
    Environment = var.environment
    Project     = var.project_name
    ManagedBy   = "terraform"
    Backup      = local.current_config.backup_enabled ? "enabled" : "disabled"
  }
}

# 設定の使用例
resource "aws_db_instance" "example" {
  instance_class    = "db.t3.medium"
  count            = local.current_config.instance_count
  multi_az         = local.current_config.multi_az
  tags             = local.common_tags
}

これらのパターンを適切に組み合わせることで、柔軟で保守性の高いインフラストラクチャコードを実現できます。次のセクションでは、これらのパターンを実装する際の注意点について解説します。

条件分岐の実装における注意点

可読性を重視した条件分岐のベストプラクティス

# 悪い例:複雑な条件の直接記述
resource "aws_instance" "server" {
  instance_type = var.environment == "prod" ? (var.high_performance ? "t3.large" : "t3.medium") : (var.minimal_resources ? "t3.nano" : "t3.micro")
}

# 良い例:localsを使用した条件の整理
locals {
  performance_tier = var.high_performance ? "high" : "standard"
  instance_types = {
    prod = {
      high     = "t3.large"
      standard = "t3.medium"
    }
    dev = {
      high     = "t3.micro"
      standard = "t3.nano"
    }
  }
  selected_instance_type = local.instance_types[var.environment][local.performance_tier]
}

resource "aws_instance" "server" {
  instance_type = local.selected_instance_type
}

主要なベストプラクティス:

  1. 複雑な条件はlocalsブロックに分離
  2. 条件分岐の深さは2層以内に抑制
  3. マップ型変数を活用した設定値の管理
  4. 命名規則の統一による意図の明確化

条件分岐によるステート管理の複雑化を防ぐ方法

# 問題のある実装:countによる配列インデックスの変動
resource "aws_instance" "servers" {
  count = var.environment == "prod" ? 3 : 1
  ami   = data.aws_ami.amazon_linux_2.id
  tags  = {
    Name = "server-${count.index}"
  }
}

# 推奨される実装:for_eachによる安定したリソース参照
locals {
  server_configs = {
    for idx in range(var.environment == "prod" ? 3 : 1) :
    "server-${idx}" => {
      name = "server-${idx}"
    }
  }
}

resource "aws_instance" "servers" {
  for_each = local.server_configs
  ami      = data.aws_ami.amazon_linux_2.id
  tags     = {
    Name = each.value.name
  }
}

ステート管理の安定化のポイント:

  1. countよりもfor_eachを優先使用
  2. リソース識別子の一意性確保
  3. 依存関係の明示的な定義
  4. バージョン管理との連携を考慮

コード変更時の影響を最小限に抑えるために、以下の原則を遵守します:

  1. リソースの一意識別子の安定性確保
   # 安定した識別子の使用例
   locals {
     unique_id = "${var.environment}-${var.component_name}"
   }
  1. データ構造の一貫性維持
   # 一貫性のある構造定義
   variable "resource_configs" {
     type = map(object({
       size    = string
       enabled = bool
     }))
   }
  1. デフォルト値の適切な設定
   # 安全なデフォルト値の定義
   variable "instance_count" {
     type        = number
     default     = 1
     description = "Number of instances to create"
     validation {
       condition     = var.instance_count > 0
       error_message = "Instance count must be positive"
     }
   }

これらの注意点を踏まえることで、保守性の高い堅牢なインフラストラクチャコードを実現できます。

実践的なコード例で学ぶ条件分岐

本番・開発環境で異なるEC2インスタンスを作成する実装例

# 環境設定
variable "environment" {
  type    = string
  default = "dev"
}

# EC2設定の定義
locals {
  ec2_configs = {
    prod = {
      instance_type = "t3.medium"
      volume_size   = 100
      ebs_optimized = true
      monitoring    = true
      instances = {
        "app-1" = { az = "ap-northeast-1a" }
        "app-2" = { az = "ap-northeast-1c" }
      }
    }
    dev = {
      instance_type = "t3.micro"
      volume_size   = 30
      ebs_optimized = false
      monitoring    = false
      instances = {
        "app-1" = { az = "ap-northeast-1a" }
      }
    }
  }

  current_config = local.ec2_configs[var.environment]
}

# EC2インスタンス作成
resource "aws_instance" "application" {
  for_each = local.current_config.instances

  ami               = data.aws_ami.amazon_linux_2.id
  instance_type     = local.current_config.instance_type
  availability_zone = each.value.az
  ebs_optimized     = local.current_config.ebs_optimized
  monitoring        = local.current_config.monitoring

  root_block_device {
    volume_size = local.current_config.volume_size
    volume_type = "gp3"
  }

  tags = {
    Name        = "app-${var.environment}-${each.key}"
    Environment = var.environment
  }
}

環境変数に応じてRDSインスタンスのスペックを変更する方法

# データベース設定
locals {
  db_configs = {
    prod = {
      instance_class    = "db.t3.large"
      allocated_storage = 100
      multi_az         = true
      backup_retention = 7
      parameters = {
        max_connections = 1000
        innodb_buffer_pool_size = "4294967296"
      }
    }
    dev = {
      instance_class    = "db.t3.small"
      allocated_storage = 20
      multi_az         = false
      backup_retention = 1
      parameters = {
        max_connections = 100
        innodb_buffer_pool_size = "1073741824"
      }
    }
  }

  # 現在の環境の設定を取得
  db_config = local.db_configs[var.environment]
}

# パラメータグループの作成
resource "aws_db_parameter_group" "mysql" {
  name   = "mysql-${var.environment}"
  family = "mysql8.0"

  dynamic "parameter" {
    for_each = local.db_config.parameters
    content {
      name  = parameter.key
      value = parameter.value
    }
  }
}

# RDSインスタンスの作成
resource "aws_db_instance" "mysql" {
  identifier        = "mysql-${var.environment}"
  instance_class    = local.db_config.instance_class
  allocated_storage = local.db_config.allocated_storage
  multi_az         = local.db_config.multi_az

  engine         = "mysql"
  engine_version = "8.0"

  backup_retention_period = local.db_config.backup_retention
  parameter_group_name   = aws_db_parameter_group.mysql.name

  # 条件付きのパフォーマンスインサイト有効化
  performance_insights_enabled = var.environment == "prod"

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

これらの実装例では、以下のポイントに注目してください:

  1. マップ型を使用した環境別設定の一元管理
  2. 動的なリソース生成におけるfor_eachの活用
  3. パラメータグループなど関連リソースの条件付き設定
  4. タグ付けによる環境の明確化

よくある質問と回答

Terraformで使用できる条件演算の一覧と使い方

Terraformで使用可能な主要な条件演算子と関数:

  1. 三項演算子 condition ? true_val : false_val
# 基本的な使用例
locals {
  instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
}

# ネストした条件
locals {
  storage_size = (
    var.environment == "prod" ? 100 :
    var.environment == "stg"  ? 50  :
    20
  )
}
  1. can関数 – エラーを防ぐための条件評価
locals {
  db_config = can(var.custom_db_config) ? var.custom_db_config : local.default_db_config
}
  1. coalesce関数 – 最初の非nullな値を返す
locals {
  backup_retention = coalesce(var.backup_days, local.default_backup_days, 7)
}

条件分岐実装時のトラブルシューティング方法

一般的な問題と解決方法:

  1. count/for_eachの変更によるリソース再作成
# 問題のあるコード
resource "aws_instance" "server" {
  count = var.create_server ? 1 : 0  # 変更するとリソースが再作成される
}

# 解決策:for_eachを使用
resource "aws_instance" "server" {
  for_each = var.create_server ? toset(["enabled"]) : toset([])
}
  1. データ型の不一致による条件評価エラー
# エラーが発生するコード
variable "port_number" {
  type = string
}

locals {
  is_valid = var.port_number == 80  # 文字列と数値の比較

# 修正後のコード
locals {
  is_valid = tonumber(var.port_number) == 80
}
  1. 複雑な条件のデバッグ方法
# デバッグ用の出力
output "debug_config" {
  value = {
    environment = var.environment
    is_prod     = var.environment == "prod"
    config      = local.current_config
  }
}
  1. 条件分岐の依存関係エラー
# 問題のあるコード
resource "aws_instance" "main" {
  count = var.create_instance ? 1 : 0
}

resource "aws_eip" "main" {
  instance_id = aws_instance.main[0].id  # インスタンスが作成されない場合にエラー
}

# 解決策
resource "aws_eip" "main" {
  count      = var.create_instance ? 1 : 0
  instance_id = aws_instance.main[0].id
}

トラブルシューティングのベストプラクティス:

  1. プラン実行前のterraform validateによる構文チェック
  2. terraform consoleを使用した式のテスト
  3. デバッグ用の一時的なoutputブロックの追加
  4. バージョン管理による変更の追跡

これらの問題に遭遇した場合は、まず小規模な環境でテストし、段階的に変更を適用することを推奨します。