モチベーション
自分ひとりで使うぶんには、作業したら確認くらいでよいのだが
不慣れなチームメンバーに機械学習を頼むと、気付いたら天井超えてた、みたいになる(なった)
予算アラートじゃなくて、毎日傾向をチェックしたい
合計だけじゃなく、サービス別も知りたい
当日分だけじゃなく、過去7日分くらい一気に見たい
あれこれ付け足していったら
結構手間取ったので、後日の自分のためにメモ
環境
- SES メール通知
- Cost Explorer 費用管理おなじみ
- Lambda(python3.7) Cost Explorerから取得した結果をメール成型して投げる
コード
import boto3
import json
import os
import datetime
import pandas as pd
'''
env ={
REGION : "ap-northeast-1"
MAIL_TO : "sample@exsample.com"
USD_RATE : 137
Metrics:"UnblendedCost"
}
'''
# 日別の合計
def get_cost_dayly(days):
Metrics_type = os.environ['Metrics']
today = datetime.datetime.now()
start = today - datetime.timedelta(days=days)
ce = boto3.client('ce')
details = ce.get_cost_and_usage(
TimePeriod={
'Start': start.strftime('%Y-%m-%d'),
'End' : today.strftime('%Y-%m-%d')
},
Granularity='DAILY',
Metrics= [
Metrics_type
]
)
res = []
grouped_costs = details['ResultsByTime']
for dayliy in grouped_costs:
dayliy_res = {}
dayliy_res['Day'] = dayliy['TimePeriod']['Start']
cost_usd = dayliy['Total'][Metrics_type]['Amount']
cots_yen = float(cost_usd) * float(os.environ['USD_RATE'])
dayliy_res['Total'] = '¥{:,.1f}'.format(cots_yen)
res.append(dayliy_res)
return res
# サービス別の明細
def get_cost_srvice(days):
Metrics_type = os.environ['Metrics']
today = datetime.datetime.now()
start = today - datetime.timedelta(days=days)
ce = boto3.client('ce')
details = ce.get_cost_and_usage(
TimePeriod={
'Start': start.strftime('%Y-%m-%d'),
'End' : today.strftime('%Y-%m-%d')
},
Granularity='DAILY',
Metrics= [
Metrics_type
],
GroupBy=[
{
'Type': 'DIMENSION',
'Key': 'SERVICE'
}
]
)
res = []
grouped_costs = details['ResultsByTime']
for dayliy in grouped_costs:
dayliy_res = {}
dayliy_res['Day'] = dayliy['TimePeriod']['Start']
for group in dayliy['Groups']:
service = group['Keys'][0]
cost_usd = group['Metrics'][Metrics_type]['Amount']
cots_yen = float(cost_usd) * float(os.environ['USD_RATE'])
dayliy_res[service] = '¥{:,.1f}'.format(cots_yen)
res.append(dayliy_res)
return res
# 月の合計
def get_cost_amount():
# 合計
now = datetime.datetime.now()
# cloudwatchのリージョンはus-east-1固定
cloudwatch = boto3.resource('cloudwatch', region_name='us-east-1')
metric = cloudwatch.Metric('AWS/Billing', 'EstimatedCharges')
response = metric.get_statistics(
Dimensions=[
{
'Name': 'Currency',
'Value': 'USD'
},
],
StartTime=now - datetime.timedelta(days=1),
EndTime=now,
Period=86400,
Statistics=['Maximum']
)
return response
def send_email(source, to, subject, body):
client = boto3.client('ses', region_name= os.environ['REGION'])
response = client.send_email(
Source=source,
Destination={
'ToAddresses': [
to,
]
},
Message={
'Subject': {
'Data': subject,
},
'Body': {
'Html': {
'Data': body,
},
}
}
)
return response
def lambda_handler(event, context):
cost_amount = get_cost_amount()['Datapoints'][0]['Maximum']
cost_dayly = get_cost_dayly(7)
cost_details = get_cost_srvice(7)
msg = f'''
<body>
<h2>AWS利用料金</h2>
<h2>合計 ${cost_amount}</h2>
'''
# 日別
data = pd.DataFrame(cost_dayly).T
msg += data.to_html(header=False)
# サービス別
data = pd.DataFrame(cost_details).T
msg += data.to_html(header=False)
msg += '</body>'
subject = f'AWS利用料金 {datetime.datetime.now().strftime("%Y-%m-%d")}'
# print(msg,subject)
mail_to = os.environ['MAIL_TO']
r = send_email(mail_to, mail_to, subject, msg)
return r
結果
はまったところ
- GroupBy付けると?日別の合計額を返してくれない
- 諦めて日別とサービス別で2回取得した
- 合計をCloudWachから取る方法はわりとすぐに見つかったが、Cost Explorerのサンプルコードがなかなか見つからなかった
- いい感じにフォーマットするのに手間取った
- 最低でも1週間ぶんは見たかったので&JSON.dumpsはさすがにユーザにやさしくないので
- 諦めてpandas使った
- Metricsのキーがクエリに応じて変わる
- 変数化
そのほか
Lambda Layerでpandas入れるのに手間取ったり、
権限まわりで躓いたり、
SESの設定したり。
まあそのへんはネットにだいたい転がっていたので割愛
いちおLambdaにつけるIAMのテンプレ
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXX:*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:ap-northeast-1:XXXXXXXXXXX:log-group:/aws/lambda/SendAWSCostMail:*"
]
},
{
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*"
],
"Resource": "*"
}
]
}