AWS Chatbotと統合された AWS コスト異常検出 (Cost Anomaly Detection)を試してみた

AWS Chatbot と統合された AWS コスト異常検出 (Cost Anomaly Detection) を利用したSlack通知を AWS CloudFormation で設定してみました。
2022.04.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWSチームのすずきりょうです。

2022年3月、AWS コスト異常検出 (Cost Anomaly Detection) のアップデートで、AWS Chatbot との統合がサポートされました。

AWS コスト異常検出の結果を、AWS Chatbotを利用して Slackに通知する設定を CloudFormation で行う機会がありましたので、紹介させていただきます。

CloudFormation設定

リージョン

AWS コスト異常検出はグローバルサービスとなるため、バージニアリージョン(us-east-1)のCloudFormationを利用しました。

AnomalyMonitor

  • AWSのサービスを対象とした、コスト異常検出モニターを作成しました。
  AnomalyServiceMonitor:
    Type: AWS::CE::AnomalyMonitor
    Properties:
      MonitorName: AWS services
      MonitorType: DIMENSIONAL
      MonitorDimension: SERVICE
  • AWSサービスを対象としたモニターは、1アカウントにつき1件のみ作成可能です。

カスタムモニター

  • 一括請求(コンソリデーテッドビリング)のマスターアカウントでは、連結アカウント、コストカテゴリ、コスト分類タグを対象とした、カスタム(CUSTOM)モニターが作成可能です。

  • コスト分類タグを利用したカスタムモニター例

        Type: AWS::CE::AnomalyMonitor
        Properties:
          MonitorName: !Sub 'Tags-CmBillingGroup'
          MonitorType: CUSTOM
          MonitorSpecification: '{"Tags":{"Key":"CmBillingGroup","Values":["web"]}}'

AnomalySubscription

  • 1ドル以上のコスト影響をSNSトピックに通知するサブスクリプションを作成しました。
  • 通知の閾値(Threshold)は、検出履歴の結果や、システム規模に応じ調整してご利用ください。

  AnomalySubscriptionLow:
    Type: AWS::CE::AnomalySubscription
    Properties:
      SubscriptionName: Subscription-low-1usd
      Threshold: 1
      Frequency: IMMEDIATE
      MonitorArnList:
        - !Ref 'AnomalyServiceMonitor'
      Subscribers:
        - Type: SNS
          Address: !Ref 'TopicLow'

  TopicLow:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub '${AWS::StackName}-low'

ChatBot

以下記事で紹介された Chatbot 設定を踏襲しました。

Slackワークスペースの認証や、ワークスペースID、スラックチャネルIDの取得などは、当記事を参照ください。

Parameters:
  SlackWorkspaceId:
    Description: SlackWorkspaceId
    Type: String
    Default: T0XXXXXXXXX
  SlackChannelIdLow:
    Description: SlackChannelId (low)
    Type: String
    Default: C0XXXXXXXX3
Resources:
  SlackChannelLow:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: !Sub '${AWS::StackName}-low'
      IamRoleArn: !GetAtt 'IamRole.Arn'
      LoggingLevel: INFO
      SlackChannelId: !Ref 'SlackChannelIdLow'
      SlackWorkspaceId: !Ref 'SlackWorkspaceId'
      SnsTopicArns:
        - !Ref 'TopicLow'
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - chatbot.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies: !Ref 'AWS::NoValue'
      RoleName: !Ref 'AWS::NoValue'
  NotificationPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: Chatbot-Notification-Policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cloudwatch:Describe*
              - cloudwatch:Get*
              - cloudwatch:List*
            Resource:
              - '*'
      Roles:
        - !Ref 'IamRole'

動作例

今回設定を試みたアカウントでは、1ヶ月で11件のコスト異常検出が記録されていました。

  • コスト異常検出の検出履歴

  • Slack通知サンプル

データベース(Aurora)のメンテナンスに伴う、インポート、エクスポートなどに伴うストレージコストの増加が検出されていました。

まとめ

AWS コスト異常検出を利用する事で、AWS Budgetsの予算管理では補足できない AWSアカウントのコスト増加や、異常な利用の発生を 早期に検出出来る場合があります。

今回利用した AWS コスト異常検出、 AWS Chatbot は、利用するSNSなどのAWSサービスの実費のみで利用可能です。ぜひご活用ください。

参考テンプレート

  • AWS コスト異常検出と、AWS Budgets の月額予算の釣果を Chatbot経由で通知します。
AWSTemplateFormatVersion: '2010-09-09'
Description: Chatbot (slack) notification template for cost anomaly detection

Parameters:
  SlackWorkspaceId:
    Description: SlackWorkspaceId
    Type: String
    Default: T0XXXXXXXXX
  SlackChannelIdHigh:
    Description: SlackChannelId (high)
    Type: String
    Default: C0XXXXXXXX1
  SlackChannelIdMid:
    Description: SlackChannelId (mid)
    Type: String
    Default: C0XXXXXXXX2
  SlackChannelIdLow:
    Description: SlackChannelId (low)
    Type: String
    Default: C0XXXXXXXX3

