sagantaf

メモレベルの技術記事を書くブログ。

AWSのVPCなどのネットワークをTerraformで作成する

はじめに

AWSVPCやサブネットなどのネットワークリソースを新規にTerraformで作成します。ネットワーク内でのローカル通信、外部からのアクセス、外部へのアクセスが可能なネットワークにします。

Terraformの基本的な使い方や、インストール方法などは、 

sagantaf.hatenablog.com

を参考にしてみてください。

この先はTerraformがすでにインストールされている前提で進めます。

また、この記事で作成するTFファイルは以下で公開しています。

GitHub - detethcotaf/terraform-sample

目次

実行環境

  • Ubuntu:18.04
  • Terraform: v0.13.4

作成するリソース

AWSにて新たにネットワーク環境を構築するにあたって必要になるリソースは、

  • VPC:ネットワークアドレスを持った論理プライベートネットワーク
  • サブネット:AZを跨いで配置するVPCを分割したネットワーク
  • インターネットゲートウェイ:インターネットとVPC内を繋ぐためのサービス
  • ルートテーブル:VPC内部やインターネットゲートウェイなどとのルーティングを設定する表

です。

それぞれをTerraformにて定義していきます。

各リソースをTFファイルとして作成

各リソースの書き方のルールについては、

Terraform Registry

を参考にできます。

VPC

まずはVPCを作成します。

resource "aws_vpc" "sample_vpc" {
  cidr_block                       = "10.10.0.0/16" # VPCに設定したいCIDRを指定
  enable_dns_support               = "true" # VPC内でDNSによる名前解決を有効化するかを指定
  enable_dns_hostnames             = "true" # VPC内インスタンスがDNSホスト名を取得するかを指定
  instance_tenancy                 = "default" # VPC内インスタンスのテナント属性を指定
  assign_generated_ipv6_cidr_block = "false" # IPv6を有効化するかを指定

  tags = {
    Name  = "sample_vpc"
  }
}

必須設定はcidr_blockのみです。

上記では、任意の設定のうち、デフォルト値から変えていない設定もあえて記載しています。なぜ設定しているかと言うと、Infrastructure as Codeとしてインフラの設定をコードとして残しておきたい場合は、デフォルトの設定でも必要なものは明記しておいた方が賢明だからです。「デフォルト値で良いと判断して何も書かなかった場合」と「その任意設定を知らない(意識していない)ことで何も書かなかった場合」の混在を避けることができるためです。

なお、instance_tenancyは、VPC内で起動するインスタンスのテナント属性を設定できます。テナント属性の種類は下記のように3種類あります。

  • default: 共有ハードウェアインスタンスの実行
  • dedicated: 専用ハードウェアインスタンスの実行(同AWSアカウントでの共有)
  • host: 専有ホスト上で実行

さらなる詳細は、Terraform Registry AWS VPCを参考にしてください。

サブネット

次にサブネットを作成します。

resource "aws_subnet" "sample_subnet_a" {
  vpc_id                          = aws_vpc.sample_vpc.id # VPCのIDを指定
  cidr_block                      = "10.10.1.0/24" # サブネットに設定したいCIDRを指定
  assign_ipv6_address_on_creation = "false" # IPv6を利用するかどうかを指定
  map_public_ip_on_launch         = "true" # VPC内インスタンスにパブリックIPアドレスを付与するかを指定
  availability_zone               = "ap-northeast-1a" # サブネットが稼働するAZを指定

  tags = {
    Name = "sample_subnet_1a"
  }
}

resource "aws_subnet" "sample_subnet_c" {
  vpc_id                          = aws_vpc.sample_vpc.id
  cidr_block                      = "10.10.1.0/24"
  assign_ipv6_address_on_creation = "false"
  map_public_ip_on_launch         = "true"
  availability_zone               = "ap-northeast-1c"

  tags = {
    Name = "sample_subnet_1c"
  }
}

2つのAZそれぞれにサブネットを作成する内容にしています。

必須設定はvpc_idcidr_blockです。

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

続いてインターネットゲートウェイを作成します。

