Terraformの構成 ver.2017冬

https://qiita.com/uraura/items/f20b21baf7f28101a9d9 の続編です．

おおざっぱな説明だけで具体的な構成の話がなくてよくわからん :thinking: って感じなので，続きを書きます．

# ディレクトリ構成
```shell
.
├── 10_network
│   ├── main.tf
│   ├── output.tf
│   └── variable.tf
├── 10_network.tf
├── 20_backend
│   ├── main.tf
│   ├── output.tf
│   └── variable.tf
├── 20_backend.tf
├── 25_app_backend
│   ├── main.tf
│   ├── output.tf
│   └── variable.tf
├── 25_app_backend.tf
├── 25_app_datastore
│   ├── main.tf
│   ├── output.tf
│   └── variable.tf
├── 25_app_datastore.tf
├── README.md
├── terraform.tf
└── variables.tf
```

ディレクトリ名のプレフィックスの数値自体に意味はありません．ディレクトリと同じ名前のファイルがあり，それに`module`が定義されています．
数値の大小で依存関係を表していて，数値の大きいmoduleは数値の小さいmoduleに依存しています．言いかえると，数値の小さいmoduleの`output`を数値の大きいmoduleは利用できる，ということです．

各moduleにどういうリソースが入ってるかは下図の通りです．
![image.png](https://qiita-image-store.s3.amazonaws.com/0/18974/aeea029f-eb34-c3ff-a3d0-267a783a7bc9.png)

各層に何を入れるか，は厳密にわけるのは難しいのでフィーリングな部分もあります👻が，大きな方針は

- アプリケーションによらない，AWSでシステム運用するのに必要なリソースを`network`層に
- アプリケーションで共通で使用するようなリソースを`backend`層に
- アプリケーション固有のリソースを`app_backend`層に
- アプリケーションがデータストアを必要とするのであれば`app_datastore`層に

です．backendとかapp_xxxとか名称は試行錯誤してたらこんな感じになってたので深い意味はあまりない．．．😔
`app_backend`と`app_datastore`が分かれているのは，特にマイクロサービス的な構成をとると，データストアを必要としないサーバーもあったりするためです．

# module
20_backendを見てみます．

## 20_backend.tf
```tf:20_backend.tf
module "backend" {
  source = "./20_backend"
}

output "backend_aws_iam_role_lambda" {
  value = "${module.backend.aws_iam_role_lambda}"
}

output "backend_aws_key_pair_ec2_key" {
  value = "${module.backend.aws_key_pair_ec2_key}"
}

output "backend_aws_s3_bucket_config" {
  value = "${module.backend.aws_s3_bucket_config}"
}
```

トップレベルにあるtfファイルには，moduleの定義と，そのmoduleからのoutputを定義します．
ポイントは

- **このmoduleは外部にどういう値をエクスポートしているのか**が明確になる
    - `terraform output`で見れるので，実際はわざわざソースを見る必要はない
- `output`する必要ないリソースのことは完全に無視できる(当該moduleをいじるときだけ気にすればいい)

## 20_backend/variable.tf
```tf:20_backend/variable.tf
data "terraform_remote_state" "aws_main" {
  backend     = "s3"
  environment = "${terraform.workspace}"

  config {
    bucket = "tfstate"
    key    = "aws/main/terraform.tfstate"
    region = "ap-northeast-1"
  }
}
```
Terraform Providerを利用するための設定です．
Terraformのworkspaceを利用して，環境(dev/stg/prd)を表現しています．`environment`に渡すことにより，1つのtfstateで環境を考慮していい感じにやってくれます．

## 20_backend/main.tf
```tf:20_backend/main.tf
locals {
  network_aws_subnet_jump                 = "${data.terraform_remote_state.aws_main.network_aws_subnet_jump}"
  network_aws_route53_zone_main           = "${data.terraform_remote_state.aws_main.network_aws_route53_zone_main}"
  network_aws_security_group_localnet     = "${data.terraform_remote_state.aws_main.network_aws_security_group_localnet}"
}

data "aws_route53_zone" "main" {
  zone_id = "${local.network_aws_route53_zone_main}"
}

data "aws_security_group" "localnet" {
  id = "${local.network_aws_security_group_localnet}"
}
```
ここは実際のリソースが定義されています．
`locals`内にある，`${data.terraform_remote_state.aws_main.network_aws_route53_zone_main}`が，Terraform Providerを利用して値を取ってきてるところです．
ここは`20_backend`なので，それより下位の`10_network`の`output`だけを利用しています．

とりあえずファイルは1つにして全部おしこんでいますが，リソース数によっては

- ファイルを分ける
- サブモジュールとする
- そもそも階層を増やす

などを考える必要がありそうです．
ファイルを分けるとリソースをどのファイルへ書く?問題が起きますし，サブモジュールにすると見通しが．．．?階層増えるとそれはそれでメンドいか．．．?など，まだ自分の中でもベストアンサーはありません🙇

### local
余談ですが，`local`は便利なので積極的に使うと良いと思います．

`internal_flag`がtrueの場合に何かリソースを生成する，みたいな定義がある場合に，わざわざフラグで判定しなくてもよくなります．

```tf
module "foo" {
  internal_flag = true
  # internal_flagがONの場合に作られるリソースの設定値
  config = {
    name = "xxx"
  }
}

# internal_flagによってリソースを作ったり作らなかったりする
resource "aws_lb" "bar" {
  count = "${internal_flag ? 1 : 0}"
  :
}
```
↓↓↓

```tf:sample.tf
module "foo" {
  config = {
    name = "xxx"
  }
}

# moduleにconfigがあるかどうかでフラグを計算
# デフォルト値は{}にしておく必要がある
locals {
  internal_flag = "${length(keys(var.config)) != 0}"
}

# configがあるかによってリソースを作ったり作らなかったりする
resource "aws_lb" "bar" {
  count = "${internal_flag ? 1 : 0}"
  :
}
```

のようになり，moduleの外で設定しなければならない値が減って良い感じになります．

## 20_backend/output.tf
```
output "aws_s3_bucket_config" {
  value = "${aws_s3_bucket.config.id}"
}
```

外部で使用したいリソースのID等をひたすらoutputするだけです．
20_backend.tfにも書いてるじゃないか!と思うかもしれませんが，Terraform Providerを使うにはrootレベルでoutputされている必要があるため，moduleのoutputをそのままrootでoutputする，という感じになってしまっています．ちょっと面倒ですがまぁ仕方ないかな．．．と．

outputする値は，`data`が使える場合は識別子(ARNやresource id等)のみ，使えない場合は↓な感じでmapにしています．

```
output "aws_iam_access_key_ci" {
  value = {
    id = "${aws_iam_access_key.ci.id}"
  }
}
```

# 他のmodule
`25_app_backend`や`25_app_datastore`も，上記と同様の方針でファイルを構成しています．
違うのは，25層は下位の10，20層のどちらのoutputも使用できる点ぐらい．

# `plan/apply`する
現時点での構成では，moduleわけはしたもののリポジトリ単位でわかれているわけではないので，そのまま`plan`などとすると全モジュール読みこまれてしまい時間がかかる問題は解決されません．

ですが，module分割を行ったことにより，module単位で実行することは可能になります．

```shell
terraform plan -target=module.backend
```
などと引数を付けて実行することで，指定したモジュールだけ(今回の構成でいけば指定した層だけ)を対象にして実行できるので，下位層に変更がないことが分かってる場合は大幅に実行速度が上がります．

# 注意しなければならない点
- 下位層の`output`追加と，上位層での利用を同時に追加したら死ぬ
    - 依存関係があるので，先に下位層の`output`追加をapplyまでしてからでないと上位層でいきなり参照してはいけません
- `output`追加だけだと`plan`しても`no changes`になってしまうので`apply`を忘れがちになってしまう

今までハマったのはこれぐらい．．．．?もっとあったかも．．．

あとは，今は単一リポジトリでやっていますが，複数のリポジトリに完全に分離してしまった場合，**下位層の変更を上位層はどうやって検知すればいいのか?**という問題があるような気がしています．

そのへんは試行錯誤中なのでまたそのうち😶