1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【AWS】VPC を Terraform で作成する

Last updated at Posted at 2025-09-10

Terraform を用いて VPC を定義する サンプルコード を作成しました。
バグや改善点などがあれば、教えてください。コメントでも PR でも大歓迎です。

工夫

  • NAT ゲートウェイを作成するかどうかを変数によって選ぶことができる
  • パブリックサブネットを作る AZ (アベイラビリティゾーン) と プライベートサブネットを作る AZ を異なるように設定できる
  • サブネットに割り当てる Cidr block の大きさを設定できる(現状は、すべてのサブネットで一定値でしか設定できません)

それぞれ、何が嬉しいかを説明します。

NAT ゲートウェイを作成するかどうかを変数によって選ぶことができる

NAT ゲートウェイは有料のリソースです。必要ない場合にはたてたくありません。コストを抑えることにつながります。
また、外部との接続のために VPC エンドポイントを使用する際も、OFF の設定ができると便利です。aws_route リソースを作成することでルートテーブルに新しくルートを追加することができます。

パブリックサブネットを作る AZ と プライベートサブネットを作る AZ を異なるように設定できる

検証用の環境などで、可用性が必要ないケースもあると思います。そのような際に、不必要なサブネットの作成を抑制できます。

サブネットに割り当てる cidr block の大きさを設定できる

プライベートサブネットにより大きな値が必要になるケースも多々あると思いました。別々の値が指定できるようにするべきでしたが、大変そうでやめました。
cidr の計算が大変で、 cidrsubnet 関数で対応できる範囲の工夫になりました。

Terraform コードに関する解説

Terraform コードを書いていて、ポイントだと思った点を解説します。

cidr の分割

subnet_cidr_blocks = {
  public = {
    for i, v in var.public_availability_zones :
    v => cidrsubnet(var.vpc_cidr_block, var.subnet_cidr_block_newbit, i)
    }
  private = {
    for i, v in var.private_availability_zones :
    v => cidrsubnet(var.vpc_cidr_block, var.subnet_cidr_block_newbit, i + length(var.public_availability_zones))
    }
  }

上のように cidrsubnet 関数と for文を用いて分解しています。これによって、AZ 名をキーとして cidr block を値とする連想配列が作成できます。

NAT ゲートウェイ (Elastic IP) を作成するかどうかの選択

resource "aws_eip" "this" {
  count = var.need_nat_gateway ? 1 : 0

  domain = "vpc"
  tags = merge(local.tags, {
    Name = "${var.prefix}-eip-for-nat-gateway"
  })
}
resource "aws_nat_gateway" "this" {
  count = var.need_nat_gateway ? 1 : 0

  allocation_id = aws_eip.this[0].id
  # TODO: Since the order of Availability Zones is not guaranteed, 
  # the chosen subnet from the list may vary across runs or regions.
  subnet_id = values(aws_subnet.public)[0].id
  tags = merge(local.tags, {
    Name = "${var.prefix}-nat-gateway"
  })
  depends_on = [aws_internet_gateway.this]
}

上のように、 count = var.need_nat_gateway ? 1 : 0 と三項演算子を使用することで、作成する数を調整しています。
注意点として、 aws_eip.this, aws_nat_gateway.this を参照する際は aws_eip.this[0], aws_nat_gateway.this[0] としなければならなくなります。なぜなら、 count によって配列としてリソースが定義されているからです。
また、 var.need_nat_gateway = false の際に、 aws_eip.this[0], aws_nat_gateway.this[0] による参照が、エラーになります。空の配列となるからです。

細かな点ですが、 aws_nat_gateway.thissubnet_id の指定も values(aws_subnet.public)[0].id となっています。aws_subnet.publicfor_each によって作成されているので AZ 名をキーとする連想配列によってリソースが定義されています。これをインデックスで参照したいので、値の配列を values 関数によって作成しています。今回は、どこに NAT ゲートウェイを作成するかを選べない仕様になっています。

プライベートサブネットのルートテーブル

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.this.id
  dynamic "route" {
    for_each = var.need_nat_gateway ? [aws_nat_gateway.this[0].id] : []
    content {
      cidr_block     = "0.0.0.0/0"
      nat_gateway_id = route.value
    }
  }
  tags = merge(local.tags, {
    Name = "${var.prefix}-private-route-table"
  })
  depends_on = [aws_nat_gateway.this]
}

dynamic 構文が使用されています。NAT ゲートウェイを作成するかどうかを変数によって選択できるようにしました。 NAT ゲートウェイを作成する場合は、ルートとして追加したいです。一方で作成しない場合は、ルートを空にしたいです。
このように、変数によってリソースの設定を動的に切り替える必要がある時に、 dynamic 構文がとても役に立ちます。

使い方

module "vpc" {
  source = ...
}

のようにモジュールとしても使用できます。main.tf とバージョン指定がしてあります。これ以外のバージョンでも大体動くと思いますが、動かないかもしれません。

変数指定の例は、 terraform.tfvars.tmpl にしてあります。これを terraform.tfvars とリネームして使用すると簡単かもしれません。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?