Terraform を使う前に考えること

フューチャー[Advent Calender 2021](https://qiita.com/advent-calendar/2021/future) 10日目の記事です。
昨日は、@iwaaya さんによる「[SwiftUIの実装例(MVVMパターン, CoreData, API実行+非同期処理)](https://qiita.com/iwaaya/items/0b472c7cb8a0a9c66ed2)」でした。

# はじめに

クラウド環境を新規で構築する時にTerraform を手に取る方は多いと思います（Cloud Formation などを使っている方はすみません）
そして、すぐコードを書いて`terraform apply`したくなる方もいると思います。

ただ、少しだけ待ってください。
プロジェクトが大きくなった時に大きな負債を背負ってしまう可能性があります。
後でこうしておけばよかった。。と思ってからでは遅いです（遅くはないかもだけど、かなりのコストを要します）
そこで、この記事では、個人的にコードを書く前に何を考える必要があるのかを以下の内容でざっくりと書いています。

* Terraform の実行環境をどうするか
* ディレクトリ構成

これ以外にもありますが、この2つが非常に大切なため挙げさせて頂きました。

# Terraform の実行環境をどうするか

Terraform の実行環境は、早めに定めて環境を整えた方がいいです。
個人で作る規模であれば、ローカル環境を実行環境とするのも良いと思います。
しかし、複数人で運用する場合は、そうもいきません。
そこで、以下のように実行環境を例として、ざっくりとどれがおすすめかを紹介していきたいと思います。

1. ローカル環境
2. EC2 とかのサーバ環境
3. CloudShell などのブラウザベースのCLI 環境
4. CI/CD 環境
5. Terraform Cloud

##1. ローカル実行環境

使い慣れたエディタでコーディングし、すぐさまターミナルで実行できる環境。
とても魅力的な環境です。
しかし、ローカル環境で実行する場合、かなり強力な権限を付与したキーを発行する必要があります。
これはセキュリティ上、非常にリスクです。
また、リリース前まではローカル環境で実行して、リリース後は、脱ローカル環境を行う！と志す時がありますが、往々にしてローカルのままとなります。
いずれ実行環境を移すという考えがあるならリリース前に実施した方がいいです。

ちなみに、TFenv は非常に便利なので導入をおすすめします。

### tfenv

[tfenv](https://github.com/tfutils/tfenv)は、Terraform のバージョン管理を簡単に行うためのツールです。
TFenv をインストールしたら以下のコマンドで使用したいバージョンをインストールし、`use`で切り替えることが可能となります。

```
$ tfenv install 1.1.0
$ tfenv use 1.1.0
```

##2. EC2 とかのサーバ環境

手動実行する環境としては、ローカル環境より断然良い実行環境です。
サーバ自体の証跡ログなどの仕込みや、認証などを行うことで監査的に把握することも可能になります。
しかし、これらの環境を整えるコストや運用コストが発生してしまいます。
また、手動実行は変わらないのでミスや実行コストが発生します。
結果、ローカル環境より良いが、そこまでおすすめではないです。

##3. CloudShell などのブラウザベースのCLI 環境

クラウドプロバイダは、ブラウザベースのCLI 環境を提供しています。
この環境を利用することで手軽にTerraform の実行環境を手に入れることが可能です。
また、CloudShell のコンテナイメージを変更することが可能なため、予めTerraform を導入できます。
サーバを用意するよりも手軽に運用できます。
したがって、EC2 などでTerraform 実行環境を用意するよりもおすすめです。
ただし、複数人で構築する場合、Terraform 自体のバージョンやプロバイダのバージョンを統一する必要がありますので、
以下のように`versions.tf`に記述しているようにすると良いです（ローカル環境含め）

```terraform:versions.tf
terraform {
   required_version = "~> 1.1.0"
 
   required_providers {
     aws = {
       source  = "hashicorp/aws"
       version = "3.38.0"
     }
   }
 }
```

##4. CI/CD 環境

GitHub Actions やCircleCI などでTerraform 実行を行う環境です。
GitHub と連携することで自動実行することが可能です。
Terraform のコードレビューを行ないたい時に、PR に貼っていた`Terraform plan`の実行結果をCI 画面から確認することができます。
そして、問題なければマージし、マージタイミングで`terraform apply`を自動実行させることが可能です。
また、CI 画面でPlan 結果を確認するのが面倒な場合は、メルカリさんがOSS として公開している[tfnotify](https://github.com/mercari/tfnotify)がおすすめです。
PR コメントに`Terraform plan & apply`の実行結果を記載してくれます。

したがって、CI/CD 環境をTerraform 実行環境にするのは、おすすめです。
ただ、デメリットとしては、強力な権限を付与したキーを発行する必要があるため、管理とライフサイクルを整える必要があります。
また、実行環境を整えるまでの構築コストも発生します。

ちなみに、CI に以下のツールも併せて組み込むと捗ります。

### TFlint

[TFlint](https://github.com/terraform-linters/tflint)は、Terraform のLinter です。

`terraform plan`では検知できず、apply 時にエラーが発覚するケースがよくあると思います。
そこで、このTFlint を導入することでapply 時の不整合などを検知することができます。

### TFsec

[TFsec](https://github.com/aquasecurity/tfsec)は、Terraform コード用の静的解析するセキュリティスキャンツールです。
AWS に[Security Hub](https://aws.amazon.com/jp/security-hub/)というサービスがありますが、これは構築されているリソースに対してセキュリティチェックを行います。
TFsec は、構築する前にコードレベルでチェックができます。
したがって、TFsec もCI の中に組み込むと捗ります。

ちなみに、TFsec はAqua Security に買収されてTrivy に取り込まれているので以下を確認すると良いです。

https://aquasecurity.github.io/trivy/v0.19.1/misconfiguration/

##5. Terraform Cloud

[Terraform Cloud](https://www.terraform.io/cloud)は、Terraform のSaaS 版です。
Terraform Cloud を利用することで以下のメリットを得ることが可能です。

* Terraform plan/apply の実行履歴
* ソース管理: GitHubなどと連携が可能
* パイプライン
* tfstate管理: セキュアにtfstate を管理可能（権限管理）
* Sentinel 機能: セキュリティベストプラクティスに則っているかのチェック
* 通知: Slack やメールへの通知、Webhook の利用が可能
* 証跡ログの管理

CI/CD 環境を準備するコストを抑えたい、セキュリティレベルの高い環境をスピーディに構築したい場合におすすめです。
ただ、全ての機能を利用したい場合は、有償版になるため、費用対効果を見定めて選定することをおすすめします。

https://www.hashicorp.com/products/terraform/pricing


# ディレクトリ構成どうするか

Terraform のディレクトリ構成をどうするかは、巷でたくさん議論されており非常に難しい問題です。
また、リソースが増えていった時にディレクトリ構成の変更をするのは非常にコストがかかります。
実行環境と同様に早めに構成を考えるのが大切です。

そこで、ディレクトリ構成を考えるにあたって、どんな方式があるのか、どれを採用するのが良いのかについて紹介していきます。
ここでいう環境というのは、本番・検証・開発環境などです。

主に環境の分離方式としては、大きく以下があります。

1. ディレクトリ分割
2. Module
3. Workspace

##1. ディレクトリ分割

ディレクトリ分割は、各環境の差異が生じても容易に対応可能です。
ただ、コードが重複してしまうのでDRY 原則に反してしまう点はあります。
しかし、構成がわかりやすいのでTerraform を初めて利用する人におすすめです。

```shell
.
├── prod
│   ├── xxx.tf
│   └── xxx.tf
├── stg
│   ├── xxx.tf
│   └── xxx.tf
└── dev
    ├── xxx.tf
    └── waf.tf
```

##2. Module

Module で環境毎に分割することでDRY に構成することが可能です。
再利用性を意識して構成する必要がありますが、非常にメリットが大きいです。
ただし、Terraform 初めましての方にはわかりにくい可能性があります。
また、Module は、[Terraform Registry](https://registry.terraform.io/)に様々なリソースが公開されています。

```shell
.
├── module
│   ├── xxx.tf
│   └── xxx.tf
├── prod
│   ├── xxx.tf
│   └── xxx.tf
├── stg
│   ├── xxx.tf
│   └── xxx.tf
└── dev
    ├── xxx.tf
    └── xxx.tf
```

##3. Workspace

[Workspace](https://www.terraform.io/docs/language/state/workspaces.html)は、環境毎にディレクトリを分けずに構成することが可能です。
以下のディレクトリを例にWorkspace について説明します。

```
.
├── ec2.tf
└── locals.tf
```

Workspace の作成と切り替えは、以下のコマンドを発行すると切り替えられます。

```shell
### Workspace 作成
$ terraform workspace new dev
### Workspace 切り替え
$ terraform workspace select dev
Switched to workspace "dev".
```

Workspace は、`${terraform.workspace}`という変数を用います。
この変数には、先ほど指定したWorkspace の`dev`が割り当てられているとします。

```terraform:ec2.tf
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.web[terraform.workspace][type]

  tags = {
    Name = "${terraform.workspace}-hoge"
    Env = "terraform.workspace
  }
}
```

変数の指定をlocals.tf で行います。


```terraform:locals.tf
locals {
  instance_web = {
    prod = {
      type   = "c4.large"
      xxx = "xxx"
    }
    stg = {
      type   = "t3a.medium"
      xxx = "xxx"
    }
    dev = {
      type   = "t3a.medium"
      xxx = "xxx"
    }
  }
}
```

Workspace を利用することでDRY に記述ができるのと環境を容易に増やすことができます。
しかし、環境で異なるリソースなどが発生する場合は、`count`などを使って吸収する必要があるため、多発すると可読性が悪くなります。

あと、ターミナル操作中に自分がどのWorkspace にいるかわからないため、事故る可能性があると考える方もいます。
そのため、Workspace を見送るという意見も聞きます。
しかし、以下のようにターミナルを変えることで自分のWorkspace が視覚的にわかるようになります。
Workspace を利用する際にはカスタマイズをおすすめします。

![スクリーンショット 2021-12-10 8.58.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152721/16a8ec3f-65f4-f349-1512-916167689705.png)

https://www.swtestacademy.com/customize-mac-terminal/


## 個人的にどのディレクトリ構成が良いか

個人的には、Workspace とModule のハイブリッドが良いと思っています。
共通的なIAM などのリソースはModule に寄せて、実際のアプリが乗るサービス系やネットワーク系は、環境差異が出にくいためWorkspace に寄せます。
仮に環境で異なるリソースが発生したらコメント付与して`count`などで吸収する方式が個人的にはベストと考えています。

## その他

実は、Uber から[Astro](https://github.com/uber/astro)というTerraform のリソース依存を吸収するツールが公開されています。
これを利用することでYAML ベースでリソースの実行順序を制御することが可能です。

https://style.biglobe.co.jp/entry/2021/03/19/100000#Astro%E3%81%A8%E3%81%AF

Terraform のツールなどを調べたい時は、以下を参考にすると良いです。


https://github.com/shuaibiyy/awesome-terraform

# まとめ

Terraform を利用する前に考えるべき大切なことを書きました。

* プロジェクトの規模を加味した実行環境の構成を決める
* メンバのスキルセットなどを加味したディレクトリ構成を決める

少しでも誰かのお役に立てれば幸いです。

明日は、@datake914 さんです！


# 参考

https://chroju.dev/blog/how_to_use_terraform_modules