開発日報

窓際エンジニアの開発備忘。日報は嘘です。

【連載】terraform によるAWS環境構築入門 第3回 ~ ネットワークとセキュリティーグループ ~

はじめに

この記事ではterraformを用いたVPC構築・セキュリティグループの作成方法について学びます。
マルチAZにも対応させましょう。

連載記事一覧

本連載での使用技術

  • AWS
  • Docker
  • VPC
  • ECS(fargate)
  • ELB
  • IAM
  • Route53
  • RDS
  • ElastiCache
  • codexxx

1. パブリックネットワークの作成

VPC・インターネットゲートウェイ定義

まずはじめにVPCを定義しましょう。
「enable_dns_support」「enable_dns_hostnames」をtrueに設定し、
AWSDNSサーバーによる名前解決とホスト名の割り当てを有効にしましょう。

resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "example"
  }
}

次にインターネットゲートウェイを定義し、VPCとインターネットが通信できるようにしましょう。「vpc_id」に↑で作成したVPCを指定して紐付けましょう。

# インターネットゲートウェイ
resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id
}

パブリックサブネットの構築

パブリックサブネットを定義しましょう。CIDRは「10.0.1.0/24」、アベイラビリティーゾーンは1aと1cの双方を作成しマルチAZに対応させましょう。
また、「map_public_ip_on_launch」をtrueにし、当該サブネットで起動したインスタンスにパブリックIPアドレスを付与させるようにしましょう。

# パブリックサブネット 1a
resource "aws_subnet" "public_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

# パブリックサブネット 1c
resource "aws_subnet" "public_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = true
}

ルートテーブルの作成

パブリックネットワーク用のルートテーブルを作成しましょう。

# パブリックルートテーブル
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
}

# ルートの作成、ルートテーブルと紐付ける
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}

サブネットとルートテーブルの関連付け

最後に作成したサブネットとルートテーブルを紐付けましょう。

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "public_0" {
  subnet_id      = aws_subnet.public_0.id
  route_table_id = aws_route_table.public.id
}

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "public_1" {
  subnet_id      = aws_subnet.public_1.id
  route_table_id = aws_route_table.public.id
}

2. プライベートネットワークの作成

プライベートサブネットの定義

先に作成したパブリックサブネットとは違うCIDR ブロックを指定することに注意しましょう。
また、パブリックIPは不要なので、map_public_ip_on_launchはfalseにします。

# プライベートサブネット 1a
resource "aws_subnet" "private_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.65.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

# プライベートサブネット 1c
resource "aws_subnet" "private_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.66.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = false
}

NATゲートウェイの作成とEIPの割当

NATゲートウェイを作成し、プライベートネットワークからインターネットにアクセスできるようにしましょう。NATゲートウェイはパブリックサブネットに紐付けます。
また、NATゲートウェイにはEIPが必要なので、こちらも付与しましょう。

depends_onを使って依存を明示すると、インターネットゲートウェイ作成後に、EIP や NAT ゲートウェイを作成するよう保証できます。

# EIP (NATゲートウェイ 1a)
resource "aws_eip" "nat_gateway_0" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# EIP (NATゲートウェイ 1c)
resource "aws_eip" "nat_gateway_1" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# NATゲートウェイ 1a
resource "aws_nat_gateway" "nat_gateway_0" {
  allocation_id = aws_eip.nat_gateway_0.id
  subnet_id     = aws_subnet.public_0.id
  depends_on    = [aws_internet_gateway.example]
}

# NATゲートウェイ 1c
resource "aws_nat_gateway" "nat_gateway_1" {
  allocation_id = aws_eip.nat_gateway_1.id
  subnet_id     = aws_subnet.public_1.id
  depends_on    = [aws_internet_gateway.example]
}

ルートテーブルの作成

プライベートネットワークからインターネットへ通信するために、ルートを定義しま す。゙デフォルトルートをdestination_cidr_blockに指定し、NAT ゙ートウェイにルーティングするよう設定します。  

gateway_id」を設定していましたが、リスト7.11では「nat_gateway_id」を設定しています

# プライベート用ルートテーブル 1a
resource "aws_route_table" "private_0" {
  vpc_id = aws_vpc.example.id
}

# プライベート用ルートテーブル 1c
resource "aws_route_table" "private_1" {
  vpc_id = aws_vpc.example.id
}

# プライベート用ルート 1a
resource "aws_route" "private_0" {
  route_table_id         = aws_route_table.private_0.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_0.id
  destination_cidr_block = "0.0.0.0/0"
}

# プライベート用ルート 1c
resource "aws_route" "private_1" {
  route_table_id         = aws_route_table.private_1.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_1.id
  destination_cidr_block = "0.0.0.0/0"
}

サブネットとルートテーブルの関連付け

最後に作成したサブネットとルートテーブルを紐付けましょう。

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "private_0" {
  subnet_id      = aws_subnet.private_0.id
  route_table_id = aws_route_table.private_0.id
}

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "private_1" {
  subnet_id      = aws_subnet.private_1.id
  route_table_id = aws_route_table.private_1.id
}

3. セキュリティグループの作成

セキュリテイグループの定義

