AWS Lambdaを使用して、AWS利用料金のお知らせをSlackに届くようにしてみた

AWS Lambdaを使用して、AWS利用料金のお知らせをSlackに届くようにしてみた

エンジニアの志村です。 先日AWS Summit Tokyo2016に2日間行ってきました。

今サミットではLambdaを用いたサーバレスアーキテクチャ、またKinesisを利用したリアルタイムストリーミング解析の話題が中心でした。 Lambdaは少々前から興味があったにも関わらず、実務で使用したことが無かったのでタイトルのようなものを実装してみました。 リクルートさんがやっていたのを真似て、今回は、Lambdaのcloudwatch-alarm-to-slack-pythonというBlueprintを使用し、Pythonにて実装しています。

サーバーワークスさんのブログを参考にさせて頂きました。 blog.serverworks.co.jp

ゴール

下記のような通知を、毎日朝10時にSlackに流すようにします。 f:id:cluex-developers:20160611112826p:plain

やること

下記のような流れで実装を進めていきます。

  1. Slack APIのIncoming Webhooksの設定
  2. IAMのKMSを用いてIncomig WebhooksのURLを暗号化
  3. cloudwatch-alarm-to-slack-pythonというBlueprintを使用し、Lambdaファンクションを作成
  4. Lambdaで作成したロールにCloudwatchに対する権限を付与
  5. Lambdaが起動するトリガーを設定

Slack APIのIncoming Webhooksの設定

ではSlackの設定から行いましょう。

  1. Slackのメニューから「App & Integrations」を選択します。 f:id:cluex-developers:20160611103351p:plain

  2. 下記のように「Incoming Webhooks」を入力し、選択します。 f:id:cluex-developers:20160611114126p:plain

  3. 自分の所属しているチームが表示されているので、該当チームの「Configure」を選択します。 f:id:cluex-developers:20160611103642p:plain

  4. 「Add Configration」を選択します。 f:id:cluex-developers:20160611103804p:plain

  5. 投稿したいチャンネルを選択します。 f:id:cluex-developers:20160611103908p:plain

  6. Webhook URLが必要になるので控えておきましょう。ついでにemojiも設定しちゃいましょう!(僕らはお金のemojiを設定しています) f:id:cluex-developers:20160611104319p:plain

IAMのKMSを用いてWebhook URLを暗号化する

上記で取得したURLですが、そのままLambda Functionに貼り付けて使用するのはセキュリティ上好ましくないですよね。 なのでKMSを用いて、暗号化した状態で使用しましょう。

  1. AWSコンソールからIAMを選択 f:id:cluex-developers:20160611104531p:plain

  2. 左下に「暗号化キー」という項目があるので選択

f:id:cluex-developers:20160611104802p:plain

  1. エイリアスを入力します。 f:id:cluex-developers:20160611104930p:plain

  2. キー管理アクセス許可の定義は環境やルールに従って決めて下さい。

  3. キーポリシーのプレビューで確認をし、「完了」を押す

  4. 下記コマンドを使用し、暗号化を行う(AWS CLIが必要になります。)

$ aws kms encrypt --key-id alias/先ほど設定したエイリアス --plaintext "先ほど取得したWebhook URL"

上記を実行すると、暗号化されたURLとARNというものが表示されます。こちらは Lambda Functionで使用するのでコピーしておきましょう。

cloudwatch-alarm-to-slack-pythonというBlueprintを使用し、Lambdaファンクションを作成

さて、いよいよLambdaの方に入っていきます!

  1. バージニア北部」リージョンを選択 f:id:cluex-developers:20160611105232p:plain

  2. AWSコンソールからLambdaを選択 f:id:cluex-developers:20160611105322p:plain

  3. Create Functionを選択 f:id:cluex-developers:20160611105458p:plain

  4. 「Select blueprint」という画面が出てくるので「cloudwatch-alarm-to-slack-python」というBlueprintを選択 f:id:cluex-developers:20160611105700p:plain

  5. Configure Event Sourcesはスキップ(後で設定します)

  6. Lambda Functionのページに飛びます。ENCRYPTED_URLには先ほど暗号化したURLを、SLACK_CHANNELにはIncoming Webhookで設定したチャンネルを入力しましょう。

最終的なコードは下記のようになりました。

# -*- coding: utf-8 -*-

from __future__ import print_function

import boto3
import json
import logging
import datetime

from base64 import b64decode
from urllib2 import Request, urlopen, URLError, HTTPError


ENCRYPTED_HOOK_URL = '上記で暗号化したURL'
SLACK_CHANNEL = 'Slackのチャンネル'  # Enter the Slack channel to send a message to

HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext']

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

client = boto3.client('cloudwatch')


def lambda_handler(event, context):
    logger.info("Event: " + str(event))

    startDate = datetime.datetime.today() - datetime.timedelta(days = 1)

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

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

    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': "%sまでのAWSの料金は、$%sです。" % (date, maximum)
    }

    req = Request(HOOK_URL, json.dumps(slack_message))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

# -*- coding: utf-8 -*-を宣言してあげないと、encodingのエラーが出て、lambdaが正しく動作しないので注意して下さい。

  1. Name, Descriptionを入力 Runtimeは今回Python2.7を選択 f:id:cluex-developers:20160611111009p:plain

  2. Cloudwatchから情報を得る権限が必要なので、Roleから「Basic execution role」を選択 f:id:cluex-developers:20160611111100p:plain

  3. IAMロールは「新しいIAMロールを作成」 好きなロール名を入力し、「許可」を選択 f:id:cluex-developers:20160611110638p:plain

  4. 他の値はそのままで良いので「Next」を選択

  5. 内容を確認し、「Create Function」を押す

Lambdaで作成したロールにCloudwatchに対する権限を付与

先ほどLambda内で作成したロールに対し、Cloudwatchの情報を得ることが出来る権限を付与する必要があります。

  1. AWSコンソールからIAMを選択 f:id:cluex-developers:20160611104531p:plain

  2. 「ロール」を選択

  3. 先ほど作成したロールを選択する f:id:cluex-developers:20160611111430p:plain

  4. 「ポリシーのアタッチ」をクリックし、「CloudWatchReadOnlyAccess」をアタッチ f:id:cluex-developers:20160611111634p:plain

Lambdaが起動するトリガーを設定

さて、いよいよ最後です!

  1. AWSコンソールからLambdaを選択 f:id:cluex-developers:20160611105322p:plain

  2. 先ほど作成したFunctionを選択

  3. 「Event Sources」タブをクリックし、「Add event source」を選択 f:id:cluex-developers:20160611111950p:plain

  4. Event source typeの選択画面が出る 今回は時間指定でSlack通知を行いたいので「CloudWatch Events - Schedule」を選択 f:id:cluex-developers:20160611112212p:plain

  5. 「Rule name」には好きな名前を、「Schedule expression」にcron式を入力 今回「毎日朝10時に通知をする」ので下記のような式になります。 ※ UTCで設定を行う必要があるので注意して下さい。 f:id:cluex-developers:20160611112532p:plain

cron式については下記を参照して下さい。 docs.aws.amazon.com

こんな感じです。あっさりと出来ました! 初めてLambdaを使用しましたが、様々なイベントを感知して、AWSのサービスを自由に動かすことが出来るので工夫次第でもっと大きなことが出来そうです!