AWSLab

AWSの利用額を毎朝メールで受け取る【CloudFormationで組む】

スポンサーラベル
AWSの利用額を毎朝メールで受け取る構成。EventBridge SchedulerがLambdaを起動し、Cost Explorerから取得した金額をSNS経由でメール送信する AWS

当サイトはアフィリエイト広告を利用しています。

検証用のEC2を1台動かしているだけでも、月末に請求を見て「思ったよりかかっている」となることがあります。

気づくのが遅いのが問題です。 消し忘れたリソースは、気づくまで課金され続けます。

そこで「前日までの利用額を毎朝メールで送る」仕組みを作りました。CloudFormation のテンプレート1枚で、かかるのは月30円程度です。

AWSの利用額を毎朝メールで受け取る構成。EventBridge SchedulerがLambdaを起動し、Cost Explorerから取得した金額をSNS経由でメール送信する

1. 標準機能はAPIから作れない

先に確認しておくと、AWS Budgets には「レポート」という機能があります。 日次・週次・月次で請求サマリをメール送信するもので、まさにやりたいことそのものです。

ところが、この機能はAPIに用意されていません。

# budgets クライアントの操作一覧
CreateBudget / CreateNotification / CreateSubscriber
DeleteBudget / UpdateBudget ...

予算そのものやアラートは作れますが、レポートの作成に相当するAPIがありません。CloudFormation にも AWS::Budgets::Budget はありますが、レポートのリソースはありません。

つまりコンソールでの手作業になります。 それでよければ5分で終わる話です。

私は毎回手で作るのが嫌だったので、自分で組むことにしました。内訳の出し方を自由に決められるのも理由です。

2. 構成

4つのサービスをつなぐだけです。

EventBridge Scheduler(毎朝8:00 JST)
  → Lambda(集計して本文を作る)
      → Cost Explorer(日次・サービス別で取得)
      → SNS(メール送信)

ここで最初につまずきました。置き場所です。

3. なぜ us-east-1 に置くのか

Cost Explorer の API エンドポイントは us-east-1 にしかありません。

東京リージョンに Lambda を置く場合、クライアント生成時に region_name="us-east-1" を明示すれば呼べます。ただしスタックを分けると管理が面倒になるので、関連リソースをまとめて us-east-1 に置きました。

SNS も EventBridge Scheduler も、どのリージョンでも同じように動きます。メールが届くリージョンが変わるわけではありません。

東京に慣れていると忘れがちなので、テンプレートの先頭にコメントで書いています。

# 【なぜ us-east-1 に置くか】
# Cost Explorer の API エンドポイントは us-east-1 にしか無い。

4. Cost Explorer の呼び方

ここが設計の肝でした。このAPIは1リクエストあたり $0.01 かかります。

無料ではないので、呼ぶ回数を減らすことを考えます。毎日実行するなら月30回で $0.30(約45円)です。

4.1 Endは排他。未来日を渡してよい

期間指定は TimePeriod で行いますが、End は排他(その日を含まない)です。

# End に today を渡すと、前日までが入る
TimePeriod={"Start": str(start), "End": str(today)}

「昨日まで」が欲しいので、End には今日を渡します。

そして重要なのが、未来日を渡してもエラーにならないことです。手元で確認しました。

# 今日が 9/13 のとき、End に来月1日(10/01)を指定
TimePeriod={'Start': '2026-09-01', 'End': '2026-10-01'}
# → period: {'Start': '2026-09-01', 'End': '2026-10-01'} が普通に返る

これが分かると月初の実行で落ちなくなります。 「今月1日〜今日」という指定は、1日に実行すると Start == End でエラーになりますが、End を先に倒しておけば回避できます。

4.2 日次でまとめて取って、自分で集計する

「今月分」と「直近30日分」の両方を出したくなりました。素直に書くと2回呼ぶことになり、$0.02 かかります。

そこで、日次(DAILY)で35日分を一度に取得して、集計はローカルでやります。

res = ce.get_cost_and_usage(
    TimePeriod={"Start": str(start), "End": str(today)},
    Granularity="DAILY",                       # 日ごとに分かれて返る
    Metrics=["UnblendedCost"],
    GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
)

