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に流すようにします。
やること
下記のような流れで実装を進めていきます。
- Slack APIのIncoming Webhooksの設定
- IAMのKMSを用いてIncomig WebhooksのURLを暗号化
- cloudwatch-alarm-to-slack-pythonというBlueprintを使用し、Lambdaファンクションを作成
- Lambdaで作成したロールにCloudwatchに対する権限を付与
- Lambdaが起動するトリガーを設定
Slack APIのIncoming Webhooksの設定
ではSlackの設定から行いましょう。
Slackのメニューから「App & Integrations」を選択します。
下記のように「Incoming Webhooks」を入力し、選択します。
自分の所属しているチームが表示されているので、該当チームの「Configure」を選択します。
「Add Configration」を選択します。
投稿したいチャンネルを選択します。
Webhook URLが必要になるので控えておきましょう。ついでにemojiも設定しちゃいましょう!(僕らはお金のemojiを設定しています)
IAMのKMSを用いてWebhook URLを暗号化する
上記で取得したURLですが、そのままLambda Functionに貼り付けて使用するのはセキュリティ上好ましくないですよね。 なのでKMSを用いて、暗号化した状態で使用しましょう。
AWSコンソールからIAMを選択
左下に「暗号化キー」という項目があるので選択
エイリアスを入力します。
キー管理アクセス許可の定義は環境やルールに従って決めて下さい。
キーポリシーのプレビューで確認をし、「完了」を押す
$ aws kms encrypt --key-id alias/先ほど設定したエイリアス --plaintext "先ほど取得したWebhook URL"
上記を実行すると、暗号化されたURLとARNというものが表示されます。こちらは Lambda Functionで使用するのでコピーしておきましょう。
cloudwatch-alarm-to-slack-pythonというBlueprintを使用し、Lambdaファンクションを作成
さて、いよいよLambdaの方に入っていきます!
「バージニア北部」リージョンを選択
AWSコンソールからLambdaを選択
Create Functionを選択
「Select blueprint」という画面が出てくるので「cloudwatch-alarm-to-slack-python」というBlueprintを選択
Configure Event Sourcesはスキップ(後で設定します)
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が正しく動作しないので注意して下さい。
Name, Descriptionを入力 Runtimeは今回Python2.7を選択
Cloudwatchから情報を得る権限が必要なので、Roleから「Basic execution role」を選択
IAMロールは「新しいIAMロールを作成」 好きなロール名を入力し、「許可」を選択
他の値はそのままで良いので「Next」を選択
内容を確認し、「Create Function」を押す
Lambdaで作成したロールにCloudwatchに対する権限を付与
先ほどLambda内で作成したロールに対し、Cloudwatchの情報を得ることが出来る権限を付与する必要があります。
AWSコンソールからIAMを選択
「ロール」を選択
先ほど作成したロールを選択する
「ポリシーのアタッチ」をクリックし、「CloudWatchReadOnlyAccess」をアタッチ
Lambdaが起動するトリガーを設定
さて、いよいよ最後です!
AWSコンソールからLambdaを選択
先ほど作成したFunctionを選択
「Event Sources」タブをクリックし、「Add event source」を選択
Event source typeの選択画面が出る 今回は時間指定でSlack通知を行いたいので「CloudWatch Events - Schedule」を選択
「Rule name」には好きな名前を、「Schedule expression」にcron式を入力 今回「毎日朝10時に通知をする」ので下記のような式になります。 ※ UTCで設定を行う必要があるので注意して下さい。
cron式については下記を参照して下さい。 docs.aws.amazon.com
こんな感じです。あっさりと出来ました! 初めてLambdaを使用しましたが、様々なイベントを感知して、AWSのサービスを自由に動かすことが出来るので工夫次第でもっと大きなことが出来そうです!