【Terraform】ステートファイルを共有管理する

# 概要
`terraform apply`を実行した際に作成されるステートファイルをクラウド上で管理する方法を記載します。
AWS上のリソースを`terraform`で管理しているケースを想定しています。

## 管理方法
`apply`を実行した際の現在の結果を記録しているファイルが`tfstate`ファイルとなります。（このファイルを見て差分を割り出してくれていますね）
デフォルトではローカルに生成されますが、チーム開発を行っていくにあたって特定の人物のローカルにしかないと困ってしまいますね。
そこで通常`tfstate`ファイルは、以下のように管理します。

- Cloud上にステートファイル管理用のバケット（`ECS`や`GCS`）を作成
- Terraform Cloudを利用

### AWS S3 Bucketの利用
管理対象となるAWS上で手軽に管理します。
なお、**ステートファイルを保管するバケット自体は`terraform`での管理するインフラストラクチャ外にあること**が理想です。（[公式ドキュメント](https://www.terraform.io/docs/backends/types/s3.html#multi-account-aws-architecture)より）
このバケットを管理するためのアカウントを別に発行することが望ましいようですが、ややこしくなるので、ここでは`aws-cli`を用いてバケットを作成し、`terraform`の管理外とすることを実現します。

#### 1. ステート管理バケットの作成
単純に作成するだけでも構いませんが、以下の設定を入れておくと安心でしょう。

- バージョニング　　　　　　：不用意な削除等が行われた場合でも復元することができる
- 暗号化　　　　　　　　　　：バケット自体の暗号化を行う
- ブロックパブリックアクセス：バケットを公開しないよう設定

```bash:Terminal
# バケット作成
aws s3api create-bucket           --bucket ${bucket_name} --create-bucket-configuration LocationConstraint=${region}
# バージョニングの有効化
aws s3api put-bucket-versioning   --bucket ${bucket_name} --versioning-configuration Status=Enabled
# バケットの暗号化
aws s3api put-bucket-encryption   --bucket ${bucket_name} \
          --server-side-encryption-configuration '{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}'
# ブロックパブリックアクセス
aws s3api put-public-access-block --bucket ${bucket_name} \
            --public-access-block-configuration '{
              "BlockPublicAcls"      : true,
              "IgnorePublicAcls"     : true,
              "BlockPublicPolicy"    : true,
              "RestrictPublicBuckets": true
            }'
# タグ付け
aws s3api put-bucket-tagging      --bucket ${bucket_name} \
          --tagging 'TagSet=[{Key=description,Value=manage mintak-terraformer tfstate}]'
```
<img width="1360" alt="スクリーンショット 2020-06-21 15.49.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/c5515297-15d3-c02f-87a0-60679359b824.png">

#### 2. DynamoDBテーブル作成
DynamoDBを組み合わせることによって、ステートロックを実現可能とします。
※真偽は明らかではないですが、パーティションキー名称は`LockID`でないとうまく動作しませんでした。

```bash:Terminal
aws dynamodb create-table \
      --table-name ${state_lock_table_name} \
      --attribute-definitions '[{"AttributeName":"LockID","AttributeType": "S"}]' \  # S=stringの意味
      --key-schema            '[{"AttributeName":"LockID","KeyType": "HASH"}]' \   # HASH=パーティションキーの意味
      --provisioned-throughput '{"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}' # 念のため最低値を設定しておく
```
<img width="909" alt="スクリーンショット 2020-06-21 16.54.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/71c9bbc0-268d-2779-1aa8-e96447c0a9a6.png">



#### 3. backend設定
ステートファイルを上記で作成したバケットに保管するように設定するための実装を行います。
tfファイルがある階層に`backend.tf`を新たに作成します。

```terraform:backend.tf
terraform {
  backend s3 {
    bucket         = "1で作成したバケット名"
    key            = "terraform.tfstate"
    dynamodb_table = "2で作成したテーブル名"
    region         = "ap-northeast-1"
    encrypt        = true
  }
}
```
この状態でapplyやdestroyを行ってみると、ローカルにステートファイルが作成されることがなくなり、
S3バケット上に作成されるようになります。
<img width="1536" alt="スクリーンショット 2020-06-21 16.59.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/bd0e7718-1d09-6633-50ae-090f4c972ca7.png">




### Terraform Cloudの利用
GithubやGitLabと連携して`plan`や`apply`を行う機能もあるようですが、
今回はあくまで**ステートファイルの共有管理**の目的に閉じて利用する前提での例となります。
変更内容（履歴）がUI上で確認できる点が優れていると感じました。

#### 1. サインアップ
[Terraform Cloud](https://www.terraform.io/)の「Sign up for Cloud」ボタンをクリックしてアカウントを作成します。必要なものはメールアドレスのみです。
作成が必要なものは、アカウント・組織・ワークスペースの3つとなります。

#### 2. ワークスペース設定
あくまで**ステートファイルの共有管理**のみに閉じるため、次の設定を行います。

- 実行モードをLocalに

ワークスペース->Settings->generalの画面`Execution Mode`を`Local`に設定します。
<img width="910" alt="スクリーンショット 2020-06-21 6.05.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/e3eaf16f-b52d-de74-dbcd-6427fa31dece.png">

- VCS provider off

ワークスペース設定時にVCS providerを設定している場合は今回の用途では不要なためオフにします。
ワークスペース->Settings->Version Controlよりdeleteを行います。

#### 3. CLIログイン
コンソールからログインを行います。このタイミングでAPI Keyを発行します。（key情報は`~/.terraform.d/credentials.tfrc.json`に保管されます。[^1]）
`Do you want to proceed? (y/n)`は「y」を入力してください。


```bash:Terminal
$ terraform login

Do you want to proceed? (y/n) y

# ここでTerraform CloudのUIでAPI Keyを発行する画面へ遷移する。そのまま発行を行いコピーして、コンソールにペーストする

Terraform must now open a web browser to the tokens page for app.terraform.io.

If a browser does not open this automatically, open the following URL to proceed:
    https://app.terraform.io/app/settings/tokens?source=terraform-login


---------------------------------------------------------------------------------
Generate a token using your browser, and copy-paste it into this prompt.

Terraform will store the token in plain text in the following file
for use by subsequent commands:
    /Users/mintak/.terraform.d/credentials.tfrc.json

Token for app.terraform.io:

Retrieved token for user mintak21
---------------------------------------------------------------------------------

Success! Terraform has obtained and saved an API token.
```

#### 4. backend設定
ステートファイルを`Terraform Cloud`に保管するように設定するための実装を行います。
tfファイルがある階層に`backend.tf`を新たに作成します。

```terraform:backend.tf
terraform {
  backend remote {
    hostname     = "app.terraform.io"
    organization = "組織名"

    workspaces {
      name = "ワークスペース名"
    }
  }
}
```

この状態で`apply`や`destroy`を行ってみると、ローカルにステートファイルが作成されることがなくなり、
`Terraform Cloud`上に作成されるようになります。
現在の状態と前回からの差分をUI上で確認することが可能です。

<img width="1413" alt="スクリーンショット 2020-06-21 6.19.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/77e0c23d-78f2-1b4d-71b0-f7a4d16d3171.png">



# References
- [Terraform Cloud](https://www.terraform.io/)



[^1]: 環境変数`TF_CLI_CONFIG_FILE`を設定することで、任意の場所に配置しているcredentialファイルを参照することが可能です。credentialファイルには[こちら](https://www.terraform.io/docs/commands/cli-config.html#credentials-1)のような形式でトークンを保持します。