TerraformのtfstateファイルをGCSで管理する

# 概要
Terraformによって作成される、環境情報を管理するstate(tfstateファイル)をクラウドストレージであるGCSで管理するまでのお話です。

# 前提
OS: macOS 10.14.4
Terraformバージョン: v0.11.13

# stateをクラウドストレージで管理するメリット
以下のようなメリットが考えられます。

- stateをリモートで一元管理できる
- チーム単位で管理する場合は現在のインフラ情報をチーム内で同期できる
- stateをバージョン管理できる
- 同時更新によるstateの競合を防げる

参考

- [Backends - Terraform by HashiCorp](https://www.terraform.io/docs/backends/index.html)

# 事前準備
## サービスアカウントの作成
TerraformがGCP APIを叩く用のサービスアカウントを[こちら](https://console.cloud.google.com/apis/credentials/serviceaccountkey?_ga=2.76612949.-1747551614.1537171419)から作成し、JSONファイルをダウンロードします。

- [Getting Started with the Google provider - Terraform by HashiCorp](https://www.terraform.io/docs/providers/google/getting_started.html#adding-credentials)

## パス設定
Terraformに認証情報を渡すためにダウンロードしたJSONファイルパスを`GOOGLE_CLOUD_KEYFILE_JSON`にセットします。
同様に、TerraformからGCSにアップロードできるようにするためにアプリケーションのデフォルト認証情報として、1と同じJSONファイルパスを`GOOGLE_APPLICATION_CREDENTIALS`にセットします。


```zsh:.zshrc
export GOOGLE_CLOUD_KEYFILE_JSON={{path}}
export GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_CLOUD_KEYFILE_JSON
```

- [サーバー間での本番環境アプリケーションの認証の設定 &nbsp;|&nbsp; 認証
      &nbsp;|&nbsp; Google Cloud](https://cloud.google.com/docs/authentication/production)

# tfファイル作成
## バケット作成
まず、tfstateファイルを管理するバケットを作成するリソース設定です。
今回は[Always free](https://cloud.google.com/free/?hl=ja)枠内でGCSを利用するため、locationやstorage_classは以下のように設定しています。
他に、過去のtfstateファイルを管理するためのバージョニングを有効にしたり、過去バージョンは5つまで管理するためのライフサイクル設定をしています。

```
resource "google_storage_bucket" "terraform-state-store" {
  name     = "my-terraform-bucket"
  location = "us-west1"
  storage_class = "REGIONAL"

  versioning {
    enabled = true
  }

  lifecycle_rule {
    action {
      type = "Delete"
    }
    condition {
      num_newer_versions = 5
    }
  }
}
```

- [Google: google_storage_bucket - Terraform by HashiCorp](https://www.terraform.io/docs/providers/google/r/storage_bucket.html)
- [オブジェクトのバージョニング &nbsp;|&nbsp; Cloud Storage
      &nbsp;|&nbsp; Google Cloud](https://cloud.google.com/storage/docs/object-versioning?hl=ja)
- [オブジェクトのライフサイクル管理 &nbsp;|&nbsp; Cloud Storage
      &nbsp;|&nbsp; Google Cloud](https://cloud.google.com/storage/docs/lifecycle?hl=ja)

## tfstateファイルの保管先
tfstateファイルの保管先(backend)の設定です。
backendとしてGCSを指定します。

(追記)※詳しくは後述しますが、backendタイプを指定する前にバケットが作成されている必要があります。

```
terraform {
  backend "gcs" {
    bucket  = "my-terraform-bucket"
  }
}
```

- [Backend Type: gcs - Terraform by HashiCorp](https://www.terraform.io/docs/backends/types/gcs.html)

## 最終的なtfファイル
tfstateファイルをGCSで管理するのに必要な設定だけを記述しています。

```tf:main.tf
provider "google" {
  project = "<プロジェクトID>"
  region  = "us-west1"
  zone    = "us-west1-a"
}

terraform {
  backend "gcs" {
    bucket  = "my-terraform-bucket"
  }
}

resource "google_storage_bucket" "terraform-state-store" {
  name     = "my-terraform-bucket"
  location = "us-west1"
  storage_class = "REGIONAL"

  versioning {
    enabled = true
  }

  lifecycle_rule {
    action {
      type = "Delete"
    }
    condition {
      num_newer_versions = 5
    }
  }
}

...(続く)
```

# 実行

## (追記)※注意
@kodai305さんからコメントを頂き、一部説明が足りなかったので追記します。
**バケットがない状態でbackendタイプを指定**して初期化すると失敗します。
backendタイプの変更の前にあらかじめバケットを作成しておく必要があります。

## 初期化
backendをローカルからGCSに変更するために初期化をします。

```
$ terraform init
```

途中で聞かれることには`yes`と答えます。
すでにtfstateファイルがローカルにある場合はこれだけでtfstateファイルがGCSにアップロードされます。
今後はapplyやdestroyするたびにGCS上のtfstateファイルが変更されます。

以上