Go x Next.js(SPA) な環境をTerraformでAWSに構築してみます!
バックエンドのGoには、ECS Fargateで。
フロントエンドのSPAには、CloudFront・S3を使用して静的ホスティングをしてます。
また、System エージェントをインストールすることでFargateコンテナ内に接続できるようにしています。
はじめに
連載記事でこの環境を構築していきます。
本記事では、ネットワーク環境・ドメインHTTPS化・SPA環境の構築を行います!
フォルダ構成とか書き方、モージュル化などは、【ネットワーク環境構築】terraform AWS環境構築 第1回この記事とほぼ同じなので気になる方は御覧ください!
全体のソースコード:github
環境は以下です。
| OS | Cataline 10.15.6 |
| Terraform | 0.14.4 |
| Go | 1.16.3 |
| React | 17.0.2 |
基本構文などこちらにまとめてますので、よかったらみてください!
AWS Terraform 基本コード まとめ
連載一覧
- Go x Next.js(SPA) をTerraformでさっさと構築 1/3 ←ここ
- Go x Next.js(SPA) をTerraformでさっさと構築 2/3
- Go x Next.js(SPA) をTerraformでさっさと構築 3/3
やること
以下の定義と作成をします。
ネットワーク環境構築
【ネットワーク環境構築】terraform AWS環境構築 第1回こことほぼ同じなのでコードのみ貼っておきます。
./main.tf
# 追記
module "network" {
source = "./network"
app_name = var.app_name
}
./network.tf/main.tf
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
tags = {
Name = "${var.app_name}-vpc"
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.id
availability_zone = var.azs[count.index]
cidr_block = var.public_subnet_cidrs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-public-${var.azs_name[count.index]}"
}
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.this.id
availability_zone = var.azs[count.index]
cidr_block = var.private_subnet_cidrs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-private-${var.azs_name[count.index]}"
}
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.app_name}-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.app_name}-rtb"
}
}
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
gateway_id = aws_internet_gateway.this.id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
route_table_id = aws_route_table.public.id
subnet_id = element(aws_subnet.public.*.id, count.index)
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.app_name}-rtb-private"
}
}
resource "aws_route_table_association" "private" {
count = length(var.private_subnet_cidrs)
route_table_id = aws_route_table.private.id
subnet_id = element(aws_subnet.private.*.id, count.index)
}
./network/variables.tf
variable "app_name" {}
variable "vpc_cidr" {
default = "109.10.0.0/16"
}
variable "azs" {
default = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
}
variable "azs_name" {
default = ["1a", "1c", "1d"]
}
variable "public_subnet_cidrs" {
default = ["109.10.0.0/24", "109.10.1.0/24", "109.10.2.0/24"]
}
variable "private_subnet_cidrs" {
default = ["109.10.10.0/24", "109.10.11.0/24", "109.10.12.0/24"]
}
./network/outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
}
output "public_subnet_ids" {
value = aws_subnet.public.*.id
}
output "private_subnet_ids" {
value = aws_subnet.private.*.id
}
ドメインのHTTPS化
【ドメインhttps化・ACM(SSL)証明書発行】terraform AWS環境構築 第2回こことほぼ同じなのですが、
発行しているリージョンをバージニアにしています。
CloudFrontでhttps化したドメインを適用するには、バージニアリージョンで発行されたものしか使えないためです。(;´Д`)(これどうにかならないものか。。)
./main.tf
module "network" {
source = "./network"
app_name = var.app_name
}
# 追記
module "acm" {
source = "./acm"
domain = var.domain
}
./terraform.tfvars
domain = "<your domain>"
./acm/provider.tf
provider "aws" {
region = "us-east-1" # バージニアリージョン
alias = "virginia" # エリアス設定
}
./acm/main.tf
resource "aws_acm_certificate" "this" {
provider = aws.virginia
domain_name = var.domain
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "this" {
depends_on = [aws_acm_certificate.this]
for_each = {
for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
zone_id = data.aws_route53_zone.this.zone_id
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
}
resource "aws_acm_certificate_validation" "this" {
provider = aws.virginia
certificate_arn = aws_acm_certificate.this.arn
validation_record_fqdns = [for record in aws_route53_record.this : record.fqdn]
}
./acm/variables.tf
variable "domain" {}
./acm/data.tf
data "aws_route53_zone" "this" {
name = var.domain
private_zone = false
}
SPA環境構築
CloudFront・S3を使用して静的ホスティングできるように構築します。
./main.tf
module "network" {
source = "./network"
app_name = var.app_name
}
module "acm" {
source = "./acm"
domain = var.domain
}
module "spa" {
source = "./spa"
app_name = var.app_name
domain = var.domain
acm_id = module.acm.acm_id
}
- acm_id :先程作成したACM証明書のId
./spa/variables.tf
variable "app_name" {}
variable "domain" {}
variable "acm_id" {}
S3
まずは、Next.jsファイルを保存するようのS3バケットを定義します。
./spa/main.tf
locals {
bucket_name = var.app_name
}
resource "aws_s3_bucket" "this" {
bucket = local.bucket_name
}
resource "aws_s3_bucket_policy" "this" {
bucket = aws_s3_bucket.this.id
policy = data.template_file.s3_policy.rendered
}
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.this.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
- aws_s3_bucket_policy :S3バケットのポリシー
- bucket :適用するバケット
- policy :適用するポリシー
- aws_s3_bucket_public_access_block :S3バケットのアクセスブロックの設定
- bucket :適用するバケット
- block_public_acls
- block_public_policy
- ignore_public_acls
- restrict_public_buckets
全部Trueに設定することで「パブリックアクセスをすべて ブロック」扱いになる。
./spa/data.tf
data "template_file" "s3_policy" {
template = file("./spa/s3_policy.json")
vars = {
origin_access_identity = aws_cloudfront_origin_access_identity.this.id
bucket_name = local.bucket_name
}
}
data "aws_route53_zone" "this" {
name = var.domain
private_zone = false
}
./spa/s3_policy.json
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${origin_access_identity}"
},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::${bucket_name}/*"]
}
]
}
CloudFrontからS3に接続できるポリシーを定義しておきます。
CloudFront
コンテンツを配信するCloudFrontを定義します。
locals {
bucket_name = var.app_name
s3_origin_id = "S3-${var.app_name}"
}
resource "aws_cloudfront_origin_access_identity" "this" {
comment = var.app_name
}
resource "aws_cloudfront_distribution" "this" {
aliases = [var.domain]
origin {
domain_name = aws_s3_bucket.this.bucket_domain_name
origin_id = local.s3_origin_id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.this.cloudfront_access_identity_path
}
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
viewer_protocol_policy = "redirect-to-https"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
enabled = true
is_ipv6_enabled = true
price_class = "PriceClass_All"
default_root_object = "index.html"
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = var.acm_id
minimum_protocol_version = "TLSv1.2_2019"
ssl_support_method = "sni-only"
}
}
resource "aws_route53_record" "this" {
type = "A"
name = var.domain
zone_id = data.aws_route53_zone.this.id
alias {
name = aws_cloudfront_distribution.this.domain_name
zone_id = aws_cloudfront_distribution.this.hosted_zone_id
evaluate_target_health = false
}
}
- aws_cloudfront_origin_access_identity :CloudFrontのOAI
- aws_cloudfront_distribution
- aliases :エイリアス
- origin :オリジン設定
- domain_name :S3のドメイン名
- origin_id :オリジンID
- s3_origin_config :S3オリジンにアクセス設定
- default_cache_behavior :キャッシュの設定
- allowed_methods :許可するメソッド
- cached_methods;キャッシュするメソッド
- target_origin_id:ターゲット
- viewer_protocol_policy:プロトコルのポリシー
- forwarded_values:クエリ・クッキーの設定
- enabled:エンドユーザがアクセスできるかの可否
- is_ipv6_enabled:IPv6を有効化すかどうか
- price_class:価格クラスの設定
- default_root_object:root URLのアクセス先
- viewer_certificate:SSL設定
- acm_certificate_arn:証明書ID
- minimum_protocol_version:SSLバージョン
- ssl_support_method:SSLメソッド
- aws_route53_record:CloudFrontのエリアスレコード
[terraform] $ terraform plan
作成されるリソースの確認。
[terraform] $ terraform apply
リソースの作成。

作成していれば完了です。
S3バケットにNext.jsプロジェットをビルドしたファイルをアップロードし、https化したドメインにアクセスして表示されれば完了です。※ある程度時間を置かないと表示されずS3の方にリダイレクトされるときがあります。
おわり
これで、フロントエンドのアプリケーションデプロイは完了しました。
CloudFrontを使えば、S3にファイルをアップロードをするだけで済むのでECSと使うバックエンドに比べると簡単ですね!
次回は、API用のドメインhttps化・アプリケーションロードバランサー構築・RDSの定義を行います。
最後までご覧いただきありがとうございます!
何か疑問に思うことがあれば、何でもいいのでコメントくれれば精一杯答えさせていただきます。
Twitterとかフォローしてくれると嬉しいです。では、次回お会いしましょう!
参考記事



コメント