Terraform で、AWS ECS(Fargate), ALB, RDS の構成をコード化した。

## 概要
AWS ECS(Fargate), RDS, ALB 構成をTerraformでコード化してみましたので、ポイントをまとめます。

アーキテクチャの概要は、下記の記事にまとめています

https://qiita.com/hiiragiya/items/7df1af73b6f3e34b63ab

レポジトリはこちらで公開しています。

https://github.com/sora33/terraform-ecs-rds

### 参考
ネット上にすごく良い情報があるので、それらから多くを学びました。
ただ、皆さんベースは、「実践Terraform　AWSにおけるシステム設計とベストプラクティス」という印象です。こちらの本は見た方がいいです。

- [著書：野村 友規「実践Terraform　AWSにおけるシステム設計とベストプラクティス」](https://www.amazon.co.jp/dp/B07XT7LJLC)
- [Terraform で ECS 環境を構築する① 〜ネットワーク編〜](https://blog.linkode.co.jp/entry/2020/10/15/090000)
- [YouTube: それ、どこに出しても恥ずかしくないTerraformコードになってるか？](https://www.youtube.com/watch?v=0IQ4IScqQws)
- [YouTube: ベストなTerraformディレクトリ構成を考察してみた](https://www.youtube.com/watch?v=raU19TGHD9w&t=391s)
- [Terraform ベストプラクティスを整理してみました。
](https://dev.classmethod.jp/articles/terraform-bset-practice-jp/)


## ディレクトリ構成・それぞれのファイルについて
案件規模、チームによって変わってくるところだと思いますが、
下記のような構成でスタートすれば、とりあえず安定するはず。。

ご指摘あればください。

### ディレクトリ構成
ディレクトリ構成は**envs**と**modules**に分かれており、それぞれの環境（本番、ステージング、テスト）で独立した設定を行うことができます。

モジュール分割について、あまり細かく分けると**output**と**variable**が増えて煩雑になるので、根幹となる**ecs-alb-rds**は１つのモジュールとしています。

利用頻度の多い**IAM**や**セキュリティグループ**は、再利用しやすいようにモジュール化しました。

```
.
├── envs （それぞれの環境下で、terraform initなどコマンドを叩く）
│   ├── prod （本番環境）
│   │   ├── backend.tf （tfstateの管理場所S3）
│   │   ├── local.tf （ローカル）
│   │   ├── variable.tf （変数）
│   │   ├── main.tf （モジュールを呼び出して、リソース作成するところ）
│   │   ├── provider.tf （リージョン、tagのデフォルト設定）
│   │   ├── terraform.tfvars （環境変数）
│   │   └── version.tf （terraform,awsのバージョン管理）
│   ├── stg （ステージング環境: prodと同じファイル構成）
│   └── test （テスト環境: prodと同じファイル構成）
└── modules （モジュール）
    ├── ecs-alb-rds （env/hoge/main.tfから利用される）
    │   ├── alb.tf （ロードバランサ、リスナー、セキュリティグループ）
    │   ├── ecr.tf （ECR, ライフサイクルポリシー）
    │   ├── ecs.tf （ECSのクラスター、タスク定義、サービス、ターゲットグループ、セキュリティグループ）
    │   ├── network.tf （VPC, Subnet, route table, Nat Gateway）
    │   ├── output.tf （出力 （ターミナルに表示）したい値）
    │   ├── rds.tf （RDS, セキュリティグループ）
    │   ├── route53.tf （Route53, ACM情報を取得）
    │   └── variable.tf （main.tfで利用時に必要な変数）
    ├── iam_role （modules/ecs-alb-rds/hoge.tfから利用される）
    │   ├── main.tf （リソース定義）
    │   ├── output.tf （出力値）
    │   └── variable.tf （引数）
    └── security_group （modules/ecs-alb-rds/hoge.tfから利用される）
        ├── main.tf （リソース定義）
        ├── output.tf （出力値）
        └── variable.tf （引数）
```

### それぞれのファイルについて
#### envs/prodフォルダの中身
```terraform:backend.tf
terraform {
  backend "s3" {
    bucket = "tfstate-hc-ecs"
    key    = "envs/prod/terraform.tfstate"
    region = "ap-northeast-1"
  }
}
```
Terraformのバックエンド（状態を保存する場所）の設定を行います。ここでは、Amazon S3をバックエンドとして利用し、**tfstate-hc-ecs** バケットの **envs/prod/terraform.tfstate** キーでtfstateファイルを保存しています。

S3バケット作成する方法は後述します。

```terraform:local.tf
locals {
  project          = "hcEcsProject"
  env              = "prod"
}
```
ローカル変数を設定しています。**project** と **env** 変数を使い、後で他のファイルで再利用できるようにしています。

```terraform:main.tf
module "main" {
  source = "../../modules/ecs-alb-rds"
  project          = local.project
  env              = local.env
  domain           = "hogehoge"
  db_username      = "myapp"
  db_password      = "password" # TODO: Change this to a secure password
  rails_master_key = var.rails_master_key
}

output "alb_dns_name" {
  value = module.main.alb_dns_name
}
output "rds_identifier" {
  value = module.main.rds_identifier
}
```
実際のAWSリソース（**ECS, ALB, RDS**など）を構築するモジュールを呼び出しています。変数には先程設定したローカル変数や、他の変数（例：db_username, db_password など）を渡しています。

後述しますが、dbパスワードは、作成後に変更する前提です。
**domain**や**db_username**などを変数にしてもいいのですが、直接書いた方が見やすいので、とりあえずコチラに記述しています。

**project**と**env**は、**local**にすることで、変更すべきというのを明示しています。
**rails_master_key**は、**terraform.tfvars**で管理するので、変数化しています。

```terraform:provider.tf
provider "aws" {
  region = "ap-northeast-1"
  default_tags {
    tags = {
      Project     = local.project
      Environment = local.env
      ManagedBy   = "Terraform"
    }
  }
}
```
AWSプロバイダを設定しています。リージョンやデフォルトのタグも指定しています。

```terraform:variable.tf
variable "rails_master_key" {
  description = "The master key for Rails"
  type        = string
  sensitive   = true
}
```
このファイルで、外から入力可能な変数（このケースではRailsのマスターキー）を設定しています。**sensitive = true **はこの変数が機密情報であることを示しています。

```terraform:version.tf
terraform {
  required_version = "1.5.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.16.1"
    }
  }
}
```
このファイルで、使用するTerraformとAWSプロバイダのバージョンを指定しています。これにより、環境が異なる開発者間でも同じバージョンが使われます。


```terraform:terraform.tfvars
rails_master_key = "hogehoge"
```
このファイルで、**variable.tf**で定義した変数（このケースでは**rails_master_key**）の実際の値を設定しています。

**.gitignore**に設定して、ローカルのみで保持するファイルです。

これらのファイルは、TerraformでAWSのリソースを効率的に管理するための一例です。この構成を参考に、プロジェクトに最適な設定を行ってください。

#### モジュールフォルダの中身は
ググればたくさん出てくるので、そちらを参考にしてください。。
すみません。


## 実行手順
続いて、実行手順を説明します。

### 前提条件
この手順を始める前に、AWS CLIが設定済みである必要があります。

### tfstateとS3バケット
まず、Terraformのステートファイル（tfstate）は、クラウド環境で管理するのがベストプラクティスです。この理由としては、クラウド環境での管理が、セキュリティ、可用性、スケーラビリティの面で優れているからです。

### 依存関係の注意点
tfstateを管理するS3バケットをTerraformで直接構築することは控えています。これは、tfstate自体がS3バケットの状態を管理するものであり、そのS3バケットをtfstateで管理しようとすると、依存関係が循環してしまう可能性があるからです。


### 実行手順
1. tfstate格納用のS3バケットを作成
2. cd envs/prod
2. ./backend.tfの**keyファイル**にS3情報を記述
2. ./variable.tfや.local.tfの値を変更
4. 初期化 terraform init
5. 確認  terraform plan
6. 適用  terrafrom apply

以上が基本的な実行手順です。これを順に実行することで、Terraformプロジェクトの構築と運用が行えます。

## tfstate用のS3を作成する方法
以下の手順では、AWS S3バケットを作成し、バージョニング、暗号化、およびブロックパブリックアクセスの設定を行います。これによって、Terraformのステートファイル（tfstate）を安全に保存できます。

### 作成
このコマンドで、**tfstate-hc-ecs**という名前のS3バケットを **ap-northeast-1**（東京リージョン）に作成します。
```
aws s3api create-bucket --bucket tfstate-hc-ecs --create-bucket-configuration LocationConstraint=ap-northeast-1
```
### バージョニング
このコマンドにより、バケットに対してバージョニングが有効になります。これは、**ファイルの履歴を保持**することで、誤って削除や上書きをした場合でも復元できるようにします。
```
aws s3api put-bucket-versioning --bucket tfstate-hc-ecs --versioning-configuration Status=Enabled
```
### 暗号化
このコマンドで、バケット内のすべてのオブジェクト（ファイル）がサーバーサイドで**AES256アルゴリズムを使用して暗号化**されます。これにより、データの機密性が向上します。

```
aws s3api put-bucket-encryption --bucket tfstate-hc-ecs --server-side-encryption-configuration '{
  "Rules": [
    {
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      }
    }
  ]
}'
```
このコマンドにより、バケットに対して**パブリックアクセスを完全にブロック**します。これは、未認証のユーザーによるデータの読み取りや書き込みを防ぐために重要です。
### ブロックパブリックアクセス
```
aws s3api put-public-access-block --bucket tfstate-hc-ecs --public-access-block-configuration '{
  "BlockPublicAcls": true,
  "IgnorePublicAcls": true,
  "BlockPublicPolicy": true,
  "RestrictPublicBuckets": true,
}'
```

## デプロイ時の手順
下記はデプロイの手順です。コードフォーマットをしてね、ということです。
下記はローカルで実行することを想定していますが、CIに組み込むのがいいと思います。

1. 書式を標準化 terraform fmt -recursive
2. 構文や属性の妥当性を検証 terraform validate
3. プッシュなど git push

## ドメインについて（Terraformの責務外）
ドメイン設定は、Terraformの管理対象外としています。
以下は、一般的な手順です。

1. お名前.comなどで取得
2. Route53でホストゾーンを作成
3. お名前.comなどでNSを編集
4. ADM（SSL化）に登録

## RDS作成後はパスワードを変更する
RDSインスタンスが作成された後は、セキュリティ強化のためにマスターパスワードを変更することが推奨されます。
```
aws rds modify-db-instance --db-instance-identifier 'example' --master-user-password 'NewMasterPassword!'
```
AWS CLIを使用して、上記のコマンドで簡単にパスワードを変更できます。'example'はデータベースのインスタンスIDであり、'NewMasterPassword!'は新しいパスワードです。適宜これらを自分の環境に合わせて変更してください。

他にも、SSMなどを使ってセキュアにする方法がありますが、
上記はシンプルな方法です。

## クリーンアップ
- terraform destroyでリソースを削除（注意: データも削除されます）
- 事前に削除保護はfalseにして、スナップショットをtrueにする必要があります。
```
enable_deletion_protection = false
skip_final_snapshot    = true
```

## まとめ
はTerraformを用いたAWSリソース管理の一例ですが、参考にして自分のプロジェクトに適した形にカスタマイズすることができます。

ぜひ参考にしてください。


https://qiita.com/hiiragiya/items/7df1af73b6f3e34b63ab

https://qiita.com/hiiragiya/items/1eb5879a7244dc90fbd8