Resources:
  # CE AnomalyMonitor
  AnomalyServiceMonitor:
    Type: AWS::CE::AnomalyMonitor
    Properties:
      MonitorName: AWS services
      MonitorType: DIMENSIONAL
      MonitorDimension: SERVICE

  AnomalySubscriptionHigh:
    Type: AWS::CE::AnomalySubscription
    Properties:
      SubscriptionName: Subscription-high-30usd
      Threshold: 30
      Frequency: IMMEDIATE
      MonitorArnList:
        - !Ref 'AnomalyServiceMonitor'
      Subscribers:
        - Type: SNS
          Address: !Ref 'TopicHigh'
  AnomalySubscriptionMid:
    Type: AWS::CE::AnomalySubscription
    Properties:
      SubscriptionName: Subscription-mid-10usd
      Threshold: 10
      Frequency: IMMEDIATE
      MonitorArnList:
        - !Ref 'AnomalyServiceMonitor'
      Subscribers:
        - Type: SNS
          Address: !Ref 'TopicMid'
  AnomalySubscriptionLow:
    Type: AWS::CE::AnomalySubscription
    Properties:
      SubscriptionName: Subscription-low-1usd
      Threshold: 1
      Frequency: IMMEDIATE
      MonitorArnList:
        - !Ref 'AnomalyServiceMonitor'
      Subscribers:
        - Type: SNS
          Address: !Ref 'TopicLow'

  # SNS
  TopicHigh:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub '${AWS::StackName}-high'
  TopicMid:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub '${AWS::StackName}-mid'
  TopicLow:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub '${AWS::StackName}-low'

  # Budget
  Budget:
    Type: AWS::Budgets::Budget
    Properties:
      Budget:
        BudgetLimit:
          Amount: 50
          Unit: USD
        TimeUnit: MONTHLY
        BudgetType: COST
      NotificationsWithSubscribers:
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 120
          Subscribers:
            - SubscriptionType: SNS
              Address: !Ref 'TopicHigh'
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 80
          Subscribers:
            - SubscriptionType: SNS
              Address: !Ref 'TopicMid'
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 60
          Subscribers:
            - SubscriptionType: SNS
              Address: !Ref 'TopicLow'

  # Chatbot
  ## SlackChannelConfiguration
  SlackChannelHigh:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: !Sub '${AWS::StackName}-high'
      IamRoleArn: !GetAtt 'IamRole.Arn'
      LoggingLevel: INFO
      SlackChannelId: !Ref 'SlackChannelIdHigh'
      SlackWorkspaceId: !Ref 'SlackWorkspaceId'
      SnsTopicArns:
        - !Ref 'TopicHigh'
  SlackChannelMid:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: !Sub '${AWS::StackName}-mid'
      IamRoleArn: !GetAtt 'IamRole.Arn'
      LoggingLevel: INFO
      SlackChannelId: !Ref 'SlackChannelIdMid'
      SlackWorkspaceId: !Ref 'SlackWorkspaceId'
      SnsTopicArns:
        - !Ref 'TopicMid'
  SlackChannelLow:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: !Sub '${AWS::StackName}-low'
      IamRoleArn: !GetAtt 'IamRole.Arn'
      LoggingLevel: INFO
      SlackChannelId: !Ref 'SlackChannelIdLow'
      SlackWorkspaceId: !Ref 'SlackWorkspaceId'
      SnsTopicArns:
        - !Ref 'TopicLow'

  ## Chatbot:LogGroup
  LogGroupSlackChannelHigh:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/chatbot/${AWS::StackName}-high'
      RetentionInDays: 7

  LogGroupSlackChannelMid:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/chatbot/${AWS::StackName}-mid'
      RetentionInDays: 7

  LogGroupSlackChannelLow:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/chatbot/${AWS::StackName}-low'
      RetentionInDays: 7

  ## Chatbot:IAM
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - chatbot.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies: !Ref 'AWS::NoValue'
      RoleName: !Ref 'AWS::NoValue'

  NotificationPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: Chatbot-Notification-Policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cloudwatch:Describe*
              - cloudwatch:Get*
              - cloudwatch:List*
            Resource:
              - '*'
      Roles:
        - !Ref 'IamRole'

  ReadonlyCommandsPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: Chatbot-ReadonlyCommands-Policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Deny
            Action:
              - iam:*
              - s3:GetBucketPolicy
              - ssm:*
              - sts:*
              - kms:*
              - cognito-idp:GetSigningCertificate
              - ec2:GetPasswordData
              - ecr:GetAuthorizationToken
              - gamelift:RequestUploadCredentials
              - gamelift:GetInstanceAccess
              - lightsail:DownloadDefaultKeyPair
              - lightsail:GetInstanceAccessDetails
              - lightsail:GetKeyPair
              - lightsail:GetKeyPairs
              - redshift:GetClusterCredentials
              - storagegateway:DescribeChapCredentials
            Resource:
              - '*'
      Roles:
        - !Ref 'IamRole'