Terraform を書く時に使ってるテクニック

## IaC は大変

「IaC をした方がいいよ。」なんていう話をよく聞くようになったと思います。
だけれども、実際のところ、今までマネジメントコンソールで簡単にできた作業が、IaC 化しようと思うとめちゃくちゃ大変！
「結局諦めてマネジメントコンソールで管理している」なんて事ありませんか？

今回は、Terraform という IaC のためのツールを使って、マネジメントコンソールで作るのと同じくらい簡単にリソースを作成していくことを目標にしていきます！

## 簡単に IaC するための手順

1. マネジメントコンソール or AWS CLI で実際に動く環境を作る
1. Terraformer を使って、機械的に tf ファイルを作成する
1. 実際に、Terraform を書いていく。
    1. `Standard Module Structure` を意識する。
    1. Terraformer で作成したコードはあくまでも参考用で、生成されたものを利用はしない。
    1. JetBrain 系 IDE の Terrafrom Plugin を利用する
    1. Terraform の公式ドキュメントを利用する
1. terraform import/plan を使って、マネジメントコンソールで作った環境との差分を確認して修正する
1. terraform apply をして意図したリソースが作成されているか確認する

### マネジメントコンソール or AWS CLI で実際に動く環境を作る

Terraform の設定をする際に、大量の設定項目から適切な設定値を探し出すことが大変だったり、マネジメントコンソールでは直感的に簡単に作れるリソースも、裏側でいろいろなリソースが使われていて、いざ IaC をしようと思うと簡単に作れない。ということがよくあります。
そういったときに差分を検出できるように、まずはマネジメントコンソールからリソースを作成しておきましょう。
ただし、大量のリソースを作りたい。Terraform Modules によって簡単に作成できることがわかっている。など、Terrafrom で記述した方が簡単に作れるようなものをわざわざマネジメントコンソールで作る必要はありません。

### Terraformer を使って、機械的に tf ファイルを作成する

Terraformer は、AWS Config の情報を読み取って設定情報を Terraform の tf ファイルにしてくれます。機械的に生成されるものなので、これによって完全なものが出来上がるわけではありませんが、設定値の参考としては非常に便利です。とりあえず、手元に現状の環境のリソースを作っておきましょう。

使い方について説明します。

まずは、インストール

```
brew install terraformer
```

でインストールできます。

実行コマンドは、

```
terraformer import aws --resources='*' --regions=ap-northeast-1
```

で、実行できます。

サポートされているサービスの一覧は、
https://github.com/GoogleCloudPlatform/terraformer/blob/master/docs/aws.md
こちらに記述があります。

すべてのリソースを作成してしまうと大きすぎるという場合には、VPC ID でフィルタリングできます。

```
--filter="vpc-xxxxxxxxxxxxxxxxxx"
```

出力先のディレクトリ構成は、リソースの種類ごとにファイルが分かれていて、、どこにどのリソースについて書かれているのかが、すごくわかりやすくなっていて良いなと思いました。

```
.
├── generated
│   └── aws
│       ├── acm
│       │   ├── provider.tf
│       │   └── terraform.tfstate
│       ├── alb
│       │   ├── lb.tf
│       │   ├── lb_listener.tf
│       │   ├── lb_target_group.tf
│       │   ├── outputs.tf
│       │   ├── provider.tf
│       │   ├── terraform.tfstate
│       │   └── variables.tf
```

Terraformer で作成したコードはあくまでも参考用です。生成されたものをそのまま利用することは考えないことをお勧めします。

理由は、いくつかあります。
まず、Terraformerによって生成されるコードが古いというところです。(Terraform 1系が生成されない)
また、すべての依存関係の解決までしてくれるわけではないので、そのままデプロイすることができるようなものではありません。
依存関係の arn などがハードコーディングされていて、複数の環境に展開できなくなっているため、この辺は解決する必要があり、意外と大掛かりとなってしまいますので、Terraformer のコードを出発点にするのではなく、あくまでも参考実装としてチラ見する程度のものだと思ってください。


## Standard Module Structure

複数の環境 (dev, test, prod, ...) などに IaC をプロビジョニングするときに、よくある問題点として、CPU のサイズなどの環境によって異なる設定値をどのように設定するかという問題だと思います。

これらの問題に対応するため、リソースを各モジュールの中で作成して、モジュールに渡す var の値を使って適切に条件分岐するというような方法が挙げられます。

私はよく、以下のようなディレクトリ構成、ファイル構成を用いています。


