明けましておめでとうございます! 今年もよろしくお願いします!
今年は、「個人ブログで技術ネタ50記事書く」という目標も立てたので早速1本目を書きます!
今回作りたいのはAWS Lambdaを使ってAWS利用費を毎朝通知する仕組みです。
イメージとしては、今日が2019年12月25日なら、2019年12月1日から2019年12月24日までのAWS利用費を朝7:00にSlackに通知するというものです。
では、さっそくいってみましょう!
- Slack Appを作る
- AWS Lambdaのコードを書く
- AWSコンソールからLambda実行用IAMサービスロールを作成する
- AWSコンソールからLambdaの関数を作成する
- AWSコンソールからCloudWatch Eventsの設定
- 最後に
Slack Appを作る
まず、AWS利用費を通知するためにSlack Appを作成して通知先のエンドポイントを発行する必要があります。
以下のURLにアクセスします。
アクセスしたら、 Create New App
をクリックします。
クリックするとモーダルが表示されるので、名前
と Workspace
を指定します。
Workspace
は自身のものや会社のSlackのWordspaceを指定すれば良いと思います。
Create App
をしたら以下のように Basic Information
のページに遷移するので、
そこで表示されている、Incoming Webhook
をクリックします。
Incoming Webhook
をクリックしたら、以下のページに遷移します。
Activate Incoming webhookをon
にしてあげます。
すると、以下のように表示内容が変わります。
まだ、WorkspaceにWebhookを追加してない状態なので、Add New Webhook to Workspace
をクリックします。
クリックしたら、以下のようにアクセス権限を与えて良いか確認されます。
どのチャンネルにAWS利用費を通知するか決めたら、そのチャンネルを選択して、許可
をクリックします。
許可したら、Webhookが追加されます。
ここで発行されたWebhook URLが必要になってくるので、コピーしておくなどしておいてください。
ひとまず、Slack Appの準備は完了です。
AWS Lambdaのコードを書く
次はSlackへ通知するための、AWS Lambdaを作っていきます。 サクッとやってしまいたいのでFramework等は使わず、GoでLambdaを書いてみたかったので言語はGoを使います。
コードはこんな感じ。
package main import ( "bytes" "context" "encoding/json" "fmt" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/costexplorer" "net/http" "time" ) const ( SlackApi string = "ここにSlack Appで発行したWebhook URLをコピペする" DateLayout string = "2006-01-02" ) func NewCostExplorerClient() *costexplorer.CostExplorer { sess := session.Must(session.NewSessionWithOptions(session.Options{ Config: aws.Config{ Region: aws.String("ap-northeast-1"), }, SharedConfigState: session.SharedConfigEnable, })) return costexplorer.New(sess, aws.NewConfig().WithRegion("ap-northeast-1")) } type CostInfo struct { Start string `json:"start"` End string `json:"end"` Amount string `json:"amount"` } type SlackMessage struct { Text string `json:"text"` Mrkdwn bool `json:"mrkdwn"` } type TimeStringHelper struct { Location *time.Location Now time.Time } func NewTimeStringHelper(locationName string) *TimeStringHelper { location, _ := time.LoadLocation(locationName) return &TimeStringHelper{ Location: location, Now: time.Now().In(location), } } func (helper TimeStringHelper) GetBeginningOfLastMonth() string { return time.Date(helper.Now.Year(), helper.Now.Month() -1, 1, 0, 0, 0, 0, helper.Location).Format(DateLayout) } func (helper TimeStringHelper) GetBeginningOfMonth() string { return time.Date(helper.Now.Year(), helper.Now.Month(), 1, 0, 0, 0, 0, helper.Location).Format(DateLayout) } func (helper TimeStringHelper) GetYesterday() string { return time.Date(helper.Now.Year(), helper.Now.Month(), helper.Now.Day() -1, 0, 0, 0, 0, helper.Location).Format(DateLayout) } func (helper TimeStringHelper) GetToday() string { return time.Date(helper.Now.Year(), helper.Now.Month(), helper.Now.Day(), 0, 0, 0, 0, helper.Location).Format(DateLayout) } func (helper TimeStringHelper) IsTodayFirst() bool { return helper.Now.Day() == 1 } func (helper TimeStringHelper) GetStartTimePeriod() string { if helper.IsTodayFirst() { return helper.GetBeginningOfLastMonth() } else { return helper.GetBeginningOfMonth() } } func GetCostInfo(helper *TimeStringHelper) *CostInfo { start := helper.GetStartTimePeriod() end := helper.GetToday() costExplorer := NewCostExplorerClient() output, err := costExplorer.GetCostAndUsage(&costexplorer.GetCostAndUsageInput{ Granularity: aws.String("MONTHLY"), Metrics: []*string{ aws.String("AmortizedCost"), }, TimePeriod: &costexplorer.DateInterval{ Start: aws.String(start), End: aws.String(end), }, }) if err != nil { panic(err) } total := output.ResultsByTime[0].Total["AmortizedCost"] amount := aws.StringValue(total.Amount) return &CostInfo{ Start: start, End: end, Amount: amount, } } func makeSlackMessage(costInfo *CostInfo, helper *TimeStringHelper) SlackMessage { return SlackMessage{ Text: fmt.Sprintf("*期間*: `%s ~ %s`\n*料金*: `$%s`", costInfo.Start, helper.GetYesterday(), costInfo.Amount), Mrkdwn: true} } func PostToSlack(message SlackMessage) { input, _ := json.Marshal(message) fmt.Println(string(input)) http.Post(SlackApi, "application/json", bytes.NewBuffer(input)) } type Response struct { Message []byte `json:"message"` } func BillingNotification(ctx context.Context) (Response, error) { helper := NewTimeStringHelper("Asia/Tokyo") fmt.Println(helper.Now.Format("2006/01/02 15:04:05")) costInfo := GetCostInfo(helper) message := makeSlackMessage(costInfo, helper) PostToSlack(message) json, _ := json.Marshal(message) return Response{Message: json}, nil } func main() { lambda.Start(BillingNotification) }
Githubにもアップしてるので、試してみたい方はぜひ使ってみてください。
コードに関して全ては説明しないですが、ピックアップして説明します。
AWS利用費の取得には、Cost Explorer
を使います。
以下の部分ですね。
GranularityにはMONTHLY
を指定します。
TimePeriodは、利用料の取得期間です。
たとえば、Startに 2019-12-01
、 Endに 2019-12-31
を指定すると、
2019-12-01 から 2019-12-30までのAWS利用料を取得できます。
ここが間違えやすところかもしれません。
output, err := costExplorer.GetCostAndUsage(&costexplorer.GetCostAndUsageInput{ Granularity: aws.String("MONTHLY"), Metrics: []*string{ aws.String("AmortizedCost"), }, TimePeriod: &costexplorer.DateInterval{ Start: aws.String(start), End: aws.String(end), }, })
Slackへ通知する際のmessageのpayloadも決まっているので、 それに合わせてメッセージをPostする必要があります。
func makeSlackMessage(costInfo *CostInfo, helper *TimeStringHelper) SlackMessage { return SlackMessage{ Text: fmt.Sprintf("*期間*: `%s ~ %s`\n*料金*: `$%s`", costInfo.Start, helper.GetYesterday(), costInfo.Amount), Mrkdwn: true} }
参照: api.slack.com
定数のSlackApi
にはSlack Appで発行したWebhookのURLを貼り付けておいてください。
これができたら、ビルドしてzip化します。
ビルドは以下のコマンドで実行してください。
オプションをつけ忘れるとLambdaが実行できないので気をつけましょう!
GOARCH=amd64 GOOS=linux go build
ビルドしたら実行ファイルが生成されるので、以下のコマンドでzip化します。
zip billing-notification.zip ./billing-notification
そうすると、billing-notification.zip
というzipファイルが生成されます。
これはあとでAWSコンソールでLambda関数を作るときに使います。
AWSコンソールからLambda実行用IAMサービスロールを作成する
IAMサービスロールにアタッチするポリシーのJSONは以下のように書きます。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "ce:GetCostAndUsage" ], "Resource": "*" } ] }
IAMサービスロールはLambdaを選択してください。
あとはよしなにIAMサービスロールを作成します。
AWSコンソールからLambdaの関数を作成する
AWSコンソールからLambdaを開いて作成してきます。
関数名: billing-notification
ランタイム: Go 1.x
実行ロール: 先程作成したサービスロールを指定するので、既存のロールを使用する
を選択し、セレクトボックスから作成したロールを探して選択します。
関数を作成できたら、LambdaのトリガーにCloudWatch Events
を指定しておいてください。
そして、zip化したLambdaのコードをアップロードします。
ハンドラ名は billing-notification
と入力し、保存します。
もうこれでSlackに通知することができます!
試しにテストしてみましょう。
テストイベントの設定をします。
作成したことがあれば以下のように表示されますし、してなければ テストイベントの設定
と表示されているはず?なので、
テスト
をクリックします。
もし新規で作る場合は以下のようにモーダルが表示されるので、適当な名前をつけて保存してください。
そして、もう一度テストをクリックすると、Slackに利用料が通知されるはず!!
ほぼできた🎉🎉🎉
作業もあと少しです!
AWSコンソールからCloudWatch Eventsの設定
Slackに通知する仕組みは作れました。
あとは、毎朝7時にAWS Lambdaを実行するようにしておきたいですよね?
そのためにCloucWatch Eventsを利用します。
Lambdaを作成したときにトリガーにCloudWatch Eventsを指定したのはそのためです。
それでは、AWSコンソールからCloudWatchを開きます。
開いたら以下のように、ルール
と書かれた箇所をクリックします。
クリックしたら以下のようにルールの作成
をクリックします。
そうしたら、以下のように設定してみてください。
Cron式
に 0 22 * * ? *
と指定していますが、UTC表記なのでこのままで問題ありません。
設定できたら 設定の詳細
をクリックします。
ステップ 2: ルールの詳細を設定する
画面では、状態を有効化してルールを作成してください。
これで、毎朝7時にLambdaが実行されてSlackに通知が来るはずです🎉🎉🎉
最後に
いかがでしたでしょうか?
Lambdaを使えばサクッとAWS利用費を通知できるので、今どれぐらい使っていて請求が来るのか把握しやすくなりました!
もしこの記事が良ければ、Twitterなどでシェアしてくださいね!
読んでくださってありがとうございました!
※ アイキャッチには@tentenのGopher by tenntenn CC BY 3.0を利用させていただいています。