Terraform未経験からS3, CloudFrontの作成ができるようになるまでにやったこと

## 本記事について

terraformを初心者がこなすためにつまづく点やどうするべきかといったポイントのメモ

## 対象

- UdemyでAWSについて学んだことのあるレベルを想定
- terraformは使ったことないけど、なんとなく概要はわかる。


## 本記事でやること

環境構築
STEP1 S3作る
STEP2 S3とCloudFront作る

## 環境

Mac OS 12.5 M2


## terraformとは？
AWSに関しては、

AWSの設定書いて、terraform apply したら反映されるやつ。

## インストール

dockerで構築するって手もあるが、tfenvのほうが楽そうだったのでtfenvを選択。

```
$ brew install tfenv

# バージョン確認
$ tfenv list-remote

# インストールでバージョン指定しなかったら失敗した。指定したら成功した。
$ tfenv install 1.2.8
$ terraform -v
```

AWSCLIもインストール

IAMでユーザー作ってaccess_keyを発行し、登録しておく。
以下のようにファイル作られていればOK

```
~$ cat .aws/config
[default]
region = ap-northeast-1
output = json
~$ cat .aws/credentials
[default]
aws_access_key_id = hogehoge
aws_secret_access_key = hogehoge
```

## 作業フォルダ作成

いろいろベストプラクティスなフォルダ構成の記事は探したが、
初学者に求められるフォルダ構成はシンプルさだけかなと思うので、めっちゃシンプルな構成にする。
共通化は考えない。

```
first-terraform(master)$ tree .           
.
├── README.md
├── dev
│   ├── main.tf
│   ├── terraform.tfstate
│   └── terraform.tfstate.backup
└── docs
    ├── 00_command.md
    ├── 10_s3.md
    └── 11_cloudfront.md
```

README.md このリポジトリでなにをする？とかをメモ
dev 作業フォルダ。prodを作る気はないが直下に作るのが嫌だったので。
dev/main.tf ここにterraformの中身書いていく。**ファイル分割は必要に思えるまでしない**。
docs 残しておくべきメモの量が多く、issueやPRだと管理しづらかったためドキュメントフォルダを作った。

git管理は作業ディレクトリ直下。

```.gitignore
#  Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# .tfvars files
*.tfvars
```
このgitignoreはどこかで拾った。

## 初期化

```
# devフォルダ内で実行する
# lockファイルとかができる。
terraform init
```

## STEP1 S3作る

こうなった。

```main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.29.0"
    }
  }

  required_version = "1.2.8"
}

provider "aws" {
  profile = "default"
}

resource "aws_s3_bucket" "b" {
  bucket = "teach-tf-test-bucket"

  tags = {
    Name        = "teach-tf-test-bucket"
    Environment = "Dev"
  }
}

resource "aws_s3_bucket_ownership_controls" "b" {
  bucket = aws_s3_bucket.b.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_public_access_block" "b" {
  bucket = aws_s3_bucket.b.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

```
$ terraform fmt
$ terraform validate
Success! The configuration is valid.

# 確認
$ terraform plan
# 反映
$ terraform apply
```


公式の[aws-get-started](https://learn.hashicorp.com/tutorials/terraform/aws-build?in=terraform/aws-get-started)を参考に書き換えている。

required_providersのawsのversionはterraform registryを見て最新を取得
https://registry.terraform.io/providers/hashicorp/aws/latest

required_versionは`terraform -v`で出たやつ

providerの設定はローカルであればprofileに書いてあるやつそのまま使えばいいかなということで、profileから情報取得するようにした。
https://registry.terraform.io/providers/hashicorp/aws/latest/docs#shared-configuration-and-credentials-files

regionはprofileに記載されているので省略。

resourceの書き方
https://www.terraform.io/language/resources/syntax#resource-syntax

s3の設定は主に[ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#private-bucket-w-tags)を見ている。

ただしここに載っているサンプルの設定はコンソールで手動で設定するときの「推奨」の設定にはなっていない。
そこで推奨の設定になるようにサンプルとは一部を変更して設定した。

具体的には
- aws_s3_bucket_ownership_controls
- aws_s3_bucket_public_access_block

の2つ

### terraform.tfstateについて
いろんな記事でterraform.tfstateはS3に配置することが推奨されている。
しかし、本記事においては環境は、「作ったら削除する」使い捨て環境なのでtfstateを管理する必要がない。そこでS3にアップロードしないようにしている。

## STEP2 S3とCloudFront作る

```main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.29.0"
    }
  }

  required_version = "1.2.8"
}

provider "aws" {
  profile = "default"
}

resource "aws_s3_bucket" "b" {
  bucket = "teach-tf-test-bucket"

  tags = {
    Name        = "teach-tf-test-bucket"
    Environment = "Dev"
  }
}

resource "aws_s3_bucket_ownership_controls" "b" {
  bucket = aws_s3_bucket.b.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_public_access_block" "b" {
  bucket = aws_s3_bucket.b.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_policy" "b" {
  bucket = aws_s3_bucket.b.id
  policy = data.aws_iam_policy_document.s3_cf_policy.json
}

data "aws_iam_policy_document" "s3_cf_policy" {
  statement {
    sid    = "AllowCloudFrontServicePrincipalReadOnly"
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }

    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.b.arn}/*"]

    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = [aws_cloudfront_distribution.s3_distribution.arn]
    }
  }
}

resource "aws_cloudfront_distribution" "s3_distribution" {
  origin {
    domain_name = aws_s3_bucket.b.bucket_regional_domain_name
    origin_id   = aws_s3_bucket.b.id

    origin_access_control_id = aws_cloudfront_origin_access_control.s3_oac.id
  }

  default_cache_behavior {
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = aws_s3_bucket.b.id
    cache_policy_id        = data.aws_cloudfront_cache_policy.default.id
    compress               = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["JP"]
    }
  }

  enabled         = true
  is_ipv6_enabled = true

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

data "aws_cloudfront_cache_policy" "default" {
  name = "Managed-CachingOptimized"
}

resource "aws_cloudfront_origin_access_control" "s3_oac" {
  name                              = "teach-tf-test-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

```

[ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/4.29.0/docs/resources/cloudfront_distribution)

こちらもS3と同様にterraformのサンプルを参考にしつつ、手動でコンソールでCloudFrontを一度作成してみて、それと同一になるようにterraform側で設定した。

ポイントは以下

■ default_cache_behaviorのcache_policy_id
サンプルと同様の方法でやるとLegacyになるのでcache_policy_idを使う
[設定の仕方](https://github.com/hashicorp/terraform-provider-aws/issues/14373#issuecomment-784813532)

■ aws_cloudfront_origin_access_control

OAIはLegacyなのでOACを使う。
[参考](https://zenn.dev/kou_pg_0131/articles/tf-cloudfront-oac)

[リファレンス](https://registry.terraform.io/providers/hashicorp/aws/4.29.0/docs/resources/cloudfront_origin_access_control)


descriptionはRequiredって書いてあるけどなくても大丈夫そうな。

aws_s3_bucket_policyは[Developer Guide]とterraformのドキュメント見て同じになるように設定

これをapplyしてS3にオブジェクトアップロードするとcloudfront経由で取得できる。

リンク例
https://d1hogehogevm.cloudfront.net/box_3d.png