Open7

基本的なネットワークをterraformで作成する

おっちー(O.S)おっちー(O.S)

ゴール

  • 基本的なネットワークをterraformで作成し、そのネットワーク内にEC2サーバーを立てる
  • やりたいことをまとめると、以下のような構成になる。
サブネット名 サーバーの役割
パブリックサブネット APPサーバー用のEC2サーバー
プライベートサブネット DBサーバー(RDS)

実際にpushした差分

https://github.com/ochi-sho-private-study/terraform-practice/pull/2

参考文献

ネットワークの構築に関する文献

terraformの書き方

おっちー(O.S)おっちー(O.S)

1. VPCを作成する

概要

  • AWS上へ仮想的なネットワークを作成する
  • ちなみに、VPC(Virtual Private Cloud)に割り当てられるIPアドレス範囲は、プライベートIPアドレスである。

CIDRブロックについて

  • IPアドレスの形式
    • 我々がよく見るIPアドレスは、10進数なので192.168.2.1のような形になっている。
    • コンピューターには2進数の形で伝わるので、110000010101 ...のような形に変換される。(電気信号である0or1の情報でないといけない。)
  • IPアドレスは必ず○.○.○.○という形を取り、この一つ一つの○はそれぞれ8ビットの情報を持つ
    • 8ビットの情報を持つということは、10進数に置き換えると2の8乗で256通り(0~255)まで数字が取れるということ

  • ホスト部とネットワーク部
    • IPアドレスは32ビットの中でネットワーク部とホスト部という2つのパートに分かれており、「どのネットワークに属しているか」を把握する為のネットワーク部は固定値に、「その中のどのコンピューターか」を把握する為のホスト部はホスト毎にユニーク値となっている。

  • CIDR表記について
    • IPアドレスのネットワーク部/ホスト部をどこで区切るかを示している。
    • 仮にVPCのCIDRを10.0.0.0/16とすると、後ろ16ビット分(2の16乗)、65536個のIPアドレスがホスト部として利用可能となる。
      • 理論上は2の16乗、つまり65,536個のIPアドレスがあるが、以下の理由から、5個のアドレスが既に予約されている。

        最初の4つのIPアドレス(例: 10.0.0.0、10.0.0.1、10.0.0.2、10.0.0.3)は、ネットワークアドレス、VPCルーター、DNSサーバー、および将来の使用のために予約されています。
        最後のアドレス(例: 10.0.255.255)はブロードキャストアドレスとして予約されています。
        そのため、実際には65,536個からこれら5個を差し引いた、65,531個のIPアドレスが利用可能となります。

      • 理論上はこのネットワーク内に65,531個の通信機器(サーバー、コンピューターなど)を配置することができる。

terraformのコード

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

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "basic_network"
  }
}
おっちー(O.S)おっちー(O.S)

2. VPC内にSubnetを作成する

概要

  • VPC上へ小規模な仮想的なネットワークのグループ(ネットワーク範囲)を作成する。
  • 今回は以下の東京リージョンのAZ(アベイラビリティゾーン)1つにCIDR表示のIPアドレスを割り当てる。(後述するが、publicサブネットにはEC2サーバー、privateサブネットにはRDSを配置する)
    • ap-northeast-1a
      • 10.0.1.0/24
        • ネットワーク部が24ビットかつホスト部が8ビットになっているので、256個のIPアドレス分の通信機器を配置できる。
    • ap-northeast-1a
      • 10.0.2.0/24
        • 同上。256個のIPアドレス分の通信機器を配置できる。

ここまでのインフラ構成図

terraformのコード

  • aws_vpc.main.idのようにすると、入れ子になっている情報にアクセスできる。
# ....同上

resource "aws_subnet" "public_1a" {
  vpc_id = aws_vpc.main.id

  availability_zone = "ap-northeast-1a"
  cidr_block        = "10.0.1.0/24"

  tags = {
    Name = "basic_network-public-1a"
  }
}

resource "aws_subnet" "private_1a" {
  vpc_id = aws_vpc.main.id

  availability_zone = "ap-northeast-1a"
  cidr_block        = "10.0.2.0/24"

  tags = {
    Name = "basic_network-private-1a"
  }
}
おっちー(O.S)おっちー(O.S)

3.インターネットゲートウェイ

概要

  • VPCはデフォルトだとIN/OUTともにインターネットへの疎通は行えないため、インターネットへの出入り口を作るリソースのこと。
      1. で作成したVPCはあくまでAWS内での占有領域であり、外部との接続を行うことができない。
    • インターネットゲートウェイはその内外の窓口の役割を果たしており、通信が外のパブリック空間へと出ていくためには、必ずこのインターネットゲートウェイを通過しないといけないことになっている。
  • そこで、今回は以下の手順でインターネットゲートウェイの作成とアタッチを行う。
    1. IGW(インターネットゲートウェイ)の作成を行う。
    2. IGWとVPCと紐付けを行う。

ここまでのインフラ構成図

