AWSの料金をSlackに報告してくれるBotを作成した話

ogp

こんにちは。今年の4月に新卒で入社し、SRE部MA基盤チームに配属された川津(@jon_ground)です。

MA基盤チームではMAで利用しているインフラの使用料金が把握できていない問題があります。そこで気軽に料金を確認できるようにAWSの料金をSlackに報告してくれるBotを作成しました。本記事では上記の問題を解決するため、Botを作成するまでに至った経緯や数ヶ月運用して得られたメリットについて紹介します。

作った経緯

MA基盤チームでは、MA関連のインフラをAWS上で構築しており、開発、ステージング、本番と環境毎にAWSのアカウントを分けて運用しています。AWSの使用料金を確認する際、都度料金を確認するために各環境のAWSのコンソールからCost Explorerを確認する必要があり非常に面倒な作業でした。またCost Explorerを閲覧するためには強い権限が必要であり、確認できる人が限られていました。これによって料金が上がった部分を見落としてしまい、検証用のインスタンスを落とし忘れてしまったり、必要以上にAWSのリソースを使用してしまったことがありました。

こうした無駄な消費を避けるためにチームの人が毎日確認でき、リソース毎の使用料金も確認できるようにする必要がありました。サービスを運用する上でインフラのコストを意識し、妥当な料金であるか把握することは大事です。

AWS料金Botの機能

現状の問題点を解決するためには以下の要件を満たす必要がありました。

  • チームの人が毎日AWSの料金を把握できるようにする
  • AWSの強い権限を持っていない人でも料金を確認できるようにする

上記の要件を満たすためにMA基盤チームが利用しているSlackのChannelに料金の情報を流すことにしました。理由としては対象のChannelで勤怠の確認を行っており、毎朝チーム全員が必ずSlackを見るからです。また、ZOZOテクノロジーズのSlackは原則オープンチャンネルなのでMA基盤チーム以外の人も見ることができ、強い権限を持っていなくてもAWSの料金を確認できます。

Slackに料金の情報を表示する方法にAWS チャットボットを利用する方法もあります。しかしリソース毎に料金が知りたい点とリソースの使いすぎを防止するために一昨日と昨日の料金の差分を表示し、利用料金がどれだけ変化したのかを知りたい要望があり、今回の要件には合わないためBotを自作することにしました。

作成したアプリケーションの機能としては、平日朝10時に対象のSlack Channelに各環境毎の使用料金をまとめたメッセージを送ります。また、使用料金の内訳も表示されます。全リソースの使用料金を表示するとメッセージが長くなるので、1USD以下の金額のリソースはその他の金額にまとめています。

aws-billing-bot-slack-message

実装

今回使用した言語、ツールは下記のものです。

  • 言語:Go 1.14
  • プロビジョニングツール:CloudFormation(CFn)、Serverless Application Model(SAM)

アーキテクチャーは下記の通りです。 aws-billing-bot-slack-message

作成されたアプリケーションはLambda上で動作しており、CloudWatch Eventsでcronを平日10時に設定してLambdaを動かしています。また、AWSの料金の取得はAWSのCost Explorer APIを使って取得しています。

今回使用したGo言語ではAWS SDKが提供されているのでGetCostAndUsageを呼び出すことによりAWSの使用料金を取得できます。具体的には以下のコードのように関数を作成し料金を取得できるようにしました。

package costexplorer

import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/costexplorer"
    "github.com/aws/aws-sdk-go/service/costexplorer/costexploreriface"
)

type CostExplorer struct {
    session costexploreriface.CostExplorerAPI
}

func NewCostExplorer(session costexploreriface.CostExplorerAPI) CostExplorer {
    return CostExplorer{
        session: session,
    }

}

func (c CostExplorer) GetCostForDaily(time_start string, time_end string, metrics []string) (*costexplorer.GetCostAndUsageOutput, error) {
    granularity := aws.String("DAILY")
    metric := aws.StringSlice(metrics)
    resp, err := c.session.GetCostAndUsage(&costexplorer.GetCostAndUsageInput{Metrics: metric, Granularity: granularity, TimePeriod: &costexplorer.DateInterval{Start: aws.String(time_start), End: aws.String(time_end)}})
    if err != nil {
        return nil, err
    }
    return resp, nil
}
func (c CostExplorer) GetCostDetail(time_start string, time_end string, metrics []string) (*costexplorer.GetCostAndUsageOutput, error) {
    granularity := aws.String("DAILY")
    metric := aws.StringSlice(metrics)
    group := costexplorer.GroupDefinition{Key: aws.String("SERVICE"), Type: aws.String("DIMENSION")}
    resp, err := c.session.GetCostAndUsage(&costexplorer.GetCostAndUsageInput{GroupBy: []*costexplorer.GroupDefinition{&group}, Metrics: metric, Granularity: granularity, TimePeriod: &costexplorer.DateInterval{Start: aws.String(time_start), End: aws.String(time_end)}})
    if err != nil {
        return nil, err
    }
    return resp, nil
}