resource "aws_internet_gateway" "sample_igw" {
  vpc_id = aws_vpc.sample_vpc.id # VPCのIDを指定

  tags = {
    Name  = "sample_igw"
  }
}

必須設定はvpc_idのみです。

ルートテーブル

さて、VPC/サブネットとインターネットゲートウェイを作成したので、それぞれでルーティングできるようにルートテーブルと、紐付け設定を作成していきます。

まずはルートテーブルです。ルートテーブルは「送信されたパケットの宛先IPアドレスを確認し、どこに通信を流すかを判断する時に使う表」です。

そのため、ルートの設定には、宛先IPアドレスとターゲットの属性(local、gateway、特定のEC2、など)を指定する必要があります。以下では、外部向け通信を可能にするためのルート設定を加えています。

resource "aws_route_table" "sample_rt" {
  vpc_id = aws_vpc.sample_vpc.id # VPCのIDを指定

  # 外部向け通信を可能にするためのルート設定
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.sample_igw.id
  }

  tags = {
    Name  = "sample_rt"
  }
}

なお、サブネット内での通信を可能にするためのルートの設定も必要ですが、これは自動的に作成されるので、設定していません。

ルートテーブルを作成したので、実際のVPCやサブネットと紐づける設定を作成します。

resource "aws_main_route_table_association" "sample_rt_vpc" {
  vpc_id         = aws_vpc.sample_vpc.id # 紐づけたいVPCのIDを指定
  route_table_id = aws_route_table.sample_rt.id # 紐付けたいルートテーブルのIDを指定
}

resource "aws_route_table_association" "sample_rt_subet_a" {
  subnet_id      = aws_subnet.sample_subnet_a.id # 紐づけたいサブネットのIDを指定
  route_table_id = aws_route_table.sample_rt.id # 紐付けたいルートテーブルのIDを指定
}

resource "aws_route_table_association" "sample_rt_subnet_c" {
  subnet_id      = aws_subnet.sample_subnet_c.id
  route_table_id = aws_route_table.sample_rt.id
}

それぞれ、VPC本体、サブネット2つとルートテーブルを紐づけています。

作成したTFファイルを適用して実際にAWSリソースを作成する

ここまでの設定を全て書いたnetworks.tfというファイルを用意します。

また以下の内容でproviders.tfを作成し、AWSへのリソース作成であることを明示します。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.10.0"
    }
  }
}

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

これらのファイルがあるディレクトリで、以下のコマンドを実行し、AWSリソースを作成します。(アクセスしたいAWSアカウントのconfigureの設定は完了している前提です。)

terraform init # 初期化
terraform plan # 設定が正しいかを事前確認
terraform apply # 適用

以下のメッセージが出れば問題なく設定完了です。

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

AWSのコンソールを確認することで、設定した通りのVPCやルートテーブルが作成されていることがわかると思います。

別途、作成したVPCを紐付けたEC2を作成することで、sshによるログインや外部からのライブラリのインストールなどが可能なことを確認できます。

環境変数およびmoduleを利用して扱いやすくする

さて、以上で問題なく新たなネットワークを作成できましたが、ここではTFファイルをさらに扱いやすくするため、環境変数化とmodule化をしていきます。

環境変数

環境変数を利用してTFファイルを再利用可能にします。

環境変数を利用するには、variables.tfをいうファイルで環境変数を定義しておく必要があります。

今回は、VPC/サブネットのCIDRと環境(devかprodか)を環境変数として設定してみました。 以下はvariables.tfの例です。

variable ENV {
  type = string
  description = "環境(prod/dev)"
  default = "dev"
}

variable VPC_CIDR {
  type = string
  description = "VPCのCIDR"
  default = "10.0.0.0/16"
}

variable SUBNET_A_CIDR {
  type = string
  description = "subnet aのCIDR"
  default = "10.0.1.0/24"
}

variable SUBNET_C_CIDR {
  type = string
  description = "subnet cのCIDR"
  default = "10.0.2.0/24"
}

また、networks.tfを以下のように変更します。

変更点としては、各所で環境変数を利用した点と、tagにENVの情報を付与した点です。

