created: 2023-10-22T01:58:05.462Z

terraform でGCPのalert_policyをfor_eachでまとめて定義する

CloudFunction から API を叩いて処理するようなコードで、たたく API があんまり安定しないという場合がある。 ただ、API エラーの種類ごとにアラートを設定したい場合もある。今回はタイムアウトはけっこう発生するけど TLS コネクションエラーも少数発生するので、それぞれ通知する閾値を変えたい。

前提として CloudFunctionFailure というログベースのメトリクスを生成しておいて、それに対してのフィルタリングと閾値を変化させて alert_policy のリソースをたくさん生成するということをする。terraform の for_each を使う。

なお、ほとんどの定義は terraform import で生成するもので、下記のコードは完全な定義ではない(いろいろ省いたもの)。完全な定義を取得する方法はこちらで書いた。

コード

locals {
  failure_alert_definitions = [
    {
      failure_type  = "HttpReadTimeout",
      function_name = "Collector",
      threshold     = 10,
      duration      = "1800s"
    },
    {
      failure_type  = "HttpConnTimeout",
      function_name = "Collector",
      threshold     = 10,
      duration      = "1800s"
    },
    {
      failure_type  = "HttpsTlsConnReset",
      function_name = "Collector",
      threshold     = 3,
      duration      = "1800s"
    },
    {
      failure_type  = "HttpUnexpected",
      function_name = "Collector",
      threshold     = 1,
      duration      = "1800s"
    }
  ]
}

resource "google_monitoring_alert_policy" "Failure" {
  for_each = {
    for i in local.failure_alert_definitions :
    "${i.function_name}_${i.failure_type}" => i
  }
  display_name = "${each.value.function_name}-${each.value.failure_type}"

  conditions {
    display_name = "${each.value.function_name}-${each.value.failure_type}"
    condition_threshold {
      threshold_value    = each.value.threshold
      duration           = each.value.duration
      comparison         = "COMPARISON_GT"
      denominator_filter = null
      filter             = <<-EOS
        resource.type="cloud_function"
        AND metric.type="logging.googleapis.com/user/CloudFunctionFailure"
        AND metric.labels.function_name="${each.value.function_name}"
        AND metric.labels.failure_type="${each.value.failure_type}"
      EOS
      aggregations {
        alignment_period     = "600s"
        cross_series_reducer = "REDUCE_SUM"
        group_by_fields      = []
        per_series_aligner   = "ALIGN_DELTA"
      }
      trigger {
        count   = 1
        percent = 0
      }
    }
  }
  documentation {
    content   = <<-EOS
      # ${each.value.function_name}/${each.value.failure_type}
      - 期間 ${each.value.duration}
      - 閾値 ${each.value.threshold}
    EOS
    mime_type = "text/markdown"
  }
}

Array => Map

terraform では、Array を for_each させることはできない。これは生成されゆくリソースを一意に識別できる必要があるためで、これはたぶん React でループから要素を生成するときに key 属性が必要なのと同じような理由なのだろう。

Array はダメだけど、要素が重複せず一意になるデータ構造である Set や Map なら for_each することができるので、配列を Map に変換してリソース定義に使う必要がある。

Python のリスト内包表記のようにして配列から Map を生成するコードはこのようになる。 i が配列の要素であり、"${i.function_name}_${i.failure_type}" と長い部分が Map のキーになる。

for_each = {
  for i in local.failure_alert_definitions :
  "${i.function_name}_${i.failure_type}" => i
}

参考

子育てブレスト: その手があったか!67のなるほど育児アイデア集
[ad] 子育てブレスト: その手があったか!67のなるほど育児アイデア集
佐藤 ねじ, 佐藤 蕗 (単行本)