febc技術メモ

Japanese version of http://febc-yamamoto.hatenablog.com

【モダンTerraform】v0.11以降でdynamicとfor_eachが実装されるかも

f:id:febc_yamamoto:20180130182943p:plain

モダンTerraformシリーズです。

今回は最近のhashicorp/terraformでの開発状況から、現時点での最新版であるv0.11.3で未実装な機能の中で個人的にかなり期待している機能について紹介します。

countパラメータとその限界

全てのリソースにはMeta-parametersが指定できる

Terraformには全てのリソースに対しMeta-parametersという属性を指定できます。
これはData Sourcesに対しても指定可能となっています。

参考: Terraformドキュメント - Meta-parameters

具体的には以下のようなものがあります。(詳細はドキュメントを参照)

  • count (int)
    リソースの作成数を指定 ※現時点ではモジュールには指定不可
  • depends_on (list of strings)
    リソースが依存するリソースを指定 -> 具体的には作成順序などが制御される
  • provider (string)
    マルチプロバイダーの定義をしている場合に利用するプロバイダーを指定できる
    (例: AWSのリージョンごとにプロバイダー定義している場合にaws.westみたいに指定)
  • lifecycle (configuration block)
    リソースの作成/更新/破棄の挙動を調整する

それぞれに興味深いトピックはあるのですが、今日はcountについて扱います。

countパラメータの基本的な使い方

countパラメータは以下のように利用します。 以下の例ではディスクリソースを2つ作成しています。

resource sakuracloud_disk "disks" {
  name = "${format("disk-%02d", count.index+1)}"
  count = 2
}

countパラメータを指定すると、そのリソースの定義内でcount.indexという属性が利用できます。
これは自身のインデックスを参照するもので、0から始まる数値型の値となっています。
これを利用することで、localsなどとの併用で各リソースに固有の値を指定しておくことが可能です。

locals {
  # 監視先のドメインとパスを指定
  targets = [
    {
      domain = "example.com"
      path   = "/"
    },
    {
      domain = "example.jp"
      path   = "/foo/"
    },
  ]
}

resource sakuracloud_simple_monitor "monitors" {
  # 自身のインデックスでマップ(targetsの各要素)を取得、キーがdomainの値を参照する
  target = "${lookup(locals.target[count.index], "domain"}"

  health_check = {
    protocol   = "https"
    delay_loop = 60
    path       = "${lookup(locals.target[count.index], "path"}"
    status     = "200"
  }

  count = "${length(locals.target)}"
}

countが利用できない場面

上手く使えばtfファイルを非常にシンプルにしてくれるcountですが、利用できない場面というのがあります。

  • リソースの属性
  • モジュール

例えば以下の例ではリソースにsettingという属性があるのですが、これに対してのcount指定はできません。

resource "aws_elastic_beanstalk_environment" "myEnv" {
  name = "test_environment"
  application = "testing"

  setting {
    # !ここではcountが使えない!
    namespace = "aws:elasticbeanstalk:application:environment"
    name      = "HTTP_PROXY"
    value     = "10.1.2.1:8080"
  }
  setting {
    namespace = "aws:elasticbeanstalk:application:environment"
    name      = "TMPDIR"
    value     = "/var/myapp/tmp"
  }
}

議論中: dynanicとfor_each

この問題はGitHub上でも解決に向けた議論が行われています。

参考: GitHub(hashicorp/terraform) - Support count in resource fields #7034

このIssueの中で提案されているのがdynamicブロックとfor_eachです。 これを利用して先ほどの例を書き直すと以下のようになります。

# DRAFT: まだ提案段階でv0.11時点ではこの書き方は利用できません
resource "aws_elastic_beanstalk_environment" "myEnv" {
  name = "test_environment"
  application = "testing"

  dynamic "setting" {
    for_each = var.environment_variables # map型の変数を指定
    content {
      namespace = "aws:elasticbeanstalk:application:environment"
      name      = setting.foreach.key # 現在のイテレーションの要素は`foreach`キーで参照できる
      value     = setting.foreach.value
    }
  }
}

マップ型変数や他で定義したリソースの属性などを利用して、各要素をイテレーションしながら値の参照ができるようです。 この例だとsettingブロック内のfor_eachイテレーションされる各要素はsetting.foreachで参照でき、それぞれkeyvalueのように参照できるようです。

以下のようにイテレーターに名前をつけることでネストも可能なようです。

# DRAFT: Not yet integrated into Terraform, and details may change before final release

  dynamic "example" {
    for_each = var.example_items
    iterator = parent # 外側のループのイテレータの名前を指定
    content {
      dynamic "example" {
        for_each = parent.children 
        iterator = child # 内側のループのイテレータの名前を指定
        content {
          value        = child.value
          parent_value = parent.value
        }
      }
    }
  }

これは嬉しいですね!
これまでこれを行うにはプロバイダー側で親リソース/子リソースに分割して実装する必要があり、 プロバイダーの実装者としてはなかなか辛い思いをしていました。
(リソースを親子に分割すると作成順序とかロックとか色々考慮が増えるのです…)

現在の実装状況は?

本日の時点では@apparentlymart氏が実験的に実装を進めているようです。
(前出のIssue内のコメントを参照)

期待して待ちましょう!!!

以上です。