Carpe Diem

備忘録。https://github.com/jun06t

AWSのCost Explorerをスクレイピングして毎日の利用料金をslackへ通知

概要

使っていないインスタンスが起動しっぱなしだったせいで、AWSの利用料がいつの間にか大きくなっていたことがきっかけです。
一方毎日CostExplorerを見に行くのも手間が多いので、slackに通知するようにしたいと思い作ってみました。
また環境構築が面倒なので、docker化してどこでも実行できるようにしました。

成果物

今回作ったものはこちら。

github.com

環境

  • Ubuntu 16.04
  • chrominum 62.0.3202.94
  • chromedriver 2.33.506092
  • python3

前準備

BillingというAWSのManaged Policyを持ったIAMユーザを作っておいてください。
でないとログインしても情報を閲覧する権限がありません。

f:id:quoll00:20171203153037p:plain

説明

AWSの前日コストをSlackに通知してみた。概算でなくて正確な値で。 - Qiitaの記事をほぼ流用する形で実装しています。
一部環境変数化していたり、見たい情報が違ったのでその辺をいじったりしています。

importからchromeの準備

#!/usr/bin/python3
# coding: UTF-8

import os
import datetime
import slackweb
from time import sleep
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from collections import OrderedDict

# headless chrome
CHROME_BIN = "/usr/bin/chromium-browser"
CHROME_DRIVER = os.path.expanduser("/usr/bin/chromedriver")

options = Options()
options.binary_location = CHROME_BIN
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1280,3000")

driver = webdriver.Chrome(CHROME_DRIVER, chrome_options=options)

CHROME_BINCHROME_DRIVERは実行環境に依って異なります。
例えばmacOSであれば

CHROME_BIN = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

という感じになります。 options.add_argument("--no-sandbox")を入れているのは、dockerコンテナがrootから実行しているため、付けないと

Running as root without --no-sandbox is not supported

と怒られるためです。

ログイン

# login
print('login')
account_id = os.environ.get("ACCOUNT_ID")
username = os.environ.get("USERNAME")
password = os.environ.get("PASSWORD")
login_url = "https://%s.signin.aws.amazon.com/console" % account_id
driver.get(login_url)
driver.find_element_by_id('username').send_keys(username)
driver.find_element_by_id('password').send_keys(password)
driver.find_element_by_id('signin_button').click()
sleep(3)

環境変数を代入するようにしています。すぐ遷移できるわけではないので、sleep()を入れています。

ダッシュボード

print('move cost report dashboard')
driver.get("https://console.aws.amazon.com/cost-reports/home?#/savedReports")
sleep(3)

print('move saved report')
driver.find_element_by_link_text('Monthly costs by service').click()
sleep(3)

# set period
driver.find_element_by_xpath('//*[@class="picker-dropdown"]').click()

# target_at
target_date = os.getenv("TARGET_DATE", "1")
target_at = datetime.date.today() - datetime.timedelta(int(target_date))
target_at_str = target_at.strftime('%m/%d/%Y')

# from
elem = driver.find_element_by_xpath('//label[text()="From"]/following::input')
elem.clear()
elem.send_keys(target_at_str)
sleep(1)

# to
elem = driver.find_element_by_xpath('//label[text()="To"]/following::input')
elem.clear()
elem.send_keys(target_at_str)
sleep(1)

# apply
elem = driver.find_element_by_xpath('//div[text()="Apply"]').click()
sleep(1)

# change granularity
elem = driver.find_element_by_xpath('//granularity//div[@class="ui-dropdown"]').click()
elem = driver.find_element_by_link_text('Daily').click()
sleep(1)

xpathを使いながらゴリゴリ進んでいます。
要件が使ってるサービス毎の料金が分かるようにしたいだったので、デフォルトで用意されているダッシュボードの内Monthly costs by serviceというものを使うようにしています。
Webコンソールだと以下のように分かりやすく棒グラフで表示されます。

f:id:quoll00:20171203152802p:plain

要件として毎日の料金を取得したいというのがあったので、これをDaily単位で表示しています。

データの抽出とslack送信

# for slack report
slack_link = driver.current_url

# get cost
costs = OrderedDict()
for row in range(1, 7):
    title_xpath = '//div[@class="left-container"]//tr[%d]/td' % row
    title = driver.find_element_by_xpath(title_xpath).text
    value_xpath = '//div[contains(@class, "right-container")]//tr[%d]/td[1]' % row
    value = driver.find_element_by_xpath(value_xpath).text
    costs[title] = value

# post slack
webhook_url = os.environ.get("WEBHOOK_URL")
slack = slackweb.Slack(url=webhook_url)
# create message
text = target_at.strftime('%Y/%m/%d') + "\n"
text += "```\n"
for k, v in costs.items():
    text += k + ":\t" + v +"\n"
text += "```\n"
text += "<"+slack_link+"|cost explorer>"

slack.notify(text=text)

driver.quit()

Webコンソールだとデフォルトでコストがかかっている順にソートしてくれているので、Totalを含めた上位5件をループで取得するようにしています。
後はslackに送るようテキストを整形しています。

incoming webhookの用意

ググるといくらでも情報が出てくるのでそちらを参考に。。

Incoming Webhooks | Slack

動作検証

ビルドします。

$ docker build -t aws-cost-explorer .

実行します。

$ docker run --name cost_explorer \
 -e ACCOUNT_ID=wwwwwwwwww \
 -e USERNAME=xxxxxxxxxxxx \
 -e PASSWORD=yyyyyyyyyyyy \
 -e TARGET_DATE=1 \
 -e WEBHOOK_URL=https://hooks.slack.com/services/xxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzzzzzzzzzzzzz \
 aws-cost-explorer

README.mdにも書いてありますが、パラメータは以下の通りです。

環境変数
ACCOUNT_ID AWSのアカウントID
USERNAME Billingの権限持ったIAMユーザ
PASSWORD パスワード
TARGET_DATE 何日前のレポートにするか
WEBHOOK_URL slackに飛ばすwebhookのURL

すると以下の様に通知が飛んできます。

f:id:quoll00:20171203153455p:plain

リンク先にはCostExplorerのページがあります。

f:id:quoll00:20171203153800p:plain

週や月比較したい時はプルダウンをちょっといじればすぐできます。

まとめ

先日CostExplorerのAPIが提供されました。なのでそちらを使うのも選択肢としてはもちろんありだと思います。
僕の場合は後からリンクで週比較・月比較とかもサクッと見れたら嬉しかったので、今回のようにWebコンソールをスクレイピングする形にしました。

ソート