🌟

AWS Chatbot で予算アラートを Slack に通知する

2024/05/15に公開

はじめに

AWSをはじめ、クラウドサービスってほんとに便利ですよね。個人開発者にも優しい無料枠もたくさんあって嬉しい限り。ですが従量課金ゆえ、気をつけないといつだってクラウド破産と隣り合わせです。
今回はそんなクラウド破産を防ぐべく、設定した予算に到達したら通知をSlackに送ってもらえる予算アラートをAWS CDKを使って組んでいきたいと思います。

対象読者

  • AWSを利用している方
  • AWSコストの監視をしたい方
  • Slackを利用している方
  • AWS CDKでインフラを管理したい方
  • Typescriptを使ったことがある方

技術スタック

  • AWS CDK(Typescript)
    • aws-cdk(v2.141.0)
    • node(v20.0.0)
  • AWS Budget
  • AWS Chatbot
  • AWS SNS

概要

全体フローは以下になっています。

  1. AWS Budgetに予算アラートを設定
  2. 1で設定した閾値に到達したらAWS SNSトピックに通知が発行される
  3. 2をAWS Chatbotで取得し、Slackのチャンネルに送信する

事前準備

前提としてAWSとSlackの連携が必要なため、解説していきます。

AWS ChatbotにSlackチャットクライアントを設定

AWS Chatbotにまず使用している自分のSlackワークスペースを連携しなければいけません。
すでに連携済みの方はスキップしてください。
前提条件としてAWSコンソールとSlackにログイン済みとします。

AWSコンソールでAWS Chatbotを開くと、右上に設定プルダウンがあるのでそちらからSlackを選択して、クライアントを設定ボタンを押してください。

その後Slackにリダイレクトします。Slackにawsのボットが入っていれば設定完了です。

通知したいSlackチャンネルを作成

今回の予算アラート通知を受信するためのチャンネルを作成します。DMでもいいですが、今回はチームで共有することも多いだろうということでプライベートのチャンネルを作成します。

チャンネル作成後先ほど現れたawsボットをチャンネルに招待します。
チャンネルで@awsと送信して、招待するかどうか聞かれるので招待しましょう。

これで事前準備は完了です。

コード解説

CDKで以下のリソースを作成していきます。

  • AWS Chatbot
  • AWS SNS
  • AWS Budget

ソースはGithubに公開しています。ワンコマンドでデプロイ可能です。
https://github.com/Tomoaki-Moriya/aws-chatbot-cost-notification-slack

今回はわかりやすいように一つのスタックですべて作成します。
実際の運用ではスタックをご自身で分けるほうがおすすめです。

作成するリソースごとに関数にしていますので追って説明します。

全体フロー

Stackのインスタンスを作成します。
SlackのワークスペースIDとチャンネルIDはハードコーディングを避けたいのでコンテキストで引数で受け取ります。
SNSトピック作成->チャットボット作成->予算作成の順序になっています。

lib/aws-chatbot-cost-notification-slack-stack.ts
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
  super(scope, id, props);

  const slackChannelId = scope.node.tryGetContext("slackChannelId") as string;
  const slackWorkspaceId = scope.node.tryGetContext("slackWorkspaceId") as string;

  const idPrefix = `aws-chatbot-cost-notification-slack-${slackChannelId}`;

  const topic = this.createSnsTopic(idPrefix);

  this.createChatbot(idPrefix, topic, slackWorkspaceId, slackChannelId);

  this.createBudget(idPrefix, topic);
}

createSnsTopic

AWS SNSトピックを作成します。
principalsにbudgets.amazonaws.com、actionにsns:Publishを設定することで、
AWS Budget側から予算アラートメッセージをSNSに発行できるロールをアタッチします。

lib/aws-chatbot-cost-notification-slack-stack.ts
private createSnsTopic(idPrefix: string) {
  const topic = new aws_sns.Topic(this, `${idPrefix}-topic`, {
    displayName: `${idPrefix}-topic`,
    topicName: `${idPrefix}-topic`,
  });

  topic.addToResourcePolicy(
    new aws_iam.PolicyStatement({
      effect: aws_iam.Effect.ALLOW,
      actions: ["sns:Publish"],
      principals: [new aws_iam.ServicePrincipal("budgets.amazonaws.com")],
      resources: [topic.topicArn],
    })
  );
  return topic;
}

createChatbot

