LoginSignup
0
0

Lambda + SES + Cost Explorer でAWSの日別・サービス別コストをメール通知する

Posted at

モチベーション

image.png
↑をコンソールにログインしないで確認したい

自分ひとりで使うぶんには、作業したら確認くらいでよいのだが
不慣れなチームメンバーに機械学習を頼むと、気付いたら天井超えてた、みたいになる(なった)
予算アラートじゃなくて、毎日傾向をチェックしたい
合計だけじゃなく、サービス別も知りたい
当日分だけじゃなく、過去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


結果

image.png

はまったところ

  • 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": "*"
        }
    ]
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0