【モダンTerraform】VariableとLocal Valuesの使い分けについて

f:id:febc_yamamoto:20180130182943p:plain

はじめに

ナウでイケてるヤングな皆様におかれましてはTerraformを使うのはもはや当たり前ですよね?

このTerraformですが日々バージョンが上がっており、ネット上で公開されているtfファイルの書き方が若干古いものもちょいちょい見受けられます。

特にTerraform v0.10.3(2017/8/30リリース)で導入されたLocal Valuesについては利用している例が少ないように思いますので今回通常のvariableとの違いなどについてまとめてみます。

TL; DR 今北産業

  • tfファイル内の変数は基本的にLocal Valuesを使おう
  • 特に判定処理はLocal Valuesで明確な名前をつけよう
  • Variableを使うのは外部からのインプットにする場合だけ

そもそもLocal Valuesってなによ?

Local Valuesとはモジュール内に閉じて使える変数です。モジュール内でのローカル変数のようなものですね。

参考: Terraform ドキュメント - Local Values

なお通常の(これまでもあった)variableについてのドキュメントはこちらです。

参考: Terraform ドキュメント - Input Variables

Local Valuesは以下のように使います。

# Local Valuesとして変数定義
locals {
   switch_name = "my-switch-name"
}

# Local Valuesを使う
resource sakuracloud_switch "sw" {
  # "local." というプレフィックスで参照できる
  name = "${local.switch_name}"
}

localsブロックは複数記述可能

localsブロックはモジュール内に複数記述できます。もちろん変数名はモジュール内で一意である必要があります。

# サーバ関連の変数を定義
locals {
  server_name   = "foobar"
  server_core   = 2
  server_memory = 4
}

# ディスク関連の変数を定義
locals {
  disk_name = "foobar"
  disk_size = "20"
}

様々なデータ型が使える

Terraformで使える様々なデータ型を指定することが可能です。

locals {
  # bool型
  enabled = true

  # 数値型(10進数)
  num10 = 10

  # 数値型(16進数)
  num16 = 0x16

  # 文字列
  strvar = "example"

  # リスト
  listvar = ["item1", "item2", "item3"]

  # マップ
  mapvar = {
    item1 = "foo"
    item2 = "bar"
    item3 = "baz"
  }

  # 複合型(map/リスト/文字列など)
  compvar = {
    tags = ["tag1", "tag2"]
    metadata = {
      foo    = "1"
      bar    = "2"
      nested = ["foo", "bar"]
    }
  }
}

Variableとの違い

関数や他リソースの参照が使える

Local Valuesには関数や他リソースの参照などが書けます。
例えば任意の変数が設定されているか(空文字以外が指定されているか)を判定した結果を変数として保持しておけます。

variableを利用する場合、三項演算子などでvariableの値を判定して分岐させるというような処理を行っていました。

variable use_load_balancer {}

resource sakuracloud_load_balancer "lb" {
  # use_load_balancer変数が設定されていたらcountを1に、以外の場合は0にしてリソース作成しない
  count = "${var.use_load_balancer == "" ? 1 : 0}"
}

resource sakuracloud_switch "sw" {
  # use_load_balancer変数が設定されていたらcountを1に、以外の場合は0にしてリソース作成しない
  count = "${var.use_load_balancer == "" ? 1 : 0}"
}

同じ判定を行なっている箇所が複数あってDRYじゃないですね。

これをLocal Valuesを使って書き直すと以下のようになります。

variable use_load_balancer {}

# 判定処理をlocalsブロック内に局所化
locals {
  load_balancer_count = "${var.use_load_balancer == "" ? 1 : 0}"
  switch_count        = "${local.load_balancer_count}"
}

resource sakuracloud_load_balancer "lb" {
  count = "${local.load_balancer_count}"
}

resource sakuracloud_switch "sw" {
  count = "${local.switch_count}"
}

判定処理についてはわかりやすい名前をつけておくことで可読性も上がりますし、どういう判定をしているのか追いやすい(定義を見ればよい)ですね。

Local Valuesは外部からの値の設定ができない

variableは以下のように様々な方法で値の設定を行うことができます。

  • apply実行時に対話的に入力
  • コマンドラインから-varオプションや-ver-fileオプションで指定
  • terraform.tfvarsファイルで指定
  • 環境変数(TF_VAR_xxxなど)で指定
  • variableの定義時にデフォルト値を明示

このため、variableをtfファイルの簡易化といった目的で利用していた場合は意図しない値が入力される可能性もあったりします。
例えばcount構文と組み合わせる場合に以下のような書き方をすることがありました。

#
# Local Valuesがない時代の書き方
#

# サーバに割り当てるIPアドレスのリスト
variable ip_list {
  default = ["192.2.0.1", "192.2.0.2", "192.2.0.3"]
}

resource sakuracloud_server "servers" {
  # ip_listの要素数分のサーバを作成
  count = "${length(var.ip_list)}"

  # 自身のインデックスでIPアドレスリストを参照
  ipaddress = "${var.ip_list[count.index]}"
}

ip_listはvariableなため、外部から意図しない値が入力される可能性があります。 Local Valuesであればこの辺りを気にせずに使用可能です。

#
# Local Valuesを利用した書き方
#

# サーバに割り当てるIPアドレスのリスト
locals {
 ip_list = ["192.2.0.1", "192.2.0.2", "192.2.0.3"]
}

resource sakuracloud_server "servers" {
  # ip_listの要素数分のサーバを作成
  count = "${length(local.ip_list)}"

  # 自身のインデックスでIPアドレスリストを参照
  ipaddress = "${local.ip_list[count.index]}"
}

ということで、意図しない値の設定を防ぐためにもtfファイル上で変数を扱う際はまずLocal Valuesを利用し、外部から値の入力が必要な場合のみvariableを利用するのがオススメです。

まとめ

ということでLocal Valuesを積極的に使いましょう。 以上です。