
AWS Step Functions(JSONata)でAWS料金をSlackへ通知【Lambda無し】
「直近1週間ぐらいにAWSアカウント内で掛かったコスト」を Slack通知させる仕組みを作ってみました。
アーキテクチャと通知内容サンプルはこちら(↓)。
AWSコスト監視くん
とってもシンプルですね。 シンプルに済んでいるのは Step Functions 内で JSONata が裏側で頑張っているためです。
JSONata はJSONデータ用の軽量なクエリおよび変換言語です。 複雑なクエリを表現でき、豊富な組み込み演算子と関数も備えています。 2024/11 より Step Functions にて、この JSONata が使えるようになりました。
本ブログは「JSONataのキャッチアップ」と 「良い感じのAWSコスト通知」 をモチベーションに書きました。
さっそくアーキテクチャの展開方法とJSONataについて説明します。
アーキテクチャを展開する
事前準備#1: Slackクライアントの設定
Amazon Q Developer in chat applications(旧: AWS Chatbot) にてSlackクライアントを設定しておきます。 ※すでに設定済みであればスキップください。
[新しいクライアントを設定] > [設定]
対象のワークスペースを選択して [許可する]
設定済みクライアントにあることを確認
事前準備#2: Q Developer アプリを追加
Amazon Q Developer アプリを通知させたいSlackチャンネルに追加しておきます。
[インテグレーション] > [アプリを追加する]
Amazon Q Developer アプリを [追加]
CloudFormationスタックの作成
テンプレートファイルは GitHub Gist のコチラ をアップロードしてください。
参考:Gistの内容
パラメータは以下のとおり。
パラメータ | 備考 |
---|---|
Project | 主にリソース名のプレフィクス。デフォルト: aws-cost-watcher |
SlackWorkspaceID | SlackワークスペースID。マネコンで確認可能 |
SlackChannelID | SlackチャンネルID。チャンネルを右クリックして「リンクをコピー」 → URLの末尾部分 |
AngryThreshold | お怒りメッセージ 通知になるしきい値。単位は USD |
展開後のリソースはこんな感じです。
Project=aws-cost-watcher の場合
動作確認
作成した Step Functions を実行してみましょう。
aws stepfunctions start-execution --state-machine-arn ${ステートマシンのARN}
# {
# "executionArn": "arn:aws:states:ap-northeast-1:111111111111:execution:aws-cost-watcher:86d685fb-a076-4c82-9882-751cf2d4e866",
# "startDate": "2025-04-04T17:52:51.336000+09:00"
# }
コストが通知されました
しきい値(AngryThreshold)を超えると「お怒りメッセージ」になります。
「お怒りメッセージ」例
JSONataについて
JSONataは主に以下のタスクで活用しました。
Cost Explorer API 実行時の「開始日/終了日」を決定する
Start: |-
{%
/* 7日前の日付(YYYY-MM-DD)を持ってくる */
($millis() - 86400000 * 7) ~> $fromMillis('[Y0001]-[M01]-[D01]')
%}
End: |-
{%
/* 今日の日付(YYYY-MM-DD)を持ってくる */
/* ※CEの最新コスト反映にはラグがあることに注意 */
$millis() ~> $fromMillis('[Y0001]-[M01]-[D01]')
%}
API実行結果から「コストの合計」と「コストになっているサービス」を集計する
CostSum: |-
{%
/* 全グループアイテムのコストを取得して、合計を求める */
$states.result.ResultsByTime[].Groups[].Metrics.UnblendedCost.Amount.$number()
~> $sum() ~> $round(1)
%}
CostSorted: |-
{% (
/* 全グループアイテムの「サービスとコストのペア」を取得 */
$all_entries := $map(
$zip(
$states.result.ResultsByTime[].Groups[].Keys[0],
$states.result.ResultsByTime[].Groups[].Metrics.UnblendedCost.Amount.$number()
),
function($v) { {"Service": $v[0], "Amount": $v[1]} }
);
/* サービス名の重複を排除したリストを作っておく */
$services := $all_entries.Service ~> $distinct();
/* サービス名ごとのコストを計算する */
$cost_per_service := $map(
$services,
function($s){
{
"Service": $s,
"Total": $all_entries[Service=$s].Amount ~> $sum() ~> $round(1)
}
}
);
/* 降順でソート */
$sort($cost_per_service, function($l, $r){ $l.Total < $r.Total });
) %}
Slack通知のメッセージを作成する
title: |-
{%
/* コスト合計がしきい値を超えたら「お怒りメッセージ」にする */
$states.input.CostSum > $AngryThreshold ?
":serious_face_with_symbols_covering_mouth: コスト監視くんはお怒りです"
: ":simple_smile: コスト監視くんは平常心を保っています"
%}
description: |-
{%
"ここ1週間ぐらいのコストは "
& $string($states.input.CostSum)
& " USD です。"
& "\n"
& "\n:one: " & ( $states.input.CostSorted[0].Service ~> $replace(/^(AWS|Amazon)\s*/,"") ) & ": " & $states.input.CostSorted[0].Total & " USD"
& "\n:two: " & ( $states.input.CostSorted[1].Service ~> $replace(/^(AWS|Amazon)\s*/,"") ) & ": " & $states.input.CostSorted[1].Total & " USD"
& "\n:three: " & ( $states.input.CostSorted[2].Service ~> $replace(/^(AWS|Amazon)\s*/,"") ) & ": " & $states.input.CostSorted[2].Total & " USD"
& "\n:four: " & ( $states.input.CostSorted[3].Service ~> $replace(/^(AWS|Amazon)\s*/,"") ) & ": " & $states.input.CostSorted[3].Total & " USD"
& "\n:five: " & ( $states.input.CostSorted[4].Service ~> $replace(/^(AWS|Amazon)\s*/,"") ) & ": " & $states.input.CostSorted[4].Total & " USD"
%}
JSONataには便利な組み込み関数がたくさんあります。 それらを多く活用しました(以下、今回使った関数たち)。
カテゴリ | 関数 | できること |
---|---|---|
String Functions | $replace() | 文字列内のパターンを指定された置換文字列に置き換える |
Numeric Functions | $number() | 数値にキャストする |
Numeric Functions | $round() | 指定された小数点以下の桁数に四捨五入する |
Aggregation Functions | $sum() | 数値の合計を計算する |
Date/Time Functions | $millis() | 現時刻のUNIX時刻(単位: ミリ秒)を返す |
Date/Time Functions | $fromMillis() | UNIX時刻を指定フォーマットのタイムスタンプに変換する |
Array Functions | $zip() | 複数の配列をグループ化して新しい配列を作成する |
Array Functions | $distinct() | 配列から重複する値を除去する |
Array Functions | $sort() | 配列を指定された基準に従ってソートする |
Higher Order Functions | $map() | 配列の各要素に対して指定した関数を適用し、新しい配列を作成する |
Higher Order Functions | $filter() | 指定された条件に一致する配列要素のみを抽出する |
おわりに
AWSコスト通知の仕組みを JSONata芸で作成してみました。
JSONataに入門してみましたが、非常に便利に感じました。 JSONPathと比べて表現できる幅が格段に上がりますね。 ちょっとした加工や集計であれば、 Lambda無しでもなんとかなりそうです。
以上、参考になれば幸いです。
参考
- JSONata Documentation · JSONata
- AWS Step Functions simplifies developer experience with Variables and JSONata transformations - AWS
- Custom notifications using Amazon Q Developer in chat applications - Amazon Q Developer in chat applications
- 「これでLambdaが不要に?!Step FunctionsのJSONata対応について」というタイトルでCM re:Growth 2024 OSAKAに登壇しました #regrowth_osaka | DevelopersIO
- SlackへのAWS料金通知を(ほぼ)Step Functionsの機能だけで作ってみた | DevelopersIO