terraform を便利に運用するために大事な少しのことを言語化

# はじめに

この記事は[terraform Advent Calendar 2019](https://qiita.com/advent-calendar/2019/terraform)の23日目の記事です。

書く書く詐欺が横行してランキングから除外されるの嫌なので、がんばってお茶を濁しますｗ

# TL;DR

- terraform を便利に運用維持管理していくために大事なことがいくつかあります
- それらについてそうすることで **なぜ** 便利になるのか **何が** 便利になるのか、があまり真剣に語られることは少ないです
- そのせいで初心者に伝わりにくいのかなと思ったので、解消のために言語化する試みです
- わかってる人は読まなくていいと思います
- 異論は認める
- 基本的にはこれから始める人向け、AWSでWeb系アプリケーションみたいな前提で書いています。それ以外の素敵な使い方をしている場合はそれを教えてほしいし、それなら見てないでカレンダーに記事書けよｗ

# terraform を便利に運用維持管理していくために大事なこと

1. tfenv を使う
2. direnv を使う
3. ansible を使う
4. packer を使う
5. workspaceは（できれば）使わない
6. CI/CDは常時回す（常にplanをかける）
7. モジュールのバージョンは固定しない（varsionを書かない）
8. 差分の吸収に常時務める

# 1. tfenv を使う

[tfenv](https://github.com/tfutils/tfenv)は、terraform のバージョン管理ツールです。
ディレクトリに .terraform-version ファイルを置いて、使用される terraform のバージョンを指定することに使います。
この意味は、当該ディレクトリで使用する terraform のバージョンをリポジトリの共有メンバーに周知することにあります。
tfenv を使用していれば、チームメンバーで足並みを揃えられる、ということです。

tfstate ファイルには使用している terraform のバージョン番号が含まれていて、そのバージョンより前のバージョンでは更新できなくなります。
このため、足並みを揃えないメンバーがついうっかり最新バージョンをインストールして、apply してしまうと辛いことになります。

使用しているツールのバージョンの足並みを揃えるということは、とても大事なことです。
特に terraform では前述の理由もあり必須です。


# 2. direnv を使う

[direnv](https://github.com/direnv/direnv)は、シェル拡張のひとつです。
たくさんの使い道がありますが、主にユーザ固有、チーム共有の環境変数を設定することにあります。
この意味は tfenv と同様に当該ディレクトリで使用する環境変数をリポジトリの共有メンバーに周知することにあります。

[The twelve-factor app](https://12factor.net/ja/)等で語られているアプリケーションの振る舞いを分離するための変数は、アプリケーションに直接記述せずに環境変数等を使って渡すのがよいとされています。
具体的には AWS Provider のアプリケーションキーやシークレットキー（これらは多くの場合ユーザ固有です）や、DataDog Provider のアプリケーションキー（これらは通常プロジェクトで１つです）の共有として使われます。

direnv を使用することで、AWS のキーは変数名としてこれを使っているから自分のキーをセットして使用する、DataDog のキーはプロジェクトで共通なのでこれを使用する、といったことが .envrc.sample などが配置されている時点で表現できます。
（言い方はアレですが、ドットファイルがリポジトリに配置されていて、それぞれのファイルの意味がわからない、自分で調べられない、理解できない、等のひよこエンジニアのためにこの記事が書かれていると言えます）

direnv を使用することで得られる素晴らしいメリットは、プロジェクトが使用する変数名を表現している **だけ** という点にあります。
自己責任の範囲において、同等の結果が得られるのであれば direnv を使わずに自PCの環境変数ををそのまま使ってもいいし、値を直接書きこまずに、aws cli の設定から値を使うことも可能だということです。（terraform 外の部分で環境変数値を求められている場合に異なる結果になるかもしれませんが、そこらへんが自己責任だということ）

また、当該ディレクトリに入れば自動的に適用されるため、複数のリポジトリを所有している場合に、それぞれに必要な権限にスイッチする必要がなくなる、というのが素直に便利です。

ツールを強要することなく、情報を共有するための表現方法のひとつとして考えると、逆に direnv なしでどうやって運用していたのか思い出せないくらい重要です。


# 3. ansible を使う

[ansible](https://github.com/ansible/ansible)は構成管理ツールです。（って書くと多方面からいろんなツッコミがきそうだけどスルー）
便利さ云々はさておき（これはterraformの記事なので）terraformの文脈でansibleを語ると、その便利さは主に２つ。逆にこの２つ以外では（terraform の文脈では）必要ありません。

- terraform remote state 用の s3 bucket を作る（s3に限らないけど）
- 後述の packer から使用して AMI を構築する

terraform は terraform.tfstate というファイルをローカルに配置しますが、これをリモートに配置するために s3 がよく利用されます。
この s3 bucket は terraform が実行されるより前に存在しなくてはいけないので、何かしらの手段で先に作成する必要があり、ここに出番があります。
単純なプレイブックで単一の s3 bucket を作ればいいわけです。誰ですか？手でマネジメントコンソールから作っているのは。

次に packer を使用して AMI （AWS以外のクラウドは適宜読み替えてください）を作成することで、インスタンスを作成する系のリソースから解放されます。
逆に未だに AMI 化せずにインスタンスを作成するために ansible を方々から叩いているとか、aws_instance リソースを使用しているのであれば、今すぐ方針を見直すべきです。

この辺りのために[Ansible Advent Calendar 2019 の 4日目](https://qiita.com/raki/items/06bcf762c984f052040e)に記事を書いています。よろしければどうぞ。


後述する CI/CD と合わせ、常に自動的に最新の AMI を作成し、terraform で（aws_launch_configurationなどに）適用することで、定期的に最新のクラウドに適合していくことが可能です。

もちろん Chef や puppet などを使っても構いません。チームメンバーの同意が得られるのであれば。

# 4. packer を使う

[packer](https://github.com/hashicorp/packer) は AMI（やその他マシンイメージ）を作成するための構成ツールです。
前述のとおり、ansible と組み合わせて使用することで、terraform と高い親和性があります。
正確には生成された AMI を data source で引っ張るだけなので、完全な疎結合ですが。

クラウドネイティブ時代に突入し、コンテナ全盛期とも言える流れになったと思っていますが、クラウドインスタンスが必要な場面はまだまだ多いため、packer のような共通の設定ファイルで各種クラウドインスタンスのためのマシンイメージを作成するラッパーは重要です。
ちなみに docker にも対応していますが、docker build は普通に cli を使った方がわかりやすいと思うので、適材適所というのはあると覚えておきましょう、無理に全てを packer で賄う必要はありません。

マシンイメージを作成することのメリットは、使用するインスタンスをペットではなく家畜化することにあります。
つまり使い捨て可能なインスタンスにするということです。
使ってない時間は停止、ではなく捨てる。次に起動した時は最新の状態になっている。
そうやってクラウドサービス自体や、パッケージの最新化に追従しやすくすることで、データセンターに置いた固定インスタンスをがんばってメンテナンスしたり、サーバ室に置いたベアメタルのマシンのハードディスク障害で泣いたりしなくてよくなるわけです。

packer は terraform と同じ HashiCorp 製品なので代替ソフトウェアにはあまり興味がないのですが、ご存知の方はコメントに残していってくれるといいと思います。


# 5. workspaceは（できれば）使わない

[12日目の記事](https://qiita.com/raki/items/29e4aa8d4ac550db2907) でディレクトリ分割とワークスペースについて書きました。
必ずしも絶対ではありませんが、やはり workspace を使用するのは個人的にお勧めしません。

一部の優秀な人たちは、mapやlistの入れ子の variables を見て、それがどこにはまり、どのように扱われるのか、を一瞬で判断するスキルを持っていらっしゃるようですが、平凡エンジニア代表である自分には将来に渡ってそれを維持することはできそうにありません。
ワークスペースのスイッチコストや、伴うミスの防止という意味で、apply テストのためにワークスペースを使うことすら不要だと考えています。

が、先の記事にも書いた通り、向き不向きとチームや仕事の状況にもよると思います。
ご利用は計画的に。。。

# 6. CI/CDは常時回す（常にplanをかける）

[GitHub Actions Advent Calendar 2019 の 5日目](https://qiita.com/raki/items/7464887fb747216367e4)に GitHub Actions で terraform の CI を回す記事を書きました。

CI/CD についての説明は今更しませんが、terraform の文脈では後述するバージョン（providerやモジュール）の更新に常についていく、という意味で重要です。

一昔前はデータセンターにマシンを立てて、アプリケーションを入れて動かせば、あとはOSのアップデートや、下手するとセキュリティパッチすらあてない、なんてことがありました（まぁ普通に今もありますよねｗ）

クラウド時代でそれをやると、そう遠くない将来に泣くことになります。

クラウド自体のアップデートによって設定方法が変わったり、よりよいサービスに切り替わったりしますし、OSやライブラリやセキュリティパッチの更新もどんどん高頻度になっています。あわせて terraform 自体や provider 、使用しているモジュールも日々バージョンアップを繰り返していて、それに追従しないという選択は緩やかな死に向かっているのと同義です。

terraform plan は、コードとリソースの差分を確認します。これは、コードとリソースの状態が整っていることの確認と同時に、意図しない変更を確認したり、（見え方は同じだし結果も同じだけど）リソースサービス側の仕様変更に気づくきっかけになります。昨日まで差分なしだったし、誰も弄ってないはずなのに差分が出たら、４割はクラウド側、３割はモジュール、２割は別メンバー、１割はその他の要因による変更です（個人の感想です）。

terraform が今年（2019年）v0.12.0 をリリースした後、v0.11 以前でバージョンアップをしていなかった人たちが、そこそこ苦労しているはずです。（あれ？まだ更新終わってない人いるの？）

変化させずに数年乗り切れば、という覚悟でバージョンアップをしない、という選択をする会社やチームに所属しているなら、できるだけ早く距離を取ることをお勧めします。インフラエンジニアやSREとして成長の機会を逃してしまいますから。

話がずれた気がしますが、コード以外の部分のアップデートに追従するため、その更新を把握するため、常に CI/CD を利用して健全性を保つ努力をしましょう、という話です。

# 7. モジュールのバージョンは固定しない（versionを書かない）

前述のとおり、terraform は tfenv 等でバージョンを指定します（固定しろとは言ってない）。
定期的に terraform のバージョンアップは、メンバーの同意を得ながら実施しましょう。
同様に、そのタイミングで、provider やモジュールもバージョンアップしましょう

```
terraform init --upgrade
```

普段から GitHub Actions やその他の方法で terraform の CI を回していれば、module ソースに version を記述する必要はないはずです（常に最新の状態で走ってくれるはずなので）
差分が出た時、その吸収が困難であるとか、実はバグっているなどのケースで、一時的に解消するまで version を書くことはありますが、それ以外の普段はバージョンを書かないことをお勧めします。

大企業さんとかだと、コード管理してるんだからバージョンもきちんと管理しろ、みたいなことを言う人もいらっしゃいますが、~~控えめに言って無能か無知~~ 正直無駄な考えです。

前述までのとおり、自分が書いたコードだけではなく、terraform 自身、provider、モジュール、なによりクラウド側が日々アップデートされています。
コードでインフラを管理できるようになった、から、使用しているモジュールにバージョンを書いておけば一定のコミットでどのバージョンが使われていたかわかるし、その時点に巻き戻すことが可能だ、と思われがちですが、ぶっちゃけ無理です。
クラウドがバージョンアップして、該当の機能がなくなったらそのバージョンは使えません。
モジュール類に顕著ですが、バグっていれば修正しますし、過去のバージョンの動作は保証してくれません。
それ以前に過去を振り返ってどうこうする暇があるなら、次々と先に進めていくほうがよっぽど有意義です。

terraform のバージョンは .terraform-version で表現、常に最新に保つために module.version は書かない、バグ等で避けられない場合だけ一時的に使用する、という感じで運用していくと、そうでない場合に比べて後々楽ができます。


# 8. 差分の吸収に常時務める

前述までのとおり、定期的に terraform のバージョンアップをして、CI/CD を回して、常に plan 差分を確認して、日々のアップデートについていけるようにしましょう。

繰り返し述べているように、日々確認していれば、あとディレクトリ分割を採用していれば、アップデートがあっても、個々の修正は微々たるもののはずです。
v0.11からv0.12になった時のような大きな変更や、re:Invent や CloudNextのような大きなイベントの後でもない限りは、通常運用の範疇です。
逆にその範疇から逸脱するほど大量の変更が発生したら、何かをさぼっていたツケだと思うので、そういったことにならないように注意しましょう。

ちなみに弊社でも本日急にCIで差分が発覚して、メンバーに確認してみたところ、そのメンバーの端末では差分がでない。
これはと思って調べてみると、数日前にバージョンアップしたモジュールがあり、ずいぶんと酷い（ように思える）変更がされていました。実際には幅広いケースで必要になる（かもしれない）変更で、対象外の場合はちょっとゴミができる、程度のことでしたが。

terraform でモダンなインフラ管理、は基本的には幻想で、日々の泥臭い努力で成立している、と毎日 terraform を触っていて思いますｗ



# 最後に

誤解を恐れずに言えば、terraform はコードとクラウドリソースを繋ぐインターフェースです。

Aって書けばAというリソースができます。それが明日もAである保証はありません。
アップデートでA'になったり、誰かがいじってBになったり、壊れて失われてしまうかもしれません。
実は欲しいのはAではなくてCなのかもしれません。

これらを効率よく管理するために terraform が最適である、と確信しています。
この記事が terraform でインフラ運用をしている人たちの参考になれば幸いです。

最後の最後に宣伝ですが、[terraform-jp](https://t.co/rlNg75MmHK?amp=1)は、そんな terraform が大好きなユーザが集まる slack ワークスペースです。
これから terraform を始める人や、terraform に悩む人、コントリビュートしたい人、terraform に関わる全ての人に参加してもらいたいです。
ぜひ参加してみてください（参加の挨拶は #自己紹介 でどうぞ）


# おまけ

- リストにリモートステートを使う、を乗せなかったのは記事のカラーとちょっと違うかな、と思ったから
- consul を乗せなかったのは SaaS がないので自前運用が面倒だから
- vault を乗せなかったのはこの記事の読者には時期尚早だから
- 外部モジュールを活用する、を乗せなかった以下同上