Python
AWS
CloudWatch
lambda
Slack

AWSの課金情報をSlackへ送るまでにとても苦労した話

はじめに

  • 毎月のAWSの課金情報をちゃんと把握したい
  • いちいちAWSにログインして確認したしたくない
  • そろそろLambda使ってみたい
  • 自分が把握するものをSlackに集約している

こんな理由から、やってみようと考えた。

Slack

まずは課金情報を受け取るChannelを作成する。
名前や目的は後で修正できるけれど、ちゃんと決めておくことをオススメします。
Slackのチャンネル作成

続いて、Webhook URLの取得。
https://slack.com/services/new/incoming-webhook から取得できる。
通知を受け取るチャンネル(さっき作ったやつ)を指定する。
Webhook URLが出来上がるので、忘れずにコピーしておく。

チャンネルを指定しよう

Webhook URLができた

ローカル(Mac)

いきなり壁にぶつかる

Terminalで以下コマンドを叩いてlambda-uploaderをインストール。
・・・とはうまくいかなかった。

$ pip install lambda-uploader 

なんでだろ?と考えた結果、以下のメッセージが出ているからでは?と考えた。

You are using pip version 10.0.1, however version 18.0 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

よしわかった、アップグレードしてやろうじゃないか。

$ pip install --upgrade pip

途中でエラー発生。

Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/Library/Python/2.7/site-packages/pip-10.0.1-py2.7.egg/EGG-INFO/PKG-INFO'
Consider using the `--user` option or check the permissions.

仕方ない、やり直し。

$ pip install --upgrade pip --user

Successfullyは出ているけれど、ちゃんとできているか不安。
ここでPythonのバージョンを確かめてみようと思った。

$ python --version
Python 2.7.10

Python 3.5.0とか入れてなかったっけ?何インストールできるんだっけと思って、pyenvを実行。

$ pyenv install --list
-bash: pyenv: command not found

pyenvが使えない・・・仕方がないので、gitから持ってくることにする。

$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

ちょっと前のコマンドを実行。

$ pyenv install --list

インストールできるバージョンのリストが表示されたので、その中から3.5.0をインストールすることに。

$ pyenv install 3.5.0

インストールがエラーなくうまくいったので、2.7.10から3.5.0にバージョンを変えてみる。

$ pyenv global 3.5.0
$ python --version
Python 2.7.10

あれ?
Pythonはどこを見ている?

$ which python
/Users/xxxxxx/.pyenv/shims/python

色々調べて、以下の通り~/.bash_profileに追記をすることに。

$ export PATH="'$HOME/.pyenv/shims:$PATH"
$ source ~/.bash_profile

さて、これでどうだ。

$ python --version
Python 3.5.0

あぁ、ようやくうまくいった。

各種インストール

lambda-uploaderをインストール。

$ pip install lambda-uploader

OK、次にaws cliをインストール。

$ pip install awscli

続いてaws configureで設定をするのだが、Access KeyとSecret Access Keyが必要になる。
作成していない場合は、セキュリティ認証情報のページから作成する。

$ aws configure
AWS Access Key ID [None]: *****************
AWS Secret Access Key [None]: *****************
Default region name [None]: ap-northeast-1
Default output format [None]: text

東京リージョンを指定する場合は、ap-northeast-1を指定するようだ。

AWS

CloudWatch

AWSの管理コンソール、請求ダッシュボード->設定に移動して、請求アラートを受け取る にチェックが付いていることを確認する。
付いていない場合はチェックを入れておく。

CloudWatchのアラート設定

Lambda関数を作成

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

Lambdaで 一から作成 を選択。
名前とロール名はわかりやすいものを指定、ランタイムは Python3.6 にした。
ロールは テンプレートから新しいロールを作成 を選択し、ポリシーテンプレートは 基本的な Lambda@Edge のアクセス権限 (CloudFront トリガーの場合) を選択。
これで大丈夫なはず。多分・・・。
関数の作成 ボタンを押して、空の関数の作成は完了。

関数の作成を選択

一から作成を選択

設定値を入力

空の関数が完成

IAMロール

Lambda関数を作成した時に、IAMロールを作成した。(テンプレートから新しいロールを作成を選んだから)
そのIAMロールにCloudWatchReadOnlyAccessポリシーをアタッチする。
コスト情報をLambdaから読みだすのに必要とのこと。
アタッチ後に、ロールARNをコピーしておく。

作成したロールを選択

ポリシーをアタッチするよ

CloudWatchReadOnlyAccessをチェック

アタッチを確認

コード(ローカル)

ローカルでコードを編集する。
個人的に好きなATOMを利用して編集する。

ディレクトリとファイルは以下のようにする。

ファイルの構成

lambda_function.py

lambda_function.py
#!/usr/bin/env python
# encoding: utf-8

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

# Logger の設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

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

# CloudWatch
# region_nameはus-east-1にしておくこと
response = boto3.client('cloudwatch', region_name='us-east-1')

# ValueはUSDじゃないとダメ
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):

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

    color = "#ffffff" # black

    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)

lambda.json

  • name: Lambda関数の名前を入れる
  • description: Lambda関数の説明を入れる
  • region: リージョンを入れる、東京ならこのまま。
  • handler: ハンドラーの名前を入れる
  • role: IAMロールのARNを入れる
  • slackPostURL: SlackのWebhookURLを入れる
  • slackChannel: Slackで投稿するチャネルを入れる ex.#aws
lambda.json
{
  "name": "Lambda関数の名前",
  "description": "Lambda関数の説明",
  "region": "ap-northeast-1",
  "handler": "lambda_function.lambda_handler",
  "role": "IAMロールのARN",
  "timeout": 300,
  "memory": 128,
  "variables":
    {
      "slackPostURL":"Slack WebhookのURL",
      "slackChannel":"Slackの投稿チャネル"
    }
}

requirements.txt

requirements.txt
requests

デプロイする

デプロイするときは、上記ファイルを置いたディレクトリに移動してから行うこと。

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

AWS-CloudWatch

CloudWatchのイベントを作成する。
CloudWatchに移動して、イベント->ルールの作成 を選択する。
CloudWatch Events作成

「スケジュール」にチェックを入れる。
「Cron式」に値を入れる。Cron式の書き方は参考にリンクを付けた。
ターゲットは「Lambda関数」で、機能で作成した関数を選択する。
ルールの作成

ルールの名前と説明はお好きなように。
ルールの詳細設定

これでCloudWatchのルールが完成。
ルール完成

結果

綺麗にうまくいった!と言いたいところだが、実際はうまく飛んでこなくて試行錯誤。
その結果が、上のコードです。
lambda_function.pyregion_name と Dimensionsの Value かな、引っ掛かったのは。
それ以外はうまくいった。

参考