環境ごとにAtlasのEnvironmentを分けてTerraformのテンプレートをproduction環境とstaging環境で共有する


# 概要

Terraformのテンプレートを作成したとき、production環境に適用する前に、staging環境に適用して確認したいですよね。

Terraform標準ではenvironmentsの機能は存在しないため、environmentsを表す変数を用意し、それを`terraform`コマンド実行時に渡すことでenvironments機能を再現させます。

また、このときTerraformで作成したリソースの状態を保存するTerraform Stateもenvironmentsごとに分けるようにします。
ここではTerraform StateをAtlasに保存することとします。

# テンプレートの書き方

まず、environmentsを表す`env`という変数を用意します。そして、environmentsによって使う値を切り替えたいものについて、environments名をキーとしたマップを作成します。

```:variables.tf
variable "env" {
    description = "Environment name (production or staging)"
}

variable "instance_types" {
    default = {
        production = "c3.2xlarge"
        staging = "t2.small"
    }
}

...
```

こうすることで、`lookup()`を使い、`env`の値によって取得する値を切り替えられるようになります。

```:main.tf
resource "aws_launch_configuration" "as_conf" {
    ...

    instance_type = "${lookup(var.instance_types, "${var.env}")}"

    ...
}
```


# AtlasにEnvironmentを作る

Atlasの[Environmens](https://atlas.hashicorp.com/environments)から[New Environment](https://atlas.hashicorp.com/configurations/import)を選択し、新しいEnvironmentを作成します。

このAtlasのEnvironmentの名前には、`env`変数に渡すenvironment名をサフィックスとして付与します。
例えば、`sample/app-production`/`sample/app-staging`のようにします。

それ以外は通常どおりEnvironmentを作成します。


# Terraform Stateの保存場所の指定

上記で作成したAtlas EnvironmentをTerraform Stateの保存先として指定します。

Terraformを実行したいenvironment名に該当する方のAtlas Environmentです。

```bash
$ terraform remote config -backend-config="name=sample/app-staging"
```


# terraformコマンドの実行の仕方

`env`変数にenvironment名が渡るように、`terraform`コマンドにオプションを付与します。

```bash
$ terraform plan -var 'env=staging'
$ terraform apply -var 'env=staging'
```

# environmentの切り替え方

別のenvironmentに切り替える場合は、いったん現在のTerraform Stateを無効にする必要があります。

これには`.terraform`ディレクトリをまるごと削除します。

```bash
$ rm -rf .terraform/
```

そして、切り替えたいenvironment名に該当するAtlas Environmentをremote configで指定します。

```bash
$ terraform remote config -backend-config="name=sample/app-production"
```

remote configを無効にするコマンドも存在しますが、こちらでは`terraform.tfstate.backup`ファイルが残存し、そのファイルが存在する状態で別のAtlas Environmentを設定すると、Atlasに保存されているTerrafrom Stateが壊れてしまいます。

```bash
$ terraform remote config -disable
```

もし、誤ってAtlasに保存されているTerraform Stateを壊してしまった場合は、`https://atlas.hashicorp.com/<Organization名>/environments/<Atlas Environment名>/changes`を開き、Statesにチェックを入れるとTerraform Stateの履歴一覧が表示されるので、正常だったときのTerraform Stateを開いてRollbackから復旧させてください。

参考: [Managing Multiple Environments (Stage, QA, Prod) - Help | Atlas by HashiCorp](https://atlas.hashicorp.com/help/intro/use-cases/multiple-environments)


# 簡易的なラッパースクリプト

上記のremote configの切り替えを毎回行うのは面倒ですし、現在どのAtlas Environmentを設定しているのか忘れてしまいます。

そこで以下のような`terraform`コマンドをラップするスクリプトを用意しました。`jq`コマンドもインストールされていることが前提です。

`./tf.sh environment名 terraformコマンドに渡すオプション`という形で呼び出します。

```bash:tf.sh
#!/usr/bin/env bash

set -e

atlas_organization="sample"
atlas_environemnt_base="app"

tfstate_file=".terraform/terraform.tfstate"

env=$1
shift

atlas_config_name="${atlas_organization}/${atlas_environemnt_base}-${env}"

configure_tfstate() {
    terraform remote config -backend-config="name=${atlas_config_name}"
}

clear_tfstate() {
    # terraform remote config -disableでは不十分
    rm -rf ./.terraform/
}

check_same_environment() {
    local tfstate_config_name=$(cat $tfstate_file | jq -r ".remote.config.name")
    [[ $tfstate_config_name == $atlas_config_name ]]
}

if ! [[ -e $tfstate_file ]]; then
    configure_tfstate
else
    if ! check_same_environment; then
        clear_tfstate
        configure_tfstate
    fi
fi

terraform "$@" -var "env=${env}"
```


# 今後のTerraform側の対応について

TerraformでのEnvironmentsの対応は以下の本家Issueでも提案されており、今後対応することに期待します。

- [Support multiple isolated deployments per one TF root · Issue #1295 · hashicorp/terraform](https://github.com/hashicorp/terraform/issues/1295)