CloudFront+ALB+S3でリダイレクト+静的サイトホスティングする環境をTerraformで作ってみた

[ディップ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dip-dev) の24日目の記事となります。

今年は3つのAdvent Calendarを別々の場所書いておりその最後になります。
1つ目2つ目はこちら

1.  [SRE Advent Calendar 12日目 - SLI/SLOを策定するために考えたこと](https://qiita.com/t-okibayashi/items/9a5085803ac0b11554a0)
- [Engineering Manager vol.2 Advent Calendar 2019 19日目 - ボトムアップのエンジニアリングマネジメントを行って感じたこと](https://qiita.com/t-okibayashi/items/4f4017a728c828ce8ae3)

2つとも技術系というよりはマネジメント系によってしまったので最後ぐらいは軽く技術に触れようと本タイトルでの記事投稿をさせていただきました。

現在ディップSREチームではインフラ構成管理にTerrafromの導入をはじめております。
サービスが多くあることから全体のベストプラクティスのTerraform設計から考え、新規サービスや機能などのインフラ構成をTerraform化させやっとはじめの１歩を歩んだ状態です。
環境分割の部分については同じくSREチームの @hayaosato が環境分割について触れていますのでよければご覧ください。

- [Terraformでの環境分割について
](https://qiita.com/hayaosato/items/4c223a5658a4582809fa)

さて、今回行いたいこととしてはCloudFront+ALB+S3でリダイレクト+静的サイトホスティングする環境を作ることです。

構成図的にはこんな感じ
<img width="1000" alt="スクリーンショット 2019-12-24 11.32.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139461/c4bceeb4-2bb7-971d-b605-5ebd3033878e.png">

CloudFrontでは特定のURLパス（aaa/ bbb/ ccc/ ）の時はS3コンテンツを見せ、それ以外はALBに振り、ALBに来たアクセスは別ドメインにリダイレクトするという設定を行なっております。


今回はこの中の以下の部分をTerraform化しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139461/2feb3154-8ff4-f2ec-e353-c006dfa1bf0c.png)

コンテンツ用のディレクトリは、destroyしてしまうとコンテンツの中身自体が消えてしまうためterraformでの推奨はされていないという記事もあったためこちらは手動での作成を行なっております。

また、CFやRoute53については諸々の理由で既存で存在していたためこちらも対象外にしてあります。

実際のコードTreeとコードはこんな感じ

```
[AWSアカウント名]/
└──[環境名]
    └── [サービス名]
        └── [機能名]
            ├── alb.tf
            ├── cloudfront.tf
            ├── main.tf
            ├── provider.tf
            └── s3.tf
```

```s3.tf
// CloudFrontアクセスログ用バケット作成
resource "aws_s3_bucket" "cf-accesslog-hogehoge-proxy" {
  bucket = var.s3bucket_cf_accesslog
  acl    = "private"
  tags = {
    create_type        = "terraform"
  }
}

resource "aws_s3_bucket_public_access_block" "cf-accesslog-hogehoge-proxy" {
  bucket = "${aws_s3_bucket.cf-accesslog-hogehoge-proxy.id}"
  block_public_acls   = true
  block_public_policy = true
  ignore_public_acls  = true
  restrict_public_buckets = true
}

// S3アクセスログ用バケット作成
resource "aws_s3_bucket" "s3-accesslog-baito-app-proxy" {
  bucket = var.s3bucket_cf_accesslog
  acl    = "private"
  tags = {
    create_type        = "terraform"
  }
}

resource "aws_s3_bucket_public_access_block" "s3-accesslog-hogehoge-proxy" {
  bucket = "${aws_s3_bucket.s3-accesslog-hogehoge-proxy.id}"
  block_public_acls   = true
  block_public_policy = true
  ignore_public_acls  = true
  restrict_public_buckets = true
}
```

```alb.tf
resource "aws_lb" "hogehoge_rd" {
  name                       = var.hogehoge_rd
  load_balancer_type         = "application"
  security_groups            = ["sg-aaaa", "sg-bbbb"]
  subnets = ["subnet-cccc", "subnet-dddd"]
  enable_deletion_protection = false
  enable_http2               = true
  idle_timeout               = "60"
  internal                   = false
  ip_address_type            = "ipv4"
  tags = {
    create_type = "terraform"
  }
}

resource "aws_lb_target_group" "hogehoge_rd" {
  name       = var.hogehoge_rd
  port       = "80"
  protocol   = "HTTP"
  slow_start = "0"
  deregistration_delay = "300"
  vpc_id      = "vpc-eeee"
  tags = {
    create_type = "terraform"
  }

  health_check {
    enabled             = true
    healthy_threshold   = "5"
    interval            = "30"
    matcher             = "200"
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    timeout             = "5"
    unhealthy_threshold = "2"
  }
}

resource "aws_lb_listener" "hogehoge_proxy_80" {
  load_balancer_arn = "${aws_lb.hogehoge_rd.arn}"
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "redirect"
    order = "1"
    redirect {
      host        = "#{host}"
      path        = "/#{path}"
      port        = "443"
      protocol    = "HTTPS"
      query       = "#{query}"
      status_code = "HTTP_301"
    }
  }
}

resource "aws_lb_listener" "hogehoge_proxy_443" {
  load_balancer_arn = "${aws_lb.hogehoge_rd.arn}"
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = "arn:aws:acm:ap-northeast-1:fffff:certificate/ggggg"
  default_action {
    order            = "1"
    target_group_arn = "${aws_lb_target_group.hogehoge_rd.arn}"
    type             = "forward"
  }
}

resource "aws_lb_listener_rule" "hogehoge_proxy_443" {
  listener_arn = "${aws_lb_listener.hogehoge_proxy_443.arn}"
  priority     = "1"
  action {
    order = "1"

    redirect {
      host        = var.redirect_host
      path        = "/#{path}"
      port        = "443"
      protocol    = "HTTPS"
      query       = "#{query}"
      status_code = "HTTP_301"
    }

    type = "redirect"
  }

  condition {
    path_pattern {
      values = ["*"]
    }
  }

}
```


```cloudfront.tf

resource "aws_cloudfront_distribution" "hogehoge_com" {
  aliases = ["hogehoge.com"]

  default_cache_behavior {
    allowed_methods = ["GET", "HEAD"]
    cached_methods  = ["GET", "HEAD"]
    compress        = true
    default_ttl     = "86400"

    forwarded_values {
      cookies {
        forward = "none"
      }

      headers      = ["*"]
      query_string = true
    }

    max_ttl                = "31536000"
    min_ttl                = "0"
    smooth_streaming       = false
    target_origin_id       = "${var.target_origin_id_ELB}/*"
    viewer_protocol_policy = "redirect-to-https"
  }

  enabled         = true
  http_version    = "http2"
  is_ipv6_enabled = true

  logging_config {
    bucket          = var.s3_contents_domain
   include_cookies = false
  }

  ordered_cache_behavior {
    allowed_methods = ["GET", "HEAD"]
    cached_methods  = ["GET", "HEAD"]
    compress        = false
    default_ttl     = "86400"

    forwarded_values {
      cookies {
        forward = "none"
      }

      query_string = false
    }

    max_ttl                = "31536000"
    min_ttl                = "0"
    path_pattern           = "/aaa/"
    smooth_streaming       = false
    target_origin_id       = var.target_origin_id_S3
    viewer_protocol_policy = "redirect-to-https"
  }

  ordered_cache_behavior {
    allowed_methods = ["GET", "HEAD"]
    cached_methods  = ["GET", "HEAD"]
    compress        = false
    default_ttl     = "86400"

    forwarded_values {
      cookies {
        forward = "none"
      }

      query_string = false
    }

    max_ttl                = "31536000"
    min_ttl                = "0"
    path_pattern           = "/bbb/"
    smooth_streaming       = false
    target_origin_id       = var.target_origin_id_S3
    viewer_protocol_policy = "redirect-to-https"
  }

  ordered_cache_behavior {
    allowed_methods = ["GET", "HEAD"]
    cached_methods  = ["GET", "HEAD"]
    compress        = false
    default_ttl     = "86400"

    forwarded_values {
      cookies {
        forward = "none"
      }

      query_string = false
    }

    max_ttl                = "31536000"
    min_ttl                = "0"
    path_pattern           = "/ccc/"
    smooth_streaming       = false
    target_origin_id       = var.target_origin_id_S3
    viewer_protocol_policy = "redirect-to-https"
  }

  origin {
    custom_origin_config {
      http_port                = "80"
      https_port               = "443"
      origin_keepalive_timeout = "5"
      origin_protocol_policy   = "match-viewer"
      origin_read_timeout      = "30"
      origin_ssl_protocols     = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }

    domain_name = var.elb_domain
    origin_id   = "${var.target_origin_id_ELB}/*"

  origin {
    domain_name = var.s3_contents_domain
    origin_id   = var.target_origin_id_S3

    s3_origin_config {
      origin_access_identity = "origin-access-identity/cloudfront/${var.cloudfront_id}"
    }
  }

  price_class = "PriceClass_All"

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  retain_on_delete = false
  tags = {
    create_type = "terraform"
  }

  viewer_certificate {
    acm_certificate_arn            = var.hogehoge_acm_certificate_arn
    cloudfront_default_certificate = false
    minimum_protocol_version       = "TLSv1.1_2016"
    ssl_support_method             = "sni-only"
  }

  wait_for_deployment = true
}
```

今回、vars部分は省略しますが、無事作成することができました。

また、社内ではTerraform導入の先駆者がいないためtfファイルの作成は初めは苦労してしまいます。
その中で私自身が行なっている作成方法を紹介したいと思います。

1. 手動でAWS上に構築
  - まずはコンソール上で触れてみる、大事。
2. １で作ったものをterraformer import でリソースを取得
  - terraform import だとstateファイルから追わないといけないので初心者には厳しい
3. default設定で不要なもの、空情報がそのまま取得され通らないものを除去しTerraform実行できるように修正
  - terraformerが独自の書き方などをしているところもあるのでimportをしてもそのままapplyできない場合が多い、あとバージョンの違いあくまでも１参考として活用する
4. deployして問題ないか確認
  - １回では通らないことが多い
5. 他環境へのdeployのことも考えvariableで変数化して再度deployし差分がないか確認
  - ここでまた名前とか変えたくなって苦労する

実際まだ現在進行形でtfファイルの整備を進めておりますが、細かいところまで手が回っておらずtfファイルについては参考にならない部分が多いかもしれません。

とはいえこれからもAWSのリソース作成はTerraformを軸に進めていこうという状況なので、おそらくこの記事は今年中にもう少しアップデートさせていきます。

ということで、皆さま良いクリスマスイブをお過ごしください。