Terraformのかゆい所に手をのばす 〜 meta-arguments 〜

この記事は[フューチャー Advent Calendar 2019（2）](https://qiita.com/advent-calendar/2019/future2)の5日目の記事です。4日目の記事はKhdbbleさんの[EbitenでImage操作 ~キャラクターを動かす~](https://qiita.com/Khdbble/items/491a48effc8dd4bd1133)でした。筆者もゲームエンジンで遊びたくなりました。

# はじめに

業務内で、Terraformのコードやissueを見ていると、「案外、知られていないのでは？」と思うような設定項目いくつかあることに気づきました。また、`resource`ごとにそれらが作成される際の個数や挙動を変えたいことがしばしばあり、ちょうどそのような内容のissueをいくつか担当しました。

本記事では、そんなTerraformのかゆい所に手が届く（かもしれない）`resource`のmeta-argumentについてまとめてみます。

# 意外と知られていないmeta-argumentたち

`resource`のmeta-argumentとは、どんなリソースタイプにも使えて、そのリソースの動作を変更できる設定のことです。

`resource`で設定できる`meta-argument`は以下の通りです。

* `depends_on`
* `count`
* `for_each`
* `provider`
* `lifecycle`
* `provisioner`
* `connection`

ここでは、`depends_on`, `count`, `lifecycle`を紹介します。
その他のmeta-argumentについては、[こちら](https://www.terraform.io/docs/configuration/resources.html)を参照してください。

## depends_on

`depends_on`は、`resource`の依存関係を解決するmeta-argumentです。すべての`resource`ブロック内で利用可能です。「Terraformって、依存関係を自動的に見てくれるのでは？」と疑問に思われるかもしれませんが、参照関係がない`resource`間で依存関係を設定したい場合に、depends_onを利用します。

以下はAWSでの例です。[リファレンス](https://www.terraform.io/docs/configuration/resources.html#depends_on-explicit-resource-dependencies)のコードを抜粋しました。

```sh
resource "aws_iam_role_policy" "example" {
  name   = "example"
  role   = aws_iam_role.example.name
  policy = jsonencode({
    "Statement" = [{
      # EC2がS3にアクセスするためのポリシー
      "Action" = "s3:*",
      "Effect" = "Allow",
    }],
  })
}

resource "aws_instance" "example" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  # aws_iam_instance_profile.exampleは定義済み
  # Terraformが参照から依存関係を推測します
  iam_instance_profile = aws_iam_instance_profile.example

  # S3へのアクセスが必要になるので、aws_iam_role_policy.exampleが作成されていて欲しい
  depends_on = [
    aws_iam_role_policy.example,
  ]
}
```

便利な`depends_on`ですが、リファレンスに「最終手段(last resort)」と、強気な書き方がされている[^1]ように、利用する際は注意が必要です。

[^1]: https://www.terraform.io/docs/configuration/resources.html#depends_on-explicit-resource-dependencies

依存関係の管理はできるだけTerraformに任せるべきであり、、開発者が依存関係を考えながらコードを書くのは好ましくありません。
どうしても`depends_on`を利用する場合は、他の編集者に設定の意図が伝わるようにドキュメントやコメントを残し、`depends_on`に設定する依存関係は最小限なるように心掛けましょう。

## count

`count`は数値を設定することで、設定されたリソースの個数を指定することができます。これも`resource`ブロックで利用可能です。例えば、GCPのリソースの場合は以下に使います。

```sh
variable "count_standard" {
  default = {
    "prod" = 1
    "dev"  = 2
  }
}

resource "google_compute_instance" "standard" {
  name         = "sample"
  count        = var.count_standard["${terraform.workspace}"]
  machine_type = "n1-standard-1"
  zone         = "asia-northeast1-b"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }
  ...（以下略）...
}
```

この場合、`prod`環境[^2]では1つ、`dev`環境では2つのGCEインスタンスが作成されます。
`variable`のブロックを別ファイルに書き出すことで、リソースの個数を別のファイルで管理することもできます。

[^2]: [Workspace機能](https://www.terraform.io/docs/state/workspaces.html)を利用すると、同じコードを環境ごとに使い分けることができます

## lifecycle

`lifecycle`ではTerraformのリソース操作を変更できます。こちらも全ての`resource`ブロック内で利用可能です。項目としては以下のようなものがあります。

* `create_before_destroy`
* `prevent_destroy`
* `ignore_changes`

例えば、リソースによっては設定に変更があった場合、そのリソースを一度削除して再作成します。このとき、リソースの削除が再作成に先行してしまうと、もしそのリソースのへの参照を持ったリソースがある場合に名前解決ができなくなってしまいます。
そこで`create_before_destroy`を用います。先に新しい設定を取り込んだリソースを作成し、参照を切り替えた後に古いリソースを削除することで、参照元が名前解決できるようになります。

また、`ignore_changes`は特定の設定を無視するためのmeta-argumentです。

再作成の生じるリソースにおいて、設定を変更するたびに削除・作成が行われるのは、時間がかかることもあり、好ましくありません。そこで、`ignore_changes`で例えば、Terraform管理外のラベル（タグ）の変更を実体に反映させないことで、無駄な再作成を防ぐことができます。

# 終わりに

`resource`のmeta-argumentについて紹介させていただきました。筆者もTerraform歴がまだまだ浅いので、これからもTerraformの利用方法についても考えていければと思います。