制御構文

この講座では、Terraformの制御構文について学びます。

  • lifecycle(ライフサイクル制御)の使い方
  • count による繰り返し処理
  • for_each による繰り返し処理
  • 条件分岐(三項演算子)の書き方

1. ライフサイクル制御

1.1 記載方法

ライフサイクルとは

ここまで、リソースの作成や依存関係について学んできました。通常、Terraformは設定ファイルの記述通りにリソースを「作成」したり、変更があれば「更新(または削除して再作成)」したりします。

しかし、運用していると以下のような要望が出てくることがあります。

  • 設定を変更したけど、リソースを作り直す時にダウンタイム(停止時間)を発生させたくない
  • 大事なデータベースだから、間違っても削除されないように保護したい
  • この設定値だけは、Terraformで管理せず、AWS側の自動変更に任せたい

こうしたリソースごとの「振る舞い」を細かく制御するために使うのが、lifecycle ブロックです。

create_before_destroy(削除する前に作成)

通常、リソースの再作成が必要な変更(例:EC2のAMI変更など)が発生した場合、Terraformは「古いものを削除」してから「新しいものを作成」します。これでは、リソースが存在しない時間ができてしまいます。

create_before_destroy = true を設定すると、「新しいものを作成」してから「古いものを削除」する動きに変わり、ダウンタイムを最小限に抑えられます。

記載方法としては、lifecycle のブロックの中に、create_before_destroy = true とすることで実現できます。

  lifecycle {
    create_before_destroy = true
  }

実際のサンプルコードを確認します。下記は、web_sg というセキュリティグループについて、「削除する前に再作成」を行う流れとなります。セキュリティグループは、名前を変更した場合、セキュリティグループが再作成されます。create_before_destroy を指定していないと、他のリソースに紐づいている時に挙動がおかしくなるため、create_before_destroy = true が有効になります。

resource "aws_security_group" "web_sg" {
  name        = "web-sg-v1"
  description = "Allow HTTP traffic"

  # インバウンドルールの定義
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # ライフサイクル設定
  lifecycle {
    # 置換が必要な変更(nameの変更など)があった場合、
    # 新しいセキュリティグループを作成してから、古いものを削除する
    create_before_destroy = true
  }
}

prevent_destroy(削除の防止)

prevent_destroy = true を設定すると、そのリソースを削除しようとする操作(terraform destroy など)が実行されたときにエラーとなり、削除をブロックします。

本番環境のデータベースやストレージなど、絶対に消えてはいけないリソースを守るために使用します。

記載方法としては、lifecycle のブロックの中に、prevent_destroy = true とすることで実現できます。

  lifecycle {
    prevent_destroy = true
  }

以下のサンプルコードは、RDSに対して、prevent_destroy = true を指定することで、RDSを terraform destroy などで削除しようとすると、エラーとなります。

resource "aws_db_instance" "production_db" {
  allocated_storage    = 20
  engine               = "mysql"
  engine_version       = "8.0"
  instance_class       = "db.t3.micro"
  username             = "admin"
  password             = "password12345678" # 実際はSecrets Manager等での管理を推奨
  skip_final_snapshot  = true

  tags = {
    Name = "Production-DB"
  }

  # ライフサイクル設定
  lifecycle {
    # このRDSを削除しようとする操作(destroy)をブロックしてエラーにする
    prevent_destroy = true
  }
}

ignore_changes(変更の無視)

Terraformはコードと実際の環境の差異(差分)を検知して修正しようとします。しかし、Terraform以外の要因で変更された値を元に戻したくない場合があります。

ignore_changes を使うと、指定した属性の変更をTerraformが無視(スルー)するようになります。

以下のように、lifecycle ブロックの中に ignore_changes = [ 属性名 ] を定義することで設定できます。

lifecycle {
  ignore_changes = [
    属性名
  ]
}

実際の書き方を見てみましょう。

下記のサンプルでは、パラメータストアの作成時に、値としてダミーの値を設定しています。ignore_changes を設定することで、マネジメントコンソールで値を変更した場合でも、その変更がTerraformによって上書きされなくなります。ignore_changes を設定していないと、画面から変更してもダミーの値に戻されてしまいます。

