terraform v0.12へアップグレードしたときにやったこと

ついにTerraform v0.12がリリースされました。
昨年夏後半にできるよー、って言ってた気がしたんですが、それから９か月くらい経ちました。
エンドレスサマーもようやく終わりを迎えたようです。
お疲れさまでした。

とはいえ、使う側からすればこれからなので、さっそく本番で利用しているtfファイルをアップグレードしてみました。
最速でアップグレードしてやるぞ！と意気込んでいましたが、すでにアップグレードしてる方が多数でN番煎じになってしまいました。残念...

書くこと

* もともとの状況(前提)
* アップグレードの手順(と言えるほどでもないけどおおざっぱに)
* 確認が必要だったところ
* アップグレード時にtfstateはいつ更新されるのか

## 前提

* 既存のファイルはv0.11に対応しています
* TerraformのコードはGitHubで管理しています
* GitHubのリポジトリはmonorepoです
* moduleはregistryなど外部からダウンロードするものを使わず自分で作っています
* workspaceは利用していません
* サービスや共通リソース(ネットワーク/IAMなど)の単位でTerraformの実行単位が分かれています
  * 別のリソースはterraform_remote_stateで共有しています

```:例
common/network/{main.tf, etc...}
prod/service1/{main.tf, etc...} ※commonのリソースはterraform_remote_stateで共有
dev/service1/{main.tf, etc...}  ※commonのリソースはterraform_remote_stateで共有
```

* 利用しているproviderはv0.12に対応していました
* upgradeコマンド以外でv0.12の文法に修正することはしません

## アップグレードの流れ

公式のガイド：https://www.terraform.io/upgrade-guides/0-12.html
新しい文法の説明：https://www.hashicorp.com/blog/terraform-0-1-2-preview
前回考えた内容： https://qiita.com/fullsat_/items/759a81c6034e98e9ae17
今回実施した内容：ざっくり以下の通り

v0.11.4のchecklistを確認してほしいとのアナウンスでしたがやっていません。
※真似してはいけません

* 0. アップグレードする順番の決定
  * 前提に書いた通りTerraformの実行単位が分かれています
  * この実行単位ごとにv0.12へコード修正してレビューをしていきます
  * terraform_remote_stateはv0.11のterraformからv0.12のtfstateを読み込めないため事故が起きないよう順番を考えます
  * まずterraform_remote_stateを利用しているかしていないかで順番を決めます
     * 利用しているほうを先にアップグレードします
     * アップグレードのタイミングがずれて併存期間が発生しても問題ないようにするためです
  * 次にmoduleを利用しているかしていないかで順番を決めます
     * 利用していないほうを先にアップグレードします
     * 利用しているとコードの変更量が多くなります
     * そのためレビュワーがv0.12に慣れるために少ない量を先にします
  * まとめると実行単位ごとに以下の順番でアップグレードしました
     * 1. (remote_state利用している, module利用していない) 
     * 2. (remote_state利用している, module利用している)
     * 3. (remote_state利用していない, module利用していない)
     * 4. (remote_state利用していない, module利用している)
* 1. v0.11を使ってるかどうか確認
  * 念のためv0.10が紛れ込んだりしていないか確認しておきました
* 2. providerのプラグインが対応しているか確認
  * aws/templateあたりは公式アナウンスとして対応済みです
  * githubは対応しているという情報は見つけられませんでしたがhcl修正時にinit/plan/applyした限りでは問題が出なかったので対応済みと判断しました
  * terraform initすればpluginとしてはv0.12で利用できるかが分かります
  * v0.12に対応しているpluginがないと0.12upgradeコマンド自体が使えなかったためinit作業は必須でした
* 3. 移行ツールでv0.12用のhclファイルに変更
  * 後述(確認が必要だったところの項目にどういう修正があったか書いています)
* 4. コードの修正
  * v0.12の文法を用いて手動で修正はしませんでした
  * 過去に変更したポリシーが適用されていなかったところは一部修正しましたが今回の趣旨とはズレるので書きません
* 5. その他UpgradeGuideに書かれていた確認事項
  * いろいろ書かれていたけど気にしなくていい内容だったので気にしませんでした
  * 気にしなかったことによってGitHubのwebhookの部分で不具合？を踏みました(後述)
