八雲文庫ブログ

組版できる新感覚の小説・画像投稿サイト「八雲文庫」の運営日誌です。

AWSの利用料を定期的にSlackに通知する仕組みをTerraformで自動構築

サービスを運営しているとどうしても気になるのが運営費。特にクラウドサービスを使っていると料金体系が従量課金なので時間/日単位で請求額が増えるので気がつくと予定以上の支払いとなることもあります。自分としては現状気にする程ではないのですが、最近使い始めたSlackに定期的に通知してくれる機能があればなぁ、ということで自作することにしました。自作したもう1つの動機としてはFaaS(Function as a Service)が様々な用途での発展が期待できそうなので今後も見据えて触れておきたかったという気持ちも大きかったりします。

github.com

Githubの方に説明は書いていますがGoogle翻訳に頼りきった拙い英語なので改めて記事の中で解説します。Githubで公開したソースはあくまでAWSの利用料のみ通知する仕組みにしていますが、実際自分で使っているものはAWS以外のサービスの利用料も取得して併せてSlackに通知するようにしています。当然ですがそうした場合は当該のサービスも外部から請求額を取得できることが前提となります。

フロー概要

f:id:yakumobooks:20180505000931p:plain

  1. CloudWatch EventsにLambda関数の定期実行を設定
  2. Lambda関数で事前に暗号化されたSlackの着信WebHookのURLを復号化
  3. Cost Explorer APIを利用してサービスごとの料金を取得しSlackにメッセージを送信

Terraformで上記構成をAWS上に自動で構築するわけですが、その時に重要になるのが図には出てこない太字部分<Slackの着信WebHookのURLをKMSで暗号化する作業>になります。TerraformのソースでいうとData Source: aws_kms_ciphertextを使用している部分にあたります。plaintext引数に文字列を設定しciphertext_blob属性で暗号化された文字列を取得することができます。KMSに暗号化・復号化用のマスターキー (CMK)を作るわけですが、このキーの管理コストが月に$1かかります。着信WebHookのURLがそこまでセキュアな情報ではなくURLがLambda上で平文で管理されることを許容できるのであればKMSを使わない選択もあるかと思います。ただ、アクセスキーやパスワードといった機密情報をLambdaで扱う要件があるのであればKMSは使ったほうが良いでしょう。

Labmdaに必要な権限

  • KMSによる復号化
  • CloudWatch Logsへの出力
  • Cost Explorer APIからの利用料の取得

上記を可能にするポリシーはTerraformで表すと以下のようになります。

data "aws_iam_policy_document" "lambda_slack_policy" {
  statement {
    sid       = "1"
    actions   = ["kms:Decrypt"]
    effect    = "Allow"
    resources = ["${aws_kms_key.key.arn}"]
  }

  statement {
    sid       = "2"
    actions   = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]
    effect    = "Allow"
    resources = ["arn:aws:logs:*:*:*"]
  }

  statement {
    sid       = "3"
    actions   = ["ce:GetCostAndUsage"]
    effect    = "Allow"
    resources = ["*"]
  }
}

Slackにメッセージを通知するPythonプログラム

AWSの管理コンソールから「Lambda>関数>関数の作成」に進み、「設計図」から「cloudwatch-alarm-to-slack-python3」を選ぶとSlackにメッセージを送信するPython3.6プログラムのテンプレートが入手できます。今回作成したソースもそちらをベースに作成しました(ちなみにPythonをちゃんと書いたのはこれが初めてです)。テンプレートの方ではSlackに送るメッセージにタイトルと本文しか設定していませんが、Slackではメッセージに豊富な装飾が可能ですので詳しくはSlackの公式ドキュメントをご参照ください。

api.slack.com

AWSの料金の取得には昨年末(2017年12月)から使えるようになったCost Explorer APIを使用します。クラスメソッド株式会社様の以下の記事が参考になるかと思います。

Cost ExplorerのAPIの提供が開始されました | Developers.IO

Python3で書いた場合は以下のようになりました。

    ce = boto3.client('ce')
    ce_res = ce.get_cost_and_usage(
        TimePeriod = {
            'Start': '<開始日>',
            'End'  : '<終了日(指定した日は含まない)>'
        },
        Granularity = 'MONTHLY',
        Metrics     = ['BlendedCost', 'UnblendedCost'],
        GroupBy     = [{
            'Type': 'DIMENSION',
            'Key' : 'SERVICE'
        }]
    )

上記スクリプトで開始日と終了日(の前日まで)の期間の各サービスの使用料金が取得することができます。集計単位は月(MONTHLY)か日(DAILY)のいずれかを指定します。今回は使いませんでしたがfilterパラメータを使用すると複雑な条件式でフィルタリングすることもできます。詳しくはAPIドキュメントを参照してください。

CostExplorer — Boto 3 Docs 1.7.12 documentation

ドキュメントを読むと結果が複数ページに渡る場合があるようで、その場合は次ページの結果セットを取得するためのトークンを使ってリクエストを再送する必要があるようです。

メトリクスには"BlendedCost" , "UnblendedCost" , "UsageQuantity"の3つを指定できます。UsageQuantityは少し特殊でそのままだと異なる単位(時間やByteなど)を纏めて集計した結果を返そうとしてくるので使用する際はフィルターと組み合わせて単位が混ざらないようする必要があるようです。

メトリクス 意味
BrendedCost 無料枠の分配やボリュームディスカウントの適用によって発生するブレンド価格で算出された利用料金
UnblendedCost AWSサイト内に掲載されている通常価格で算出された利用料金
UsageQuantity 利用時間やデータ転送などリソースによって異なる単位の利用量

ちなみにAWSの利用料はCost Explorer API以外にもCloudWatchのAWS Billing and Cost Management メトリクスからも取得できますが、取得できる項目はかなり限られます。

AWS Billing and Cost Management のディメンションおよびメトリクス - Amazon CloudWatch

ネット上ではAWSの使用料を取得する方法としてCloudWatchのメトリクスを使われている方が多いようですが、(Cost Explorer APIが比較的最近使えるようになったというのもあるとは思いますが)それもそのはず、なんとCost Explorer APIは呼び出すたびに$0.01取られます。前述のKMSの料金も合わせると、毎日料金を通知したら現在のレートで140円程かかります。ということで間違っても大量にAPIを呼び出してしまわないよう注意しましょう。

f:id:yakumobooks:20180506022954p:plain

開発のために何回か呼び出していたら既に$0.1を超えていました。

最後に

今回のサーバーレス構成は料金通知以外にも様々な応用が可能ですので是非ご活用頂ければと思います。そして運営しているサービスへのアカウント登録を宜しくお願いします(ダイレクトマーケティング)。