% cat

AWSのコストを監視する仕組みを実装したお話

こんにちは、ベーシックのDevOpsエンジニア 奥山です。
普段は主に弊社が運営しているサービス ferret One の開発・保守の他、インフラ周りの保守運用をやっています。

今回は私がDevOps業務の一つとして行っているAWSのコスト監視についてご紹介します。

コストを監視する仕組みを導入した背景

AWSのコストは何らかの要因で急激に増加することがあります。それはサービスのインフラ周りの変更や、急激なアクセス増加が要因だったりします。

その時にできるだけ早く異変に気づける仕組みが必要だったのと、コストの無駄を詳細に分析できる体制が必要だったため、ベーシックではコストを監視する仕組みを導入しました。

コストを監視する流れ

AWSにはもともと請求アラート機能やコストを分析できるコストエクスプローラーという機能がありますが、通知や分析を柔軟に行えるようにするためベーシックでは独自にコスト監視の仕組みを実装しています。
全体の構成は下記のようになっています。

まずはコストをサービスや環境ごとに振り分けるため、事前にAWSの各リソースにタグを付与します。

タグエディターを使うと複数リソースのタグを一括で編集できて便利です。タグエディターはAWSコンソールの上部メニューからリソースグループ > タグエディターで開けます。

AWSのコンソール画面にある請求ダッシュボードで請求レポートを受け取るよう設定すると、定期的にS3の指定したバケットに請求データが出力されます。

毎朝cronでバッチを実行してS3のバケットに出力された請求データを集計します。
バッチスクリプトはRubyで実装していて、以下はその抜粋です。

# S3のバケットからから請求データをダウンロード
csv_file = "#{aws_acount_id}-aws-cost-allocation-#{yyyy_mm}.csv";
s3 = AWS::S3.new
bucket = s3.buckets['bucket-name']
o = bucket.objects[csv_file]
File.open(@path+"/input/tmp", "w") do |f|
  o.read do |chunk|
    f.write chunk
  end
end
File.open(@path+"/input/"+csv_file, "w") do |csv|
  File.open(@path+"/input/tmp", 'r:utf-8') do |f|
    f.each_line do |line|
      next if /^"Don't see your tags/ =~ line
      next if /^""/ =~ line
      next if /InvoiceTotal/ =~ line
      csv.write line
    end
  end
end

# リソースに付与したタグに従ってコストを振り分ける
csv_data = CSV.read(@path+"/input/"+csv_file, headers: true)
file = File.open(@path+"/output/analyze_#{ym}.tsv", 'w')
output = "Service\tProductCode\tUsageType\tCostBeforeTax\tuser:Name\tuser:Service\n"
file.write(output)
csv_data.each do |data|
  if data["CostBeforeTax"].to_f == 0 || !data['ProductCode']
    next
  end
  if data['user:Service'].include?("hoge")
    service = "hoge"
  elsif
    # 〜〜省略〜〜
  end
  output = "#{service}\t#{data['ProductCode']}\t#{data['UsageType']}\t#{data['CostBeforeTax']}\t#{data['user:Name']}\t#{data['user:Service']}\n"
  file.write(output)
end

以下は前日比と月間の着地予測を計算するロジックです。

lastday = Date.new(target_year), target_month, -1)
days = (Date.today - Date.parse("#{target_year}-#{target_month}-01"))

# 前日比
if prev_cost
  if cost_current > prev_cost
    cost_oneday = cost_current - prev_cost
  else
    cost_oneday = 0
  end
else
  cost_oneday = cost_current
end

# 着地予測
cost_avg = cost_current / days
cost_prognosis = cost_avg * (lastday.day + AJUST_MONTH_COST_DAYS)
cost_prognosis = cost_current if cost_prognosis < cost_current

バッチで集計した結果は以下の3つに連携しています。

Slack

毎朝、前日にかかったコストをSlackで通知しコストに異常がないかを確認します。突然コストが跳ね上がる時がありヒヤッとしますが、次のスプレットシート等で急増しているコストを分析してすぐ対策するようにしています。また、当月の着地予想も通知して、月間コストが予算内に収まるかを常に監視するようにしています。

以下はSlack通知の例です。

Googleスプレッドシート

コストの詳細な内訳をGoogleスプレッドシートに書き出します。スプレッドシートはコストの分析の他、月ごとにシートを分けて出力しているので、前月データとの比較や、コストレポートの記録としても利用しています。

Prometheus+Grafana

監視ツールのPrometheusとデータ可視化ツールのGrafanaを組み合わせてAWSのコストをグラフ等で可視化しています。Prometheus+Grafanaという組み合わせは非常に強力で、弊社ではAWSのコストだけでなく、サーバ監視など様々なメトリクスの監視に利用しています。

終わりに

コストを監視したりコストの無駄を探す作業はとても地味ですが、それは経費を削減して会社の利益に貢献することにつながります。また私の場合、このコストレポートができたことで、サービスのインフラを考える時や何かを実装する時に常にコストを意識できるようになりました。

コストの分析を自動化したり、日次ではなく時間毎にチェックしたりなど、まだ改善できることがあるので、今後もサービスの成長のためコストの監視周りを改善させていききます!

最新記事

「良いプロダクト」とは、どんなプロダクトのことでしょうか?

こんにちは! プロダクトオーナー兼開発部マネージャーをしている長谷川([@roki1801](https://github.com/roki1801))です。普段は山形県山形市にあるベーシックのサテライトオフィス「[山形ラボ](...

roki1801
2019年03月29日

大量アクセスに耐え得る在庫管理システムの構成を考え実装してみた

皆さん「在庫管理」ってどうしてます?itemsテーブルに、stockカラム作ってdecrementしてますか? まぁ正直、それでも良い感じしますよね。楽だし何やってるかわかりやすい。 しかし! **超人気商品に超ア...

mmusasabi
2019年03月13日

kubernetes で Ruby on Rails を動かして kubern...

巷で話題の kubernetes ですが、とってもとってもとっつきにくいですよね そんな kubernetes ですが手元で動かすことができたので解説してみます (情報が間違ってたらごめんなさい! 🙇) 目標はこちら ...

tkhr0
2019年03月07日