【参考訳】HashiCorp Terraform 0.12 プレビュー:For と For-Each

13.07.2018 in HashiCorp using tags Terraform , HashiCorp , translation

HashiCorp の blog に、 Terraform 0.12 プレビューに関する投稿が今日(現地2018年7月12日 原題: HashiCorp Terraform 0.12 Preview: For and For-Each )にありました。例によって参考訳として共有させていただきます。以下どうぞ。

HashiCorp Terraform 0.12 Preview: For and For-Each

(Terraform 0.12 の主要な新機能に関する、連載3回めの投稿です)

Terraform 0.12 リリース につながる一環として、機能プレビューに関する一連のブログ投稿を公開します。今週の投稿は新しい繰り返し機能 for 式と for_each 式についてです。

Terraform 0.11 以前のバージョンでは、値やリソースの数を決める時、共通した設定上の問題があります。それは、固定した値ではなく、動的な表現が用いられている状況への対応です。全体的に繰り返しに関する問題は大きなものです。この問題を解決するため、Terraform 0.12 は機能性を改善するために、別の新しい機能を導入します。それが for 式と for_each ブロックです。また、私たちは更なる拡張を議論していますが、こちらは後ほど扱います。

リストとマップ変換の表記

リストとマップを扱う時、ある集まりにある各要素から新しい集まりを作り出すため、フィルタや変換が必要になるのは共通しています。Terraofrm 0.12 に先立ち、Terraform はこのような作業のために、formatlist(フォーマットリスト)という限定的な演算をサポートしています。これは個々の必要性に応じて補完(代入)する機能です。

Terraform 0.12 では for 式という新しい構造を導入しました。これはリストまたはマップの構成要素が、他のリストやマップの要素によって変換及びフィルタリングできるようにします。以下がこちらを使った例です:

# Terraform 0.12 向けの設定

variable "vpc_id" {
  description = "セキュリティ・グループを作成する場所の AWS VPC の ID"
}

variable "subnet_numbers" {
  description = "接続を許可する base_cidr_block のリストで、8ビット数のサブネット"
  default = [1, 2, 3]
}

data "aws_vpc" "example" {
  id = var.vpc_id
}

resource "aws_security_group" "example" {
  name        = "friendly_subnets"
  description = "フレンドリーなサブネットからのアクセスを許可"
  vpc_id      = var.vpc_id

  ingress {
    from_port = 0
    to_port   = 0
    protocol  = -1

    # subnet_numbers にある数値ごとに、サブネット CIDR プリフィックスを生成するため、
    # リクエストされた VPC の CIDR プリフィックスを展開します。
    # subnet_numbers のデフォルト値は上述の通りであり、VPC CIDR プリフィックスは 10.1.0.0/16 なので、
    # これをもとに作成されるのは: ["10.1.1.0/24", "10.1.2.0/24", "10.1.3.0/24"]
    cidr_blocks = [
      for num in var.subnet_numbers:
      cidrsubnet(data.aws_vpc.example.cidr_block, 8, num)
    ]
  }
}

前述の通り、 for 式で角括弧( [] )で囲むと、その結果はリストとなります。同様に、 for 式で中括弧( {} )で囲むとマップを正視します。例:

# Terraform 0.12 向けの設定

output "instance_private_ip_addresses" {
  # 結果は次のように、インスタンス ID が提供するプライベート IP アドレスをマップします:
  #  {"i-1234" = "192.168.1.2", "i-5678" = "192.168.1.5"}
  value = {
    for instance in aws_instance.example:
    instance.id => instance.private_ip
  }
}

また、オプションで if 条件を使っても入力コレクションをフィルタできます。そのため、入力にサブネット項目を含むアイテムだけを表示できます:

# Terraform 0.12 向けの設定

output "instance_public_ip_addresses" {
  value = {
    for instance in aws_instance.example:
    instance.id => instance.public
    if instance.associate_public_ip_address
  }
}

あとは、 for 式で形成するマップにはグループ化モード(grouping mode)があります。ここではマップのキーを、それぞれのキーをリストの中で識別するためのグループ・アイテムとしても使えます。このモードを有効にするには、値を記述した末尾に省略記号(...)を付けます:

# Terraform 0.12 向けの設定

output "instances_by_availability_zone" {
  # 結果はアベイラビリティ・ゾーンに対応するインスタンス ID をマップします。このように:
  #  {"us-east-1a": ["i-1234", "i-5678"]}
  value = {
    for instance in aws_instance.example:
    instance.availability_zone => instance.id...
  }
}