AWS Chatbotを作成します。
AWS Chatbotは外部チャットツールのチャンネルと連携する設定を、チャットクライアントごとにチャネルという単位で持つことができます。

今回はSlackを通知先として利用するのでaws_chatbot.SlackChannelConfigurationクラスを使い、コンテキストから受け取ったSlackのワークスペースとチャンネルを設定します。
また先ほど作成したSNSトピックから通知を受信するので、arnを設定します。

通知を受信し、Slackに転送するためには権限が必要なので、principalでchatbot.amazonaws.comを、actionでsns:Subscribeをロールに付与し、リソースは上記のトピックのarnを追加します。

lib/aws-chatbot-cost-notification-slack-stack.ts
private createChatbot(
  idPrefix: string,
  topic: cdk.aws_sns.Topic,
  slackWorkspaceId: string,
  slackChannelId: string
) {
  const chatbotRole = new aws_iam.Role(this, `${idPrefix}-role`, {
    assumedBy: new aws_iam.ServicePrincipal("chatbot.amazonaws.com"),
  });

  chatbotRole.addToPolicy(
    new aws_iam.PolicyStatement({
      actions: ["sns:Subscribe"],
      resources: [topic.topicArn],
    })
  );

  new aws_chatbot.SlackChannelConfiguration(this, `${idPrefix}-chatbot`, {
    slackChannelConfigurationName: `${idPrefix}-chatbot`,
    slackWorkspaceId,
    slackChannelId,
    notificationTopics: [topic],
    role: chatbotRole,
  });
}

createBudget

AWS予算アラートを作成します。
今回は100ドルを上限として設定し、そのうち50%に到達した際にアラートが設定されるようにしています。
つまり請求額が50ドルに到達したらアラートが飛ぶことになります。
タイムラグもあるので余裕をもった上限と閾値を設定することをおすすめします。

通知先を設定しないと、アラート状態になるだけで通知がとんでこないので、先ほど作成したSNSトピックを通知先として設定します。

lib/aws-chatbot-cost-notification-slack-stack.ts
private createBudget(idPrefix: string, topic: cdk.aws_sns.Topic) {
  new aws_budgets.CfnBudget(this, `${idPrefix}-budget`, {
    budget: {
      budgetLimit: {
        amount: 100, // お好きな金額を
        unit: "USD",
      },
      budgetName: `${idPrefix}-budget`,
      budgetType: "COST",
      timeUnit: "MONTHLY",
    },
    notificationsWithSubscribers: [
      {
        notification: {
          notificationType: "ACTUAL",
          comparisonOperator: "GREATER_THAN",
          threshold: 50, // お好きな閾値を
          thresholdType: "PERCENTAGE",
        },
        subscribers: [
          {
            subscriptionType: "SNS",
            address: topic.topicArn,
          },
        ],
      },
    ],
  });
}

デプロイ

スタックを作成したらさっそくデプロイしてみます。
SlackのワークスペースIDとチャンネルIDをコンテキスト引数として渡す必要があるので取得しましょう。

ワークスペースID

AWSコンソールからAWS Chatbotを開いて、事前準備で作成したSlackのチャットクライアントページの上部にワークスペースIDが載っているのでコピーします。

チャンネルID

事前準備で作成したSlackのチャンネルの設定画面からコピーできます。

コマンド

cdk deploy AwsChatbotCostNotificationSlackStack -c slackWorkspaceId=[ワークスペースID] -c slackChannelId=[チャンネルID] --profile [プロファイル名]

動作確認

デプロイが完了したらAWSコンソールからBudgetsを開き、確認してみます。

作成できてますね!

閾値を変更

ちゃんと通知が送信されるかどうかテストしてみます。
コンソール上から一度予算の閾値を0にしてみましょう。

  1. 作成した予算を選択
  2. すべてのアラートを表示
  3. 画面下のアラートの編集をクリック
  4. 閾値を0にする

上記の設定をしたら保存してください。
テストのために一時的に0にするだけなので、もとの閾値に戻すことをお忘れなく。

Slackを確認

通知が届きました!

おわり

いかがだったでしょうか。
運用でAWSを使ってるチームはもちろん設定済みではあると思いますが、意外と個人開発者だとやってない方もいらっしゃると思います。
高額請求でなんとかなった系の記事もちょこちょこみますが、精神的によろしくないのでコスト管理はしっかりやってきたいですね。

Discussion