terraformでNew RelicのインフラメトリクスのダッシュボードをDRYに書きたい

インフラのメトリクスはCPU使用率/メモリ使用率/ディスク使用率などなど、
同一の項目をたくさんのホストに対して見れるようにしたいというニーズがあると思います。
New Relicのダッシュボードは取得したメトリクスの可視化が容易に可能ですが、
GUIで同じ上記のような設定をしていくのはなかなか面倒が多いかと思います。
今回はそんなニーズに対するtipsです。

## 今回のサンプルの要件

- Staging環境と本番環境がある
- 本番環境はBl系とGr系の2系統がある
- 各環境には4台ずつのEC2インスタンスがある
- Staging環境, Bl系, Gr系それぞれ別のページでみたい
- 各ページに表示するグラフはCPU使用率/メモリ使用率

## コード

コードとしては以下の2ファイルだけです。
(provider.tfなどTerraformを動かすのに必要なファイルや記述は割愛)

```ディレクトリ構成
/
┣ aws_metrics_page.json
┗ main.tf
```

```main.tf
# 各環境とそこに属するホスト名をlocal変数で定義
locals {
  staging_hosts = [
    "stage-example-ec2-web01",
    "stage-example-ec2-web02",
    "stage-example-ec2-web03",
    "stage-example-ec2-web04",
  ]
  production_hosts_bl = [
    "prod-example-ec2-web01",
    "prod-example-ec2-web02",
    "prod-example-ec2-web03",
    "prod-example-ec2-web04",
  ]
  production_hosts_gr = [
    "prod-example-ec2-web05",
    "prod-example-ec2-web06",
    "prod-example-ec2-web07",
    "prod-example-ec2-web08",
  ]
}

# ダッシュボードを作成
resource "newrelic_one_dashboard_json" "infra_monitoring" {
  json = jsonencode({
    "name" : "ダッシュボード名",
    "description" : "ダッシュボードの説明",
    "permissions" : "PUBLIC_READ_WRITE",
    "variables" : [],
    "pages" : [
      # ここから1つのページ
      jsondecode(templatefile("ec2_metrics_page.json", {
        environment_name = "Stage" # テンプレート内で定義した変数に値を渡す
        environment_type = "All"   # 同上
        hostnames        = "'${join("','", local.staging_hosts)}'" # 同上
      })),
      # ここまでで1つのページ
      # ここから1つのページ
      jsondecode(templatefile("ec2_metrics_page.json", {
        environment_name = "Prod"
        environment_type = "Bl"
        hostnames        = "'${join("','", local.production_hosts_bl)}'"
      })),
      # ここまでで1つのページ
      # ここから1つのページ
      jsondecode(templatefile("ec2_metrics_page.json", {
        environment_name = "Prod"
        environment_type = "Gr"
        hostnames        = "'${join("','", local.production_hosts_gr)}'"
      }))
      # ここまでで1つのページ
    ]
  })
}
```

```ec2_metrics_page.json
{
  "name": "[${environment_name}][${environment_type}] infra metrics",
  "description": null,
  "widgets": [
    {
      "title": "CPU使用率(%)",
      "layout": {
        "column": 1,
        "row": 1,
        "width": 12,
        "height": 3
      },
      "linkedEntityGuids": null,
      "visualization": {
        "id": "viz.line"
      },
      "rawConfiguration": {
        "facet": {
          "showOtherSeries": false
        },
        "legend": {
          "enabled": true
        },
        "nrqlQueries": [
          {
            "accountId": {{アカウントID}},
            "query": "SELECT average(host.cpuPercent) AS 'CPU used %' FROM Metric FACET host.hostname WHERE host.hostname IN (${hostnames}) SINCE this week TIMESERIES AUTO "
          }
        ],
        "platformOptions": {
          "ignoreTimeRange": false
        },
        "yAxisLeft": {
          "max": 100,
          "min": 0,
          "zero": false
        }
      }
    },
    {
      "title": "メモリ使用率(%)",
      "layout": {
        "column": 1,
        "row": 4,
        "width": 12,
        "height": 3
      },
      "linkedEntityGuids": null,
      "visualization": {
        "id": "viz.line"
      },
      "rawConfiguration": {
        "facet": {
          "showOtherSeries": false
        },
        "legend": {
          "enabled": true
        },
        "nrqlQueries": [
          {
            "accountId": {{アカウントID}},
            "query": "SELECT average(host.memoryUsedPercent) AS 'Memory used %' FROM Metric FACET host.hostname WHERE host.hostname IN (${hostnames}) SINCE this week TIMESERIES AUTO "
          }
        ],
        "platformOptions": {
          "ignoreTimeRange": false
        },
        "yAxisLeft": {
          "max": 100,
          "min": 0,
          "zero": false
        }
      }
    }
  ]
}
```

## 解説

New Relicのダッシュボードはjson形式で表現することができます。
既存のダッシュボードのjson表現は右上のツール群の `Copy JSON to clipboard` から取得できます。

![スクリーンショット 2022-12-22 20.33.56（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203936/1a2cc4f4-9256-87cd-bd64-5312bde052f6.png)

このterraformのコードでは

- ダッシュボード内のページを別のjsonファイルとして切り出してテンプレート化
- ページごとに異なる部分を変数として外から注入
- main.tf内でダッシュボード全体を1つのjsonファイルに構築する

という構成になっています。
ページごとにテンプレートファイルを選択可能なので一部はRDSやElastiCache、ALBなどタイプの異なるページでも対応できます。

### もう少し詳しく

```main.tf
# テンプレートファイル内で${key}となっている箇所をvalueに変換するTerraform組み込み関数
templatefile("テンプレートファイル", { key, value})

# ec2_metrics_page.json内で${hostnames}としたところに以下の関数の結果を変数展開する
hostnames = "'${join("','", local.staging_hosts)}'"

# 何も考えずにカンマでホスト名をつなげてしまうと
join(",", local.staging_hosts)
# stage-example-ec2-web01,stage-example-ec2-web02,...
# のようになってしまうが、NRQLに渡したいのは
# 'stage-example-ec2-web01','stage-example-ec2-web02',...
# という形式なので少しトリッキーだが
"'${join("','", local.staging_hosts)}'"
# という形でhostnamesを渡している
```

## さいごに

New Relicで観測する膨大な量のデータをIaCでスマートに定義できるとカッコいいですね💪( ᐛ )ﾊﾟﾜｰ!!