Terraform使ってみて

2023/10/12に公開

Terraformを使ってみて

主にモジュールを作るときに気を付ける点

ARNの持たせ方は統一する

プログラム全般に言えるが統一は大事。特にモジュールを作るとき。

arn:aws:ec2:us-east-2:111122223333:subnet/subnet-0bb1c79de3EXAMPLE

あるモジュールでは、arn://~~~の形でフルARNを受け取るが、別のモジュールではARNの最後(subnet-0bb1c79de3EXAMPLE)を受け取って前半部分を補完するというような一貫性のないつくりにはしない。

チーム(プロジェクト)内でどちらかに統一する。
個人的には、一番下に書いたようにモジュールは使いまわしがきくようにシンプルな作りがよいと思っているので、フル形式で統一がよいと思う。

モジュールの中のループには注意

Terraformで作成したサブネットに、Route Tableを関連付ける例。

以下コード例。VPCの作成部分は省略。Route TableはManagement Consoleもしくは別のコードで作ったものを思ってください。

resource "aws_subnet" "main" {
  for_each = toset(["a", "c", "d"])
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"

  tags = {
    Name = "apne1${each.value}-subnet"
  }
}

module "rtb_association" {
  source = "./modules/rtb_association"

  subnet_ids = aws_subnet.main.*.id
  route_table_id = "rtb-xxxxxx"
}

module側の実装
サブネットのIDが複数渡され、for_eachでループし、Route Tableを関連付けする。
複数のサブネットに同一のRoute Tableを割り当てる時を想定して実装。

resource "aws_route_table_association" "main" {
  for_each = toset(var.subnet_ids)
  
  subnet_id      = each.value
  route_table_id = var.route_table_id
}

上記エラーになります。以下エラーメッセージ(コードは全然違うけど、原因は一緒)

| Error: Invalid for_each argument
|
| on a.tf line 9, in resource "local_file" "bar":
| 9: for_each = toset(local_file.foo.*.id)
| |----------------
| | local_file.foo is tuple with 3 elements
|
| The "for_each" set includes values derived from resource attributes that cannot be determined until apply, and so Terraform
| cannot determine the full set of keys that will identify the instances of this resource.
|
| When working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your
| configuration and where only the values contain apply-time results.
|
| Alternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on,
| and then apply a second time to fully converge.

for_eachのkeyがリソースが作られるまで確定しないから、for_eachには使えない。ワークアラウンドは二つ

  • keyを固定で定義したmapを使う
  • -targetを使って途中まで作る

らしい。。。。で、モジュールを修正。

表示されたワークアラウンドに従ってvar.subnet_idをmapにしてみようとするも、toset()が固定でかかるので、マップを渡すことができない。。。

ということで、モジュールでループを入れる場合は以下のようにした方がよい。

  • 当然ながらドキュメントに何でループするか記載する(for_eachなのか、countなのかも)
  • ループする変数はいじらない。Setでループするのか、Mapでループするのかは呼び出し側で選択できるようにする

もしくは

  • モジュール側にループは入れない。あくまで単一リソースの作成に特化
  • ループさせたいなら呼び出し側でループさせて選ばせる

というつくりにしておくと汎用性が高い気がする

モジュールには、あんまりロジックを入れないほうがいいかも

がちがちに仕様が決まってればいいが、変更する可能性が高い、いろんな場面で使いたい、というような場合は、モジュールにあれこれロジックを入れちゃうと応用がきかない

なので、あんまりロジック入れないほうがいいかと思ったりした。

ただ、それだとあんまりなので2層にしてもいいのかと思った。

  • base module: ほとんどTerraformのマニュアルを写経。もらった変数を加工せずそのままリソースを作るモジュール
  • logic module: ここは、プロジェクトの仕様をガチガチにいれたモジュール。base moduleを呼び出してリソースを作る

base moduleいるかなと思ったけど、オプションの指定とかのバラツキを抑止可能と思うので、あってもいいかなと。

Discussion