AWSの請求を日次でLambda for RubyでSlackに通知する

先行事例をググると Python の例が多かったので、[Ruby][doc] で手でポチポチやったときの最低限の設定メモ。


# 1. AWS Lambda をランタイム Ruby で作成

WebのLambdaコンソール画面からボタンポチポチで作成

- ランタイムを `Ruby 2.7` で
- 実行ロールに `ce:GetCostAndUsage` 付与
    - [Cost Explorer の権限は root で有効にすると出てくる][ce_auth]はず

# 2. Rubyで取得処理を書く

```ruby:lambda_functions.rb
require 'aws-sdk'
require 'net/http'
require 'uri'
require 'json'

# Lambdaのエントリーポイント
def lambda_handler(event:, context:)
  fetch_cost
    .then { |response| pretty_response(response) }
    .then { |message| notify_slack(message) }

  # 一応HTTPレスポンスとして返す
  { statusCode: 200, body: 'ok' }
end

# 請求情報の取得
def fetch_cost(time = Time.now)
  # 認証は既にすんでるのでcredentialsいらない(後述)
  client = Aws::CostExplorer::Client.new(region: 'us-east-1')

  client.get_cost_and_usage(
    time_period: {
      start: Date.new(time.year, time.month, 1).strftime('%F'),
      end: Date.new(time.year, time.month, -1).strftime('%F'),
    },
    granularity: 'MONTHLY',
    metrics: ['AmortizedCost'],
    group_by: [ { type: "DIMENSION",key: 'SERVICE' }]
  )
end

# APIの返り値の整形
def pretty_response(res)
  # 省略。適当に
end

# Slackに投稿
def notify_slack(message)
  # incoming Webhook設定したURLを環境変数にセット
  uri = URI.parse(ENV['SLACK'])
  params = { text: message }
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  res = http.start do
    request = Net::HTTP::Post.new(uri.path)
    request.set_form_data(payload: params.to_json)
    http.request(request)
  end

  unless res.is_a? Net::HTTPSuccess
    raise 'Failed POST Slack'
  end

  res
end
```

[省略なしの全ソース(gist)はこちら](https://gist.github.com/oieioi/46c46cca4af3823082bbfb43feb04dd3)。やってることは[Develpers.IOの先行事例のPythonでの取得処理][classmethod]とだいたい同じです。

見たいものを変えたい場合は [`Aws::CostExplorer::Client#get_cost_and_usage` に渡すもの][api_doc]を変えたり、 `pretty_response` をよしなにすればいいです。

# 3. CloudWatch Events で起動時間を設定

1. Lambdaの関数設定画面の「トリガーを追加」
2. 「EventBridge（CloudWatch Events)」を選択
3. cron と同じように設定できる(参考: [AWS_Cron式のワイルドカード][cron])

# IAM認証でハマったとこ

Lambda関数実行時のIAM認証をどこから取るのかわからなかったが、 aws-sdk gem 使ってるなら何もしなくても取り終えてる。理由は以下。

- [Lambdaの実行環境では環境変数 (`ENV`) に `AWS_ACCESS_KEY_ID` と `AWS_SECRET_ACCESS_KEY` が設定される][ENV]。
- [aws-sdk gem は上の環境変数を勝手に読み込んで認証取る][rdoc]。
- なので何もしなくていい。

最初これがわからなくて実行用のロールと別にIAMを作ってそのアクセスキーとパスを使ってたので無駄だった。


# 参考にしたウェブページ

- [AWSサービス毎の請求額を毎日Slackに通知してみた - Developers.IO][classmethod]
    - Pythonの例。こちらをRubyに移植した感じです。
- [Ruby による Lambda 関数のビルド - AWS Lambda 開発者ガイド][doc]
    - AWS Lambda の公式 Ruby ドキュメント。
- [AWS Lambda 環境変数の使用 - AWS Lambda 開発者ガイド][ENV]
    - Lambda の環境変数について。最初からいくつもセットされてる。
- [AWS SDK for Ruby - Version 3 API Document][rdoc]
    - aws-sdk gem の公式APIドキュメント。
- [Aws::CostExplorer::Client#get_cost_and_usage API Document][api_doc]
    - aws-sdk の CostExplorer#get_cost_and_usage の公式APIドキュメント。
    - 引数次第で日次請求額とかいろいろとれる。
- [AWS_Cron式のワイルドカード][cron]
    - AWS の cron の `?` 知らなかった。
- [アクセス許可の管理の概要 - AWS 請求情報とコスト管理][ce_auth]
    - CostExplorer を IAM ロールでさわれるようにする設定。

[doc]:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-ruby.html
[ENV]:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-envvars.html#env_limits
[classmethod]:https://dev.classmethod.jp/articles/notify-slack-aws-billing/
[rdoc]:https://docs.aws.amazon.com/sdk-for-ruby/v3/api/#Configuration
[cron]:https://qiita.com/da-sugi/items/ef3bb45a8a99a4acacb1
[ce_auth]:https://docs.aws.amazon.com/ja_jp/awsaccountbilling/latest/aboutv2/control-access-billing.html
[api_doc]:https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CostExplorer/Client.html#get_cost_and_usage-instance_method