気をつけるべき点

Lambdaのコードをアップロードする際の問題

最初はCFnを使って全てのインフラリソースを管理していました。しかし、Lambda部分をCFnでInfrastructure as code(IaC)化している最中、Lambdaのコードをアップロードする部分で問題が発生しました。CFnではLambda上で動かすコードをCFnに直接埋め込む形と、S3で管理する形があります。

前者はAWS::Lambda::FunctionリソースのCodeプロパティのZipFileで管理できます。

しかし全ての言語に対応しているわけではなく、AWS料金Botの作成時点ではNode.jsとPythonしか対応していませんでした。また、CFnにコードを埋め込むとファイルが肥大化し、インフラとアプリケーションのコードが混ざってしまうのでコードの管理が大変になります。
後者ではLambdaのリソース作成時点でS3に対象のファイルが存在していない場合はエラーになります。そのため、Lambdaの作成リソースとLambdaのコードを管理するS3 bucketの作成リソースを同じCFnで管理すると、S3 bucketの中身は空なので作成エラーが発生します。

上記の理由によりCFnを適用する前にS3 bucketを作成し、Lambdaで動かすコードを事前にアップロードする必要があります。また、コードの変更がある際は対象のファイルのobject versionをS3ObjectVersionに指定しなければならず、コードの変更がある毎にobject versionを確認してCFnに変更を加えなければなりません。

Serverless Application Modelの採用

前述の理由からCFnでLambdaを管理するのには限界がありました。代替の方法がないかチームの人と相談した結果、Serverless Application Model(SAM)を採用しました。

SAMはAWSがオープンソースで提供しているサーバレスアプリケーションを構築するためのフレームワークです。SAMの特徴は以下の点です。

  • SAMはCFnの拡張で、CFnに似たフォーマットなので学習コストが低い
  • Localで開発する際に便利な揃っている環境でLambdaのテストが可能
  • Serverless関連のリソースのデプロイが簡単になる

さらにSAMはSAM CLIが提供されています。変更セットの進行状況や変更セットのdiffが出力されるので、CIを組み込む場合でも親和性は高そうだなと思いました。

CloudFormationとServerless Application Modelの比較

具体的にCFnとSAMの相違点をまとめると下記の表で示す相違点があります。

項目 CloudFormation Serverless Application Model
デプロイ aws cliを用いてデプロイできるが複数コマンドを組み合わせる必要がある sam deployコマンドを用いてデプロイできる
変更セットの確認 AWSのコンソール上からしか確認できない CLI上に変更セットが出力される
テスト AWSのリソース上に上げないと確認できない Local環境でテストできる
Lambdaのコード管理 CFnに埋め込む or S3で管理(ユーザがversioningする必要有り) S3で管理(versionigはSAM側で行ってくれる)

上記の表の比較から、Lambdaで扱うコードの管理をよしなに行ってくれ、Localで開発する際に便利な機能が揃っているSAMを採用しました。

まとめ

数ヶ月運用してみたのですが、毎日見るチャンネルに使用料金が通知されるようになったので、朝会のタイミングでAWSの料金について話す機会が生まれチームの人がAWSの料金に気を配るようになりました。また、AWSのCost Explorerを閲覧する権限を持っていない人でも確認できるので余分な権限を渡す必要もありません。

このタスクを通してMA内で使われているAWSの料金を知ることができ、その後の開発の際に使われるリソースの料金体系を気にするようになりました。また、Infrastructure as codeを行う事で他のAWS環境へ導入する際に反映する時間が短くなり、かつコードとしてインフラの構成を残しておけるのでInfrastructure as codeはアプリケーションを開発する上で必要であると感じました。

最終的にこのBotは間接的ですが会社で運用しているサービスの無駄なコストを省けるようになり、その分キャンペーンや企画にコストをかけられ、ユーザーにより質の良いサービスを提供できたと思います。

今回採用したSAMはまだまだ開発途上です。最近はAWS Step Functionsに対応しました!今後のアップデートに期待したいです。

最後に

ZOZOテクノロジーズではより良いサービスを提供するための基盤作りを開発したい仲間を募集中です。以下のリンクからご応募ください。

tech.zozo.com

カテゴリー