12th LAB

GASでAWS Cost Explorerから料金を取得する

ゴール

自動でAWSの月額コストを取得して、Google スプレッドーシートに書き込むということをやる

image block

やってみた結果

Slackで取得してもよかったが、過去のコスト情報として残しておきたかったのでGoogleスプレッドシートを使用した。

認証部分は先人のライブラリを使うことでシュッとできたが、APIの引数の取得が大変だった。

手順

1. Googleスプレッドシートを用意します

手順は割愛

2. Apps Scriptの画面を開く

手順は割愛

3. aws.gsファイルを作成し、コードを貼り付ける

aws.gsファイルを作成します。

image block

aws.jsファイルの中身をコピーします。

aws.gsファイルに貼り付けます。

image block

4. テスト関数を設定する

まずはテスト関数を作成し、AWSの認証と情報取得ができることを確認します。

ここが上手く行かないと、AWS Cost Explorerの細かいパラメータ作成ができません。

main.gsファイルを作成し、下記(テスト関数)をコピー&ペーストします。

main.gs

function test(){
  const awsAccessKey = PropertiesService.getScriptProperties().getProperty("AWS_ACCESS_KEY");
  const awsSecretKey = PropertiesService.getScriptProperties().getProperty("AWS_SECRET_KEY");

  AWS.init(awsAccessKey, awsSecretKey)
  
  const res = AWS.request(
    'ce',
    'us-east-1',
    'AWSInsightsIndexService.GetCostAndUsage',
    {},
    'POST',
    {"TimePeriod": {"Start": "2023-06-01", "End": "2023-07-01"}, "Granularity": "MONTHLY", "Metrics": ["BlendedCost"]},
    {"Content-Type": "application/x-amz-json-1.1"},
  )

	const code = res.getResponseCode()
  const text = res.getContentText();
	Logger.log(code)
  Logger.log(text)
}

5. スクリプトプロパティを設定します

スクリプトプロパティを設定します。

プロパティ
AWS_ACCESS_KEY AWSのIAMから取得したものを設定
AWS_SECRET_KEY AWSのIAMから取得したものを設定

image block

6. テスト実行

テスト関数を実行します

実行したあと、下記のようなログが出力されれば正常に情報が取得されています。

そして Amount に金額は入っています。

image block

ここまででAWSの認証とAWS Cost Explorerから情報を取得できました。

7. Cost Explorerのパラメータを作成する

今度はAWS Cost Explorerのパラメータを作成します。

先ほどのテスト関数は先月分の全体の金額を取得するものでした。

今回は特定のタグがついたリソースの金額を取得することを目標とします。

このパラメータ作成はこちらの記事がとても参考になります。

この記事で紹介されているパラメータの確認の方法はAWS CLIのログから確認するという方法です。まずはAWS CLIで特定のタグが付いたリソースの金額を取得してみます。

TAG-VALUEには実際のタグの値を入れてください

aws ce get-cost-and-usage --time-period Start="2023-06-01",End="2023-07-01" --granularity MONTHLY --metrics "BlendedCost" "UnblendedCost" --group-by Type=TAG,Key=Customer --filter '{"Tags": { "Key": "Customer", "Values": ["TAG-VALUE"] }}' --debug

ログの一部

2023-07-11 14:44:31,108 - MainThread - botocore.endpoint - DEBUG - Making request for OperationModel(name=GetCostAndUsage) with params: {'url_path': '/', 'query_string': '', 'method': 'POST', 'headers': {'X-Amz-Target': 'query_string', 'Content-Type': 'application/x-amz-json-1.1', 'User-Agent': 'aws-cli/2.9.21 Python/3.9.11 Darwin/21.6.0 exe/x86_64 prompt/off command/ce.get-cost-and-usage'}, 'body': b'{"TimePeriod": {"Start": "2023-06-01", "End": "2023-07-01"}, "Granularity": "MONTHLY", "Metrics": ["BlendedCost", "UnblendedCost"], "GroupBy": [{"Type": "TAG", "Key": "Customer"}]}', 'url': 'https://ce.us-east-1.amazonaws.com/', 'context': {'client_region': 'aws-global', 'client_config': <botocore.config.Config object at 0x7fab5825a730>, 'has_streaming_input': False, 'auth_type': 'v4', 'signing': {'region': 'us-east-1', 'signing_name': 'ce'}}}