resource "aws_ssm_parameter" "db_password" {
  name  = "/production/database/password"
  type  = "SecureString"
  value = "dummy_initial_value"

  lifecycle {
    # value の変更を無視する
    # 運用中にパスワードを変更しても、Terraformが "dummy_initial_value" に戻さないようにする
    ignore_changes = [
      value,
    ]
  }
}

1.2 実際に使ってみる

実際に、ライフサイクルを使った操作を行ってみます。

create_before_destroy

まずは、リソースの削除順序をコントロールする create_before_destroy から確認します。

少し長くなってしまいますが、以下のコードを main.tf に貼り付けてみてください。

provider "aws" {
 region = "ap-northeast-1"
}

# VPC
resource "aws_vpc" "vpc" {
 cidr_block = "10.0.0.0/16"
 tags = {
   Name = "test-vpc"
 }
}

# サブネット
resource "aws_subnet" "subnet" {
 vpc_id     = aws_vpc.vpc.id
 cidr_block = "10.0.1.0/24"
 tags = {
   Name = "test-subnet"
 }
}

# セキュリティグループ
resource "aws_security_group" "web_sg" {
 name        = "web-sg-v1"
 description = "Allow HTTP traffic"
  vpc_id      = aws_vpc.vpc.id

 ingress {
   from_port   = 80
   to_port     = 80
   protocol    = "tcp"
   cidr_blocks = ["0.0.0.0/0"]
 }
  egress {
   from_port   = 0
   to_port     = 0
   protocol    = "-1"
   cidr_blocks = ["0.0.0.0/0"]
 }
}

# EC2インスタンスを作成してセキュリティグループを割り当て
resource "aws_instance" "web_server" {
 ami           = "ami-03d1820163e6b9f5d"
 instance_type = "t3.micro"
 subnet_id     = aws_subnet.subnet.id
 vpc_security_group_ids = [aws_security_group.web_sg.id]
 tags = {
   Name = "test-ec2-instance"
 }
}

これは、テスト用のセキュリティグループを作り、EC2インスタンスに割り当てしています。関連するVPCやサブネットも一緒に作っています。

これを実行すると、web_server のEC2インスタンスに、web_sg のセキュリティグループが割り当てられます。

この状態で、セキュリティグループの名前を変更すると、AWSの使用上、セキュリティグループの作り直しがされます。しかし、セキュリティグループはすでにEC2インスタンスに割り当てられているため、削除ができずエラーになります。そのため、create_before_destroy の設定が有効になってきます。

まずは、create_before_destroy が割り当てられていない状態で、terraform apply コマンドで、関連リソースを作成します。

terraform apply
⚠️ 「InvalidAMIID.NotFound」エラーが出る場合
サンプルコードのAMI ID(ami-03d1820163e6b9f5d)は例示用のため、実際には存在しない可能性があります。
AWSコンソールでEC2の「AMI カタログ」から、現在利用可能なAmazon Linux 2023のAMI IDを確認し、置き換えてください。

terraform apply が正常終了したら、セキュリティグループの名前を変えてみます。web_sgname 属性を、web-sg-v2 に変更してみます。

resource "aws_security_group" "web_sg" {
 name        = "web-sg-v2"  # ここを変更
 description = "Allow HTTP traffic"
  vpc_id      = aws_vpc.vpc.id

 ingress {
   from_port   = 80
   to_port     = 80
   protocol    = "tcp"
   cidr_blocks = ["0.0.0.0/0"]
 }
  egress {
   from_port   = 0
   to_port     = 0
   protocol    = "-1"
   cidr_blocks = ["0.0.0.0/0"]
 }
}

この状態で、まずは terraform plan を実行してみます。

terraform plan

以下のように二つの変更が読み取れます。