以上の例を通して見てきたように、数えられるリソースであれば、リストとしても振る舞えます。これにより aws_instance.example にあるすべてのインスタンスから、アベイラビリティ・ゾーンごとにグループ化して繰り返し処理できるようになります。

これらの新しい式、リストやマップ式によって、あらゆる引数に対する値を生成するために使えるようになります。また、Terraform 0.12 では、 list や map が許容されているのは module inputs も含んでおり、あらゆる場所で使えるようになります。

動的にネストするブロック(Dynamic Nested Block)

いくつかのリソース型(タイプ)では、設定の一部を繰り返して定義するため、ネストした(入れ子になった)設定ブロックを使います。Terraform 0.12 では新しい構造を導入し、ネストした設定ブロックの集まりを動的に構築します。

たとえば、 aws_autoscaling_group リソースはネスト化ブロックを使ってタグを宣言できます。これはあらゆる EC2 インスタンスに対して影響があるかもしれませんし、そうではないか分かりませんでした。以下の例は Terrafrom 0.12 より以前の構文です

# Terraform 0.11 以前向けの設定

resource "aws_autoscaling_group" "example" {
  # ...

  tag {
    key                 = "Name"
    value               = "example-asg-name"
    propagate_at_launch = false
  }

  tag {
    key                 = "Component"
    value               = "user-service"
    propagate_at_launch = true
  }

  tag {
    key                 = "Environment"
    value               = "production"
    propagate_at_launch = true
  }
}

これらのネスト化ブロックは静的なものであり、以前は動的な挙動の実装は難しいか不可能でした。動的なブロックを生成する手法は、細部の実装を使った手法がいくつかありましたが、代替策であり信頼性が足りないものでした、これは。Terraform がネスト化したブロックとみなすために、自由な記述を許していなかったからです。 Terraform 0.12 では特別な新しい動的ブロック構造を導入しました。これにより動的な設定をサポートし、業界一流の手法として利用できるようになりました。先ほどの例は、以下の Terraform 0.12 のものと同じです:

# Terraform 0.12 向けの設定

locals {
  standard_tags = {
    Component   = "user-service"
    Environment = "production"
  }
}

