LambdaでSlackにAWS使用料金を通知させてみました(CloudWatch Events編)

やりたいこと



Lambdaを使ってAWSでの月額使用料金を定期的にslackに通知させるようにします。

slackに通知させるところまでは前回の記事に書いたので、
今回は月額使用料金を取得するところをメインに書きます。



mtomitomi.hatenablog.com





下記を参考にさせて頂きました。

今回やりたいことの流れが、図を使ってわかりやすく記載されています。



blog.serverworks.co.jp






リージョンの設定



月額使用料金を監視するためには、CloudWatchのAPIを叩いてメトリクスを取得する必要があります。

このメトリクスを取得できるのは「米国東部(バージニア北部)/ us-east-1」リージョンのみです。
取得した値は全てのリージョンを合計した値になります。

Lambda関数やSNSトピックの作成は、全て「米国東部(バージニア北部)」のリージョンで行うよう注意してください。
��私はここができていなくて何時間もハマりました。)






請求アラートを有効にする



有効にすることで、予想AWS請求額をモニタリングして請求メトリクスデータを使用したアラームを作成することができます。

ルートアカウントでサインインし、下記リンク先で左の項目にある「設定」をクリックして「請求アラートを受け取る」にチェックを入れます。



https://console.aws.amazon.com/billing/home?#/





初めて予想請求額のモニタリングを有効にした後、請求データの表示と請求アラートの設定ができるようになるまで約15分かかるようです。

ちなみに、IAM ユーザーがこの請求アラートを有効にすることはできないです。
IAMユーザーが請求の参照をするためには、別途Billingというポリシーをアタッチする必要があります。






Webhook URLを暗号化



slackで作成したWebhook URLをコードにそのまま書くのはセキュリティ上良くないため、暗号化します。

AWSコンソール画面でサービスからIAMを選択し、管理者画面の左にある「暗号化キー」の項目を選択します。
リージョンを「米国東部 (バージニア北部)」に変更して「キーの作成」ボタンをクリックします。

下記設定をしてキーを作成します。
アクセス許可の定義では、今回は「admin」ユーザーと「lambda_basic_execution」ロールにチェックを入れました。




  • エイリアス (必須)      :適当な名前を設定


  • タグの追加         :空欄のままでOK


  • キー管理アクセス許可の定義 :Lambdaの実行権限のあるユーザーとロール


  • キーの使用アクセス許可の定義:Lambdaの実行権限のあるユーザーとロール








Lambda関数の作成



AWSコンソール画面からLambdaを選択し、関数の作成ボタンをクリックします。
「設計図」を選択して「cloudwatch-alarm-to-slack-python3」を選択して「設定」をクリックします。
��設計図をBlueprintというみたいです。)



f:id:mtomitomi:20171229100031p:plain





下記の設定をして関数を作成します。
ロールは前回の記事で作成した、CloudWatchReadOnlyAccessのポリシーがアタッチされているロールを使用しています。
SNSの入力欄がありますが、この時点では米国東部(バージニア北部)リージョンの
SNSトピックをまだ作成していないので、削除しておきました。




  • 関数名 :cost-to-slack(適当な名前)


  • ロール :lambda_basic_execution


  • SNS  :削除







環境変数の値を設定します。




  • slackChannel : #general


  • kmsEncryptedHookUrl : {https://以降のWebHook URLの値}





このとき、kmsEncryptedHookUrlは下記手順で暗号化した値を設定します。
暗号化の設定をひらいて「伝送中の暗号化のためのヘルパーの有効化」にチェックを入れます。
KMS キーに前手順で作成したキーを設定します。
環境変数の欄に「暗号化」ボタンが表示されるので、kmsEncryptedHookUrlの「暗号化」ボタンをクリックした値を暗号化します。



f:id:mtomitomi:20171229104135p:plain



設定ができたら「関数の作成」ボタンをクリックします。






pythonファイルの修正



デフォルトでコードが作成されているので、月額使用料金を取得してslackに通知するよう処理を追加します。




# -*- coding: utf-8 -*-
import boto3
import json
import logging
import os
import datetime

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

# 環境変数で設定した値を取得
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
SLACK_CHANNEL = os.environ['slackChannel']

# 暗号化した値を複合してWebhook URLを取得
HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8')

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

# Cloudwatchのクライアントを生成
client = boto3.client('cloudwatch', region_name='us-east-1')

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

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

# 予想請求額を取得(昨日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に通知するメッセージを作成
slack_message = {
'channel': SLACK_CHANNEL,
'text': "%sまでのAWSの料金は、$%sです。" % (date, maximum)
}

## slackに通知
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
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 -*-を書かないとうまくエンコードしてくれないようです。
リージョンの指定は~/.aws/configにデフォルトの記載をしていれば省略可能です。

MetricNameにEstimatedChargesを設定することで、AWS使用量に対する予想請求額を取得することができます。






SNSトピックの追加



米国東部(バージニア北部)リージョンでAWSコンソールから「Simple Notification Service」を選択し、トピックの作成をクリックします。
適当な名前をつけてトピックを作成した後、サブスクリプションの作成でプロトコルを「AWS Lambda」、
エンドポイントを前手順で作成したLambda関数に設定して作成します。
��SNSトピックの作成方法については、前回の記事でも詳しく記載してあります。)