一つは、web-sg-v1 から web-sg-v2 に変更されることにより、セキュリティグループを削除して再作成されます。もう一つは、それに伴いEC2インスタンス(web_server)に割り当てられているセキュリティグループが、新しく作成されたものに更新されます。

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_instance.web_server will be updated in-place
  ~ resource "aws_instance" "web_server" {
        id                                   = "i-0e40109f4b7d2ba95"
        tags                                 = {
            "Name" = "test-ec2-instance"
        }
      ~ vpc_security_group_ids               = [
          - "sg-08456a9ffe5662ef6",
        ] -> (known after apply)
        # (39 unchanged attributes hidden)

        # (9 unchanged blocks hidden)
    }

  # aws_security_group.web_sg must be replaced
-/+ resource "aws_security_group" "web_sg" {
      ~ arn                    = "arn:aws:ec2:ap-northeast-1:125605020607:security-group/sg-08456a9ffe5662ef6" -> (known after apply)
      ~ id                     = "sg-08456a9ffe5662ef6" -> (known after apply)
      ~ name                   = "web-sg-v1" -> "web-sg-v2" # forces replacement
      + name_prefix            = (known after apply)
      ~ owner_id               = "125605020607" -> (known after apply)
      - tags                   = {} -> null
      ~ tags_all               = {} -> (known after apply)
        # (6 unchanged attributes hidden)
    }

この後 terraform apply を実行すると以下のようなエラーが発生します。ただ、実際にエラーが発生するまで数分〜10分ほど待機(あるいは即時エラー)になってしまうため、実際に試す必要はありません。

このエラーは、「削除しようとしているセキュリティグループがEC2インスタンスで利用されているため、削除できない」というものです。

│ Error: deleting Security Group (sg-08456a9ffe5662ef6): operation error EC2: DeleteSecurityGroup, https response error StatusCode: 400, RequestID: 14d75afa-4207-4bbd-a092-3bf48d2fabc2, api error DependencyViolation: resource sg-08456a9ffe5662ef6 has a dependent object

そのため、今回のような create_before_destroy の設定が有効になります。web_sg に以下のように、lifecycle の記載をお願いします。

# セキュリティグループ
resource "aws_security_group" "web_sg" {
  name        = "web-sg-v2"
  description = "Allow HTTP traffic"

  vpc_id      = aws_vpc.vpc.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }


  # ここを追記
  lifecycle {
    create_before_destroy = true
  }
}
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
+/- create replacement and then destroy

Terraform will perform the following actions:

  # aws_instance.web_server will be updated in-place
  ~ resource "aws_instance" "web_server" {
        id                                   = "i-0e40109f4b7d2ba95"
        tags                                 = {
            "Name" = "test-ec2-instance"
        }
      ~ vpc_security_group_ids               = [
          - "sg-08456a9ffe5662ef6",
        ] -> (known after apply)
        # (39 unchanged attributes hidden)

        # (9 unchanged blocks hidden)
    }

  # aws_security_group.web_sg must be replaced
+/- resource "aws_security_group" "web_sg" {
      ~ arn                    = "arn:aws:ec2:ap-northeast-1:125605020607:security-group/sg-08456a9ffe5662ef6" -> (known after apply)
      ~ id                     = "sg-08456a9ffe5662ef6" -> (known after apply)
      ~ name                   = "web-sg-v1" -> "web-sg-v2" # forces replacement
      + name_prefix            = (known after apply)
      ~ owner_id               = "125605020607" -> (known after apply)
      - tags                   = {} -> null
      ~ tags_all               = {} -> (known after apply)
        # (6 unchanged attributes hidden)
    }

Plan: 1 to add, 1 to change, 1 to destroy.

今回のログでは、aws_security_group.web_sg の行頭が +/- になっています。 これは Terraform が「新しいセキュリティグループ(v2)を作成し、EC2インスタンスへの紐付けを新しい方に切り替えてから、古いセキュリティグループ(v1)を削除する」という手順を計画していることを表しており、意図通り設定が効いています。

Apply complete! Resources: 1 added, 1 changed, 1 destroyed.

最後に、作成したリソースは削除しておきましょう

terraform destroy

prevent_destroy

