AWS Cost Anomaly Detection を Chatbot で Slack 通知させる by Terraform

コスト異常検知、ガンガン使っていくぜよ
2023.05.31

どうも、ちゃだいん(@chazuke4649)です。

AWS Cost Anomaly Detection を AWS Chatbot で Slack 通知させるリソース一式を Terraform で作りました。

ほぼ、以下ブログのTerraform版です。

手順

  1. AWS ChatbotにSlackワークスペースを登録する
  2. Terraformコードの適用する
  3. 通知テストする

1. AWS ChatbotにSlackワークスペースを登録する

まだ、AWS ChatbotにSlackワークスペースを登録していない場合は、Chatbotコンソールより実施します。

※詳細は割愛します

2. Terraformコードの適用する

メインとなるTerraoformコードは以下となります。

cost_anomaly_detection.tf

###############################################################################
# Cost Anomaly Detection
###############################################################################
resource "aws_ce_anomaly_monitor" "default" {
  name              = "default"
  monitor_type      = "DIMENSIONAL"
  monitor_dimension = "SERVICE"
}

resource "aws_ce_anomaly_subscription" "default" {
  name      = "default"
  frequency = "IMMEDIATE"
  threshold_expression {
    dimension {
      key           = "ANOMALY_TOTAL_IMPACT_PERCENTAGE"
      values        = ["50.0"]
      match_options = ["GREATER_THAN_OR_EQUAL"]
    }
  }
  monitor_arn_list = [aws_ce_anomaly_monitor.default.arn]
  subscriber {
    type    = "SNS"
    address = aws_sns_topic.cost_anomaly.arn
  }
}

###############################################################################
# SNS Topic
###############################################################################
resource "aws_sns_topic" "cost_anomaly" {
  name = "cost_anomaly"
}

data "aws_iam_policy_document" "sns_cost_anomaly" {
  statement {
    effect    = "Allow"
    resources = [aws_sns_topic.cost_anomaly.arn]
    actions   = ["sns:Publish"]

    principals {
      type = "Service"
      identifiers = [
        "costalerts.amazonaws.com",
      ]
    }
  }
}

resource "aws_sns_topic_policy" "sns_cost_anomaly" {
  arn    = aws_sns_topic.cost_anomaly.arn
  policy = data.aws_iam_policy_document.sns_cost_anomaly.json
}

###############################################################################
# Chatbot
###############################################################################
data "aws_iam_policy_document" "cloudwatch_access" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "cloudwatch:Describe*",
      "cloudwatch:Get*",
      "cloudwatch:List*",
    ]
  }
}

resource "aws_iam_policy" "chatbot_role" {
  name   = "chatbot"
  policy = data.aws_iam_policy_document.cloudwatch_access.json
}

data "aws_iam_policy_document" "chatbot_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["chatbot.amazonaws.com"]
    }
  }
}

resource "aws_iam_role_policy_attachment" "chatbot_role" {
  role       = aws_iam_role.chatbot_role.name
  policy_arn = aws_iam_policy.chatbot_role.arn
}

resource "aws_iam_role" "chatbot_role" {
  name               = "chatbot"
  assume_role_policy = data.aws_iam_policy_document.chatbot_role.json
}

resource "aws_cloudformation_stack" "chatbot" {
  name = "chatbot"

  template_body = yamlencode({
    Description = "Managed by Terraform"
    Resources = {
      AlertNotifications = {
        Type = "AWS::Chatbot::SlackChannelConfiguration"
        Properties = {
          ConfigurationName = "CostAlertNotifications"
          SlackWorkspaceId  = "ABCDEFG123" ## SlackワークスペースID
          SlackChannelId    = "HIJKLMN456" ## SlackチャンネルID
          IamRoleArn        = aws_iam_role.chatbot_role.arn
          SnsTopicArns      = [aws_sns_topic.cost_anomaly.arn]
          GuardrailPolicies = ["arn:aws:iam::aws:policy/ReadOnlyAccess"]
          LoggingLevel      = "NONE"
          UserRoleRequired  = false
        }
      }
    }
  })
}

一部を解説します。

Cost Anomaly Detection

###############################################################################
# Cost Anomaly Detection
###############################################################################
resource "aws_ce_anomaly_monitor" "default" {
  name              = "default"
  monitor_type      = "DIMENSIONAL"
  monitor_dimension = "SERVICE"
}

