DSOC Infrastructure Group の 大澤 です。
最近自身のあまりの不健康ぶりにさすがに危機感を抱いたので、自宅から徒歩10分にある24時間営業のフィットネスクラブに申し込んできました。過去何回か挫折しているので今度こそ継続したいものです。
さて、突然ですが、皆さんは AWS や GCP などのクラウドサービスを使っていますか? 使っている人は毎月どのぐらいお金がかかっているか把握していますでしょうか? おそらく個人利用している人は毎月の利用料金についてシビアに見ている人は多いと思いますが、会社や団体などで使っている人はそこまで見ていない人が多いんじゃないかなと思います。私のタスクの一つに AWS、GCP のコスト管理があり、毎月の支払い額の把握や EC2 リザーブドインスタンスの購入計画、翌月の費用予測などをしています。
DSOC では AWS と GCP のマルチクラウドを採用しており、名刺データ化システム GEES に加え、名寄システム、新規事業も増え、利用するクラウドサービス・人員増加にともなって(金銭的)コストも増加傾向にあります。意図せぬ出費がないか日々ウォッチしていますが、スピードを求めるために少々のコストを犠牲にすることもしばしばあるため、結果として日々の積み重ねで増えていきます(私のお腹まわりのようですね)。
そこでコスト削減(ダイエット)を図るべく、開発者、R&D、データエンジニア、インフラエンジニアが一丸となってコストに向き合う、コスト削減プロジェクトが立ち上がりました。このプロジェクトは個々のメンバーにコスト意識を持ってもらうことと、コスト削減の2つを目的としています。DSOC 内の各グループから一人ずつ担当者をアサインしてもらい、それぞれ目標削減額を設定し、半年という短い期間で削減するための施策を打ち、その達成をめざします(今まさにすすめている最中です)。
コスト可視化における課題
プロジェクトのメイン担当である私が最初に取り組んだことがコストの可視化でした。コストを正確に把握することができなければ、何から始めればいいか分からず、ただ闇雲に取り組んだとしても費用対効果は小さいものになります。
今までは AWS Cost Explorer(以下、Cost Explorer)や、コスト配分タグによるタグ別のコスト算出を行ってきましたが、プロジェクトをすすめるにあたって以下の問題点がありました。
- どこにお金が多くかかっているかを深く調査できない
- 各サービス・グループの正確なコストがわからない
- AWS、GCP を横断したコストを把握しづらい
1 は Cost Explorer から EC2 や RDS などクラウドサービス別やインスタンスタイプ別にコストを知ることはできますが、リソースレベル(どのリソースか)でオペレーションレベル(どんな操作をしたのか)のコストを見ることはできません*1。ただなんとなく EC2 が高い、DynamoDB が高いことがわかってもどのリソースでどういったことをしたときが高いのかが分からず推測ベースになってしまいます。
2 は歴史的経緯から 1つの AWS アカウントに複数のサービス・システムが混在しているため、コスト配分タグによってタグ別にコスト算出をしています。タグ付けはおおよその範囲をカバーしていますが、完全とはいえずタグに紐付いていない「見えないコスト」が存在していました。また、今回のプロジェクトの目的の一つである、個々のメンバーのコスト意識を持つためにグループ単位の可視化が必要でした。
3 は AWS と GCP の請求金額を確認できる画面が別々であるため、マルチクラウドで運用している場合、全体コストが見えづらくなります。また、Cost Explorer で複数の AWS アカウントをまたいで見る場合は、支払い AWS アカウント(マスターアカウント)で見る必要があるのですが、マスターアカウントは経理など一部の人しかアクセスできないようにしているため、プロジェクトメンバーへの展開は難しいという事情があります。
BI ツールの要件
コスト可視化をするにあたり、以下の要件を満たす必要があります。
- AWS・GCP を横断したサービス・グループ毎のコストを可視化できる
- リソースレベル・オペレーションレベルでコストを追うことができる
- なるべくコストをかけたくない
- プロジェクトメンバーにとって馴染みやすい
1と2は先ほど説明した通りです。3と4は、利用者がほぼプロジェクトメンバーに限られ、個々によって利用頻度が異なるため、高価な商用ツールは導入へのハードルが高いです。また、プロジェクト期間が短めであるため、新ツール導入における学習コストをかけることは望ましくありません。
Redash の採用
様々な BI ツールを選定した結果、Redash を採用しました。採用理由は以下のとおりです。
- コストが安価
- AWS、GCP の請求データをクエリで取得でき、ダッシュボードが作成できる
- DSOC メンバーが操作に慣れている
DSOC ではエンジニア・非エンジニアに関わらず、名刺のデータ化状況や KPI、調査目的に Redash を利用しています。オープンソース版 Redash を EC2 インスタンスにホスティングしているため、ユーザ追加などによる追加料金は発生せず、かかる料金は EC2 インスタンスのみです。さらに Redash は多くのデータソースと連携ができ、今回採用した Amazon Athena と BigQuery に対応していることが決め手となりました。
ダッシュボード
Redash は保存したクエリに対してグラフを作成しダッシュボードに貼ることができます。
実際のダッシュボードはこのようになっています。これはあるサービスのダッシュボードです。
サービス全体の費用や AWS サービス別・日別の推移が見れます。
また、ピボットテーブルでリソース・オペレーションレベルで可視化することができるため、どこに多く費用がかかっているかがわかりやすくなりました。
GCP も同じダッシュボード画面に表示させています。AWS と同様にサービス全体費用、GCP サービス別・日別の推移を出しています。また、リソースにラベル(後述)を付与することでアプリケーション別の料金推移も表示させています。
GCP でもピボットテーブルでオペレーションレベルの明細を出すようにしていますが、残念ながらリソース ID が取れていないため AWS と比較して情報量が落ちます。
Redash と AWS、GCP との連携
AWS、GCP における請求データのエクスポートおよび Redash との連携方法について説明します。全体像は以下の図となります。
AWS Cost and Usage Report
AWS Cost and Usage Report(以下、CUR)によって S3 バケットに請求データがアップロードされます。Amazon Athena(以下、Athena)からクエリ実行できるようにし、Redash のデータソースに Athena を追加することで、Redash から請求データをクエリできるようになります。
CUR では、請求データが配信される S3 バケットはマスターアカウントが所有している必要があり、Athena も同じ AWS アカウントからクエリ実行する必要があります。しかし、諸般の事情で Athena を実行するのは子の AWS アカウントからにするため、2つ工夫を施しました。
1つ目は AWS Glue(以下、Glue)です。Athena で CUR を利用するには、Glue データベース、Glue クローラを設定し Athena でテーブル作成する必要があります。AWS から提供されている CloudFormation テンプレートを使うことでこれらが簡単に設定できます。しかし、CloudFormation テンプレートだとマスターアカウントで設定を行うことになります。そのため、CloudFormation テンプレートを使わず、テンプレートの内容に沿って 子 AWS アカウントで Glue を設定しました。このとき、Glue の IAM サービスロールでマスターアカウントに Assume Role でアクセスできるようにします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::<ビリング用S3バケット>/*" ] }, { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam::<マスターAWSアカウントID>:role/billing" ] } ] }
2つ目は請求データファイル(S3 オブジェクト)のアクセス権限です。CUR から S3 バケットに配信する際、S3 オブジェクトの所有者はマスターアカウントではなく CUR 自身になります。Athena には Glue のように Assume Role でアクセスできる仕組みがないため、子 AWS アカウント の Athena からクエリ実行するには S3 バケットポリシーだけでは不十分でした。そこで、S3 オブジェクトが配信されたら AWS Lambda(以下、Lambda)を実行して S3 オブジェクトに子 AWS アカウントの Read 権限を付与するようにしました。一日数回の配信なので Lambda 実行にかかるコストは微々たるものになります。
AWS リソースのタグ付け
前述したように、サービス・所属グループ毎のコストを可視化するにあたって、すでにサービス別に AWS アカウントが別れている場合を除いてリソースのタグ付けが必要です。しかし、すべての AWS リソースに対して正確なタグ付けをしていくのは大変時間がかかってしまいます。幸いなことにサービス・所属グループに由来した AWS リソース名(もしくは Name タグ)が付けられていたため、ルールに則ってリソース名からサービス・所属グループのタグを自動的に付与することにしました。例えばプレフィックスに「gees」とついていたら Service:GEES
、「randd」とついていたら Service:Randd
といった具合です。これらは簡単な例ですが、実際には様々なパターンに則ってタグを付けていきます。対象となる AWS サービスは EC2(EBS、AMI、EBS スナップショット、ELBを含む)、RDS(スナップショットを含む)、S3 といったメジャーなものから Glue、CloudWatch Logs など見逃しがちなサービスまでカバーしています。これを Lambda で日次実行しています。ステージング・開発用の AWS アカウントもタグ付けの対象になるため、Assume Role で複数の AWS アカウントにあるリソースにアクセスするようにしています。
しばしばタグ付けルールに入らないリソースが出てくるため、Redash ダッシュボードでタグ付けされていない割合とリソース一覧を見れるようにし、定期的にチェックすることで漏れを減らしていくようにしています。
Amazon Aurora クラスタのタグ付けについて
タグ付けをしていく中で頭を悩ませたのが Amazon Aurora クラスタ(以下、Aurora クラスタ)です。AWS CLI やマネジメントコンソールから Aurora クラスタ にタグ付けはできるのですが、CUR を使うにあたって以下の問題が生じました。
- CUR の
lineItem/ResourceId
に Aurora クラスタのリソース ID が表示される - Aurora クラスタがコスト配分タグに対応していない
CUR の lineItem/ResourceId に Aurora クラスタのリソース ID が表示される
lineItem/ResourceId に AWS リソース ID が表示されます。EC2 インスタンスであれば インスタンスID、S3 であれば S3 バケット名 が該当しますが、Aurora クラスタの場合はクラスタ名ではなく、Aurora クラスタに紐付いたリソース ID となります。
例えば、foo-cluster
という Aurora クラスタの場合、CUR には arn:aws:rds:ap-northeast-1:123456789012:cluster:cluster-anm724oxm5beoil5j5ptbgbihm
というように我々が認識できるクラスタ名に紐付かないため、CUR だけでは何の Aurora クラスタなのかを知ることはできません。
Aurora クラスタがコスト配分タグに対応していない
Aurora クラスタ ID の CUR レコードを見ると、コスト配分タグが記録されておらず、タグによるコスト算出ができなくなります。サポートに問合せしましたがバグではなく仕様とのことで対応時期は未定とのことでした。
解決策
この問題の解決策として、Aurora クラスタ名と リソース ID をマッピングした CSV ファイルを S3 に配置し、Athena で CUR とテーブルジョインすることにしました。具体的には、以下のようなファイルを日次で Lambda 実行して作成します。1レコードに Aurora クラスタ名、リソースIDに加えて AWS アカウント ID とコスト配分タグを含めます。
account_id | db_cluster_identifier | db_cluster_resource_id | service_tag |
---|---|---|---|
123456789012 | foo-cluster | arn:...(省略)...:cluster-abcd | foo |
123456789012 | bar-cluster | arn:...(省略)...:cluster-efgh | bar |
Athena で CUR と同じデータベースでテーブルを作成しておき、Redash からクエリ実行するときには CUR のテーブル名とこのテーブルをジョインすれば Aurora クラスタ名でタグに紐付いたコストを取得することができます。
例えば、このようなクエリを実行することで、ある Aurora クラスタの費用を出すことができます。
SELECT SUM(line_item_unblended_cost) AS cost FROM "aws_billing"."billing_cur_athena" LEFT JOIN rds_aurora_cluster_resource -- Aurora クラスタのマッピングテーブル ON billing_cur_athena.line_item_resource_id = rds_aurora_cluster_resource.db_cluster_resource_id WHERE year = '2019' AND month = '11' AND db_cluster_identifier = 'foo-cluster';
GCP(Cloud Billing)
GCP(Cloud Billing)は請求データを BigQuery にエクスポートすることができます。自動的に日毎にパーティションされており、必要に応じて保存期間を設定できます。
Redash のデータソースに BigQuery を追加することで、Redash から請求データをクエリできるようになります。
ラベリング
GCP にはラベルという機能があり、AWS 同様にラベル別にコストを算出することができます。ラベルはリソースだけではなく GCP プロジェクトに対しても付けることができます。DSOC ではサービス・チーム別に GCP プロジェクトを分けており、GCP プロジェクトにラベルを付けています。そのため、リソースにラベルを付与しなくてもコスト算出することができます。
下図は Resource Manager から GCP プロジェクトにラベルをつけています(モザイクばかりですいません・・・なんとなく雰囲気だけ感じ取ってください)。
例えば、service:foo
というラベルをつけた GCP プロジェクト別の費用を取得するには以下のようなクエリを実行します。
SELECT project.id AS project_id, -- 通常料金にクレジット、丸め誤差を加えた合計 SUM(cost) + SUM(IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0)) AS jpy FROM `<project_id>.<table>` INNER JOIN UNNEST(project.labels) AS plabels ON plabels.key = "service" AND plabels.value = 'foo' WHERE invoice.month = '201911' GROUP BY project_id ORDER BY jpy DESC;
終わりに
本記事では、AWS と GCP のコスト削除プロジェクトをすすめるにあたり Redash を採用し、 AWS や GCP におけるタグ(ラベル)別にコストを可視化するための方法や工夫を紹介しました。次回は(プロジェクトが成功すれば)コストを削減するためにやってきた方法を紹介する予定です。
*1:最近のアップデートで AWS Cost Explorer からもリソースレベルで把握できるようになりました。
https://aws.amazon.com/jp/about-aws/whats-new/2019/11/aws-cost-explorer-supports-hourly-resource-level-granularity/