1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GPUインスタンスの料金が怖いので、サービス毎の請求額を毎日LINEに通知してみた

Last updated at Posted at 2020-09-05

image.png

概要

先日、AWSの利用料金をSlackで飛ばす記事を書いた。

Slackだと気づかないこともあるので、Lineで送ることにした.

参考文献

前回記事 - GPUインスタンスの料金が怖いので、サービス毎の請求額を毎日Slackに通知してみた - Qiita

ソースコードの元記事 - AWSサービス毎の請求額を毎日Slackに通知してみた | DevelopersIO

実装結果

LineにてAWSの使用料金が送られてきていることを確認した。便利だ。

image.png

コード

LINE Notify へのPostを追加したコードは以下

import json
import os
import boto3
import urllib.request
from datetime import datetime, timedelta, date
 
 
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
LINE_TOKEN = os.environ['LINE_TOKEN']
 

def lambda_handler(event, context):
    client = boto3.client('ce', region_name='us-east-1')
 
    # 合計とサービス毎の請求額を取得する
    total_billing = get_total_billing(client)
    service_billings = get_service_billings(client)
 
    # Slack用のメッセージを作成
    (title, detail) = get_message(total_billing, service_billings)

    # Slackへ通知
    post_slack(title, detail)
    # Lineへ通知
    post_line(title, detail)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }


def get_total_billing(client) -> dict:
    (start_date, end_date) = get_total_cost_date_range()
 
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ce.html#CostExplorer.Client.get_cost_and_usage
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start_date,
            'End': end_date
        },
        Granularity='MONTHLY',
        Metrics=[
            'AmortizedCost'
        ]
    )
    return {
        'start': response['ResultsByTime'][0]['TimePeriod']['Start'],
        'end': response['ResultsByTime'][0]['TimePeriod']['End'],
        'billing': response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount'],
    }
 
 
def get_service_billings(client) -> list:
    (start_date, end_date) = get_total_cost_date_range()
 
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ce.html#CostExplorer.Client.get_cost_and_usage
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start_date,
            'End': end_date
        },
        Granularity='MONTHLY',
        Metrics=[
            'AmortizedCost'
        ],
        GroupBy=[
            {
                'Type': 'DIMENSION',
                'Key': 'SERVICE'
            }
        ]
    )
 
    billings = []
 
    for item in response['ResultsByTime'][0]['Groups']:
        billings.append({
            'service_name': item['Keys'][0],
            'billing': item['Metrics']['AmortizedCost']['Amount']
        })
    return billings
 
 
def get_message(total_billing: dict, service_billings: list) -> (str, str):
    start = datetime.strptime(total_billing['start'], '%Y-%m-%d').strftime('%m/%d')
 
    # Endの日付は結果に含まないため、表示上は前日にしておく
    end_today = datetime.strptime(total_billing['end'], '%Y-%m-%d')
    end_yesterday = (end_today - timedelta(days=1)).strftime('%m/%d')
 
    total = round(float(total_billing['billing']), 2)
 
    title = f'{start}{end_yesterday}の請求額は、{total:.2f} USDです。'
 
    details = []
    for item in service_billings:
        service_name = item['service_name']
        billing = round(float(item['billing']), 2)
 
        if billing == 0.0:
            # 請求無し(0.0 USD)の場合は、内訳を表示しない
            continue
        details.append(f' ・{service_name}: {billing:.2f} USD')
 
    return title, '\n'.join(details)
 
 
 
def get_total_cost_date_range() -> (str, str):
    start_date = get_begin_of_month()
    end_date = get_today()
 
    # get_cost_and_usage()のstartとendに同じ日付は指定不可のため、
    # 「今日が1日」なら、「先月1日から今月1日(今日)」までの範囲にする
    if start_date == end_date:
        end_of_month = datetime.strptime(start_date, '%Y-%m-%d') + timedelta(days=-1)
        begin_of_month = end_of_month.replace(day=1)
        return begin_of_month.date().isoformat(), end_date
    return start_date, end_date
 
 
def get_begin_of_month() -> str:
    return date.today().replace(day=1).isoformat()
 
 
def get_prev_day(prev: int) -> str:
    return (date.today() - timedelta(days=prev)).isoformat()
 
 
def get_today() -> str:
    return date.today().isoformat()

def post_slack(title, detail):
    print(SLACK_WEBHOOK_URL)
    url = SLACK_WEBHOOK_URL
    set_fileds = [{"title":title, "value":detail, "short":False}]
    data = {"attachments":[{'color':'danger', 'fields':set_fileds}]}
    request_headers = {'Content-Type': 'application/json; charset=utf-8'}
    body = json.dumps(data).encode("utf-8")
    request = urllib.request.Request(
        url=url, data=body, method='POST', headers=request_headers)
    urllib.request.urlopen(request)
    pass


def post_line(title, detail):
    
    url = 'https://notify-api.line.me/api/notify'
    token = LINE_TOKEN
    
    message = "\nおはようございます。\n\n" + title + "\n\n" + detail
    
    payload = {'message': message}
    
    payload = urllib.parse.urlencode(payload).encode("utf-8")
    
    request_headers = {'Authorization': 'Bearer ' + token}
    
    request = urllib.request.Request(
        url=url, 
        data=payload,
        method='POST',
        headers=request_headers)
        
    urllib.request.urlopen(request)
    
    

以上

1
4
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
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?