# セキュリテイグループの定義
resource "aws_security_group" "example" {
  name   = "example"
  vpc_id = aws_vpc.example.id
}

# セキュリテイグループの定義(インバウンド)
resource "aws_security_group_rule" "ingress_example" {
  type              = "ingress"
  from_port         = "80"
  to_port           = "80"
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example.id
}

# セキュリテイグループの定義(アウトバウンド)
resource "aws_security_group_rule" "egress_example" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example.id
}

4. まとめ(最終成果物の作成)

以上の解説をまとめて、最終成果物を作成しましょう。
セキュリティグループの作成は頻繁に使うのでモジュール化しましょう。 フォルダ構成は以下の通り。

exaple
 |
 |--- iam_role
 |    |___ main.tf
 |
 |--- security_group (新)
 |    |___ main.tf (新)
 |
 |--- network.tf (新)
 |
 |___ main.tf (変更)

ネットワーク定義

「network.tf」にネットワーク定義を記載しましょう。
以下のようになります。

# VPC定義
resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "example"
  }
}

# インターネットゲートウェイ
resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id
}

# パブリックルートテーブル
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
}

# パブリックルート
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}

# パブリックサブネット 1a
resource "aws_subnet" "public_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

# パブリックサブネット 1c
resource "aws_subnet" "public_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = true
}

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "public_0" {
  subnet_id      = aws_subnet.public_0.id
  route_table_id = aws_route_table.public.id
}

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "public_1" {
  subnet_id      = aws_subnet.public_1.id
  route_table_id = aws_route_table.public.id
}

# EIP (NATゲートウェイ 1a)
resource "aws_eip" "nat_gateway_0" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# EIP (NATゲートウェイ 1c)
resource "aws_eip" "nat_gateway_1" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# NATゲートウェイ 1a
resource "aws_nat_gateway" "nat_gateway_0" {
  allocation_id = aws_eip.nat_gateway_0.id
  subnet_id     = aws_subnet.public_0.id
  depends_on    = [aws_internet_gateway.example]
}

# NATゲートウェイ 1c
resource "aws_nat_gateway" "nat_gateway_1" {
  allocation_id = aws_eip.nat_gateway_1.id
  subnet_id     = aws_subnet.public_1.id
  depends_on    = [aws_internet_gateway.example]
}

# プライベートルートテーブル 1a
resource "aws_route_table" "private_0" {
  vpc_id = aws_vpc.example.id
}

# プライベートルートテーブル 1c
resource "aws_route_table" "private_1" {
  vpc_id = aws_vpc.example.id
}

# プライベートルート 1a
resource "aws_route" "private_0" {
  route_table_id         = aws_route_table.private_0.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_0.id
  destination_cidr_block = "0.0.0.0/0"
}

# プライベートルート 1c
resource "aws_route" "private_1" {
  route_table_id         = aws_route_table.private_1.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_1.id
  destination_cidr_block = "0.0.0.0/0"
}

# プライベートサブネット 1a
resource "aws_subnet" "private_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.65.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

# プライベートサブネット 1c
resource "aws_subnet" "private_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.66.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = false
}

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "private_0" {
  subnet_id      = aws_subnet.private_0.id
  route_table_id = aws_route_table.private_0.id
}

# サブネットとルートテーブルの紐付け
resource "aws_route_table_association" "private_1" {
  subnet_id      = aws_subnet.private_1.id
  route_table_id = aws_route_table.private_1.id
}

セキュリティグループのモジュール化

セキュリティグループの作成は頻繁に使うのでモジュール化しましょう。
「./security_group/main.tf」を以下のように記述します。
「name」「vpc_id」「port」「cidr_blocks」を外部パラメータとし、再利用可能としています。

variable "name" {}
variable "vpc_id" {}
variable "port" {}
variable "cidr_blocks" {
  type = list(string)
}

resource "aws_security_group" "default" {
  name   = var.name
  vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "ingress" {
  type              = "ingress"
  from_port         = var.port
  to_port           = var.port
  protocol          = "tcp"
  cidr_blocks       = var.cidr_blocks
  security_group_id = aws_security_group.default.id
}

resource "aws_security_group_rule" "egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.default.id
}

output "security_group_id" {
  value = aws_security_group.default.id
}

セキュリティグループモジュールの呼び出し

先に作成したセキュリティグループモジュールを呼び出し、セキュリティグループを定義しましょう。

「./main.tf」に以下を追記します。

# モジュール[security_group]呼出
module "example_sg" {
  source      = "./security_group"
  name        = "module-sg"
  vpc_id      = aws_vpc.example.id
  port        = 80
  cidr_blocks = ["0.0.0.0/0"]
}

5. 実行

まずはinitしましょう。これをやらないと、モジュールの呼び出しができません。

$ terraform init

実行計画の確認。前回にも書きましたが、破壊的な変更が入っていないか入念に確認しましょう。

$ terraform plan

実行。実行されたら、AWSの管理画面から確認しましょう。
うまくいっていればIAMページ左上の「Search IAM」のフォームに「describe-regions-for-ec2」と入力て検索ができるはずです。

$ terraform apply