返ってきた日ごとのデータを1回なめれば、前日・今月・直近30日を同時に数えられます。

for p in periods:
    d = datetime.date.fromisoformat(p["TimePeriod"]["Start"])
    for g in p["Groups"]:
        v = float(g["Metrics"]["UnblendedCost"]["Amount"])
        if d == yday:
            y_total += v          # 前日
        if d >= m_first:
            m_total += v          # 今月
        if d >= w_first:
            w_total += v          # 直近30日

1リクエストで3種類の集計が取れます。 粒度を細かくしても料金は変わらないので、取れるものは細かく取って自分でまとめるほうが得です。

なお NextPageToken が返る場合はページングが必要で、そのリクエストにも課金されます。 35日×サービス別なら通常1ページで収まりますが、対応は入れてあります。

5. できあがるメール

こういう本文が届きます。

AWS 利用額(2026-09-12 まで)

前日      2026-09-12                   6.14 USD
今月      2026-09-01 〜 2026-09-12     25.53 USD
直近30日  2026-08-14 〜 2026-09-12     79.07 USD

内訳(直近30日・上位8)
      31.20  Amazon Elastic Compute Cloud - Compute
      12.45  Claude Sonnet 4.5 (Amazon Bedrock Edition)
       9.80  EC2 - Other
       7.15  Amazon Virtual Private Cloud
       ...

「前日いくら使ったか」が毎朝わかるのが効きます。 昨日が普段より高ければ、消し忘れか何かが動いています。月末を待つ必要がありません。

件名にも金額を入れてあるので、メールを開かずに一覧で判断できます

[AWS] 2026-09-12 まで 今月 25.53 USD

6. SNSのメール購読でつまずいた

ここが一番手間取りました。作った直後は1通も届きません。

6.1 確認リンクを踏むまで届かない

SNS のメール購読は、登録すると確認メールが飛びます。本文の Confirm subscription を開くまで、通知は一切配信されません。

差出人: no-reply(sns.amazonaws.com)
件名  : AWS Notification - Subscription Confirmation

CloudFormation は購読が未確認でも「作成完了」になります。 スタックは緑になるのに何も届かない、という状態になります。

そしてこの確認メールは、かなりの確率で迷惑メールに入ります。 私の場合も Gmail の迷惑メールフォルダにありました。from:sns.amazonaws.com で検索すると、フォルダをまたいで見つかります。

本番の通知メールも同じ扱いを受けるので、確認が終わったら「迷惑メールではない」に設定しておくのが大事です。

6.2 一覧APIの状態を信じない

もうひとつ引っかかりました。購読状態を確認しようとして、こう書きました。

for s in sns.list_subscriptions_by_topic(TopicArn=topic)["Subscriptions"]:
    print(s["Endpoint"], s["SubscriptionArn"])
(登録したアドレス) -> Deleted

Deleted と出ました。 壊れたと判断して作り直しましたが、これは反映が遅れていただけでした。

ARN を指定して直接問い合わせると、正しい状態が返ります。

a = sns.get_subscription_attributes(SubscriptionArn=arn)["Attributes"]
print(a["PendingConfirmation"])   # "false" = 確認済みで有効

list_ 系は結果整合性です。 直前に変更した状態を確認するときは、個別の get_ を使うほうが確実でした。

6.3 手でsubscribeしない

CloudFormation が管理している購読に対して、確認メールを再送しようとして sns.subscribe() を手で叩きました。これは失敗でした。

管理外からの操作なので、CloudFormation が持っている状態と食い違います。再送したいときは、リソースの論理IDを変えてスタックを更新するのが確実です。削除+作成が走って、新しい確認メールが飛びます。

  # Subscription から EmailSubscription に変えると作り直される
  EmailSubscription:
    Type: AWS::SNS::Subscription

7. 料金

実際にかかるのはこれだけです。

サービス費用
Cost Explorer API$0.01 × 30回 = $0.30/月
Lambda無料枠内(月30回・数百ミリ秒)
SNS(メール)無料枠内(月1,000通まで)
EventBridge Scheduler無料枠内(月1,400万回まで)

月45円程度です。 検証用のEC2を1日消し忘れると数百円かかることを考えれば、十分に元が取れます。