先ほど作成したLambda関数の画面に戻り、作成したSNSトピックを追加します。
Designerの左にある項目から「SNS」を選択して追加します。



f:id:mtomitomi:20171229105743p:plain





SNSに作成したSNSトピックを設定し、トリガーの有効化にチェックを入れて「追加」ボタンをクリックします。






テスト



この時点で、正常にslackに通知がとぶかテストします。

前回の記事と同様、CloudWatch用の下記テストイベントを作成してテストを実行します。
正常にできていれば、slackに通知がとぶはずです。



{
"Records": [
{
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
"EventSource": "aws:sns",
"Sns": {
"SignatureVersion": "1",
"Timestamp": "1970-01-01T00:00:00.000Z",
"Signature": "EXAMPLE",
"SigningCertUrl": "EXAMPLE",
"MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
"Message": "{\"AlarmName\":\"awsrds-app-High-DB-Connections\",\"AlarmDescription\":\"Alarm when CPU exceeds 50 percent\",\"AWSAccountId\":\"123456789123\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (10.0).\",\"StateChangeTime\":\"2016-07-24T22:05:19.737+0000\",\"Region\":\"US West - Oregon\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"DatabaseConnections\",\"Namespace\":\"AWS/RDS\",\"Statistic\":\"AVERAGE\",\"Unit\":null,\"Dimensions\":[{\"name\":\"DBInstanceIdentifier\",\"value\":\"app\"}],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":10.0}}",
"MessageAttributes": {
"Test": {
"Type": "String",
"Value": "TestString"
},
"TestBinary": {
"Type": "Binary",
"Value": "TestBinary"
}
},
"Type": "Notification",
"UnsubscribeUrl": "EXAMPLE",
"TopicArn": "arn:aws:sns:EXAMPLE",
"Subject": "TestInvoke"
}
}
]
}





補足ですが、テストはサンプルイベントがAWSの公式HPに載っていましたので、それらを参考に作成するといいかと思います。



docs.aws.amazon.com






スケジュール実行の設定



Designerの左にある項目から「CloudWatch Events」を選択して追加し、
トリガーの設定欄にあるルールの項目で「新規ルールの作成」を選択します。

スケジュール式の設定方法については下記を参考にしました。



Rate または Cron を使用したスケジュール式 - AWS Lambda





今回は米国東部(バージニア北部)リージョンのせいか、

日本時間の午前9時が0 0 ? * * *になるようです。



その他を適当に設定をして「追加」ボタンをクリックします。
今回は毎朝10時に通知させたかったので下記に設定しました。



f:id:mtomitomi:20171229111614p:plain





時間になって通知がきたら成功です。

�� 下記通知がきたときのスケジュール式はcron(10 1 ? * * *)です)



f:id:mtomitomi:20171229112259p:plain





無料枠がある期間中だったので、$0を想定していたのですが...課金されています。
AWSコンソールで請求内容を確認したところ、KNSで課金されているようです。

無料で使えるようなことが書いてあったのですが、範囲外の設定をしてしまったのでしょうか...。
怖くなったのでKNSや暗号化の設定を全て消しました。






やってみた感想



わからないことが多く色々なサイトを参考にしていたのですが、最終的には
AWSの公式ドキュメントが最も正確で丁寧に記載されていました。
疑問点があった場合は、まず公式ドキュメントを調べようと思います。



CloudWatchのAPIを初めて使い、どのように情報を取得しているか理解することができました。
他のサービスでもAPIがありそうなので、もっと調べてみようと思います。



権限やリージョンでハマることが多く、想定していたよりも実現に時間がかかりました。
ただ、そのおかげで何回も関数を作成したり設定を見直したりといったことができ、理解が深まったので良かったと思います。



無料枠だからといって安心していたら課金されていたので、どのサービスが無料なのかよく調べてから使おうと思います。






1 Response to "LambdaでSlackにAWS使用料金を通知させてみました(CloudWatch Events編)"

  1. Thank you for giving comment!
    I'm very happy for you to say that for me.
    I will keep to update my blog.

    返信削除