今回は従量課金制のAWSで、自分の構築したシステム分だけ料金を把握したい。そういった場合にタグを活用して利用料金を把握した内容についてご紹介させていただきます。
背景
2022/3 NoSQL Workbenchで始めるDynamoDB設計で少し触れたのですが、本ブログはDynamoDBに移行しました。
元々はLightsail1台のみでしたので料金は把握しやすかったのですが、DynamoDBに続き今後S3やLambdaなどを組み合わせ脱Lightsailし、サーバーレス化したいと思っています。
本サイトは夜間や休日はアクセスも少なく、見積もってみたところ現在のLightsail代よりも安くなりそうでした。
ただ、従量課金である上に利用しているAWSアカウントのDynamoDB、S3、Lambdaには他のシステムも構築されており月額でいくら掛かっているのか把握しにくと感じたことが背景です。
実現概要
各DynamoDBやS3に「同じタグ」を設定することで、自分の把握したいDynamoDBやS3分だけ料金を把握できます。
だいたいのAWSのシステムにはタグ項目があります。
Lambdaだと環境変数いうアプリ内で使う項目あり混同しやすいですが、画像のように大体あります。
AWSで毎月いくらかかっているのかなどは、AWSコンソールのCostExploreから確認することができます。
しかし、毎月コンソールにアクセスするのは手間なので、月初にEventBridge経緯でCRON的にLambdaを動かし、料金を取得&Slackに通知するようにしました。
コスト配分タグ
まず、料金把握したいタグで計測できるように、タグをAWS管理画面から有効にします。
AWSの請求のコスト配分タグ画面で、ユーザー定義のコスト配分タグから計測したいタグ名を有効にします。
また、有効にする前の期間の料金までは把握できません。
さらに、今回設定したようなユーザ定義のコスト配分タグは1日程度しないと、現れなかったのでご注意ください。
通知
まず、Lambdaのコードは以下のようにしました。Node.js(TypeScript)になります。
const AWS = require('aws-sdk');
const costexplorer = new AWS.CostExplorer({region: 'us-east-1'});
const https = require('https');
import { URLSearchParams } from "url";
exports.handler = async () => {
const data = await getCostAndUsage();
if(data){
const blendedCost = Math.ceil(Number(data.BlendedCost.Amount) * 100) / 100;
const unblendedCost = Math.ceil(Number(data.UnblendedCost.Amount) * 100) / 100;
await postSlack(先月のBlogCMS料金、ブレンドコスト $${blendedCost}、非ブレンドコスト $${unblendedCost}です
);
}
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
// 先月のTimePeriod生成処理
const createLastMonthTimePeriod = ()=>{
const now = new Date();
// 先月
const last = new Date(now.getFullYear(), now.getMonth() - 1, 1);
return {
Start: ${last.getFullYear()}-${String(last.getMonth() + 1).padStart(2, '0')}-01
,
End: ${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01
,
}
}
// 利用料取得処理
const getCostAndUsage = ():Promise<any>=>{
return new Promise((resolve, reject)=>{
const params = {
Granularity: 'MONTHLY',
Metrics: [
'BlendedCost',
'UnblendedCost'
],
TimePeriod: createLastMonthTimePeriod(),
Filter: {
Tags: {
Key: 'COST',
Values: [ process.env['COST_TAG'] ]
}
},
};
// 取得
costexplorer.getCostAndUsage(params, function(err:any, data:any) {
if(err){
console.log(err, err.stack);
return resolve(null);
}
resolve(data.ResultsByTime[0].Total);
});
});
}
// Slack通知
const postSlack = (message:string)=>{
return new Promise((resolve, reject)=>{
const formData = {
token: 'xxxxxxxxxx',
channel: 'XXXXXXXXXXX',
text: message,
};
const options = {
hostname: 'slack.com',
path: '/api/chat.postMessage',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
};
const req = https.request(options, function(res:any) {
res.on('end', function() {
resolve(null);
});
});
req.write(new URLSearchParams(formData).toString());
req.on('error', function(e:any) {
console.log("Slackにメッセージを送信できませんでしたn" + e.message);
resolve(null);
});
req.end();
});
}
タグkeyは「COST」固定で、タグ値はこの通知Lambdaの環境変数COST_TAGに設定された値(前章の画像で言うとBLOG_CMS)を元に使った料金をCostExplorer APIで取得する仕組みです。
CostExplorer APIを利用していますので、本Lambdaへの権限設定も必要になりますのでご注意ください。
また、料金計算はUTC起点のようでしたので、JST10:00ぐらいにEventBridge経由で実行するようにしました。
終わりに
今回は1ヶ月単位の料金把握でしたが、個人アカウントでAWS破産が心配という方は1日単位でチェックするのも良いと思います。
また、各Lambdaやバケット1つ1つにタグを設定するのが手間だという方はCDKもおすすめです。
Stackに共通タグを設定しておくと、管理下のシステムに自動でタグをつけてくれます。
以上、従量課金がなんとなく不安だと感じられる方の参考になればと思います。