BLOG

ブログ

AWSの利用料金をLarkに通知してみた

こちらは、Mavs Advent Calendar2024の22日目の記事です!

🎄🎄🎄

こんにちは、タロウです。

今どれくらい使っているのか、円安でどれくらい請求が来るのかビクビクしている、そんな方多いかと思います。
翌月に知るよりこまめに把握できた方が心が穏やかになると思い、利用料を通知する仕組みを作ってみることにしました!

いきなり完成

Lambda + Event Bridge + Lark APIで実現しています。
毎週月曜日の朝お知らせしています。

月末最終週などになるとタイミングによっては月の合計金額が把握できないことがあるため、前の月の合計金額も合わせて表示しています。

アーキテクト図

書いてみよう!と書き出したものの、これだけでした・・。

EventBridgeで毎週月曜日の10時に実行されるように設定しました。

Screenshot

コード

Node.jsで書きました。

AWS料金はCostExplorerで取得でき、LambdaからはSDKを使用して簡単にアクセスすることができます。
取得対象の日付を取得し、当月分・先月分の2つ分料金を取得します。
料金がかかったサービスごとに取得できるため、

  • 表示用にサービス名+料金をテキストで保持
  • 料金の金額を合計

を行い、取得したすべてのサービス分処理が終わるまでループしています。

import { CostExplorerClient, GetCostAndUsageCommand } from "@aws-sdk/client-cost-explorer";

const client = new CostExplorerClient({ region: "us-east-1" });

// 日本時間を考慮した日付計算
const getJSTDate = () => new Date(new Date().toLocaleString("en-US", { timeZone: "Asia/Tokyo" }));

// 指定した月の開始日と終了日を取得する
const getMonthStartAndEndDates = (year, month) => {
  const startDate = new Date(year, month, 1);
  const endDate = new Date(year, month + 1, 0);
  return {
    start: startDate.toISOString().split("T")[0],
    end: endDate.toISOString().split("T")[0],
  };
};

// AWS Cost Explorerから料金データを取得
const getCostData = async (startTime, endTime) => {
  const command = new GetCostAndUsageCommand({
    TimePeriod: { Start: startTime, End: endTime },
    Granularity: "MONTHLY",
    Metrics: ["UnblendedCost"],
    GroupBy: [{ Type: "DIMENSION", Key: "SERVICE" }],
  });

  const response = await client.send(command);

  let costSum = 0;
  let serviceCost = "";

  response.ResultsByTime[0].Groups.forEach((service) => {
    const cost = parseFloat(service.Metrics.UnblendedCost.Amount);
    costSum += cost;
    serviceCost += `${service.Keys[0]} : $${cost.toFixed(2)}\n`;
  });

  return { costSum, serviceCost };
};

export const handler = async () => {
  const today = getJSTDate();
  let message = "💰AWS利用料金をお知らせします。\n";

  try {
    // 今月分の料金計算
    const { start: currentMonthStart, end: currentMonthEnd } = getMonthStartAndEndDates(
      today.getFullYear(),
      today.getMonth()
    );

    const currentMonthData = await getCostData(currentMonthStart, currentMonthEnd);
    message += `\n【${today.getFullYear()}/${today.getMonth() + 1}分】\n合計: $${currentMonthData.costSum.toFixed(
      2
    )}\n\n■サービス別内訳\n${currentMonthData.serviceCost}`;

    // 前月分の料金計算
    const { start: lastMonthStart, end: lastMonthEnd } = getMonthStartAndEndDates(
      today.getFullYear(),
      today.getMonth() - 1
    );

    const lastMonthData = await getCostData(lastMonthStart, lastMonthEnd);
    message += `\n\n【${today.getFullYear()}/${today.getMonth()}分】\n合計: $${lastMonthData.costSum.toFixed(2)}`;
  } catch (error) {
    console.error("エラーが発生しました。", error);
    throw new Error("料金データの取得に失敗しました。");
  }

  // メッセージをLarkに送信
  const data = {
    msg_type: "text",
    content: { text: message },
  };

  try {
    const response = await fetch(
      "https://open.larksuite.com/open-apis/bot/v2/hook/[ここにBOTのURLを指定]",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data),
      }
    );

    const resData = await response.text();
    console.log("送信結果:", resData);

    return { statusCode: 200, body: resData };
  } catch (error) {
    console.error("メッセージの送信に失敗しました。", error);
    throw new Error("Larkへのメッセージ送信に失敗しました。");
  }
};

さいごに

運用中の別システムでは料金アラートでSlack通知しているものがありますが、
閾値を超えてからの通知よりも定期的な料金通知の方が心臓に優しいかもしれません。

ご紹介したコードはコピペでどの環境でも料金を取得することができ、
Larkへ通知を出すことができるのでぜひご活用ください!
(通知先はSlackでもチャットワークでもなんでも応用がききます。)

また便利な通知ができましたらご紹介します!

RELATED ARTICLE