TerraformでCloud WatchのDashboardを作成してリソースモニタリング（ECS、RDS、ALB）

# 概要

よくある構成におけるECS、RDS、ALBの主要なAWSサービスのリソースモニタリングをTerraformで構築する。大事そうなメトリクスを抜粋。

# 流れ

1. json形式でdashboardbodyを作成
2. templeate_fileに落とし込む
3. aws_cloudwatch_dashboard リソース作成、template_fileからjsonデータを取り込む

# 実践

dashboard.json

```json
{
  "start": "-PT6H",
  "periodOverride": "inherit",
  "widgets": [
    {
      "type": "metric",
      "x": 0,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/ECS",
            "CPUUtilization",
            "ServiceName",
            "${ecs_service_name}",
            "ClusterName",
            "${ecs_cluster_name}"
          ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "ap-northeast-1",
        "title": "ECS CPU",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/ECS",
            "MemoryUtilized",
            "ServiceName",
            "${ecs_service_name}",
            "ClusterName",
            "${ecs_cluster_name}"
          ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "ap-northeast-1",
        "title": "ECS Memory",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 0,
      "y": 6,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/RDS",
            "CPUUtilization",
            "DBInstanceIdentifier",
            "${rds_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "ap-northeast-1",
        "title": "RDS CPU",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 6,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/RDS",
            "DatabaseConnections",
            "DBInstanceIdentifier",
            "${rds_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "ap-northeast-1",
        "title": "RDS Connections",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 0,
      "y": 12,
      "width": 6,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/RDS",
            "FreeableMemory",
            "DBInstanceIdentifier",
            "${rds_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "ap-northeast-1",
        "title": "RDS Free Memory",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 6,
      "y": 12,
      "width": 6,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/RDS",
            "FreeStorageSpace",
            "DBInstanceIdentifier",
            "${rds_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "ap-northeast-1",
        "title": "RDS Free Storage",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 12,
      "width": 6,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/RDS",
            "ReadLatency",
            "DBInstanceIdentifier",
            "${rds_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "ap-northeast-1",
        "title": "RDS Read Latency",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 18,
      "y": 12,
      "width": 6,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/RDS",
            "WriteLatency",
            "DBInstanceIdentifier",
            "${rds_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "ap-northeast-1",
        "title": "RDS Write Latency",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 0,
      "y": 18,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/ApplicationELB",
            "HTTPCode_ELB_5XX_Count",
            "LoadBalancer",
            "${alb_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "ap-northeast-1",
        "title": "ALB 5xx",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 18,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [
            "AWS/ApplicationELB",
            "HTTPCode_Target_5XX_Count",
            "LoadBalancer",
            "${alb_identifier}"
          ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "ap-northeast-1",
        "title": "ALB Target 5xx",
        "liveData": false,
        "legend": {
          "position": "right"
        }
      }
    }
  ]
}
```

ダッシュボード本体の構造と構文がわかりにくかった。。。

ざっくり解説。

`start` ... ウィジェットに使用する時間範囲の開始。`-PT6H`で6h前になっています。

`periodOverride` ... dashboardがロードされるときの期間。`inherit`にしておけば設定された期間が常に守られる。

`widgets` ... ウィジェットの集合体。（ウィジェットとは各監視対象項目の小さな画面という認識で良いと思います。）

`type` ... ウィジェットのタイプ。metricなので、グラフでの表現になる。（他にlogなども選べる）

`x` ...  座標位置（水平）。どうやらコンソールのDashboardは24列のグリッドになっている模様。左からどのくらいの位置にウィジェットを配置するかが決められる。

`y` ... 座標位置（垂直）。前述のyの縦version。これは24のグリッドではなく、1000のグリッドぽい。

`width` ... ウィジェットの幅。24のグリッドの範囲内で任意の数字を決められる。

`height` ... ウィジェットの高さ。1000のグリッドの範囲内で任意の数字を決められる。

`metrics` ... メトリック。0 ~ 100のメトリックを設定できる。インサイトクエリなども仕込める

構文がわかりづらいが以下の通り。NamespaceにはAWSのサービス名が入る、MetricNameには対象のメトリクス名、DimensionNameとDimensionValueは前者にはサービスごとに対象のリソースを識別するためのkeyが入り、後者には実際の識別子が入るイメージ。可変長引数なので複数指定できる。レンダリングプロパティオブジェクトではウィジェット全体に適用される値をオーバーライドできる。

metricsの指定フォーマット

```go
[ Namespace, MetricName, [{DimensionName,DimensionValue}...] {Rendering Properties Object} ]
```

`period` ... グラフ上の1つのデータポイントで表される時間の長さ。

`stat` ... メトリックの統計。Sumで合計値、Averageで平均など統計のパターンを指定できる。

`title` ... ウィジェットのタイトル。

`liveData` ... ライブデータの使用可否。詳しくは [こちら]([https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-live-data.html](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-live-data.html))を参照。

`legend` ... グラフ上の凡例表示の場所が指定できる。

cloudwatch.tf

```go
data "template_file" "dashboard" {
  template = file("./dashboard.json")
  vars = {
    ecs_service_name = "{your_project_variable}" // この辺は各々のサービス情報を入れてください。
    ecs_cluster_name = "{your_project_variable}"
    rds_identifier   = "{your_project_variable}"
    alb_identifier   = "{your_project_variable}"
  }
}

resource "aws_cloudwatch_dashboard" "main" {
  dashboard_name = "dashboard"
  dashboard_body = data.template_file.dashboard.rendered
}
```

あとはapplyしてください！

# 参考

公式（AWS）

https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/CloudWatch-Dashboard-Body-Structure.html

公式（Terraform）

https://registry.terraform.io/providers/hashicorp/aws/2.70.0/docs/resources/cloudwatch_dashboard

はてなブログ

https://zypressen.hatenablog.com/entry/2020/08/20/154702