
目次
はじめに
構成図
Terraformファイル構成
- terraform.tf
- provider.tf
VPC関連の定義
- variables.tf
- vpc.tf
AWSマネジメントコンソール上での作業
次回予告
はじめに
以前にAWS CloudFormationを使用してVPCの構築やAmazon EC2、Amazon S3の構築検証をご紹介いたしました。
今回はTerraformを使用してAWS上にWebサーバーの構築まで行いたいと思います。
Terraformで構築するAWSの構成を記載します。
構成図

Application Load Balancerを使用して、Webサーバーはプライベートサブネットに配置します。
VPN等の用意が無いと通常はプライベートサブネットに配置したEC2インスタンスへはssh接続が出来ないのですが、実用を考慮してSSMのSession ManagerでEC2へローカルから接続が出来るようにIAM Policyの設定もTerraformで後ほど書く予定です。それでは、Terraformのファイル構成を記載します。
Terraformファイル構成
terraform/ ├─vars/ │ └─terraform.tfvars ├─acm.tf ├─ec2.tf ├─elb.tf ├─iam.tf ├─provider.tf ├─route53.tf ├─securitygroup.tf ├─terraform.tf ├─variables.tf └─vpc.tf
複数のAWSサービスをTerraformで構築をしていきたいと思いますので、何回かに記事を分けて記載させていただきますので、予めご了承ください。まずは基本設定となる「terraform.tf」を書いていきます。
terraform.tf
terraform { required_version = "~> 1.1" backend "s3" { region = "ap-northeast-1" bucket = "itport-terraform-state" key = "terraform.tfstate" profile = "itport" } }
Terraformではリソースの作成をするとステータスを管理するファイル「.tfstate」ファイルが生成されますが、Git等でこのファイルが分散されると不整合が発生するため、バックエンドという仕組みでstateファイルを一貫した場所で管理できるように上記のような書き方で定義ができます。バックエンドはAmazon S3をサポートしているので、今回はAmazon S3にterraform.tfstateを配置するように書いております。6行目と8行目は適宜変更ください。bucketはバケット名、profileは自身のローカルのcredentialsに設定したprofileの名前を指定します。
~/.aws/credentialsの例
[itport] aws_access_key_id = <YOUR_AWS_ACCESS_KEY> aws_secret_access_key = <YOUR_AWS_SECRET_ACCESS_KEY>
バックエンドとなるAmazon S3のバケットをまずは事前に作成しておく必要があるためAWS-CLIでバケットを作成します。
aws s3api --profile itport create-bucket --acl private --bucket itport-terraform-state --create-bucket-configuration LocationConstraint=ap-northeast-1
次にAWSとしてTerraformを定義していくためにproviderを定義します。
provider.tf
provider "aws" { profile = var.profile region = var.region default_tags { tags = { Terraform = var.tag_terraform Env = var.env SystemName = var.tag_system_name } } }
上記providier.tf内のprofileプロパティは変数からprofile名を取得するようにしています。そしてdefault_tagsはTerraformで作成されるリソース全てにデフォルトで設定するタグが定義できます。これも変数から値を入れるようにしており、変数の値はterraform apply時に直接指定するか、.tfvarsファイルで変数の値を書いていきます。apply時に毎回変数を書くのは大変なので後ほど/vars/terraform.tfvarsにて変数の値を書いていく予定です。
VPC関連の定義
続いてVPCの定義を作成していきますが、変数を使いたいので先に「variables.tf」に変数を用意します。
variables.tf
# -------------------------------------- # # General # -------------------------------------- # # Prefix variable "name_prefix" { type = string description = "system code prefix" } # Environment variable "env" { type = string description = "tag" } # Tags variable "tag_terraform" { type = string default = "true" description = "tag" } variable "tag_system_name" { type = string description = "tag" } # Profile Name variable "profile" { type = string description = "aws cli profile name" } variable "region" { type = string default = "ap-northeast-1" description = "AWS region in which resources will get deployed. Defaults to Tokyo." } # -------------------------------------- # # VPC # -------------------------------------- # variable "vpc_cidr" { type = string description = "VPC CIDR" } variable "private_subnet_cidr_a_1" { type = string description = "Private Subnets CIDR" } variable "private_subnet_cidr_c_1" { type = string description = "Private Subnets CIDR" } variable "public_subnet_cidr_a_1" { type = string description = "Public Subnets CIDR" } variable "public_subnet_cidr_c_1" { type = string description = "Public Subnets CIDR" }
続いてVPC関連のリソースを定義します。
vpc.tf
# ------------------------------------------------------ # # VPC # ------------------------------------------------------ # resource "aws_vpc" "vpc" { cidr_block = var.vpc_cidr enable_dns_support = "true" enable_dns_hostnames = "true" assign_generated_ipv6_cidr_block = "false" tags = { Name = "${var.name_prefix}-vpc" } } # ------------------------------------------------------ # # Private Subnet # ------------------------------------------------------ # resource "aws_subnet" "private_a_1" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_subnet_cidr_a_1 assign_ipv6_address_on_creation = "false" map_public_ip_on_launch = "true" availability_zone = "ap-northeast-1a" tags = { Name = "${var.name_prefix}-private-subnet-a-1" } } resource "aws_subnet" "private_c_1" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_subnet_cidr_c_1 assign_ipv6_address_on_creation = "false" map_public_ip_on_launch = "true" availability_zone = "ap-northeast-1c" tags = { Name = "${var.name_prefix}-private-subnet-c-1" } } # ------------------------------------------------------ # # Public Subnet # ------------------------------------------------------ # resource "aws_subnet" "public_a_1" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet_cidr_a_1 assign_ipv6_address_on_creation = "false" map_public_ip_on_launch = "true" availability_zone = "ap-northeast-1a" tags = { Name = "${var.name_prefix}-public-subnet-a-1" } } resource "aws_subnet" "public_c_1" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet_cidr_c_1 assign_ipv6_address_on_creation = "false" map_public_ip_on_launch = "true" availability_zone = "ap-northeast-1c" tags = { Name = "${var.name_prefix}-public-subnet-c-1" } } # ------------------------------------------------------ # # Internet Gateway # ------------------------------------------------------ # resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = { Name = "${var.name_prefix}-igw" } } # ------------------------------------------------------ # # Elastic IP # ------------------------------------------------------ # resource "aws_eip" "ngw" { vpc = true tags = { Name = "${var.name_prefix}-eip-ngw" } } # ------------------------------------------------------ # # Nat Gateway # ------------------------------------------------------ # resource "aws_nat_gateway" "ngw_a" { allocation_id = aws_eip.ngw.id subnet_id = aws_subnet.public_a_1.id tags = { Name = "${var.name_prefix}-ngw-a" } depends_on = [aws_internet_gateway.igw] } # ------------------------------------------------------ # # Route Table # ------------------------------------------------------ # # Public resource "aws_route_table" "public" { vpc_id = aws_vpc.vpc.id tags = { Name = "${var.name_prefix}-rtb-public" } } resource "aws_route" "igw" { destination_cidr_block = "0.0.0.0/0" route_table_id = aws_route_table.public.id gateway_id = aws_internet_gateway.igw.id } resource "aws_route_table_association" "public_a" { subnet_id = aws_subnet.public_a_1.id route_table_id = aws_route_table.public.id } resource "aws_route_table_association" "public_c" { subnet_id = aws_subnet.public_c_1.id route_table_id = aws_route_table.public.id } # Private resource "aws_route_table" "private" { vpc_id = aws_vpc.vpc.id tags = { Name = "${var.name_prefix}-rtb-private" } } resource "aws_route" "ngw" { destination_cidr_block = "0.0.0.0/0" route_table_id = aws_route_table.private.id nat_gateway_id = aws_nat_gateway.ngw_a.id } resource "aws_route_table_association" "private_a" { subnet_id = aws_subnet.private_a_1.id route_table_id = aws_route_table.private.id } resource "aws_route_table_association" "private_c" { subnet_id = aws_subnet.private_c_1.id route_table_id = aws_route_table.private.id }
ネットワーク系のコンポーネントをまとめて書きました。上記の定義で以下のようなVPCが構築されます。