terraformのコード

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "basic_network"
  }
}

注意

  • この時点では、VPCとIGWのアタッチしかしていないので、サブネットはインターネットに接続できない。
おっちー(O.S)おっちー(O.S)

4. NATゲートウェイ

概要

  • プライベートサブネットからインターネットへ通信するためにNAT Gatewayを使用する。
  • NAT Gatewayは1つのElastic IPが必要なのでその割り当てを行う。
  • NAT Gatewayはインターネットゲートウェイを介して外部と通信を行うため、publicサブネット(インターネットゲートウェイが設定されているルートテーブルが関連づくサブネット)に紐づける必要がある。

NATゲートウェイとElastic IPの役割

  • Elastic IPは、AWSが提供する固定のパブリックIPアドレスのこと。
  • ざっくりいうと、NATゲートウェイはプライベート IP => Elastic IPの変換をおこなってくれる。
  • プライベートサブネット内のインスタンスはプライベートIPアドレスしか持っていないため、インターネットへ直接アクセスすることができない。
  • NATゲートウェイは、プライベートサブネット内のインスタンスからのトラフィックを受け取り、そのトラフィックの送信元IPアドレスを、NATゲートウェイに割り当てられたElastic IP(固定パブリックIP)に変換する。
    • このIPアドレス変換(プライベート IP => Elastic IPの変換)により、プライベートサブネット内のインスタンスは、自身が直接パブリックIPアドレスを持っていなくても、インターネットにアクセスできるようになる。

なぜパブリックIPアドレスに変換するとインターネットと接続できるのか?

  • パブリックIPアドレスを持つデバイスは、インターネット上の他のデバイスと直接通信できるため。
    • これによって、プライベートIPアドレスしか持たないプライベートサブネットは、Elastic IPを持つNATを通してインターネットとの通信ができる。

プライベートIPアドレスとパブリックIPアドレスの違い

  • プライベートIPアドレスは、そのネットワーク内でのみ有効であり、インターネット全体では認識されない。
    • プライベートIPアドレスは通常、NATやプロキシサーバーを介してインターネットにアクセスする。
  • パブリックIPアドレスは、インターネット上のどこからでもアクセス可能で、グローバルに一意なアドレスとなっている。

今回terraformで行うことを整理

  • NATゲートウェイを作成する。
    • 配置するパブリックサブネットを選択する。
    • Elastic IPを割り当てる。

ここまでの構成図

  • TODO: 構成図を作成する。

terraformのコード

# Elastic IP
# https://www.terraform.io/docs/providers/aws/r/eip.html
resource "aws_eip" "nat_1a" {
  # vpc = trueは非推奨
  # ref: https://github.com/aws-ia/terraform-aws-vpc/issues/125
  domain = "vpc"

  tags = {
    Name = "basic_network-natgw-1a"
  }
}

# NAT Gateway
# https://www.terraform.io/docs/providers/aws/r/nat_gateway.html
resource "aws_nat_gateway" "nat_1a" {
  subnet_id     = aws_subnet.public_1a.id # NAT Gatewayを配置するSubnetを指定
  allocation_id = aws_eip.nat_1a.id       # 紐付けるElastic IP

  tags = {
    Name = "basic_network-1a"
  }
}

参考文献

NATゲートウェイのconsole画面からの作り方

おっちー(O.S)おっちー(O.S)

5. ルートテーブルを設定する

概要

  • ルートテーブルとは、ネットワークの経路情報を設定するためのサービスのこと。
    • Subnetはデフォルトだとインターネットへ疎通できないため、Route Table でSubnetとInternet Gatewayを紐づけて疎通を可能にする必要がある
  • 今回、publicにしたいサブネットに紐づけるルートテーブルには、以下の設定をする。
    1. VPCのIDを紐づけて、ルートテーブルを作成する
    2. ルートテーブルのルートを編集して、以下の内容を追加する。(※この時点ではまだサブネットはインターネットと通信できない。)
         送信先: 0.0.0.0/0(全ての通信 という意味)
         ターゲット: 先ほどの3. で設定したインターネットゲートウェイ
      
    3. ルートテーブルにサブネットを紐付ける
  • privateにしたいサブネットに紐づけるルートテーブルには、以下の設定をする。
    1. VPCのIDを紐づけて、ルートテーブルを作成する
    2. ルートテーブルのルートを編集して、以下の内容を追加する。(※この時点ではまだサブネットはインターネットと通信できない。)
         送信先: 0.0.0.0/0(全ての通信 という意味)
         ターゲット: 先ほどの4. で設定したNATゲートウェイ
      
    3. ルートテーブルにサブネットを紐付ける

補足

2. の補足説明

このルートはVPC内のサブネットから外部(インターネット)に送信されるトラフィックに対して適用される。
具体的にいうと、以下のようになる。