頻度を減らしたければ、パラメータで変えられます。

# 毎週月曜だけにする
--schedule "cron(0 9 ? * MON *)"

8. 作り方

テンプレートを適用するだけです。

python aws/deploy_billing_report.py --plan  --email 宛先アドレス
python aws/deploy_billing_report.py --apply --email 宛先アドレス

AWS CLI ではなく boto3 を使っています。 CLI に --template-body file:// で UTF-8 のテンプレートを渡すと、スタックに保存される日本語コメントが全部 ???? に化けます。エラーが出ないので気づきにくい落とし穴です。

作成されるのは6リソースです。

Add  AWS::SNS::Topic             Topic
Add  AWS::SNS::Subscription      EmailSubscription
Add  AWS::IAM::Role              FunctionRole
Add  AWS::Lambda::Function       Function
Add  AWS::IAM::Role              SchedulerRole
Add  AWS::Scheduler::Schedule    Schedule

IAMロールは対象を絞ってあります。Scheduler が呼べるのはこの関数だけです。

- Effect: Allow
  Action: lambda:InvokeFunction
  Resource: !GetAtt Function.Arn

Cost Explorer だけはリソース単位の指定ができないので * になります。

待たずに動作を確認したいときは、直接実行します。

aws lambda invoke --function-name <関数名> --region us-east-1 out.json

9. まとめ

AWSの利用額を定期的にメールで受け取るときの要点です。

  • AWS Budgets のレポート機能はAPIから作れない。コンソール操作になる
  • 自分で組むなら EventBridge Scheduler → Lambda → SNS。Lambda は10行程度
  • Cost Explorer の API は us-east-1 にしかない。スタックごとそちらに置く
  • 1リクエスト $0.01。日次で細かく取って、集計は自分でやるほうが安い
  • TimePeriodEnd は排他today を渡すと前日までが入る
  • 未来日を渡してもエラーにならない。月初の実行で落ちなくなる
  • SNSのメール購読は確認リンクを踏むまで1通も届かない。しかも迷惑メールに入る
  • list_subscriptions_by_topic の状態は遅れる。 個別の get_subscription_attributes を見る
  • CloudFormation 管理下の購読に手で subscribe しない。論理IDを変えて作り直す

一番効いたのは、「前日いくら使ったか」が毎朝わかることでした。月次の合計だけ見ていると、増えた日を特定できません。 日単位で見えると、「この日に何かした」がすぐ分かります。

検証環境を持っているなら、作った日に一緒に入れておくのがおすすめです。EC2の自動停止と組み合わせると、消し忘れの被害がかなり小さくなります。

AWSを効率的に学習する方法

私が効率的にAWSを学習するために実施した方法は以下の通りです。
 ①最初に書籍(ハンズオンができる)を購入、座学でAWSの基礎を学習
 ②AWS資格試験を取得ための学習

※私の場合は①と②を合わせて2か月でソリューションアーキテクトを取得できました。

①AWS基礎学習

最初に購入した書籍は「Amazon Web Services 基礎からのネットワーク&サーバー構築」です。

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂4版

この本では、AWSの基本サービスを利用したハンズオンを通じて、AWSの基礎を学習することができます。

また、タイトル通りAWSのネットワークやインフラに関しても網羅しているため、もともとインフラ系の技術者ではない人たちにとっても分かりやすい内容だと思います。

とりあえずAWS上にサーバーを設定して開発を行うための準備までするには最良の一冊です。

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂4版

②AWS資格取得

AWSの基礎をある程度学習することができたら、次はAWS資格を取得しましょう。まずはAWSソリューションアーキテクトを目指しましょう。

資格勉強のための問題集をひたすら解きながら、AWSの知識を積み重ねて習得していきましょう!

苦行ではありますがこの問題集を3周ほどすればAWS用語や構成に関しても習得できているはずです。

AWS認定ソリューションアーキテクト-アソシエイト問題集

独学だと手が止まってしまう場合は、ササエル のようなインフラエンジニア向けのオンラインスクールを使う手もあります。ネットワークやサーバーの分野に絞ったカリキュラムなので、業務でインフラを触る人が学び直すのにも使えます

あわせて読みたい

コメント

タイトルとURLをコピーしました