リソースの削除保護を行う prevent_destroy を試してみます。下記のような、シンプルなS3バケットを作り、削除保護を行うコードを、main.tf に記載してください。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "example" {
  # ★重要: 世界中で一意なバケット名に書き換えてください
  bucket = "my-unique-bucket-name-20260101"

  tags = {
    Name = "Protected Bucket"
  }

  # --- 削除保護設定 ---
  lifecycle {
    # trueの場合、terraform destroy コマンドを実行してもエラーになり、
    # リソースが削除されるのを防ぎます。
    prevent_destroy = true
  }
}

terraform apply コマンドにより、まずはリソースを作成します。

terraform apply
⚠️ 「BucketAlreadyExists」エラーが出る場合
S3バケット名は全世界で一意である必要があります。
my-unique-bucket-name-20260101の部分を、日付やランダムな文字列を追加して一意な名前に変更してください。

正常終了することを確認します。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

terraform destroy コマンドを実行して、リソースの削除を行ってみます。

terraform destroy

そうすると、以下のようなエラーが発生します。

│ Error: Instance cannot be destroyed
│
│   on main.tf line 5:
│    5: resource "aws_s3_bucket" "example" {
│
│ Resource aws_s3_bucket.example has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with
│ the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target option.

このエラーは、lifecycle.prevent_destroy set の部分から、リソースに対して削除保護が働いていることが読み取れます。そのため、期待通りの動作です。

このリソースを削除したい場合は、lifecycle ブロックの部分をまず削除します。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "example" {
  # ★重要: 世界中で一意なバケット名に書き換えてください
  bucket = "my-unique-bucket-name-20260101"

  tags = {
    Name = "Protected Bucket"
  }
}

コードから lifecycle ブロックを削除した時点で、Terraform上の削除保護設定は解除されます(この設定はコード上だけのルールなので、設定変更を反映させるための terraform apply は不要です)。

その上で、terraform destroy コマンドでリソースの削除を行います。

terraform destroy

無事にコマンドが正常終了すれば、ここまではOKです。

Destroy complete! Resources: 1 destroyed.

ignore_changes

最後に、リソースの変更検知から特定の項目を除外する ignore_changes の動作を確認します。

今回は「データベースのパスワード管理」を想定したシナリオで進めます。 通常、Terraformでパスワードリソースを作ると、コードに書かれた値とAWS上の値が常に一致するように管理されます。しかし、運用中に管理者がAWSコンソールからパスワードを変更した場合、次回の terraform apply でコードに書かれた古いパスワード(ダミー値)に戻されてしまうと事故になります。

そこで、「作成時はダミー値を入れるが、その後の値の変更はTerraformで関知しない(無視する)」という設定を行います。

まずは通常通り、ライフサイクル設定を行わずにパラメータストア(パスワード保存場所)を作成します。 main.tf を以下の内容に書き換えてください。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_ssm_parameter" "db_password" {
  name  = "/production/database/password"
  type  = "SecureString"
  value = "dummy"

}

まずはこれを作成します。

terraform apply
⚠️ 「AccessDeniedException」エラーが出る場合
IAMユーザにssm:PutParameterの権限がありません。
IAMコンソールで、使用しているユーザにAmazonSSMFullAccessポリシーをアタッチするか、Systems Manager Parameter Storeへの書き込み権限を追加してください。

続いて、パラメータストアのダッシュボードを開き、今作成した /production/database/password のリンクを開きます。

すると、値の部分に dummy という内容が設定されています。

これについて、画面上で「編集」をクリックし、値を任意のないように変えてください。

この状態で、一度 terraform plan を実行してみます。

$ terraform plan
aws_ssm_parameter.db_password: Refreshing state... [id=/production/database/password]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_ssm_parameter.db_password will be updated in-place
  ~ resource "aws_ssm_parameter" "db_password" {
        id              = "/production/database/password"
        name            = "/production/database/password"
        tags            = {}
      ~ value           = (sensitive value)
      ~ version         = 2 -> (known after apply)
        # (10 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

すると、パスワードが変更されるという結果になります。なお、パラメータストアの実際の値は、セキュリティのため表示されず、(sensitive value) とマスキングされます。

続けて、下記のように lifecycle ブロックを指定してみます。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_ssm_parameter" "db_password" {
  name  = "/production/database/password"
  type  = "SecureString"
  value = "dummy"

  lifecycle {
    ignore_changes = [
      value,
    ]
  }
}

