Terraformで環境別の状態を管理する


## やりたいこと

Terraformを環境別に`development`, `production`用というように分けてそれぞれリソースを管理したい

## やったこと

それぞれの環境ごとのtfstateを作成してディレクトリで環境を分けた

ディレクトリ構成としては大体このような感じ

```
├── .terraform
│   ├── terraform.tfstate
│   └── terraform.tfstate.backup
├── development
│   ├── .terraform
│   │       ├── terraform.tfstate
│   │       └── terraform.tfstate.backup
│   ├── config.tf
│   ├── ec2.tf
│   ├── elasticache.tf
│   ├── elb.tf
│   ├── iam.tf
│   ├── rds.tf
│   ├── route53.tf
│   ├── s3.tf
│   ├── security_group.tf
│   ├── variables.tf
│   └── vpc.tf
├── production
│   ├── .terraform
│   │       ├── terraform.tfstate
│   │       └── terraform.tfstate.backup
│   ├── config.tf
│   ├── ec2.tf
│   ├── elasticache.tf
│   ├── elb.tf
│   ├── iam.tf
│   ├── rds.tf
│   ├── route53.tf
│   ├── s3.tf
│   ├── security_group.tf
│   ├── variables.tf
│   └── vpc.tf
├── route53.tf
├── s3.tf
├── config.tf
└── variables.tf
```

ポイントとしては

- ルートディレクトリにそれぞれの環境で共通のリソースを作る
  -  一部IAM Role、IAM User、Route53のZone、それぞれの環境で共通で使うS3バケット、共通のVPCなど
- 環境別にディレクトリを切る
  - それぞれの環境固有のリソースはそのディレクトリ内で作成。(環境ごとに固有のドメイン、環境固有のS3のオブジェクト、VPC、EC2インスタンスなど)  
- 上位のリソースのIDやARNなどは`outputs`経由で委譲してもらう

なぜこのような構成になったかというと、環境別にVarファイル(Terraformでの変数)を用意して`apply`実行時に切り替える方法も考えたのだけど、

- オプションの切り替えで事故りそう(`-state`, `-var`での指定で毎回正確に指定出来る自信がない)
- 事故を防ぐため複数人で作業する場合でもオプション指定無しでも実行出来るようにしたい(`terraform plan`, `terraform apply`のみで実行出来るようにしたい)

というのがあり、変数ファイルでの切り替えにはならないので書き方としては多少冗長になるものの、インフラの事故は1ミスすなわち死なので、下手に再利用性を高めるよりは明示性に寄って安全重視で行きたい、ということでこんな構成になった。

## 構築方法

- ルートディレクトリでS3をremote stateに設定をする

```bash
terraform remote config \
  -backend=S3 \
  -backend-config="bucket=your.terraform.bucket" \
  -backend-config="key=super.terraform.tfstate" \
  -backend-config="region=ap-northeast-1"
```

- 共通のリソースを作る

`route53.tf`

```yaml
resource "aws_route53_zone" "foobar" {
    name       = "foobar.com"
    comment    = "foo!"

    tags {
        name = "foobar"
    }
}

```

- `output`を定義する

ここで定義した`output`リソースは各環境に委譲され利用出来る

```bash
$ vim outputs.tf
```

`outputs.tf`

```
output "zone_id" {
  value = "${aws_route53_zone.foobar.id}"
}
```

`plan`で確認して`apply`で適用

```
terraform plan
terraform apply
```

- リモートにtfstateファイルをpush

apply後はstateファイルをリモートのS3にpushする

```bash
terraform remote push
```

- 各環境別のリソースを作成

ここから各環境用のリソースを作って行きます

```bash
$ mkdir development
$ cd development
```

環境別のremote stateをコマンドラインから設定

```bash
terraform remote config \
  -backend=S3 \
  -backend-config="bucket=your.terraform.bucket" \
  -backend-config="key=development.terraform.tfstate" \
  -backend-config="region=ap-northeast-1"
```

remote stateをリソース定義から利用するため`terraform_remote_state`の定義をする

```bash
vim config.tf
```

`config.tf`

```yaml
resource "terraform_remote_state" "super_state" {
  backend = "s3"
  config {
    bucket = "your.terraform.bucket"
    region = "ap-northeast-1"
    key = "super.terraform.tfstate"
  }
}

resource "terraform_remote_state" "development_state" {
  backend = "s3"
  config {
    bucket = "your.terraform.bucket"
    region = "ap-northeast-1"
    key = "development.terraform.tfstate"
  }
}
```

例えば開発環境用のドメインをroute53で設定したいとすると

```
$ vim route53.tf
```

`route53.tf`

```yaml
resource "aws_route53_record" "dev_api" {
    zone_id = "${terraform_remote_state.super_state.output.zone_id}"
    name = "dev.api.foobar.com"
    type = "A"
}
```

このようになる。
`${terraform_remote_state.super_state.output.zone_id}`でルートディレクトリでのoutputを委譲している
これによって複数人で作業するときもremote stateを参照してリソースの定義が出来るのでチームでの作業やリソースのコンポーネント化がやりやすくなります

リソースの定義が終わったらそれぞれの環境別のディレクトリ内でも同じく`plan`, `apply`で適用します

```bash
terraform plan
terraform apply
```


## まとめ

terraformのstateを環境別にディレクトリで分けることで管理した
tfstateは操作をミスったりリモートだけ手動で構成を変更したりすると割りとすぐ整合性が崩れて事故の元になるので運用方法には注意が必要