[小ネタ]もっと請求データを監視したい

2015.07.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

藤本です。 前職では業務でのAWS経験はありませんでしたが、 プライベートで検証用途としてAWSを利用していました。 AWSは無料利用枠もあり、その中でも多くのことを検証でき、非常に助けられました。

そんな中、ある日気づいたら1万円弱の課金がされていました。

その時思った、請求データをウォッチしたい!

もちろん請求データはCloudWatchで監視可能です。 しかも、いくつかポイントを設けることで小刻みに通知することも可能です。

ただ私のようにプライベートで可能な限り無料枠で検証したい方もいらっしゃるのではないでしょうか? またポイントを小刻みで設定するのが面倒で課金の動きを出来るだけ敏感に感じ取りたいという方もいらっしゃるのではないでしょうか?

実装します。

今回は以前から私が興味を持っていたがJavascriptを書けないために手を出すのを保留していたLambdaを使って実装します。

概要

一定間隔でクライアントからLambdaを発火 ↓ CloudWatchから現在の課金データと過去の課金データを比較 ↓ 差分があれば、SNSトピックからメール送信 差分がなければ、何もせず終了

実装

CloudWatchによる請求データの監視は以下のブログ記事をご参照ください。 支払アカウントからリンクアカウントのBilling Alertを設定する

Lambdaの実装方法は以下のブログ記事をご参照ください。 Lambda Function が Java で書けるようになりました!

S3イベントによる発火の設定方法は以下のブログ記事をご参照ください。 AWS Lambdaを始めてみる(2).Amazon S3イベントを扱う

発火はクライアント端末からS3にファイルをPUTすることでLambdaファンクションが実行されます。

またLambdaファンクションのコードは以下となります。

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Date;

import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsRequest;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsResult;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.PublishRequest;

public class LambdaFunctionHandler implements RequestHandler<S3Event, Object> {

@Override
public Object handleRequest(S3Event input, Context context) {

String topicArn = "arn:aws:sns:ap-northeast-1:********:******";
Integer hours = 30;
Integer period = 864000;

AmazonCloudWatchClient client = new AmazonCloudWatchClient();

client.setEndpoint("monitoring.us-east-1.amazonaws.com");

GetMetricStatisticsRequest request = new GetMetricStatisticsRequest();
request.setNamespace("AWS/Billing");
request.setMetricName("EstimatedCharges");
request.setStatistics(Arrays.asList("Maximum"));
request.setStartTime(toDate(LocalDateTime.now().minusHours(hours)));
request.setEndTime(toDate(LocalDateTime.now()));
request.setPeriod(period);
Dimension dm = new Dimension().withName("Currency").withValue("USD");
request.setDimensions(Arrays.asList(dm));
GetMetricStatisticsResult metrics = client.getMetricStatistics(request);

double cost = metrics.getDatapoints().stream()
.sorted((a, b) -> (int) (a.getTimestamp().getTime() - b.getTimestamp().getTime()))
.mapToDouble(d -> d.getMaximum())
.reduce((a, b) -> b - a).getAsDouble();

if (cost <= 0)
return null;

AmazonSNSClient snsClient = new AmazonSNSClient();
snsClient.setEndpoint("sns.ap-northeast-1.amazonaws.com");
PublishRequest publishRequest = new PublishRequest(topicArn, "changed cost : $" + String.valueOf(cost));
snsClient.publish(publishRequest);

return null;
}

private Date toDate(LocalDateTime time) {
return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
}
}

1日前の請求のデータポイントと比較して、 データが増えていた時にSNSトピックに対してPublishするように実装しました。 取得開始日時や期間を調整することで間隔を広げたり、狭めたりすることも可能です。 これで知らぬ間に課金されている恐怖に怯えない日々を送れるのではないでしょうか?

まとめ

この仕組みには大きな課題があります。 発火のトリガを引いてくれるクライアントが必要なことです。

トリガをS3 PUTで設定しているため、定期的にS3にPUTするクライアントを準備しなければいけません。

専用でEC2立ててcronで定期的に発火させるとそもそもの課金を最低限としたい目的に反するし、 クライアント端末から定期的に発火させるとクライアントを落としている時のデータが取れません。

ただJavaが解禁されたことによりLambdaに手を出したいと思われている方は多いのではないでしょうか? 今回のようにLambdaを利用すれば、 コードだけ実装すれば、コードが動くインフラやインプット/アウトプットをAWSが用意してくれます。 エンジニアがコードを書くことに専念できる、というクラウドによってもたらされるメリットを享受できるのではないでしょうか。