この状態で、再び terraform plan を実行すると、今度は結果が No changes となります。これにより、無事に変更検知の対象外となったことが読み取れます。

No changes. Your infrastructure matches the configuration.

最後に、作成したリソースを削除しておきましょう。

terraform destroy

2. count

2.1 概要

これまでは、1つのリソースブロックで1つのリソース(EC2やVPCなど)を作成してきました。 しかし、「同じ構成のWebサーバを3台作りたい」「サブネットを複数作りたい」という場合、コードをコピー&ペーストして増やすのは効率が悪く、修正も大変になります。

このような「同じリソースを複数個作成したい」場合に利用するのが、count というメタ引数です。

2.2 記載方法

リソースブロックの中に count = 回数 を指定するだけで、その回数分だけリソースが作成されます。

また、作成されるリソースには「インデックス番号(0から始まる連番)」が割り当てられます。この番号は count.index という変数で取得でき、名前などに利用することで、それぞれのリソースを区別できます。

resource "リソース型" "名前" {
  count = 作成数

  # count.index で現在のインデックス(0, 1, 2...)を参照可能
  属性 = "値-${count.index}"
}

2.3 サンプルコード(EC2の例)

例えば、全く同じ設定のEC2インスタンスを3台作成し、それぞれに「Web-0」「Web-1」「Web-2」という名前を付ける場合は以下のようになります。

resource "aws_instance" "web" {
  count = 3  # 3台作成する

  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  tags = {
    # count.index には 0, 1, 2 が順番に入る
    Name = "Web-${count.index}"
  }
}

2.4 実際に使ってみる

今回は、IAMユーザを3人分作成するシンプルな例で動作を確認してみましょう。 (IAMユーザは作成・削除が早く、料金もかからないため、count の挙動確認に適しています)

コードの記載

main.tf に以下のコードを記述(または追記)してください。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_iam_user" "users" {
  count = 3 # 3人のユーザを作成

  # nameに count.index を使って、user-0, user-1, user-2 とする
  name = "test-user-${count.index}"
}

リソースの作成

この状態で、terraform apply を実行します。

terraform apply
⚠️ 「EntityAlreadyExists」エラーが出る場合
同名のIAMユーザがすでに存在しています。
AWSコンソールで既存のユーザを削除するか、コード内のnameの値(例:test-user-の部分)を別の名前に変更してください。

実行プラン(Plan)を確認すると、以下のように配列形式で表示されます。

aws_iam_user.users[0]
aws_iam_user.users[1]
aws_iam_user.users[2]

これら3つのリソースが作成されることを確認し、yes で適用してください。

マネジメントコンソールのIAMの画面を確認すると、「test-user-0」「test-user-1」「test-user-2」が作成されているはずです。

確認が終わったらリソースを削除しておきます。

terraform destroy

2.5 注意点:countのインデックスずれ

count は非常に便利ですが、「リストの順番(インデックス)」に依存してリソースを管理するという特性があります。

例えば、リスト変数を使ってリソースを作成している場合、リストの途中の要素を削除すると、それ以降のインデックス番号がすべてズレてしまいます。Terraformは「インデックスが変わった=別物」と判断し、意図しないリソースの削除・再作成が発生するリスクがあります。

順序が重要でない単純な数量(例:サーバ台数)の調整には count が適していますが、特定の値をキーにして個別に管理したい場合(例:ユーザ名ごとの管理など)は、この後学習する for_each の方が安全な場合があります。

💡 ポイント
迷ったら for_each を使うことをお勧めします。count はシンプルですが、要素の追加・削除時にインデックスがずれて意図しないリソースの再作成が発生するリスクがあります。for_each はキーで管理するため、このリスクを避けられます。

3. for_each

3.1 概要