パラメータ一覧

引数番号 実際の値 引数の値の特定方法
1. サービス ce ログ中の url のサブドメインを参照
2. リージョン us-east-1 ログ中の url のサブドメインを参照
3. アクション AWSInsightsIndexService.GetCostAndUsage ログ中の headers の X-Amz-Target を参照
4. パラメータ {} ログ中の query_string を参照 (多分)
5. メソッド POST ログ中の method を参照
6. ペイロード {"TimePeriod": {"Start": 2023-06-01, "End": 2023-07-01}, "Granularity": "MONTHLY", "Filter": {"Tags": {"Key": "Customer", "Values": ["TAG-VALUE"]}}, "Metrics": ["BlendedCost", "UnblendedCost"], "GroupBy": [{"Type": "TAG", "Key": "Customer"}]} ログ中の body を参照

TAG-VALUEには実際のタグの値を入れてください
7. ヘッダ {"Content-Type": "application/x-amz-json-1.1"} ログ中の headers の Content-Type を参照
8. パス / ログ中の url_path を参照 (多分)

※この表は このサイト を参考に作成しております。

パラメータが確認できたので、あとはこれをGASで使えるように形式を整えます。

整えたら、テスト関数の該当箇所と入れ替えます。

最終的なコード

function test(){
  const awsAccessKey = PropertiesService.getScriptProperties().getProperty("AWS_ACCESS_KEY");
  const awsSecretKey = PropertiesService.getScriptProperties().getProperty("AWS_SECRET_KEY");

  AWS.init(awsAccessKey, awsSecretKey)
  
  const res = AWS.request(
    'ce',
    'us-east-1',
    'AWSInsightsIndexService.GetCostAndUsage',
    {},
    'POST',
    {"TimePeriod": {"Start": costStart, "End": costEnd}, "Granularity": "MONTHLY", "Filter": {"Tags": {"Key": "Customer", "Values": ["TAG-VALUE"]}}, "Metrics": ["BlendedCost", "UnblendedCost"], "GroupBy": [{"Type": "TAG", "Key": "Customer"}]},
    {"Content-Type": "application/x-amz-json-1.1"},
    '/'
 )

	const code = res.getResponseCode()
  const text = res.getContentText();
	Logger.log(code)
  Logger.log(text)
}

これで特定のタグが付いたリソースの先月分の金額が取得できました。

8. 取得したデータの取り出し

今度は取得したデータを取り出せるようにしていきます。

参考にしたサイトではXMLで取得されると書かれていましたが、私の環境ではJSONで取得されました。私はここで30分ハマったので気をつけてください。

// resにレスポンスデータが入っている
const code = res.getResponseCode();
const text = res.getContentText();

// JSON
const data = JSON.parse(text);

// BlendedCostの値を取得する
monthCost = Math.floor(data.ResultsByTime[0].Groups[0].Metrics.BlendedCost.Amount * 100) / 100;
Logger.log(monthCost)
  
return monthCost

これで金額を取り出すことができました。

9. スプレッドシートへの書き込み

あとはスプレッドシートへ書き込みするだけです。

これは他にもたくさん情報がでているのでそちらを見てください

ハマったところ

一番ハマったのが、他の記事ではXMLで取得されていると紹介されていましたが、実際はJSONで取得されているところでした。

ここに行き着くまでにわりと時間がかかりました。

const data = JSON.parse(text);

最後に

コスト情報は経理なども確認したい内容だと思うので、GASでコストデータを取得するというのはわりとありな気がしています。

実際はAWSマネジメントコンソールの権限を経理に付与するか、このような形でGoogleスプレッドシートで過去の金額含めて確認できるようにするか、Slack Botでピンポイントの値が確認できるようにするか、といくか方法が考えられます。

人や状況に合わせて最適なものを選択していきましょう。