resource "aws_autoscaling_group" "example" {
  # ...

  tag {
    key                 = "Name"
    value               = "example-asg-name"
    propagate_at_launch = false
  }

  dynamic "tag" {
    for_each = local.standard_tags

    content {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }
}

dynamic "tag" ブロックは、 あたかも tag ブロックが分割されているように振る舞うもので、リストまたはマップの各要素に for_each の引数が与えられます。これは dynamic が自身をネスト化したブロックとしている前提のため、コンテント・ブロックの中でもすべて同じ構文が利用できます。そのため、リテラル・タグのブロックと静的または動的なタグのブロックが有効ですので、前述で見てきたように、静的と動的なタグのブロックの両方を混在できます。これにより、任意で必要に応じた複雑な挙動も行えます。

for_each 引数はあらゆるリストやマップ式を受け入れます。そのため、他のリストとマップの値を任意に変換したものをベースに、先ほど説明したようなネスト化したブロックとして、この機能は for 式と一緒に使えます。

# Terraform v0.12 用の設定

variable "subnets" {
  default = [
    {
      name   = "a"
      number = 1
    },
    {
      name   = "b"
      number = 2
    },
    {
      name   = "c"
      number = 3
    },
  ]
}

locals {
  base_cidr_block = "10.0.0.0/16"
}

resource "azurerm_virtual_network" "example" {
  name                = "example-network"
  resource_group_name = azurerm_resource_group.test.name
  address_space       = [local.base_cidr_block]
  location            = "West US"

  dynamic "subnet" {
    for_each = [for s in subnets: {
      name   = s.name
      prefix = cidrsubnet(local.base_cidr_block, 4, s.number)
    }]

    content {
      name           = subnet.name
      address_prefix = subnet.prefix
    }
  }
}

Terraform は content ブロックの内部でも引数を扱えます。これは静的なブロックでも有効なのと同様であり、 for_each 式の値が分からない場合でもです。したがって、この手法を使うと何らかの実際の行動をとる前に、plan の時点で問題を引き起こす可能性があります。

私たちは、過度な抽象化と、動的な設定を必要以上に行わないのを推奨します。これらの動的な機能は再利用可能なモジュールの作成時に役立つでしょう。しかしながら、過度に動的な記述は、読みやすさとメンテナンス性を苦しくします。明確なのは不明確よりも優れていますし、間接的よりも直接的の方が優れています。

for_each リソース

これまでに見てきた動的なブロックを構成する要素には、 for_each 引数を用いて、リストやマップ上で繰り返し利用するアイディアがありました。これが目指すのは。リソースの引数として数をカウントするのに比べ、ネスト化したブロックを動的に作る手法の方が、より直感的かつ使いやすいためです。

Terraform 0.12 の開発期間中は、リストやマップの各要素をもとにしたリソース・インスタンスを便利にするため、 resourcedata ブロックで for_each を直接サポートするべく、基礎に向けた作業も続けています。残念ながら Terraform 0.12 の初期リリースでは、この機能を完全には実装できません。しかしながら、以降の派生リリースにおいて、マップによって与えられた要素をベースとした、同じタイプの複数リソース・インスタンスを動的に構築するのを簡単にしていきます。以下の例は 今後の Terraform 構文であり、0.12 初期のリリースには含みません。

# v0.12.0 のリリース後に計画

variable "subnet_numbers" {
  description = "アベイラビリティ・ゾーンに数字をマップし、各アベイラビリティ・ゾーンのサブネットのために使います"
  default     = {
    "eu-west-1a" = 1
    "eu-west-1b" = 2
    "eu-west-1c" = 3
  }
}

resource "aws_vpc" "example" {
  # ...
}

resource "aws_subnet" "example" {
  for_each = var.subnet_numbers

  vpc_id            = aws_vpc.example.id
  availability_zone = each.key
  cidr_block        = cidrsubnet(aws_vpc.example.cidr_block, 8, each.value)
}

新しいオブジェクト each には属性 each.keyeach.value があります。これは for_each 式の各要素に key (キー:鍵)と value (バリュー:値)でアクセスできるようにします。

for_each 引数がマップの場合、Terraform が認識する各インスタンスは数字のインデックスではなく、マップ要素のキー文字列にあるものと認識します。これは数字を繰り返してリスト作るような、現在のパターンを避けるためです。なぜなら、ここで扱うアイテムは、リストの処理途中でも追加・削除されてしまう可能性があり、後に続くインデックスがずれてしまう可能性が出てくるからです。

私たちは初期の Terraform 0.12 リリースに向けて、この機能を完全に実装しようとは考えていません。Terraform は引数名 for_eacheach 式を予約後としてみなすため、リソース型(タイプ)としては利用できません。つまり、この機能の実装が完了するのは後続のリリースであり、破壊的な変更は伴いません。

モジュール count と for_each

長い間にわたり、複数の同じモジュールの作成を簡単にするためには、module ブロック内で count メタ引数を使うのが望ましいとされていました。

再びとなりますが、私たちは Terraform 0.12 の開発に向けての基礎作りに取り組んでおり、今後のリリースで作業が完了する見込みです。module ブロックでは count と同様に、新しい for_each 引数を使えるようにします。これは、先ほどの resource ブロックで見たようなものと同じ挙動を目指します

この機能を既存のTerraform アーキテクチャ内に実装するのは、非常に複雑です。そのため、この機能をサポートするためには、より確実な作業が必要です。今後のリリースでの破壊的な変更を避けるため、0.12 では module の入力変数名 countfor_each を予約語として扱い、この機能の実装完了に備えます。

アップグレード・ガイド

新しい forfor_foreach の機能性導入にあたり、既存のリソースとモジュールに新しい予約語を導入します。これにより、 公式プロバイダ(official providers) として提供されているリソースに対しては、破壊的変更を引き起こさないことを確認しています。利用者で作成したモジュールで countfor_each を使っている場合は更新が必要です。

この投稿ではリソースとモジュールの繰り返しに関する将来的な機能性(Terraform 0.12 以降)について説明しました。破壊的な変更を伴いませんが、Terraform 0.12 以降のリリースにおいて、必要なキーワードのすべてを予約語とします。

既にある count 機能性は、これまで変わりません。しかし任意の for_each をサポートするには、根底にあるアーキテクチャの変更を伴いますが、多くの周辺仮題は解決されているでしょう。

次へ

こちらは Terraform 0.12 のプレビューに関する連続ブログ投稿の3回めです。

for 式と for_each は Terraform 0.12 で(ただし使用にあたっては特別な注意が必要)今夏にリリース予定です。Terraform 012 にアップグレードする方法については アップグレード手順 をご覧ください。こちらは Terraform 0.12 のリリースが近づくまで、継続的に更新します。何らかのフィードバックや懸念事項がありましたら、Terraform チームに公開メーリングリストを通してご連絡ください。

原文