count は「回数(数字)」を指定してリソースを作りましたが、for_each「Map」(キーと値のペア)や「Set」(文字列の集合) を元にリソースを作成します。

count との最大の違いは、リソースの管理方法です。

  • count: インデックス番号(0, 1, 2...)で管理
  • for_each: キー(名前などの文字列)で管理

これにより、リストの途中の要素を削除しても、インデックスずれによる意図しないリソースの再作成を防ぐことができるため、より安全に運用できます。

3.2 記載方法

リソースブロックの中に for_each を記述し、ループさせたいデータ(MapやSet)を渡します。

ブロック内では以下のオブジェクトを使って値を参照できます。

  • each.key: 現在のループのキー(Mapのキー、またはSetの文字列そのもの)
  • each.value: 現在のループの値(Mapの値、Setの場合はキーと同じ)
resource "リソース型" "名前" {
  for_each = {
    キー1 = "値1"
    キー2 = "値2"
  }

  属性1 = each.key   # キーを参照
  属性2 = each.value # 値を参照
}

3.3 サンプルコード

例えば、「Taro」と「Jiro」という二つのユーザを作り、タグとして「Admin、Developer」が入るMapを使うと綺麗に書けます。

resource "aws_iam_user" "users" {
  # ユーザ名(Key)と、タグに入れる役割(Value)を定義
  for_each = {
    Taro = "Admin"
    Jiro   = "Developer"
  }

  name = each.key # Taro, Jiro が入る

  tags = {
    Role = each.value # Admin, Developer が入る
  }
}

このコードを実行すると、aws_iam_user.users["Taro"]aws_iam_user.users["Jiro"] という、名前をキーにしたリソースが作成されます。

3.4 実際に使ってみる(ハンズオン)

実行例1

ここでは、シンプルなMapを使って、特定の名前を持つIAMユーザを作成してみましょう。

main.tf を以下のように書き換えてください。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_iam_user" "users" {
  for_each = {
    Taro = "Admin"
    Jiro   = "Developer"
  }

  name = each.key

  tags = {
    Role = each.value
  }
}

リソースを作成してみます

terraform apply

実行プランを確認すると、count の時のような [0] ではなく、文字列が識別子になっていることがわかります。

aws_iam_user.users["Taro"]
aws_iam_user.users["Jiro"]

yes で適用し、AWSコンソールで2名のユーザが作成されていることを確認しましょう。

確認が終わったら、リソースを削除しておきましょう。

terraform destroy

3.5 count と for_each の使い分け

機能 特徴 向いているケース
count インデックス(0,1,2)で管理 単純なコピー。中身に個性がない場合。例:同じ構成のサブネットを2つ作る、AZごとに1つ作る等。
for_each キー(文字列)で管理 個別の設定が必要な場合。順序が変わる可能性がある場合。例:ユーザ作成、特定の設定値を持つリソース作成。

基本的には、「単純な数合わせならcount、個別の管理が必要ならfor_each」 と覚えておけば間違いありません。

countfor_each の詳しい仕様や注意点は、Terraform公式ドキュメントのThe count Meta-ArgumentThe for_each Meta-Argumentに記載があります。

4. 条件分岐

条件によって属性の設定値を変更できる、Terraformにおける if 文の実現方法について解説します。

4.1 概要

条件分岐の基本

Terraformには、一般的なプログラミング言語にあるような if ブロック(if (条件) { ... })は存在しません。 その代わりに、「三項演算子」(Ternary Operator) という仕組みを使って条件分岐を実現します。

4.2 記載方法

条件 ? trueの場合の値 : falseの場合の値
  • ? の前: 条件式(true または false になるもの)
  • ?: の間: 条件が true の時に使われる値
  • : の後ろ: 条件が false の時に使われる値

パターン1:設定値の切り替え

例えば、環境(本番/開発)によって、スペックなどの設定値を切り替えたい場合によく使います。

以下の例は、EC2インスタンスのインスタンスタイプを、テスト環境と本番環境で切り分けたい場合の例です。stage という variables の値を元に、本番であれば t3.micro、それ以外の場合は性能の低い t2.micro を設定しています。