resource "aws_ce_anomaly_subscription" "default" {
  name      = "default"
  frequency = "IMMEDIATE"
  threshold_expression {
    dimension {
      key           = "ANOMALY_TOTAL_IMPACT_PERCENTAGE"
      values        = ["50.0"]
      match_options = ["GREATER_THAN_OR_EQUAL"]
    }
  }
  monitor_arn_list = [aws_ce_anomaly_monitor.default.arn]
  subscriber {
    type    = "SNS"
    address = aws_sns_topic.cost_anomaly.arn
  }
}

基本的には以下ドキュメントのサンプルを参考にしています。

aws_ce_anomaly_monitor | Resources | hashicorp/aws | Terraform Registry

aws_ce_anomaly_subscription | Resources | hashicorp/aws | Terraform Registry

モニター側で注意点としては、組織にひもづかないAWSアカウントを2023年3月27日以降に開設すると、Cost Anomaly Detecitionモニターの「AWSサービス」タイプが自動作成されます。これは複数作ることができないので、すでに作成されている場合はすでに存在するため、上記コードだとエラーになります。

サブスクリプション側では、引数の threshold が Depracated で、代わりに threshold_expression で柔軟な設定が可能になっています。

今回は、ANOMALY_TOTAL_IMPACT_PERCENTAGE (合計インパクト・パーセンテージ)を使用しており、異常と検出された金額が通常金額の何%かをしきい値とします。今回だと「50%以上」だと通知する設定にしています。

SNS

###############################################################################
# SNS Topic
###############################################################################
resource "aws_sns_topic" "cost_anomaly" {
  name = "cost_anomaly"
}

data "aws_iam_policy_document" "sns_cost_anomaly" {
  statement {
    effect    = "Allow"
    resources = [aws_sns_topic.cost_anomaly.arn]
    actions   = ["sns:Publish"]

    principals {
      type = "Service"
      identifiers = [
        "costalerts.amazonaws.com",
      ]
    }
  }
}

resource "aws_sns_topic_policy" "sns_cost_anomaly" {
  arn    = aws_sns_topic.cost_anomaly.arn
  policy = data.aws_iam_policy_document.sns_cost_anomaly.json
}

普通のSNSトピックとリソースポリシーですが、特筆すると、トピックへのアクセスを許可しているAWSサービスの識別子として costalerts.amazonaws.com を指定しています。

Creating an Amazon SNS topic for anomaly notifications - AWS Cost Management

Chatbot

###############################################################################
# Chatbot
###############################################################################
data "aws_iam_policy_document" "cloudwatch_access" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "cloudwatch:Describe*",
      "cloudwatch:Get*",
      "cloudwatch:List*",
    ]
  }
}

resource "aws_iam_policy" "chatbot_role" {
  name   = "chatbot"
  policy = data.aws_iam_policy_document.cloudwatch_access.json
}

data "aws_iam_policy_document" "chatbot_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["chatbot.amazonaws.com"]
    }
  }
}

resource "aws_iam_role_policy_attachment" "chatbot_role" {
  role       = aws_iam_role.chatbot_role.name
  policy_arn = aws_iam_policy.chatbot_role.arn
}

resource "aws_iam_role" "chatbot_role" {
  name               = "chatbot"
  assume_role_policy = data.aws_iam_policy_document.chatbot_role.json
}

resource "aws_cloudformation_stack" "chatbot" {
  name = "chatbot"

  template_body = yamlencode({
    Description = "Managed by Terraform"
    Resources = {
      AlertNotifications = {
        Type = "AWS::Chatbot::SlackChannelConfiguration"
        Properties = {
          ConfigurationName = "CostAlertNotifications"
          SlackWorkspaceId  = "ABCDEFG123" ## SlackワークスペースID
          SlackChannelId    = "HIJKLMN456" ## SlackチャンネルID
          IamRoleArn        = aws_iam_role.chatbot_role.arn
          SnsTopicArns      = [aws_sns_topic.cost_anomaly.arn]
          GuardrailPolicies = ["arn:aws:iam::aws:policy/ReadOnlyAccess"]
          LoggingLevel      = "NONE"
          UserRoleRequired  = false
        }
      }
    }
  })
}

こちらで紹介の方法を大変参考にさせていただいてます。ありがとうございます!

注意点としては、SlackワークスペースID と SlackチャンネルID はダミーなので差し替える必要があります。

ちなみに、TerraformがChatbotをサポートしていないのでCloudFormationをラップしていますが、今なら AWS Cloud Control Provider を使って直接作成する方法もあるようです、なるほど〜

3. 通知テストする

terraform apply後、Chatbotコンソールにて、テスト通知を行うと以下のような通知が届きました。

また、SNSトピックにて、Cost Anomaly Detectionのサンプルメッセージを送信すると、以下のような通知が届きました。

それでは今日はこの辺で。