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

# モチベーション
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570991/971c638e-882f-a788-30db-5804b707b8cd.png)
↑をコンソールにログインしないで確認したい

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

あれこれ付け足していったら
結構手間取ったので、後日の自分のためにメモ

# 環境
- SES メール通知
- Cost Explorer　費用管理おなじみ
- Lambda(python3.7） Cost Explorerから取得した結果をメール成型して投げる

# コード
```python
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570991/55203f4c-d685-85bd-2a56-4f076de3a3dc.png)


# はまったところ
- GroupBy付けると？日別の合計額を返してくれない
    - 諦めて日別とサービス別で2回取得した
- 合計をCloudWachから取る方法はわりとすぐに見つかったが、Cost Explorerのサンプルコードがなかなか見つからなかった
- いい感じにフォーマットするのに手間取った
    - 最低でも1週間ぶんは見たかったので＆JSON.dumpsはさすがにユーザにやさしくないので
    - 諦めてpandas使った
- Metricsのキーがクエリに応じて変わる
    - 変数化

# そのほか
Lambda Layerでpandas入れるのに手間取ったり、
権限まわりで躓いたり、
SESの設定したり。
まあそのへんはネットにだいたい転がっていたので割愛

いちおLambdaにつけるIAMのテンプレ

``` JSON
{
    "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": "*"
        }
    ]
}
```