instance_type = var.stage == "prod" ? "t3.large" : "t2.micro"

パターン2:リソース作成のON/OFF(countとの組み合わせ)

count と組み合わせることで、「条件を満たす時だけリソースを作成する」(if create) という制御が可能になります。

仕組みは単純です。

  • 条件が true なら count = 1(1つ作る)
  • 条件が false なら count = 0(作らない)

このように、「作る数を0にする」ことで「作らない」を実現します。

resource "aws_s3_bucket" "log_bucket" {
  # フラグが true なら 1個作成、false なら 0個(作成しない)
  count = var.enable_logging ? 1 : 0

  bucket = "my-log-bucket-12345"
}

この方法は、Terraformで「リソースを作るか作らないかを動的に制御したい」ときによく使われます。

4.3 実際に使ってみる(ハンズオン)

それでは、実際に条件分岐を行っていきましょう。まずは、シンプルに「値の設定を切り分ける」例です。

リソースの値の判定

先ほどのEC2インスタンスのインスタンスタイプの例でも良いのですが、EC2インスタンスは作成する関連リソースが多く複雑になるため、今回は「VPCのCIDRブロック」を制御してみたいと思います。

var.stage が「prod」であれば「10.0.0.0/16」、それ以外の場合は「10.1.0.0/16」とするように制御してみます。

provider "aws" {
  region = "ap-northeast-1"
}

variable "stage" {
  description = "環境名 (例: prod, dev)"
  type        = string
  default     = "prod"
}

resource "aws_vpc" "main" {
  cidr_block = var.stage == "prod" ? "10.0.0.0/16" : "10.1.0.0/16"

  tags = {
    Name = "handson-vpc-${var.stage}"
  }
}

作成されたリソースをみていきます。handson-vpc-prod というVPCが作成され、CIDRブロックが 10.0.0.0/16 となっていればOKです。

続いて、Variable で定義した stagedefault 値を、prodtest に変えて実行してみましょう。

⚠️ terraform.tfvars が残っている場合
前の講座(変数の利用)で terraform.tfvarsstage = "prod" を記載した場合、terraform.tfvars の値は default より優先されるため、default を変更しても反映されません。terraform.tfvars 内の stage の行を削除するか、ファイル自体を削除してから実行してください。
variable "stage" {
  description = "環境名 (例: prod, dev)"
  type        = string
  default     = "test"
}

すると、CIDRブロックが 10.1.0.0/16 で作成されます。

確認が終わったらリソースを削除しておきましょう。

terraform destroy

リソースの作成判断

続いて、条件判定と count を組み合わせて、リソースの作成判断を行っていきます。下記は、stageprod であればVPCを作成、それ以外の場合は作成しないという制御を行っています。

provider "aws" {
  region = "ap-northeast-1"
}

variable "stage" {
  description = "環境名 (例: prod, dev)"
  type        = string
  default     = "prod"
}

resource "aws_vpc" "main" {
  count = var.stage == "prod" ? 1 : 0

  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "handson-vpc-${var.stage}"
  }
}

実際にリソースを作成してみましょう

terraform apply

handson-vpc-prod というリソースが作成されていれば、ここまではOKです。

続いて、variables で定義している stagedefault 値を、test に変更します。

variable "stage" {
  description = "環境名 (例: prod, dev)"
  type        = string
  default     = "test"
}

再度リソースを作成してみましょう

terraform apply

VPCが削除(あるいは作られない)されていることを確認します。

5. まとめ

この講座では、Terraformの制御構文について学びました。

  • lifecycleブロックでリソースの振る舞いを制御できる
    • create_before_destroy:削除前に新規作成してダウンタイムを防ぐ
    • prevent_destroy:リソースの削除を防止する
    • ignore_changes:特定の属性の変更を無視する
  • countで同じリソースを複数作成できる(インデックス番号で管理)
  • for_eachでMapやSetを元にリソースを作成できる(キーで管理、より安全)
  • 三項演算子条件 ? true時 : false時)で条件分岐を実現
  • countと三項演算子を組み合わせてリソースの作成ON/OFFを制御できる