AWS Budgetsの請求アラートをAWS ChatbotでSlackに通知する構成をCDKで作る

2021.09.15

吉川@広島です。

掲題の通りです。早速いきましょう。

下記が大変参考になりました。

環境

  • npm 7.23.0
  • typescript 4.4.3
  • aws-cdk 1.122.0

Chatbotの下準備

CloudWatch AlarmをAWS ChatbotからSlack通知する仕組みをCDKで作ってみた | DevelopersIO

こちらの「AWS Chatbot の Workspace Configuration を GUI から作成」の手順を実施します。

CDKプロジェクトを構築

aws-cdk cliで雛形を作ります。

mkdir billing-alarm
cd billing-alarm
npx aws-cdk init -l typescript

コード

// lib/billing-alarm-stack.ts

import * as cdk from '@aws-cdk/core'
import * as sns from '@aws-cdk/aws-sns'
import * as iam from '@aws-cdk/aws-iam'
import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'
import * as chatbot from '@aws-cdk/aws-chatbot'
import * as budgets from '@aws-cdk/aws-budgets'

export interface MyProps {
  readonly slackChannelId: string
  readonly slackWorkspaceId: string
  readonly unit: string
  readonly amount: number
  readonly threshold: number
  readonly thresholdType: string
  readonly timeUnit: string
}

export class BillingAlarmStack extends cdk.Stack {
  constructor(
    scope: cdk.Construct,
    id: string,
    props?: cdk.StackProps & MyProps
  ) {
    super(scope, id, props)

    /**
     * SNS
     */
    const topic = new sns.Topic(this, 'billing-alarm-topic')

    topic.addToResourcePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['SNS:Publish'],
        principals: [new iam.ServicePrincipal('budgets.amazonaws.com')],
        resources: [topic.topicArn],
      })
    )

    /**
     * Chatbot
     */
    const chatbotRole = new iam.Role(this, 'billing-alarm-chatbot-role', {
      assumedBy: new iam.ServicePrincipal('sns.amazonaws.com'),
    })

    chatbotRole.addToPolicy(
      new iam.PolicyStatement({
        resources: ['*'],
        actions: ['cloudwatch:*'],
      })
    )

    new chatbot.CfnSlackChannelConfiguration(
      this,
      'billing-alarm-slack-channel-config',
      {
        configurationName: 'billing-alarm-slack-channel-config',
        iamRoleArn: chatbotRole.roleArn,
        slackChannelId: props!.slackChannelId,
        slackWorkspaceId: props!.slackWorkspaceId,
        snsTopicArns: [topic.topicArn],
        loggingLevel: 'ERROR',
      }
    )

    /**
     * Budgets
     */
    new budgets.CfnBudget(this, 'billing-alarm-budget', {
      budget: {
        budgetType: 'COST',
        timeUnit: props!.timeUnit,
        budgetLimit: {
          unit: props!.unit,
          amount: props!.amount,
        },
      },
      notificationsWithSubscribers: [
        {
          notification: {
            comparisonOperator: 'GREATER_THAN',
            notificationType: 'ACTUAL',
            threshold: props!.threshold,
            thresholdType: props!.thresholdType,
          },
          subscribers: [
            {
              // SNSに通知する
              subscriptionType: 'SNS',
              address: topic.topicArn,
            },
          ],
        },
      ],
    })
  }
}
// bin/billing-alarm.ts

#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from '@aws-cdk/core'
import { BillingAlarmStack } from '../lib/billing-alarm-stack'

const app = new cdk.App()
new BillingAlarmStack(app, 'billing-alarm-stack', {
  slackWorkspaceId: process.env.SLACK_WORKSPACE_ID!,
  slackChannelId: process.env.SLACK_CHANNEL_ID!,
  unit: process.env.UNIT ?? 'USD',
  amount: process.env.AMOUNT != null ? Number(process.env.AMOUNT) : 10,
  threshold:
    process.env.THRESHOLD != null ? Number(process.env.THRESHOLD) : 100,
  thresholdType: process.env.THRESHOLD_TYPE ?? 'PERCENTAGE',
  timeUnit: process.env.TIME_UNIT ?? 'MONTHLY',
})

環境変数の設定

環境変数から設定値を注入するようにしています。

例えば次のようにすると

  • 月額課金が10ドルを超えるとアラート発生

となります。

# .env

SLACK_WORKSPACE_ID: 'xxxxxxxx' # SlackワークスペースIDを設定
SLACK_CHANNEL_ID: 'xxxxxxxx' # SlackチャンネルIDを設定
UNIT: 'USD' # 通貨単位
AMOUNT: 10 # 課金額
THRESHOLD: 100 # しきい値
THRESHOLD_TYPE: 'PERCENTAGE' # しきい値の単位
TIME_UNIT: 'MONTHLY' # 期間単位(月毎、日毎など)