AWS
Python3
lambda

LambdaでAWSの料金を毎日Slackに通知する(Python3)


はじめに

個人アカウントは基本的に無料枠で運用しているので、少しでも請求がある場合はいち早く気づきたいです。

先日、とあるハンズオンイベントで使ったリソースを消し忘れて、最終的に$30ぐらい請求が来てしまいました。。。

CloudWatchで請求アラートは設定していますが、閾値超えが想定の場合、当然見逃すことになり、最終的な請求額に驚くハメになります。

これを防ぐためにLambdaで毎日SlackにAWS料金を通知することにします。

先日LambdaがPython3に対応したので、せっかくだし勉強がてらPython3で実装したい。

ネット上にはNode.jsでの実装例が多いようで、今回はこちらを参考にPython3で実装してみます。


必要なもの


  • Slack


    • incoming-webhooks URL



    • 適当なchannel



  • lambda-uploader


    • requestsモジュールをLambda上でimportするために利用


      • カレントディレクトリにモジュールをインストールして、モジュールごとZipに固めてアップロードでもいけるはずですが、私の環境だとうまくいかなかったので





  • aws cli


    • lambda-uploaderで必要



  • AWS


    • Lambda関数用IAM Role



      • CloudWatchReadOnlyAccessポリシーをアタッチ




    • 請求アラートの有効化


      • これを行っておかないと請求情報のCloudWatchのメトリクスが生成されません

      • スクリーンショット 2018-03-28 0.25.27.png






事前準備


ローカル


lambda-uploaderをインストール

$ pip install lambda-uploader 

こちらを参考にさせていただきました。


aws cliをインストール

$ pip install awscli


credential、リージョン設定

$ aws configure

確認
$ aws configure list


AWS(Lambda)

コードをlambda-uploaderでデプロイするためには、あらかじめ空の関数を用意する必要があります。


関数を作成

スクリーンショット 2018-03-28 10.35.24.png


IAMロール修正

関数定義の際にポリシーテンプレートから自動作成されたIAMロールにCloudWatchReadOnlyAccessポリシーをアタッチします。コスト情報をLambdaから読み込むのに必要です。

ロールARNはあとで必要になるのでメモしておきます。

スクリーンショット 2018-03-28 10.42.14.png

スクリーンショット 2018-03-28 10.42.25.png


コード


ディレクトリ構成

ディレクトリ名は任意です。関数名とは無関係です。


ディレクトリ構成

awscost_to_slack/

|--lambda_function.py
|--requirements.txt
|--lambda.json


lambda_function.py

超過金額に応じて色をつけるようにしています。

\$0.0なら緑、超過したら黄色、\$10超えで赤になります。


lambda_function.py

#!/usr/bin/env python

# encoding: utf-8

import json
import datetime
import requests
import boto3
import os
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Slack の設定
SLACK_POST_URL = os.environ['slackPostURL']
SLACK_CHANNEL = os.environ['slackChannel']

response = boto3.client('cloudwatch', region_name='us-east-1')

get_metric_statistics = response.get_metric_statistics(
Namespace='AWS/Billing',
MetricName='EstimatedCharges',
Dimensions=[
{
'Name': 'Currency',
'Value': 'USD'
}
],
StartTime=datetime.datetime.today() - datetime.timedelta(days=1),
EndTime=datetime.datetime.today(),
Period=86400,
Statistics=['Maximum'])

cost = get_metric_statistics['Datapoints'][0]['Maximum']
date = get_metric_statistics['Datapoints'][0]['Timestamp'].strftime('%Y年%m月%d日')

def build_message(cost):
if float(cost) >= 10.0:
color = "#ff0000" #red
elif float(cost) > 0.0:
color = "warning" #yellow
else:
color = "good" #green

text = "%sまでのAWSの料金は、$%sです。" % (date, cost)

atachements = {"text":text,"color":color}
return atachements

def lambda_handler(event, context):
content = build_message(cost)

# SlackにPOSTする内容をセット
slack_message = {
'channel': SLACK_CHANNEL,
"attachments": [content],
}

# SlackにPOST
try:
req = requests.post(SLACK_POST_URL, data=json.dumps(slack_message))
logger.info("Message posted to %s", slack_message['channel'])
except requests.exceptions.RequestException as e:
logger.error("Request failed: %s", e)



requirements.txt

pip installしたいモジュール名を書きます。


requirements.txt

requests



lambda.json

Lambda関数名、IAMロールのARNなどは環境に合わせてください。

用意したSlackのincoming-webhooks URLと通知するchannelを関数に渡す環境変数としてvariablesに定義します。

また、スクリプト本体のファイル名とハンドラの前半(サンプルコードではデフォルトlambda_function)を一致させないと動きません。地味にハマるので注意!


lambda.json

{

"name": "LAMBDA_FUNCTION_NAME",
"description": "DESCRIPTION",
"region": "ap-northeast-1",
"handler": "lambda_function.lambda_handler",
"role": "arn:aws:iam::XXXXXXX:role/ROLE_NAME_FOR_LUMBDA",
"timeout": 300,
"memory": 128,
"variables":
{
"slackPostURL":"https://hooks.slack.com/services/XXX",
"slackChannel":"#XXX"
}
}


デプロイ

上記ファイルを配置したディレクトリに移動して、lambda-uploaderを実行します。

$ cd awscost_to_slack

$ lambda-uploader
λ Building Package
λ Uploading Package
λ Fin


Lambda定期実行設定

CloudWatchのスケジュールイベントを定義して、lambda関数をターゲットに指定します。

時刻はUTCなので注意しましょう。

毎日UTC0:00に実行されるよう設定しました。

スクリーンショット 2018-03-28 10.38.51.png


実行イメージ

スクリーンショット 2017-06-23 14.15.54.png

こんな感じで毎朝9:00に通知がきます。

今日も無料!


まとめ

Lambdaはほぼ無料でプログラムが動かせるので楽しいですね!

Python初心者なのでコードが見苦しい点はご容赦ください。