Terraform Workspaceを廃止した話

Metaps Advent Calendar 2024 13日目の記事です。

Terraform workspaceで環境を分けていましたが、これをやめたきっかけの一部始終を記事にしました。


## Terraform Workspaceとは？ 
単一の設定ディレクトリ内で複数の状態を使用できる機能です。
`backend.tf` で `workspace_key_prefix` を設定することで環境ごとに分離することができます。
Workspace は `terraform workspace new {ワークスペース名}` で作成可能です。

設定例
```backend.tf
terraform {
  backend "s3" {
    bucket               = "state-bucket"
    key                  = "ap-northeast-1/s3.tfstate"
    region               = "ap-northeast-1"
    dynamodb_table       = "terraform_state_lock"
    workspace_key_prefix = "terraform/providers/aws/workspace"
  }
}
```

workspace を使うことで、Terraform の state を各環境に分離することができます。
| workspace | key |
|------------|-----|
| default    | s3://state-bucket/terraform/providers/aws/workspace/ap-northeast-1/s3.tfstate |
| staging    | s3://state-bucket/terraform/providers/aws/workspace/staging/ap-northeast-1/s3.tfstate |
| production         | s3://state-bucket/terraform/providers/aws/workspace/production/ap-northeast-1/s3.tfstate |

Workspace の名前は `terraform.workspace` で Terraform のリソースに書くことができます。
Workspace のステージは `terraform workspace select {ワークスペース名}`で切り替えることができます。
## Terraform Workspace を廃止した理由
理由は大きく分けて2つあります。
- Workspace のユースケースが公式で意図していたものとは異なったから
- Workspace のステージがわかりづらく、オペミスが起きてしまったから

1つづつ説明していきます。

---
### Workspace のユースケースが公式で意図していたものとは異なったから
Terraform Workspace は dev, prod の環境を分離するように使うことは非推奨だと記載されています。

https://developer.hashicorp.com/terraform/language/state/workspaces#using-workspaces
> Important: Workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls. Refer to Use Cases in the Terraform CLI documentation for details and recommended alternatives.

公式が意図した使用用途はインフラセットのコピーを作成することです。

https://developer.hashicorp.com/terraform/cli/workspaces#use-cases
> A common use for multiple workspaces is to create a parallel, distinct copy of a set of infrastructure to test a set of changes before modifying production infrastructure.

アンチパターンをこのまま使い続けるわけにはいきませんが、次の理由で廃止することが確定しました。

---
### Workspace のステージがわかりづらく、オペミスが起きてしまったから
前述したとおり、 Workspace のステージは `terraform workspace select {ワークスペース名}`で切り替えることができます。また、state も各環境に分離されています。

**Q. これが意味することは何でしょうか？**
**A. ステージ変更をミスして開発環境に意図していた変更が本番環境に適用されてしまった**

詳しく見ていきます。

以下は workspace staging の state list です。
```
aws_cloudwatch_event_rule.this["event_rule"]
aws_cloudwatch_event_target.this["event_target"]
```

以下は workspace production の state list です。
```
aws_cloudwatch_event_rule.this["event_rule"]
aws_cloudwatch_event_target.this["event_target"]
```

そう、見かけ上は全く同じ state です。
ですが、state は各環境で分離しているためパラメータが異なります。

これによって `terraform plan` を行う前に `terraform workspace select` を挟まないとどのステージにいるか分からなくなります。

人は面倒ごとを省略したがるので、workspace select を省略したせいで開発環境に適用したい変更が本番環境に適用されてしまいました...

## 代替手段を考える
Terraform Workspace を廃止することが決定しました。では代替案はどうしましょうか？
弊社では [Input Variables](https://developer.hashicorp.com/terraform/language/values/variables) を使い環境を分けることにしました。

`env` という空の変数を用意して、`terraform plan`, `terraform apply` 時に指定する形にしています。
```variables.tf
variable "env" {}
```

### 実行例
`$ terraform plan -var="env=staging"`

次に、state を workspace から移動することを考えましょう。
まずは、workspace を構成している `key`, `workspace_key_prefix` を削除します。
```backend.tf
terraform {
  backend "s3" {
    bucket               = "state-bucket"
    region               = "ap-northeast-1"
    dynamodb_table       = "terraform_state_lock"
    # key                  = "ap-northeast-1/s3.tfstate"
    # workspace_key_prefix = "terraform/providers/aws/workspace"
  }
}
```
この状態で Workspace のステージが default のみに変更されました。
しかし、1つのステージで複数環境を構成することは不可能なので、何か対策を練る必要があります。

要するに、参照先の `.tfstate` ファイルをコマンド実行時に分離させるようにすれば良いのです。
`terraform init` 実行時に backend を構成する key の参照先を変更するコマンドがあるのでこちらを使うことにしました。

https://developer.hashicorp.com/terraform/language/backend#partial-configuration

### 実行例
(`key` は backend を保管している s3 バケットのオブジェクトパスです。各自変更してください。)
`$ terraform init -reconfigure -backend-config=key=terraform/providers/aws/application/{ENV}/ap-northeast-1/s3.tfstate`

---
さて、これで Workspace のステージが default の状態で複数の環境に分離することができました。
あとは 各ファイルで `terraform.workspace` で記述していたところを `var.env` に変更すれば移行成功です。

ただし、s3 バケットから workspace で使っていた `.tfstate` ファイルをすぐ消すのは時期尚早です。
不慮の自体が起きてもいいように `.tfstate` ファイルは残しておきましょう。
(年末のタイミングで不要なオブジェクトを消すことを考えても良いでしょう。)

## 最後に
Workspace の廃止は結構スムーズに行くと思います。
ただし変更箇所が山のようにあるので慎重に行いましょう。

また、今回の変更だけだと地味にコマンド入力が面倒です... (特に `-backend-config`)
```
$ terraform init -reconfigure -backend-config=key=terraform/providers/aws/application/{ENV}/ap-northeast-1/s3.tfstate
$ terraform plan -var="env=staging"
```

弊社では以下のように工夫しています。
- terraform init, plan, applyのスクリプトを書いて自動化
- GitHub Actions 経由で `terraform apply` を実行するように整備
- ドキュメントを整備して Terraform をどのように運用するかを明確にする

この際に他にも色々問題が出てきたのですが、それはまた別の話で。