Go x Next.js(SPA) をTerraformでさっさと構築 1/3

AWS

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とかフォローしてくれると嬉しいです。では、次回お会いしましょう!

参考記事

Cloudfront+S3で最近話題のSPAを構築する(設定項目の解説有) - Qiita
AWSでSPAの構築久々にSPAの構築を実施しました。すんなりできると思っていたのですが、結構忘れていることが多かったのと、困ったところがたくさんあったので備忘録として記述しておきます。なお、解…

 

コメント

タイトルとURLをコピーしました