【AWS】 Lambdaで使用料通知!従量課金は怖くない!
更新日: 2023/12/30
概要
AWS の使用料って従量課金なので、消すの忘れてて大変な金額の請求が来た!となっては怖いですよね。
結構やらかしエピソードの記事があったりするので私もヒヤヒヤでした。
微課金でその心配を解消できる方法をお伝えします。
使用するサービス
- Lambda
- Cost Explorer
- Simple Notification Service (SNS)
- EventBridge
結論
先に結論です。
Lambda で今月の今日までの使用料を料金通知をしましょう。
理由
よくある対策として、 料金アラート
を設定する方法があると思います。
しかし、15日に上限の8割超えてしまったからそれ以降 AWS 節約する!
なんて普通できませんよね?
常時動いているサービスがあれば末日までコストが嵩んで行くのが普通だと思います。
ですので、毎日金額を確認して
それを継続的にすることで金額増加の肌感を高めましょう。
Point
ちょっといつもより増加が多いなと感じた時は「Billing and Cost Management (請求とコスト管理)」の今月の請求を「請求書」で確認しましょう。
このページでは各サービスごとにいくら使っているかを確認できます。
請求期間を変更して他の月と比べることでどこがなぜ増えているかを理解することができると思います。
デメリット
この通知を行うだけで料金がかかります。
ただし、多くても1日1回だと思います (私はそうです)。
1ドルに満たないので大幅な予算オーバーよりは安い投資 (保険) と思いますが。。。
内訳
-
Lambda・SNS・EventBridge
基本的には無料枠で使用できると思います。
AWS Lambda 料金
Amazon SNS の料金
Amazon EventBridge の料金 -
Cost Explorer
1リクエスト 0.01 USDAWS Cost Explorer API を使って、AWS Cost Explorer を強化する、インタラクティブなアドホッククエリエンジンに直接アクセスできます。各リクエストには 0.01 USD の費用がかかります。
引用: AWS Cost Explorer の料金
実装
SNS トピックの作成
新しいトピックの作成
- SNSダッシュボードの左側のナビゲーションバーで、「トピック」を選択
- 「トピックの作成」ボタンを押下
- 「名前」フィールドにトピックの名前を入力
(その他の設定はデフォルトのまま) - 「トピックの作成」ボタンを押下して完了
Emailサブスクリプションの追加
ここで、SNSトピックが作成されたらメールが飛ぶようにします。
- 上記で作成したトピックを選択
- 「サブスクリプション」タブを押下
- 「サブスクリプションの作成」ボタンを押下
- 「プロトコル」ドロップダウンメニューから「Eメール」を選択
- 「エンドポイント」に通知を受け取りたいEmailアドレスを入力
- 「サブスクリプションの作成」ボタンを押下
(この状態ではサブスクリプションの詳細を確認するとステータスが保留中の確認
になっていて使えません) - AWSから送信された確認メールを開き、メール内の確認リンクを押下して完了
(これを実施することで6で確認したステータスが確認済み
になります)
ここまで完了したらトピックの ARN
を控えておきましょう。
あとで使います。
Lambda の作成
Lambda関数の作成
- Lambdaダッシュボードの左側のナビゲーションバーで、「関数」を選択
- 「関数の作成」ボタンを押下
- 「一から作成」を選択
- 関数名を入力し、ランタイム(Python 3.9)を選択
- アーキテクチャはどちらでも可
(コストが x86 > Arm ですが、今回は無料枠の中に収まる想定ですのでどちらを選択しても問題ありません) - 「関数の作成」を押下
- 作成した関数の詳細ページの「コード」タブを押下
- コードを作成
(一番最後にコードと編集することが書いてあります。)
Lambda関数のテスト
毎回テストイベントを指定するのは大変なので1度作っておきましょう。
- 作成した Lambda関数の詳細ページの「Test」タブを押下
- 「新しいイベントを作成」を選択し、「イベント名」に名前を入力
- イベントJSONには「{}」と入力
(今回作成するものは時間によって起動するだけで入力を必要としないためなんでも大丈夫です) - 「保存」ボタンを押下して保存
- 保存すると「コード」タブからでも「Test」ボタンを押下することで発火できるようになります
- テストを実行してメールが期待通り飛ぶか確認しましょう
EventBridgeによる毎日送信するスケジュール設定
- 作成した Lambda関数の詳細ページの「トリガーを追加」を押下
- ソースを選択とあるプルダウンで「EventBridge」を選択
- 新規に作成するので「Create a new rule」を選択
- 「Rule name」を記入
- Rule type 「Schedule expression」を選択
- Schedule expressionに毎日午前10時に実行するためのCron式を入力します(例:
cron(0 1 * * ? *)
)
(この時 UTC として処理されるので時差をお忘れなく) - 「追加」ボタンを押下して完了
※ EventBridge のルールに移動すれば現地時間を確認することができます。
トリガーのルールを削除するときの注意点
Lambda関数の詳細ページの「設定」「トリガー」の中にあるルール (リレーション) を先に削除してから、EventBridge 側のルール本体を消さないとゴミが残ってしまいます。
その際は新たにルール単体を作って Lambda に接続してあげましょう。
コード
sns_topic_arn に適切な値を入れて使用してください。
import json
import boto3
from decimal import Decimal
from datetime import datetime
sns_topic_arn = "<ここにSNS トピックの作成で作った ARNを記載>"
def lambda_handler(event, context):
# Cost Explorer
client = boto3.client("ce")
now = datetime.utcnow()
start = now.replace(day=1).strftime("%Y-%m-%d")
end = now.strftime("%Y-%m-%d")
response = client.get_cost_and_usage(
TimePeriod={"Start": start, "End": end},
Granularity="MONTHLY",
Metrics=["BlendedCost"],
) # 解説1
res_list = list()
for result in response["ResultsByTime"]: # 解説2
res_list.append(
f"Time period:\n"
f" from: {result['TimePeriod']['Start']}\n"
f" to: {result['TimePeriod']['End']}\n"
f"Cost: {result['Total']['BlendedCost']['Amount']}{result['Total']['BlendedCost']['Unit']}"
)
res_list_str = "\n\n".join(res_list)
params = {
"TopicArn": sns_topic_arn,
"Subject": "Lambda(python) 今日のCost通知",
"Message": "今日までのコストは以下のようになっています。\n\n" + res_list_str,
}
# SNS でメール配信
sns_client = boto3.client("sns")
sns_client.publish(**params)
return {"statusCode": 200}
解説1
期間 (TimePeriod)・集計粒度 (Granularity)・計算方法 (Metrics)を指定しています。
計算方法の指定には以下が使えます。
-AmortizedCost - BlendedCost - NetAmortizedCost - NetUnblendedCost - NormalizedUsageAmount - UnblendedCost - UsageQuantity
私の場合は今月支払う金額が知りたいので、BlendedCost
を選択しています。
Savings Plans などを使っていて実際は今日までにどれくらいの価値を使っているかを知りたい場合は AmortizedCost
を使う方が良いかもしれません。
参考:
Understanding your AWS Cost Datasets: A Cheat Sheet
GetCostAndUsage
解説2
Cost Explorer のレスポンスですが以下のようになっています。(一部省略)
{
"ResultsByTime": [
{
"TimePeriod": {
"Start": "2023-01-01",
"End": "2023-01-31"
},
"Total": {
"BlendedCost": {
"Amount": "123.45",
"Unit": "USD"
}
},
"Groups": [],
"Estimated": True
}
]
}
ResultsByTime
結果が格納されます。今回は Granularity で MONTHLY
を指定したので月毎に出てきます。
今回のスクリプトでは1ヶ月以内を指定していますので、基本的に1つしか入ってこないと思いますが、念の為 for で回すことでメールで異変に気づけることとしました。
TimePeriod
ここにはリクエストで投げた Start
と End
の値が入ります。2ヶ月以上に跨った場合でも全データに同じ値が入るので注意です。
Estimated
推定値かどうかが示されます。今回のスクリプトではこれから払う金額についてですので True が入ります。すでに精算が終わっている過去の月を取得すれば False が返ってきます。