複数アカウントのメトリクスを1アカウントに集約するCloudWatchダッシュボードをTerraformで構築する

# はじめに
マルチアカウントで運用している環境で、同じメトリクスをいちいちスイッチロールしながら取得するのは面倒くさい。
CloudWatchダッシュボードを使えば、クロスアカウント設定でセレクタを切り替えながら確認することも可能だが、それも手間なので、一つのダッシュボードの画面で俯瞰できた方がいいよね！

ということで、やってみた。

なお、本記事は

- CloudWatchメトリクスを触ったことがある
- Terraformはある程度分かる

くらいの知識があることを前提としている。自分の書いた過去の記事でTerraform関連を読んで理解ができるのであれば、特に問題ないはずの難易度。

# IAMロールを作成する
[AWS公式のユーザーガイド](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Cross-Account-Cross-Region.html#troubleshooting-cross-account-cross-Region)に書かれているように、今回の対応をするには、集約する側のアカウントとされる側のアカウントにそれぞれ固定名のIAMロールが必要だ。
ためしにIAMロール名を変えて動かそうとしてみたが、Access Deniedになってしまった。

IAMポリシーはマネージドポリシーが用意されているので、以下のような固定名ロールを作れば良い。

```HCL
################################################################################
# 集約する側のアカウント用                                                       #
#   存在しない場合のみ作成                                                       #
################################################################################
resource "aws_iam_role" "cloudwatch_monitoringaccount" {
  name               = "AWSServiceRoleForCloudWatchCrossAccount"
  assume_role_policy = data.aws_iam_policy_document.cloudwatch_monitoringaccount_assume.json
}

data "aws_iam_policy_document" "cloudwatch_monitoringaccount_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "cloudwatch-crossaccount.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "cloudwatch_monitoringaccount" {
  role       = aws_iam_role.cloudwatch_monitoringaccount.name
  policy_arn = "arn:aws:iam::aws:policy/aws-service-role/CloudWatch-CrossAccountAccess"
}

################################################################################
# 集約される側のアカウント用                                                     #
#   存在しない場合のみ作成                                                       #
################################################################################
resource "aws_iam_role" "cloudwatch_crossaccount" {
  provider = aws.cross_account

  name               = "CloudWatch-CrossAccountSharingRole"
  assume_role_policy = data.aws_iam_policy_document.cloudwatch_crossaccount_assume.json
}

data "aws_iam_policy_document" "cloudwatch_crossaccount_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "AWS"
      identifiers = [
        "arn:aws:iam::${data.aws_caller_identity.self.account_id}:root",
      ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "cloudwatch_crossaccount1" {
  provider = aws.cross_account

  role       = aws_iam_role.cloudwatch_crossaccount.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess"
}

resource "aws_iam_role_policy_attachment" "cloudwatch_crossaccount2" {
  provider = aws.cross_account

  role       = aws_iam_role.cloudwatch_crossaccount.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchAutomaticDashboardsAccess"
}
```

# Terraformプロバイダ設定
クロスアカウントで動かすので、Terraformプロバイダを複数設定する。
cross_account側の設定は、あらかじめ```aws configure --profile myenv```で設定しておくようにしよう。
```shared_config_files```と```shared_credentials_files```の位置は適宜変更すること。

```HCL
provider "aws" {
  region = "ap-northeast-1"
}

provider "aws" {
  alias = "cross_account"

  shared_config_files      = ["/home/username/.aws/config"]
  shared_credentials_files = ["/home/username/.aws/credentials"]
  profile                  = "myenv"

  region                   = "ap-northeast-1"
}
```

# CloudWatchダッシュボードを作る
CloudWatchダッシュボードは、[Terraformの公式ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_dashboard)を見てわかるとおり、ほとんどがCloudWatchのダッシュボードの構造体のJSONを書くだけだ。[AWS公式のユーザーガイド]を見つつ書いていこう。

今回はサンプルとして```Elastic Load Balancing```の```TargetGroupsPerRegion```で作ってみる。

なお、アカウントIDは直接書くと当然ながら危険なので、```aws_caller_identity```データソースでProviderの設定から引っ張ってこよう。
同じ情報を並べて確認したいので、せっかくだからTerraformの構文でfor文を使って回して、jsonencodeで整形してあげると、コード量が減ってナイスな感じになる。

```HCL
data "aws_caller_identity" "self" {}

data "aws_caller_identity" "cross_account" {
  provider = "aws.cross_account"
}

locals {
  accounts = [
    { graph_title = "MyAccount", account_id = data.aws_caller_identity.self.account_id },
    { graph_title = "CrossAccount", account_id = data.aws_caller_identity.cross_account.account_id },
  ]
}

resource "aws_cloudwatch_dashboard" "example" {
  dashboard_name = "cloudwatch-dashboard-example"
  dashboard_body = jsonencode({
    widgets = [
      for account in local.accounts :
      {
        type = "metric"

        properties = {
          title   = account.graph_title
          view    = "timeSeries"
          stacked = false
          region  = "ap-northeast-1"
          stat    = "Average"
          period  = 300
          metrics = [
            [
              "AWS/Usage",
              "ResourceCount",
              "Type",
              "Resource",
              "Resource",
              "TargetGroupsPerRegion",
              "Service",
              "Elastic Load Balancing",
              "Class",
              "None",
              {
                "accountId" = account.account_id
              },
            ]
          ]
        }
      }
    ]
  })
}
```

# いざ、確認！
さて、ここまでできたら完成だ。```terraform apply```してコンソールで見てみよう。

![キャプチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/60a02545-6d05-9429-64b4-a7c326c09efd.png)

できた！
クロスアカウントのメトリクスは右上に「xa」のアイコンが表示され、クリックするとこのようにアカウントIDを確認することも可能だ。

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/a22f51ef-8a3c-25ba-c213-a349e393a018.png)

これで、見やすいダッシュボードを作成することができるようになった！