Terraform の使用バージョンをマイナーバージョンで制限するときの注意点

この記事は [Z Lab Advent Calendar 2020](https://qiita.com/advent-calendar/2020/zlab) の11日目の記事となります。

## はじめに

私が所属するチームでは、Terraform を利用した開発環境へのデプロイ作業を、その時に手が空いているメンバーが実施する運用なのですが、各自がインストールしている Terraform のバージョンが異なることが原因でデプロイ時に警告が出るという課題が出てきたので、使用バージョンをマイナーバージョンで揃える方針が現実的だと考え、Terraform の使用バージョンに制限をかける設定である [required_version](https://www.terraform.io/docs/configuration/terraform.html#specifying-a-required-terraform-version) の仕様を調査しました。

## required_version の仕様

required_version では [Version Constraints](https://www.terraform.io/docs/configuration/version-constraints.html) という演算子を利用して、Terraform の使用バージョンに制限をかけることが可能になっています。マイナーバージョンで制限したければ以下のように定義することになります。

```hcl:versions.tf
# v0.13.x での実行を許可する
terraform {
  required_version = ">= 0.13.0, < 0.14.0"
}
```

上記でも問題はないのですが、アロー演算子（`~>`）を利用することでシンプルに定義することができます。

```hcl:versions.tf
# v0.13.x での実行を許可する
terraform {
  required_version = "~> 0.13.0"
}
```

### :warning: 注意点

アロー演算子は最右のバージョンを評価をする仕様なので v0.13.x での実行を許可する場合には `"~> 0.13"` ではなく `"~> 0.13.0"` と定義する必要があります。`"~> 0.13"` と定義すると v0.x.x の実行を許可する設定になってしまうため注意が必要です。

> ~>: Allows only the _rightmost_ version component to increment.

https://www.terraform.io/docs/configuration/version-constraints.html#version-constraint-syntax

## 動作検証

ここで利用する設定は以下となります。

```hcl:versions.tf
# v0.13.x での実行を許可する
terraform {
  required_version = "~> 0.13.0"
}
```

はじめに v0.13.4 で実行できることが確認できました。

```bash
$ terraform --version
Terraform v0.13.4

# 正常終了
$ terraform init
$ terraform plan
$ terraform apply
```

次に v0.14.0 での実行[^1] がエラーになることが確認できました。

```bash
$ terraform --version
Terraform v0.14.0

# terraform plan/apply/destroy の実行も同様のエラーになる
$ terraform init

Error: Unsupported Terraform Core version

  on versions.tf line 10, in terraform:
  10:   required_version = "~> 0.13.0"

This configuration does not support Terraform version 0.14.0. To proceed,
either choose another supported Terraform version or update this version
constraint. Version constraints are normally set for good reason, so updating
the constraint may lead to other errors or unexpected behavior.
```

次に v0.13.0 での実行を確認したのですが、想定と異なりエラーになりました。エラー内容から state ファイルに記録されたバージョンより、使用バージョンが小さいことが原因でエラーになったことがわかります。

```bash
$ terraform --version
Terraform v0.13.0

# terraform plan/apply/destroy の実行も同様のエラーになる
$ terraform init

Initializing the backend...
Error loading state: state snapshot was created by Terraform v0.13.4, which is newer than current v0.13.0; upgrade to Terraform v0.13.4 or greater to work with this state
```

state ファイルを確認してみると、確かに v0.13.4 での実行により state ファイルのバージョンに v.0.13.4 が記録されていました。

```bash
$ gsutil cat gs://tfstate/prometheus/default.tfstate
{
  "version": 4,
  "terraform_version": "0.13.4",
  "serial": 1,
  "lineage": "981102f2-3df4-999d-1a0e-b64259ce5353",
  "outputs": {},
  "resources": [
    ...
  ]
}
```

試しに state ファイルに記録された v0.13.4 より新しい v0.13.5 で実行してみると、問題なく実行できることが確認できました。

```bash
$ terraform --version
Terraform v0.13.5

# 正常終了
$ terraform init
$ terraform plan
$ terraform apply
```

しかし、上記の実行ではリソースの変更が発生しない（state ファイルのバージョンに変化なし）ため、試しにリソースの再作成を実行してみました。

```bash
$ terraform --version
Terraform v0.13.5

# 正常終了
$ terraform destroy
$ terraform plan
$ terraform apply
```

実行後に state ファイルを確認してみるとバージョンが v0.13.4 から v0.13.5 に更新されていることがわかりました。どうやらここのバージョンには最終実行時（最後に state ファイルが変更されたタイミング）のバージョンが記録される仕様になっているようです。

```bash
$ gsutil cat gs://tfstate/prometheus/default.tfstate
{
  "version": 4,
  "terraform_version": "0.13.5",
  "serial": 3,
  "lineage": "981102f2-3df4-999d-1a0e-b64259ce5353",
  "outputs": {},
  "resources": [
    ...
  ]
}
```

ここまでの動作検証で、v0.13.x での実行を許可するような設定を適用したとしても、実際には最後に state ファイルが変更された際のバージョンに依存することがわかりました。

```hcl:versions.tf
# v0.13.x での実行を許可する
terraform {
  required_version = "~> 0.13.0" # この時点で事実上は v0.13.5 以上のバージョンでしか実行できない
}
```

## まとめ

required_version を利用して、使用バージョンをマイナーバージョンで制限することは可能ですが、実際には state ファイルに記録された最終実行バージョンより大きなバージョンが許可される仕様になっているため、マイナーバージョンで制限したい場合には、以下のような設定を適用して、「リソース変更時に state ファイルに記録されているバージョンより小さいというエラーが出たらパッチバージョンをアップデートしてリソース変更を行っても良い」という運用ルールを敷くのが良いかと思います。

```hcl:versions.tf
terraform {
  required_version = "~> 0.13.0"
}
```

上記の運用ルールが許容されない場合には、以下のように都度最終実行バージョンに揃えるか、

```hcl:versions.tf
terraform {
  required_version = "~> 0.13.5"
}
```

厳密に実行バージョンを制限するのが良いかと思います。

```hcl:versions.tf
terraform {
  required_version = "= 0.13.5"
}
```

## さいごに

今回は、Terraform のバージョンに制限をかける設定である required_version を利用して、Terraform の使用バージョンをマイナーバージョンで制限する際の注意点を紹介しました。小ネタですがどなたかのお役に立てたら幸いです。

[^1]: Terraform に限らずソフトウェアのバージョン切り替えは asdf が便利（[Banno/asdf-hashicorp](https://github.com/Banno/asdf-hashicorp)）