tfstateをS3で管理

## はじめに
こんにちは、こうへいです。
今回はtfstateをS3に保存したいと思います。
Terraform Applyでリソースを作成するときにtfstateがS3に保管されていることが必要です。

#### 記事の概要
S3を作成します。今回もregistryの[module](https://registry.terraform.io/modules/terraform-aws-modules/s3-bucket/aws/latest)を使用します。
またディレクトリ構成を記事を参考に変更しています。

#### 想定読者
- AWSのリソースをTerraformで作成しようと考えている方
- tfstateをS3で管理したい方

## 今回作成するAWSリソース
- S3
以上

## ディレクトリ構成
環境ごとにディレクトリを分けています。
main.tfからmoduleを呼び出す形式です。
2つの記事を参考にディレクトリ構成を変更しています。
- [20 Terraform Best Practices to Improve your TF](https://spacelift.io/blog/terraform-best-practices)
- [Standard Module Structure](https://developer.hashicorp.com/terraform/language/modules/develop/structure)

```bash
.
├── envs 
│   ├── dev # develop環境
│   │   ├── backend.tf # tfstateを管理
│   │   ├── main.tf # module呼び出し
│   │   ├── provider.tf # providerブロック
│   │   ├── terraform.tfvars # 変数を入力
│   │   └── variables.tf # 変数
│   ├── prd # production環境
│   └── stg # staging環境
└── modules
    ├── network # ネットワークリソース
    │   ├── main.tf
    │   ├── outputs.tf
    │   └── variables.tf
    └── tfstate # tfstate
        ├── main.tf
        ├── outputs.tf
        └── variables.tf
```
※ outputs.tfとvariables.tfは空でも作成します。

## Terraformのコードと解説

まずはtfstateを格納するためのS3を作成します。
#### modules/tfstate/main.tf
```terraform:modules/tfstate/main.tf
module "s3_bucket_dev_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "3.15.1"

  bucket = "${var.env}-qiita-bucket-s3"
  acl    = "private"

  control_object_ownership = true
  object_ownership         = "ObjectWriter"
  versioning = {
    enabled = true
  }

  server_side_encryption_configuration = {
    rule = {
      apply_server_side_encryption_by_default = {
        sse_algorithm = "AES256"
      }
    }
  }

  tags = {
    name = "${var.env}-qiita-bucket-s3"
  }
}
```

<details><summary>コード解説</summary>

acl : S3を作成した人のみアクセスできる
control_object_ownership ・　object_ownership : オブジェクトの所有権の制御を有効
versioning : バージョニングを有効化
server_side_encryption_configuration : 暗号化アルゴリズムに使用する手法
</details>

#### envs/dev/backend.tf

S3を作成しましたら、`backend`ブロックを使用してtfstateをS3に格納するように設定します。
backendブロックでは変数を使用できませんのでご注意ください。
```terraform:envs/dev/backend.tf
terraform {
  backend "s3" {
    bucket  = "dev-qiita-bucket-s3"
    key     = "dev/terraform.tfstate"
    region  = "ap-northeast-1"
    encrypt = true
  }
}
```

またmain.tfではmoduleを呼び出し、provider.tfではterraformの初期設定を定義しています。

<details><summary>envs/dev/main.tf</summary>

```terraform:envs/dev/main.tf
module "dev_tfstate" {
  source = "../../modules/tfstate"
}

module "network" {
  source = "../../modules/network"
}
```
</details>

<details><summary>envs/dev/provider.tf</summary>

デフォルトタグを使用して、作成するAWSリソースにdevというタグを付与します。
```terraform:envs/dev/provider.tf
terraform {
  required_version = "1.3.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~>5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
  default_tags {
    tags = {
      Environment = "dev"
    }
  }
}
```
</details>

## 動作確認
tfstateがs3に格納されているか確認します。
dev/terraform.tfstateに無事作成されました。
これでローカルにはtfstateが作成されません。

![スクリーンショット 2023-09-10 17.20.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2148800/b5c7a5d5-b2cf-0acb-d5fe-5755a68b4ebd.png)

## 終わりに
今回はtfstateをS3で管理するように設定しました。
tfstateファイルの管理は非常に大事なので、大切に管理しましょう。
またディレクトリ構成は2つの記事を参考にしています。
- [20 Terraform Best Practices to Improve your TF](https://spacelift.io/blog/terraform-best-practices)
- [Standard Module Structure](https://developer.hashicorp.com/terraform/language/modules/develop/structure)

次回はTerraform PlanをするようにCIを作成します。

今回作成したgithubのリポジトリ : https://github.com/hikobend/terraform-qiita
ブランチ : https://github.com/hikobend/terraform-qiita/pull/4

## DynamoDBを使用してtfstateを管理
tfstateを管理するときにS3とDynamoDBを使用して管理する方法もあります。
DynamoDBを使用すると、stateLockをかけることができます。
もしStateLockがかかっていない時に、複数人がApplyを実行してAWSリソースを作成しようとすると、Stateファイルにズレが生じてしまうことがあります。
そこでStateLockを追加すると、Applyしている人がいる場合、他の人がApplyできないようになります。
チーム開発ではtfstateをS3 + DynamoDBで管理、個人開発ではS3のみで管理すると良いと思います。
参考記事 : [State管理ベストプラクティス](https://speakerdeck.com/yuukiyo/terraform-aws-best-practices?slide=12)

<details><summary>envs/dev/backend.tf</summary>

```terraform:DynamoDBを使用
terraform {
  backend "s3" {
    bucket         = "S3バケット名"
    key            = "dev/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "DynamoDB名"
    encrypt        = true
  }
}

```
</details>


※この記事では個人でリソースを作成するので、dynamoDBは使用しません。

## 参考文献
- [AWS S3 bucket Terraform module](https://registry.terraform.io/modules/terraform-aws-modules/s3-bucket/aws/latest)
- [20 Terraform Best Practices to Improve your TF](https://spacelift.io/blog/terraform-best-practices)
- [Standard Module Structure](https://developer.hashicorp.com/terraform/language/modules/develop/structure)
- [State管理ベストプラクティス](https://speakerdeck.com/yuukiyo/terraform-aws-best-practices?slide=12)

## 今後作成予定の記事
- Terraform Plan・ApplyのCIを作成
- セキュリティグループの作成