```
.
├── environments
│   ├── dev
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   ├── host
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   └── prod
│       ├── main.tf
│       ├── outputs.tf
│       ├── variables.tf
│       └── versions.tf
└── modules
    └── base
        ├── data.tf
        ├── eks.tf
        ├── locals.tf
        ├── outputs.tf
        ├── provider.tf
        ├── variable.tf
        └── vpc.tf
```

より複雑なシステムにおいては、base モジュールだけでなく、さらに多くのモジュールを作成していくことが多いと思います。
その際には、デプロイの頻度、ライフサイクルごとにモジュールを作成していくのがお勧めです。
また、複数のマイクロサービスから、同一モジュールを参照するような構成も考えられます。

宣言的に、コード化するためには、Terraform の中に条件分岐などのロジックを持たせないことが重要です。
そうすることによって可読性や依存性を排除できるようになります。

## JetBrain 系 IDE の Terrafrom Plugin を利用する

開発体験において、非常に重要なポイントになってきます。
詳細は、以下のクラスメソッドさんの記事に譲りますが、自動補完、Required な値の自動入力、バリデーションなどが非常に高品質で組み込まれています。
これによって、マネジメントコンソールのテキストボックスに設定値を入れるだけという開発体験に非常に近くなってきます。

https://dev.classmethod.jp/articles/intellijidea-terraform-0-12/

ちなみに、プロパティの Suggest は、`ctrl+space`で実現できますが、これは一般的に「英日変換」に割り当てられているショートカットなので、利用できないことが多いと思います。

このコマンドは非常に強力で便利なコマンドなので、私は`shift+space`を利用しています。
変更方法は、以下のリンクからご確認ください。

https://reasonable-code.com/intellij-code-complete/


## terraform import/plan を使って、マネジメントコンソールで作った環境との差分を確認して修正する

terraform import コマンドを使うと、マネジメントコンソール上のリソースを Terraform 管理下に置くことができます。
最初にマネジメントコンソールで作成したリソースを、terraform import コマンドで取り込みましょう。
そして、Terraformer によって生成されたものを参考に、Standard Module Structure を意識しながら、必要なリソースについて設定を書いてきました。
ここで、マネジメントコンソール上にあるリソースを取り込んだので、自分が書いた Terraform のコードと、実際にマネジメントコンソールに作られているコードとの差分を、terraform plan コマンドで知ることができます。
差分がなくなるまで、先ほど記述した Terraform の設定を修正していきます。
細かい手順は、以下のブログがわかりやすかったので、引用しておきます。

https://tech.layerx.co.jp/entry/improve-iac-development-with-terraform-import

差分がなくなれば、最初にマネジメントコンソールで作成したリソースを IaC によって記述できたということになります。

注意点ですが、Terraform import をするときには、直下のモジュール (root module) に resource を定義する必要があります。
上記のようなディレクトリ構成をとるとき、enviroments 側で tfstate を管理しているので、そこに import したいリソースを定義する必要があります。
本来書きたい場所は、modules 以下のモジュールだと思いますが、そこだとうまく import できませんので、その点はご注意ください。
import するときに、`environments/dev/import.tf`などにリソースを定義して、terraform import コマンドが成功すれば、tfstate に取り込まれます。
そしたら、リソースの情報をモジュール側に移行できます。

この時に、どのリソースを import するべきかわからなくなったら、Terraformerによって生成された resources を確認しにいきましょう。
これを使うことで、import する先のリソースを簡単に作ることができます。



## まとめ

あらかじめ、マネジメントコンソールで大半のリソースを作成しておくことがポイントです。
そのため、これまでの構築体験とほとんど変わらず、直感的にリソースを作成できます。

また、IDE の強力な Suggest 機能を利用することで、DSL による複雑さが取り除かれます。
ほとんどマネジメントコンソールのテキストボックスに設定値を入れることと似たような体験を手に入れることができます。

そして、強力なのが、`terraform import`コマンドです。
このコマンドを利用することで、現状の AWS 上の環境との差分が見られるため、マネジメントコンソールが作ってくれる設定などとの差分がすごく簡単にわかります。
とはいえ、差分が大きすぎるとつらいので、Terraformer によって生成されたファイルを参考にしながら、設定値を埋めていくようにすると、効率よく進められます。

## References

- https://zenn.dev/sway/articles/terraform_biginner_modules
- https://dev.classmethod.jp/articles/multiple-provisionings-with-terraform-and-directory-partition/