こんにちは、 kotamat です。

Terraform 0.12がでてしばらく立ちますが、先日構築したCloudfrontの環境において、Terraform0.12のdynamicを使わないと実現できない事案が発生したため、この際にと思い、一気に0.12にバージョンアップしました。

マイグレーション方法とかは、terraform側が提供しているものを使えば80%くらいはやってくれるので(一部手動で修正が必要ですが、WARNING出してくれるのでポチポチやっていくだけです。)今回は主題のdynamicについて紹介します。

Dynamicとは

terraform 0.12で追加されたシンタックスのうちの一つです。 通常、トップレベルのリソースに関しては、一つ一つのリソースに名前をつけて記述したり、 count とかを使って繰り返しを記述していきますが、security groupcloudfront のようなネストされたリソースを記述する際には一つ一つ重複して書く必要がありました。

特にcloudfrontは機能が多く、befaviorに各パスごとのoriginを書くことによってL7スイッチになることができるため、設定が繰り返し記述になりがちであり、かつネストが多い非常に複雑な構造になっているため、dynamicの恩恵を受けることができます。

記述方法

今回は Nuxt Generate Laravel Nova on AWSが綺麗にハマった話 で紹介した形を例に考えてみます。

各種スイッチの向き先の対照表は下記になっています。

ディレクトリオリジンの向き先
/api/*API
/nova/*nova
/nova-assets/*nova
/nova-api/*nova
/vendor/nova/*nova
defaultS3(Nuxt.js)

今までであれば、/apiのブロックを一つ、novaのブロックを4つ、deafultのブロックを一つ書く必要がありましたが、dynamicを用いることによって、defaultとordered一つのブロックですべての表現をすることができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
resource "aws_cloudfront_distribution" "main" {
// api
  ordered_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
      "OPTIONS",
      "POST",
      "PUT",
      "PATCH",
      "DELETE",
    ]

    cached_methods = [
      "GET",
      "HEAD",
    ]

    default_ttl = 0

    forwarded_values {
      cookies {
        forward = "all"
      }

      headers = ["*"]

      query_string = true
    }

    max_ttl                = 0
    min_ttl                = 0
    target_origin_id       = local.api_origin_id
    viewer_protocol_policy = "redirect-to-https"
    path_pattern           = "/api/*"
  }
// nova
  ordered_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
    ]

    cached_methods = [
      "GET",
      "HEAD",
    ]

    default_ttl = 0

    forwarded_values {
      cookies {
        forward = "all"
      }

      headers = ["*"]

      query_string = true
    }

    max_ttl                = 0
    min_ttl                = 0
    target_origin_id       = local.nova_origin_id
    viewer_protocol_policy = "redirect-to-https"
    path_pattern           = "/nova/*"
  }
// 以下 nova-assets/*とかも記述していく

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
locals {
  path_patterns = [
    "/api/*",
    "/nova/*",
    "/nova-assets/*",
  ]
}
resource "aws_cloudfront_distribution" "main" {
  dynamic "ordered_cache_behavior" {
    // 何を繰り返すのかをfor_eachで指定する
    for_each = locals.path_patterns
    content {
      allowed_methods = [
        "GET",
        "HEAD",
        "OPTIONS",
        "POST",
        "PUT",
        "PATCH",
        "DELETE",
      ]

      cached_methods = [
        "GET",
        "HEAD",
      ]

      default_ttl = 0

      forwarded_values {
        cookies {
          forward = "all"
        }

        headers = ["*"]

        query_string = true
      }

      max_ttl                = 0
      min_ttl                = 0
      target_origin_id       = local.api_origin_id
      viewer_protocol_policy = "redirect-to-https"
      path_pattern           = ordered_cache_behavior.value // dynamicで指定したリソース名 + .valueでpathの中身を取得
    }
  }
}

他の使いみち

上記はbehaviorの設定をまとめるために使ってみましたが、例えばステージング環境は特定のパスだけBasic認証いれたいという要件が来たときには、今までだとlambda_function_associationだけ違う別リソースを記述する必要がありましたが、mapと組み合わせる事によって、下記のように同一ソースで記述することができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
locals {
  lambda_associations = {
    "stg": {
      // stg特有のもの(Basic認証とか)
      {
        event_type = "viewer-request"
        lambda_arn = module.viewer_request.qualified_arn
      },
      // prodと共通のもの
      {
        event_type = "origin-request"
        lambda_arn = module.origin_request.qualified_arn
      },
    },
    "prod": {
      {
        event_type = "origin-request"
        lambda_arn = module.origin_request.qualified_arn
      }
    }
  }
}

resource "aws_cloudfront_distribution" "main" {
  default_cache_behavior {
    dynamic "lambda_function_association" {
      for_each = local.lambda_associations[terraform.workspace] // workspaceの値を取得し、動的にassociationを切り替える
      content {
        event_type = lambda_function_association.value.event_type
        lambda_arn = lambda_function_association.value.lambda_arn
      }
    }
  }
}

まとめ

terraform 0.12 dyanmicの使い方を、Cluodfrontを用いて紹介させてもらいました。 特に後者の、lambda edgeとの組み合わせは非常に使い勝手がいいので、よかったら試してもらえればと思います。