TerraformのBackendについて調べてみた

# はじめに
みなさん、こんにちは。@kuroseetsです。
この記事は[terraform Advent Calendar 2023](https://qiita.com/advent-calendar/2023/terraform) 21日目の記事です。
過去のカレンダーを見返してみると、前々回も21日目に書いてました。
というわけで、Advent Calendarも今日を含めてあと5日、それまでのお付き合いよろしくお願いします。

今回は、私自身がこれまで気にすることなく決められた通りに設定していたTerraformのBackendについて、改めて調べる機会がありましたので、それを纏めて記事にしました。

# Terraform Backendとは？
Terraform Backendとは、「Terraformの状態情報（通称tfstate）をどのように管理するか」を指定する仕組みのことを指します。Backendの種類としては多種多様なものがありますが、基本的にローカルマシンのディスク上などに保存する方法とオブジェクトストレージやTerraform Cloudに保存するリモートの二つの種類に分けることができます。以下ではそれぞれ特性と設定方法を紹介しようと思います。

## ローカル
ローカルバックエンドはTerraformがデフォルトで採用するバックエンドです。状態ファイルは直接ディスクに書き込まれ、そこから読み出されます。これは一番シンプルで直感的な方法で、小規模のプロジェクトや個々の開発者がプライベートに使用する際には非常に便利です。

```
terraform {
  backend "local" {
    path = "relative/path/to/terraform.tfstate"
  }
}
```
ただし、この方法では状態ファイルを他の開発者と共有することが難しくなります。また、リモートに保存されていない状態ファイルは、開発マシンが破損した場合などに失われてしまうリスクがあります。

## リモート
リモートバックエンドを利用すると、状態ファイルをリモートストレージサービスに保存することができるだけでなく、複数の開発者間で状態情報を共有することも容易になります。さらに、一部のリモートバックエンドでは、バックエンドが状態のロックとランの実行など、Terraformの操作に対する追加の機能を提供します。

以下に、リモートバックエンドのいくつかを紹介します。

### Terraform Cloud / Terraform Enterprise
Terraform CloudとTerraform EnterpriseはHashiCorpが提供する商用のTerraformバックエンドサービスで、高度に統合されたバックエンドを提供します。これらは状態ストレージとロックを含む基本的なバックエンド機能に加え、リモートプランと適用、ポリシーとガバナンスの機能を備えています。

```
terraform {
  backend "remote" {
    hostname = "app.terraform.io"
    organization = "company"

    workspaces {
      name = "my-app"
    }
  }
}
```

### Amazon S3
TerraformのS3バックエンドは、状態ファイルをAmazon S3に保存できるようにするものです。S3は安価で、準横断的な耐久性と利用可能性を提供します。

```
terraform {
  backend "s3" {
    bucket = "mybucket"
    key    = "path/to/my/key"
    region = "us-east-1"
    dynamodb_table = "my-dynamodb-table"
  }
}
```
Amazon S3バックエンドは、DynamoDBを用いて状態のロックとアンロックを行うことも可能です。これにより、一貫性と並行性を保証しながら、大規模または複数人数での運用が安全に行えます。

今回はAmazon S3での設定方法を紹介しますが、各クラウドサービス用に同様の以下のバックエンド設定が行なえるようになっています。

* GCP→`gcs`
* Azure→`azurerm`
* Alibaba Cloud→`oss`
* Tencent Cloud→`cos`

### Kubernetes
Kubernetesバックエンドは、バックエンドの状態をKubernetes上のSecretオブジェクトで保存し、管理するためのものです。これは、Kubernetesクラスタを既に運用している組織や、KubernetesのSecretオブジェクトを利用したセキュリティ管理のメリットを活かしたい場合に適しています。
```
terraform {
  backend "kubernetes" {
    secret_suffix     = "terraform-state"
    load_config_file  = false

    host     = var.k8s_host
    username = var.k8s_user
    password = var.k8s_pass
  }
}
```
この設定では、`secret_suffix`が指定された名前のSecretオブジェクトに状態情報が保存されます。また、`load_config_file`がfalseに設定されているため、Kubernetesの設定は環境変数か `host`, `username`, `password` によって提供されます。

### HTTP
HTTPバックエンドは、状態の保存と復元を独自のHTTPエンドポイントで行うことができます。この結果、必要に応じて自分で状態保存の仕組みを設計したり、存在するAPIとTerraformを統合したりすることができます。
```
terraform {
  backend "http" {
    address = "http://myrestapi.com/terraform_state"
    lock_address = "http://myrestapi.com/terraform_state/lock"
    unlock_address = "http://myrestapi.com/terraform_state/unlock"
  }
}
```

### Consul
ConsulはHashiCorpが開発したサービスメッシュソリューションで、サービスディスカバリとヘルスチェックの機能を提供します。しかし、それだけでなく、ConsulはK/Vストアとしても利用でき、これがTerraformのバックエンドとしても利用が可能です。

```
terraform {
  backend "consul" {
    address = "consul.example.com"
    path    = "example_app/terraform_state"
  }
}
```

## PostgreSQL
PostgreSQLバックエンドは、状態情報をPostgreSQLデータベースに保存できます。これは、すでにPostgreSQLを使用している企業や組織にとっては、付加的なコストを抑えてTerraformを導入できる利点があります。

```
terraform {
  backend "pg" {
    conn_str = "postgres://user:password@localhost/mydatabase"
    schema_name = "myschema"
    skip_schema_creation = true
  }
}
```

以上がTerraformのBackendの一部とそれぞれの設定方法になります。これらの選択肢を参考に、最適な状態管理方法を選択しましょう。

## Terraform Init時に動的にBackendを変更する方法
これらのバックエンド設定は通常、設定ファイル内で静的に指定しますが、一方で適切なバックエンドを状況や環境に応じて動的に切り替えたい、という場合もあります。例えば、開発環境ではローカルのバックエンドを使いたいが、テストや本番環境ではTerraform CloudやAmazon S3のようなリモートバックエンドを使用したいといったケースなど、細かな設定を状況に応じて切り替えることが求められることもあります。

同じバックエンドの種類を利用している場合、例えば本番環境とステージング環境では別のS3バケットを利用したい場合`terraform init`時に`-backend-config`オプションを利用することで設定の上書きが可能なのですが、そのオプションではバックエンドの種類までは変更するができません。
```
terraform init -reconfigure -backend-config="bucket=mystgbucket"
```


従って、バックエンドの種類自体を動的に切り替える方法としては、フレームワークレベルでの代替案が必要となります。以下に、シンボリックリンクと `terraform init -reconfigure` を組み合わせてバックエンドの種類までを動的に切り替える方法を紹介します。

まずはバックエンド設定を含むファイルをそれぞれ作成します。ここでは例として、Amazon S3バックエンドとローカルバックエンドの設定をそれぞれ作成してみましょう。

backend_s3.config:
```
terraform {
  backend "s3" {
    bucket = "mybucket"
    key    = "path/to/my/key"
    region = "us-east-1"
  }
}
```

backend_local.config:
```
terraform {
  backend "local" {
    path = "relative/path/to/terraform.tfstate"
  }
}
```

これらの設定ファイルが使用できるようになったら、シンボリックリンクを作成してbackend.tfという名前でどちらかの設定を参照するようにします。以下はそのためのコマンドの例です：

```
# S3バックエンド用の設定を参照する場合
ln -sf backend_s3.config backend.tf
```
```
# ローカルバックエンド用の設定を参照する場合
ln -sf backend_local.config backend.tf
```
次に、terraform init -reconfigureを実行します：
```
terraform init -reconfigure
```
このコマンドは既存のバックエンド設定を無視し、backend.tfで指定した新しいバックエンド設定でTerraformを初期化します。

以上がバックエンドを動的に切り替える一連の流れです。バックエンドを切り替えるための設定ファイルは.tfの拡張子ではなく.configなどTerraformが自動で読み込まない拡張子にするか、Terraformが参照しない別のディレクトリに保存します。また、これらの設定ファイルはアクセス制限など、適切なセキュリティ対策をして管理してください。

# さいごに
TerraformのBackendについてはなかなか掘り下げて考える機会がなかったので、今回はいろいろ知ることができてありがたかったです。
ではみなさま、良いお年を！