きっかけ
別件のお試しで初めて Amazon OpenSearch に触れて、複数のPowerPointの内容を検索するシステムを構築しようとしていました。その過程でコストを抑えたくてCopilotに相談したところ、OpenSearch Serverlessの利用を提案されました。
だけど、実際には OpenSearch Serverless は最低限の検索・インデックス容量(OCU)が常時確保され、常時課金で、PoCとしては逆にコストが非常にかかる結果となりました。
これに気が付いたのが OpenSearch Serverless の collection を作ってから6日後のこと。
時すでに遅しで、$90もの料金が発生してしまっていました。。。

こんなことがまた起きないようにAWSのコストを毎日Teamsに通知する仕組みを作ることにしました。
アーキテクチャ
以下のようなアーキテクチャでTeamsに通知します。
1.AWS Cost Explorer APIで毎日の利用料金を取得
・リージョン別の料金を取得(※)するため、GetCostAndUsage APIを利用。
・集計粒度は「日次」、グループ化は「リージョン」。
※このAWSは私が管理している部内のAWSアカウントであり、
利用者はリージョンごとに区分けされているため、リージョン別の料金を取得します。
2.AWS Lambdaでスケジュール実行
・Amazon EventBridgeで毎日(例:午前9時)トリガー。
・LambdaがCost Explorer APIを呼び出し、結果を整形。
3.メール通知
・Amazon SNS(Simple Notification Service)を使って、メールで通知することにしました。
→当初は Teams Webhook を想定していましたが、個人向け通知で十分だったため、SNSのメール通知に変更しました。
SNSトピック作成
メール通知するためのSNSトピックをまず作成します。
- AWS マネジメントコンソール → Amazon SNS を開く
- 左ペイン「トピック」 → 「トピックの作成」
- タイプ:スタンダード → 「トピックの作成」
SNS サブスクリプション作成
次に送信手段であるサブスクリプションを作成します。
- 左ペイン「サブスクリプション」 → 「サブスクリプションの作成」
- 「トピック ARN」で先ほど作成したトピックを選択
- 「プロトコル」に「E メール」を選択
- 「エンドポイント」に送付先のメールアドレスを入力 → 「サブスクリプションの作成」
エンドポイントで指定したメールアドレスに「AWS Notification - Subscription Confirmation」のタイトルでメールが届きます。
メール内のリンクの「Confirm subscription」をクリックします。
自動で配信停止される場合があります!
会社等でメールサーバにスパムフィルターがある場合、「Confirm subscription」をクリックしても、自動配信停止が走ります!
その場合は、サブスクリプションを作成後、AWS CLIから以下のように subscribe してください。
aws sns confirm-subscription --region ap-northeast-1 --topic-arn arn:aws:sns:ap-northeast-1:YOUR_ACCOUNT_ID:YOUR_TOPIC_NAME --token (メールに記載のトークン) --authenticate-on-unsubscribe true
Lambda実行用 IAM ロール作成
CostExploereは有効化されていたので、そのステップは省略してまずLambdaを実行するIAMロールを作成します。
- AWS マネジメントコンソール → IAM を開く
- 左ペイン 「ロール」 → 「ロールを作成」
- 「信頼されたエンティティの種類」:AWS のサービス
- ユースケースの選択:Lambda を選択して 「次へ」
- アクセス権限の追加:ここでは一旦スキップ(後でカスタムポリシーを付与します)
もしくは CloudWatchLogs 標準ポリシー(AWSLambdaBasicExecutionRole)だけ先に付けてもOK - ロール名:lambda-cost-report-role(任意)
タグ(任意)を設定 → 「ロールを作成」
更に、インラインポリシーで CostExploere の読み取り権限と SNSの権限を付与します。
- ロール詳細画面 → 「アクセス権限」タブ → 「インラインポリシーを追加」
- JSON タブを選択し、下記を貼り付け → 「保存」(ポリシー名例:CostExplorerAndSNSPolicy)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CostExplorerReadDaily",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage"
],
"Resource": "*"
},
{
"Sid": "PublishToSpecificSNSTopic",
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "arn:aws:sns:ap-northeast-1:YOUR_ACCOUNT_ID:YOUR_TOPIC_NAME"
}
]
}
※ Cost Explorer はリソースレベル制御ができないため "Resource": "*" になります
EventBridgeからLambda関数を実行する IAMロール作成
EventBridge の「スケジュールされたルール(レガシー)」から Lambda を実行する場合、
Lambda 側のリソースポリシーだけでは実行できないケースがあります。
これは、EventBridge が Lambda を呼び出す際に
「どの権限で実行するか」を示す IAM ロールが必要になるためです。
そのため、EventBridge 専用の実行ロールを作成し、
スケジュール設定時に指定します。
- IAM コンソール → ロール → ロールを作成
- 「信頼されたエンティティの種類」:カスタム信頼ポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
3.「許可ポリシー」:AWSLambdaRole → 「次へ」
4. 「ロール名」:EventBridgeLambdaInvokeRole
Lambda関数作成
次に CostExploere API を呼び出して、SNSでメール通知する Lambda関数を作成します。
ランタイムは Python 3.14、実行ロールは先ほど作成したIAMロールを指定します。
import boto3
import datetime
import os
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
TOPIC_ARN = os.environ.get("TOPIC_ARN", "arn:aws:sns:ap-northeast-1:YOUR_ACCOUNT_ID:YOUR_TOPIC_NAME")
def lambda_handler(event, context):
# JSTで前日を求め、Cost Explorerに渡す期間はUTC日付(Endは翌日:排他的)
jst = datetime.timezone(datetime.timedelta(hours=9))
today_jst = datetime.datetime.now(jst).date()
yesterday_jst = today_jst - datetime.timedelta(days=1)
start = yesterday_jst.strftime('%Y-%m-%d') # 例: 2026-01-13
end = today_jst.strftime('%Y-%m-%d') # 例: 2026-01-14(排他的)
logger.info(f"Querying Cost Explorer from {start} to {end}")
# Cost Explorer は us-east-1
ce = boto3.client('ce', region_name='us-east-1')
try:
resp = ce.get_cost_and_usage(
TimePeriod={'Start': start, 'End': end},
Granularity='DAILY',
Metrics=['UnblendedCost'],
GroupBy=[{'Type': 'DIMENSION', 'Key': 'REGION'}]
)
except Exception as e:
logger.exception(f"Cost Explorer API failed: {e}")
raise
results = resp.get('ResultsByTime', [])
if not results:
logger.warning("No ResultsByTime returned")
cost_data = []
total = 0.0
else:
groups = results[0].get('Groups', [])
cost_data = []
total = 0.0
for g in groups:
region = g['Keys'][0]
amount = float(g['Metrics']['UnblendedCost']['Amount'])
cost_data.append((region, amount))
total += amount
# メッセージ整形
lines = [f"AWSコストレポート({yesterday_jst.isoformat()})"]
if cost_data:
for region, amt in sorted(cost_data, key=lambda x: x[0]):
lines.append(f"- {region}: ${amt:.2f}")
lines.append(f"合計: ${total:.2f}")
else:
lines.append("データがありません(前日データが未確定の可能性)")
message = "\n".join(lines)
logger.info("Message to publish:\n" + message)
# SNS クライアントはトピックのリージョンに合わせる
sns = boto3.client('sns', region_name='ap-northeast-1')
try:
sns.publish(
TopicArn=TOPIC_ARN,
Message=message,
Subject='AWS Daily Cost Report'
)
except Exception as e:
logger.exception(f"SNS publish failed: {e}")
raise
return {"status": "ok", "published_to": TOPIC_ARN, "total": round(total, 2)}
環境変数にSNSのARNを設定します。
TOPIC_ARN=arn:aws:sns:ap-northeast-1:YOUR_ACCOUNT_ID:YOUR_TOPIC_NAME
イベントスケジュール作成
最後に、毎朝9時にメール通知されるように EventBridge でスケジュールを作成します。
-
AWS マネジメントコンソール → EventBridge を開く
-
「名前」:daily-cost-report-9am → 「次へ」
-
「スケジュールパターン」:「特定の時刻 (毎月第 1 月曜日の午前 8 時 (PST) など) に実行されるきめ細かいスケジュール。」
-
「Cron 式」:
cron(0 0 * * ? *)→ 「次へ」※ EventBridge の Cron は UTC 基準です。
cron(0 0 * * ? *) は JST 9:00 に相当します。 -
「ターゲット 1」「ターゲットタイプ」:「AWS のサービス」「Lambda 関数」
-
「関数」:先ほど作成したLambda関数
-
「許可」:実行ロールを使用 (推奨)にチェックを入れる
-
「実行ロール」:「既存のロールを使用」 → EventBridgeLambdaInvokeRole → 「次へ」
-
タグ(任意)を設定 → 「ロールを作成」
メール本文
実際に動かしてみると、以下のようにコストレポートが届きます。
AWSコストレポート(2026-01-13)
- NoRegion: $-0.00
- ap-northeast-1: $9.32
- ap-northeast-2: $0.00
- ap-south-1: $0.00
- ap-southeast-1: $0.03
- ap-southeast-2: $1.99
- eu-west-1: $0.00
- eu-west-3: $0.00
- us-east-1: $0.05
- us-west-2: $0.01
合計: $11.40
最後に
OpenSearch Serverless は「Serverless」という名前から
使った分だけ課金される=PoC向き だと思い込んでいましたが、
実際には最低限の OCU が常時確保され、利用が少なくても課金され続けます。
今回のように
「とりあえず触ってみる」「数日放置する」
といった使い方をすると、想定以上のコストが発生する可能性があります。
その反省から、
「毎日コストを自分の目で確認できる仕組み」 を作ることにしました。
仕組み自体はシンプルですが、
・Cost Explorer は us-east-1 固定
・SNS サブスクリプションの自動配信停止
など、実際にやってみないと分からない落とし穴も多かったです。
同じように PoC や検証用途で AWS を使っている方は、
CloudWatch アラームや予算アラートに加えて、
日次でのコスト通知を一度自作してみる のも良い勉強になると思います。
この記事が、
「Serverless=安いと思って油断した結果、請求額を見て青ざめる」
人を一人でも減らせたら幸いです。
追記(2026/01/15)
EventBridge のスケジュールされたルールから Lambdaを呼び出すIAMロールが必要でしたので、手順を追加しました。