resource "aws_vpc" "sample_vpc" {
  cidr_block                       = var.VPC_CIDR # VPCに設定したいCIDRを指定
  enable_dns_support               = "true" # VPC内でDNSによる名前解決を有効化するかを指定
  enable_dns_hostnames             = "true" # VPC内インスタンスがDNSホスト名を取得するかを指定
  instance_tenancy                 = "default" # VPC内インスタンスのテナント属性を指定
  assign_generated_ipv6_cidr_block = "false" # IPv6を有効化するかを指定

  tags = {
    Name  = "sample_vpc_${var.ENV}"
    Env = var.ENV
  }
}

resource "aws_subnet" "sample_subnet_a" {
  vpc_id                          = aws_vpc.sample_vpc.id # VPCのIDを指定
  cidr_block                      = var.SUBNET_A_CIDR # サブネットに設定したいCIDRを指定
  assign_ipv6_address_on_creation = "false" # IPv6を利用するかどうかを指定
  map_public_ip_on_launch         = "true" # VPC内インスタンスにパブリックIPアドレスを付与するかを指定
  availability_zone               = "ap-northeast-1a" # サブネットが稼働するAZを指定

  tags = {
    Name = "sample_subnet_${var.ENV}_1a"
    Env = var.ENV
  }
}

resource "aws_subnet" "sample_subnet_c" {
  vpc_id                          = aws_vpc.sample_vpc.id
  cidr_block                      = var.SUBNET_C_CIDR
  assign_ipv6_address_on_creation = "false"
  map_public_ip_on_launch         = "true"
  availability_zone               = "ap-northeast-1c"

  tags = {
    Name = "sample_subnet_${var.ENV}_1c"
    Env = var.ENV
  }
}


resource "aws_internet_gateway" "sample_igw" {
  vpc_id = aws_vpc.sample_vpc.id # VPCのIDを指定

  tags = {
    Name  = "sample_igw_${var.ENV}"
    Env = var.ENV
  }
}


resource "aws_route_table" "sample_rt" {
  vpc_id = aws_vpc.sample_vpc.id # VPCのIDを指定

  # 外部向け通信を可能にするためのルート設定
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.sample_igw.id
  }

  tags = {
    Name  = "sample_rt_${var.ENV}"
    Env = var.ENV
  }
}

resource "aws_main_route_table_association" "sample_rt_vpc" {
  vpc_id         = aws_vpc.sample_vpc.id # 紐づけたいVPCのIDを指定
  route_table_id = aws_route_table.sample_rt.id # 紐付けたいルートテーブルのIDを指定
}

resource "aws_route_table_association" "sample_rt_subet_a" {
  subnet_id      = aws_subnet.sample_subnet_a.id # 紐づけたいサブネットのIDを指定
  route_table_id = aws_route_table.sample_rt.id # 紐付けたいルートテーブルのIDを指定
}

resource "aws_route_table_association" "sample_rt_subnet_c" {
  subnet_id      = aws_subnet.sample_subnet_c.id
  route_table_id = aws_route_table.sample_rt.id
}

これでterraform apply時に自動的にvariables.tfの中身を読み込み、デフォルト値に設定した環境変数が反映されます。

module化

TFファイルをさらに共有して使いやすくするために、module化します。

以下のようなディレクトリ構成を作り、各ファイルを格納します。

├── modules
│   ├── networks.tf
│   ├── provider.tf
│   └── variables.tf
└── services
    └── dev
        └── main.tf

main.tfには環境変数とともに実行するソースモジュールを指定します。

module "samples" {
  source  = "../../modules/"
  
  ENV     = "dev"
  VPC_CIDR = "10.10.0.0/16"
  SUBNET_A_CIDR = "10.10.1.0/24"
  SUBNET_C_CIDR = "10.10.2.0/24"
}

この状態で、services/dev ディレクトリでapplyすることで、main.tfがmodulesの全てのファイルを読み込み、AWSリソースを作成してくれます。

cd services/dev
terraform init
terraform apply

以上のようにmodule化することでmain.tfの中身を変えるだけで様々な設定のネットワークを作成することができるようになります。