※ Nat Gatewayを配置しない場合は、プライベートサブネットに配置するEC2インスタンスがyumリポジトリにアクセス出来なくなるため、その場合はAmazon S3にアクセスできるようにゲートウェイ型のVPCエンドポイントを用意する必要があります。
AWSマネジメントコンソール上での作業
EC2インスタンスをTerraformで作成する予定のため、事前にAWSマネジメントコンソール上でキーペアを作成しておく必要があります。EC2 > キーペアよりキーペアの作成をします。

次回予告
今回はここまでです。結局AWS CloudFormationと何が違うの?という疑問があるかと思いますが確かに実現できることは似ています。AWS CloudFormationはYaml形式で書きましたが、Terraformではモデルを定義していくような書き方になります。開発者の方はTerraformの方が馴染みがあり書きやすいのではないかと個人的には感じました。ただしAWS CloudFormationもAWS Cloud Development Kit(AWS CDK)を利用することでプログラミングをするような感覚で作成できるようです。
また、インフラ構成を更新する際の差分検出方法にもかなりの違いがあり、TerraformではAWSマネジメントコンソール等から手動で変更した内容もterraform apply時にtfファイルに定義した内容に合わせて修正をしてくれますが、AWS CloudFormationは「Drift detect」で差分の検知は出来るものの、スタックを更新しても修正自体はしてくれないため手作業での修正が必要です。
そして、Terraformの一番のメリットはAWSに限らないという点です。逆にデメリットとしてはTerraform自体のバージョンを管理することも考慮する必要がありますので、それらのポイントを加味してツール選定をするのが良いかと思いました。
次回は、ELBやEC2インスタンスの定義を作成していきたいと思います。