* 6. テスト環境でterraform init/plan/refresh/applyを試す
  * プラグインのバージョンによって消え去っている属性などがあるようでplanしたときchangedが出たらapplyしておきます
  * それ以外はrefreshします
  * ここまで確認したらレビューします
* 7. 本番適用
  * terraform init
  * .terraform/plugin_pathを削除
      * 通常この作業は要りませんが、別の作業時に環境変数でプラグインのパスを指定するよう変更していたので必要でした
  * terraform plan
  * terraform refresh or apply
  * 元々利用していたプラグインのバージョンが古くなければほぼrefreshで問題ないはずです
  * パイプラインを作っていないので上記全部手動です

## 確認が必要だったところ

以下のコマンドでhclを書き換えます

```
$ terraform 0.12upgrade
```

moduleを利用している場合は上記のほかに下記のコマンドを実行します

```
$ terraform 0.12upgrade path/to/module
```

これによって変更された箇所で、確認、対応した部分は以下の通りです。
これによってと書いてますが、upgradeコマンドとは関係なくプラグインのバージョンの差異による問題も入っています。ご容赦を・・・

* First-class expression
* Rich-Types
* attributesとblocksが明示的になった
* list in listになっていたら修正してほしいというTODO
* moduleにproviderのaliasを追加
* インデントがおかしくなるパターンがあったので修正
* Fargate用ecsサービスのplacement_strategyが変更状態になる
* GitHubのwebhookのinsecure_sslがbooleanではなくnumberを指定しないとだめになった

### First-class expression

https://www.hashicorp.com/blog/terraform-0-12-preview-first-class-expressions

おそらく一番多かった修正がこれです。

```
key = "${var.hoge}"
```

としていた部分が

```
key = var.hoge
```

となる変更です。
これはツールに任せるだけで問題ありませんでした。

### Rich-Types

https://www.hashicorp.com/blog/terraform-0-12-rich-value-types

```
variable hoge { type = "list" }
```

としていたものが

```
variable "hoge" { type = list(string) }
```

に変わっていました。
これもツールに任せるだけで問題ありませんでした。

### attributesとblocksが明示的になった

これもFirst-class Expressionのところで書かれていましたが、属性とブロックが明示的に分かれるようになったようです。

```
config { }
```

だったのが

```
config = { }
```

になったりしていました。
これもツールに任せるだけで問題ありませんが、今後ブロックなのか属性なのかは意識して考えないとパースエラーとなるようです。

### list in listになっていたら修正してほしいというTODO

アップグレードツールを使ってTODOが出たのは以下だけでした。

```
# TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
# force an interpolation expression to be interpreted as a list by wrapping it
# in an extra set of list brackets. That form was supported for compatibilty in
# v0.11, but is no longer supported in Terraform v0.12.
#
# If the expression in the following list itself returns a list, remove the
# brackets to avoid interpretation as a list of lists. If the expression
# returns a single list item then leave it as-is and remove this TODO comment.
```

これは内容を読んでみるとおそらく

```
key = [ var.hoge ]
```

としたとき、これまではvar.hogeがlistを返していたとしても[]でくくらなくてはならず、互換性のために認めていたけど、v0.12からはやめるという話のようです。
ただ、var.hogeがリスト型を返しているところは存在しなかったので、コメントだけすべて消しました。


### moduleにproviderのaliasを移動

moduleの外で

```
provider "aws" {
  alias = "virginia"
  region = "us-east-1"
}
```

としていたものを、moduleの中で

```
provider = "aws.virginia"
```

として使っていましたが、aws.virginiaの値が参照できないというエラーが発生していました。
プラグインの問題なのか、v0.12の問題なのか詳しく調査は行っていませんが、provider "aws"をmodule側に書くことで対応しました。

### インデントがおかしくなる

upgradeコマンドで一部インデントがおかしくなりました。

ヒアドキュメントを利用しているファイルがおかしくなります。

全部元のインデントに戻すだけで良いというのもあり、詳しく状況を追っていません。
upgradeコマンドはやはり信じすぎないでちゃんと差分を確認しようということでしょう。