VPC内のインスタンスがインターネット上のサーバー(例:Webサイト)にアクセスしようとする場合、
0.0.0.0/0 のルートが適用され、トラフィックはインターネットゲートウェイを経由してインターネットに送られる。
VPC内のインスタンスが同じVPC内の別のインスタンスと通信する場合
(例えば同じサブネット内、またはピアリングされた別のVPC内)、
この通信はインターネットゲートウェイを経由せず、VPC内で直接処理される

ここまでのインフラ構成図

TODO: サブネットがどちらもpublicになってるので、修正する。

terraformのコード

Publicサブネット用

# Publicサブネット用
# Route Table
# https://www.terraform.io/docs/providers/aws/r/route_table.html
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "handson-public"
  }
}

# Route
# https://www.terraform.io/docs/providers/aws/r/route.html
resource "aws_route" "public" {
  destination_cidr_block = "0.0.0.0/0"
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.main.id
}

# Association
# https://www.terraform.io/docs/providers/aws/r/route_table_association.html
resource "aws_route_table_association" "public_1a" {
  subnet_id      = aws_subnet.public_1a.id
  route_table_id = aws_route_table.public.id
}

Privateサブネット用

# Privateサブネット用
# Route Table (Private)
# https://www.terraform.io/docs/providers/aws/r/route_table.html
resource "aws_route_table" "private_1a" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "basic_network-private-1a"
  }
}

# Route (Private)
# https://www.terraform.io/docs/providers/aws/r/route.html
resource "aws_route" "private_1a" {
  destination_cidr_block = "0.0.0.0/0"
  route_table_id         = aws_route_table.private_1a.id
  nat_gateway_id         = aws_nat_gateway.nat_1a.id
}

# Association (Private)
# https://www.terraform.io/docs/providers/aws/r/route_table_association.html
resource "aws_route_table_association" "private_1a" {
  subnet_id      = aws_subnet.private_1a.id
  route_table_id = aws_route_table.private_1a.id
}
おっちー(O.S)おっちー(O.S)

6. セキュリティグループの設定

概要

  • インバウンドルール: どんな通信が来たら許可するのか を定めたルール
  • アウトバウンドルール: どんな通信を送ると許可されるのか を定めたルール
  • 以下の要件を満たすセキュリティグループの作成を行う。
    1. APPサーバー用
      • 一般ユーザがインターネットで閲覧するという通信
        • PORT 443(httpsの場合)もしくは、PORT 80(httpの場合)
      • 開発者がSSH接続でログインする
    2. DBサーバー用
      • EC2からの通信を許可する。

APPサーバー用セキュリティグループ

方向 タイプ プロトコル ポート番号 ソース
インバウンド HTTPS TCP 443 0.0.0.0/0
インバウンド HTTP TCP 80 0.0.0.0/0
インバウンド SSH TCP 22 [開発者のIP範囲]

DBサーバー用セキュリティグループ

方向 タイプ プロトコル ポート番号 ソース
インバウンド カスタム TCP [DBが使用するポート番号] [APPサーバー用セキュリティグループID]

DBサーバーのアウトバウンドルールについて

DBサーバーがNATゲートウェイを使用してインターネットにアクセスする場合の経路は以下のようになる。

DBサーバー
=> セキュリティグループのアウトバウンドルールチェック
=> NATゲートウェイ
=> 目的のサイト

従って、セキュリティグループのアウトバウンドルールはNAT経由でもインターネットゲートウェイ経由でも ["0.0.0.0/0"]にしないといけない。

注意

  • セキュリティグループはサブネット単位ではなく、VPC単位で作成される。
    • 各インスタンスは複数のセキュリティグループを持つことが可能。(一つのセキュリティグループはVPC内の複数のインスタンスに関連付けることができる。)
  • 従って、この時点ではセキュリティグループは作成した時点では特定のサブネットに紐づいていない状態なため、セキュリティグループのルールが有効になっていない
    • セキュリティグループをEC2インスタンスなどのリソースに割り当てることによって、そのリソースのトラフィックを制御することができる。

terraformのコード

# APPサーバー用セキュリティグループ
resource "aws_security_group" "app_sg" {
  name        = "app-sg"
  description = "Security group for application server"
  vpc_id      = aws_vpc.main.id

  # インバウンドルール
  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["YOUR_DEVELOPER_IP_RANGE"] # 開発者のIP範囲に置き換えてください
  }

  # アウトバウンドルール(全てのトラフィックを許可)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "app-sg"
  }
}

# DBサーバー用セキュリティグループ
resource "aws_security_group" "db_sg" {
  name        = "db-sg"
  description = "Security group for database server"
  vpc_id      = aws_vpc.main.id

  # インバウンドルール(APPサーバーからのアクセスを許可)
  ingress {
    description      = "Access from APP Server"
    from_port        = DB_PORT # DBが使用するポート番号に置き換えてください
    to_port          = DB_PORT
    protocol         = "tcp"
    security_groups  = [aws_security_group.app_sg.id]
  }

  # アウトバウンドルール(特定の宛先へのトラフィックを許可)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "db-sg"
  }
}

参考