[余談]そもそもヒアドキュメント使うなという声は聞こえてきそうですが、ファイル分けるよりそのまま書いちゃったほうが良いこともあるという類です。

### Fargate用ecsサービスのplacement_strategyが変更状態になる

本番環境への適用中、開発環境では確認できなかった以下の差分が出ていました

```
      + placement_strategy {
          + field = (known after apply)
          + type  = (known after apply)
        }
    }
```

開発環境のtfstateを確認したところ

```
            "placement_strategy": [],
```

こうなっており、本番環境のtfstateをrefreshしてみたところ

```
            "placement_strategy": null,
```

こう変換されていました。
プラグインのバージョン管理がずさんであることが原因か、
apply/refreshのサイクルの違いによる差異か原因は追っておりません。

これをapplyするとnullが[]に変わるだけなのでapplyしても問題はありませんでした。
正直なところFargateはそもそもplacement_strategyの変更ができないのでchangeのステータスではなく勝手に変更しててほしいですね。

## GitHubのwebhookのinsecure_sslがbooleanではだめになった

github_repository_webhookリソースにおいてinsecure_ssl = falseとしていた部分が0からfalseという変更になっていました。
こちらapplyできますがfalseと0は値が異なり **設定が外れます** 。

```
          ~ insecure_ssl = "0" -> "false"
```

https://www.terraform.io/docs/providers/github/r/repository_webhook.html
公式のドキュメント通りにやっているんだけどなぁ・・・

と、よくよく考えるとtrue/falseが暗黙的に1/0に変換されなくなることを思い出しました。

```
Another count-related change is that Terraform now requires count to be assigned a numeric value,
and will not automatically convert a boolean value to a number in the interests of clarity.
If you wish to use a boolean value to activate or deactivate a particular resource,
use the conditional operator to show clearly how the boolean value maps to a number value:
```

count関連の変更って言ってたのですっかり失念していました。
changedで0 -> falseや1 -> trueになっているとバージョンアップの影響を受けていると思われますので注意が必要です(でした)。

ここはやらかしたところなのですが、0とfalseは同じ意味だからapplyしても特に問題ないだろうという先入観で確認を怠ってしまっていました。
同じ過ちをする人は少ないと思いますが気を付けましょう。


ちなみにこの挙動は以下のように簡単に確認できます。

```
$ cat main.tf # それぞれのバージョンで以下のファイルをapplyする
variable "foo" { default = false }
output "foo" { value = "${var.foo}" }

$ terraform11 apply # version v0.11の場合

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

foo = 0


$ terraform12 apply # version v0.12の場合

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

foo = false
```

## アップグレード時にtfstateはいつ更新されるのか

そういえばtfstateっていつ更新されるのだろう、という疑問が前々からあったのですが、v0.12への移行調査ついでに確認してみました。環境がどういう状態になっているかを調べるのは重要です。
結果は以下の通りです。

* applyせずrefreshまでのパターン

```
* terraform(v0.11) apply   => tfstate version3
* terraform(v0.12) init    => tfstateに変化なし
* terraform(v0.12) plan    => tfstateに変化なし
* terraform(v0.12) refresh => tfstate version4(値は変わらず,serial変化あり,v0.12へ変化)
```

tfstateの変遷(refreshせずapplyするパターン)

```
* terraform(v0.11) apply  => tfstate version3
* terraform(v0.12) init   => tfstateに変化なし
* terraform(v0.12) plan   => tfstateに変化なし
* terraform(v0.12) apply  => tfstate version4(changedが発生してたら値も変わる,serial変化あり,v0.12へ変化)
```

この結果を見ると

* refresh/planでtfstateが変化する
* init/planでtfstateは変化しない
* planで確認時、変更/更新がなければrefreshもplanも同様の結果となる
* バージョンにも差分がない状況でrefresh/applyかけたらどうなるか(主にserial)は未確認


## まとめ

* v0.12特有の問題がほとんどなくほぼタイトル詐欺だったように思います
* ほとんどのAWSリソースをterraformで作成しているため確認作業は大変でした
* 手順や文法などは事前に確認していたため問題になりませんでした
* 今のところv0.12の恩恵はあまり感じられませんが、理解は深まったと思います