はじめに
Google Kubernetes Engine(GKE)は、Google Cloud Platform(GCP)で提供されているKubernetesのマネージドサービスです。GKEの日々の課金額は、GCPの料金レポートを見れば確認することができます。しかし、このGKEの課金額は名前空間(namespace)ごとに分かれていないため、特定の名前空間に想定以上の料金がかかっているか否かを監視できません。そこでこの問題を解決するために、GCPの公式の機能を活用し、GKEの名前空間ごとの課金額をBigQueryにより算出しました。
BigQueryには予め、GCPの詳細な課金データとGKEのリソースデータをエクスポートしておく必要があります。データのエクスポートには、公式で用意されている機能を利用します。
Cloud Billing データを BigQuery にエクスポートする
クエリ
以下が、GKEの名前空間ごとの課金額を算出するクエリです。プロジェクトIDやテーブル名は、ご自身のものに置き換えてください。
DECLARE target_project_id STRING; DECLARE target_timezone STRING; DECLARE target_start_date DATE; DECLARE target_end_date DATE; DECLARE partitiontime_start_limit DATE; DECLARE partitiontime_end_limit DATE; DECLARE start_time_limit TIMESTAMP; DECLARE end_time_limit TIMESTAMP; # 対象プロジェクト SET target_project_id = "cluster-gcp-project"; SET target_timezone = "Asia/Tokyo"; # 対象期間開始日 -- example: PARSE_DATE("%F", "2021-04-01") SET target_start_date = DATE_ADD(DATE_TRUNC(CURRENT_DATE(target_timezone), MONTH), INTERVAL -2 MONTH); # 対象期間終了日 -- example: PARSE_DATE("%F", "2021-04-30") SET target_end_date = LAST_DAY(CURRENT_DATE(target_timezone)); # パーティション開始日 SET partitiontime_start_limit = DATE_ADD(target_start_date, INTERVAL -1 DAY); # パーティション終了日 SET partitiontime_end_limit = target_end_date; # リソース使用開始日時 SET start_time_limit = TIMESTAMP(target_start_date, target_timezone); # リソース使用終了日時 SET end_time_limit = TIMESTAMP(target_end_date, target_timezone); WITH billing_sku_costs AS ( -- 課金データのテーブルから、Stock Keeping Unit(SKU)ごとの課金額と使用量を取得 SELECT sku.id AS sku_id, ANY_VALUE(sku.description) AS sku_description, SUM(cost) AS cost, ANY_VALUE(currency) AS currency, SUM(usage.amount) AS usage_amount, ANY_VALUE(usage.unit) AS usage_unit FROM `billing-gcp-project.billing-dataset.billing-table` WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND usage_start_time >= start_time_limit AND usage_end_time <= end_time_limit AND project.id = target_project_id GROUP BY sku_id ORDER BY sku_id ), billing_sku_credits AS ( -- 課金データのテーブルから、SKDごとの割引額を取得 SELECT sku.id AS sku_id, ANY_VALUE(sku.description) AS sku_description, SUM(credits.amount) AS credit_amount, ANY_VALUE(currency) AS currency, FROM `billing-gcp-project.billing-dataset.billing-table` CROSS JOIN UNNEST(credits) AS credits WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND usage_start_time >= start_time_limit AND usage_end_time <= end_time_limit AND project.id = target_project_id GROUP BY sku_id ORDER BY sku_id ), gke_ns_sku_usage AS ( -- GKEリソース使用量のテーブルから、日付・名前空間・SKDの組み合わせに対する使用量を取得 SELECT DATE(start_time, target_timezone) AS start_date, namespace, sku_id, SUM(usage.amount) AS usage_amount, ANY_VALUE(usage.unit) AS usage_unit, resource_name, cluster_name, cluster_location FROM `cluster-gcp-project.usage-metering-dataset.gke_cluster_resource_usage` WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND start_time >= start_time_limit AND end_time <= end_time_limit GROUP BY start_date, namespace, sku_id, resource_name, cluster_name, cluster_location ), gke_sku_usage AS ( -- GKEリソース使用量のテーブルから、SKDの使用量を取得 SELECT sku_id, SUM(usage.amount) AS usage_amount, ANY_VALUE(usage.unit) AS usage_unit FROM `cluster-gcp-project.usage-metering-dataset.gke_cluster_resource_usage` WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND start_time >= start_time_limit AND end_time <= end_time_limit GROUP BY sku_id ) -- 日付・名前空間・SKDの組み合わせに対する課金額を計算 SELECT gke_ns_sku_usage.start_date, gke_ns_sku_usage.namespace, gke_ns_sku_usage.sku_id, (billing_sku_costs.cost + IFNULL(billing_sku_credits.credit_amount, 0)) * (gke_sku_usage.usage_amount / billing_sku_costs.usage_amount) * (gke_ns_sku_usage.usage_amount / gke_sku_usage.usage_amount) AS namespace_sku_billing, billing_sku_costs.sku_description, gke_ns_sku_usage.resource_name, gke_ns_sku_usage.cluster_name, gke_ns_sku_usage.cluster_location FROM gke_ns_sku_usage LEFT JOIN gke_sku_usage ON gke_ns_sku_usage.sku_id = gke_sku_usage.sku_id LEFT JOIN billing_sku_costs ON gke_ns_sku_usage.sku_id = billing_sku_costs.sku_id LEFT JOIN billing_sku_credits ON gke_ns_sku_usage.sku_id = billing_sku_credits.sku_id ORDER BY gke_ns_sku_usage.start_date, gke_ns_sku_usage.namespace, billing_sku_costs.sku_description
使い方
- target_projrct_idに、GKEを利用しているプロジェクトを指定してください。複数プロジェクトのGKEをまとめて対象にする場合は、WHERE句でのプロジェクトIDを指定する条件を削除してください。
- target_timezoneに、使用したいタイムゾーンを設定してください。
- 課金額を算出したい期間を指定してください。target_start_dateが開始日、target_end_dateが終了日です。
課金額は、日付・名前空間・SKUの組に対して算出されます。出力結果は以下の通りです。
- 使用日
- 名前空間
- SKUのID
- 課金額(割引適用済み)
- SKUの説明(名前)
- 使用したリソースの名前(抽象的な種類)
- 名前空間が所属するクラスタの名前
- 名前空間が所属するクラスタのロケーション
解説
まず初めに、本記事のクエリは公式ドキュメントの「名前空間で分類されたコストをクエリで取得する方法」を参考に作成しましたが、独自のものであるため、算出の正確さは保証しません。私が試した限りでは、多少のズレはあるものの概ね合っていると感じました。おそらく、完全に正確に算出することはできないでしょう。
クエリの中身を見てきます。名前空間ごとの課金額を算出するSELECT句の前にWITH句があり、そのWITH句は4つのブロックから成り立っています。それぞれブロックについて説明していきます。
WITH句
1つ目のブロックでは、課金データのテーブルから、SKDごとの課金額と使用量を取得しています。後で、このSKUごとの課金額(実際は2つ目のブロックの割引額を足した額)を名前空間で按分することになります。SKUというのは、簡単に説明すると、課金額を計算するために必要な、詳細なリソースの単位です。GKEで利用するSKUの例として、以下のものがあります。
N1 Predefined Instance Core running in Japan N1 Predefined Instance Ram running in Japan Preemptible N1 Predefined Instance Core running in Japan Preemptible N1 Predefined Instance Ram running in Japan Storage PD Capacity in Japan
ちなみに、SKU使用量の単位は、その種類によって異なり、CPUはseconds
、メモリとストレージはbyte-seconds
です。
SELECT sku.id AS sku_id, ANY_VALUE(sku.description) AS sku_description, SUM(cost) AS cost, ANY_VALUE(currency) AS currency, SUM(usage.amount) AS usage_amount, ANY_VALUE(usage.unit) AS usage_unit FROM `billing-gcp-project.billing-dataset.billing-table` WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND usage_start_time >= start_time_limit AND usage_end_time <= end_time_limit AND project.id = target_project_id GROUP BY sku_id ORDER BY sku_id
2つ目のブロックでは、課金データのテーブルから、SKDごとの割引額を取得しています。この割引額は負の値で、後の計算では、1つ目のブロックで取得した課金額に足すことになります。
SELECT sku.id AS sku_id, ANY_VALUE(sku.description) AS sku_description, SUM(credits.amount) AS credit_amount, ANY_VALUE(currency) AS currency, FROM `billing-gcp-project.billing-dataset.billing-table` CROSS JOIN UNNEST(credits) AS credits WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND usage_start_time >= start_time_limit AND usage_end_time <= end_time_limit AND project.id = target_project_id GROUP BY sku_id ORDER BY sku_id
3つ目のブロックでは、GKEリソース使用量のテーブルから、日付・名前空間・SKDの組み合わせに対する使用量を取得します。日付には、SKUの使用開始日を用いています。resource_name
は、抽象的なリソースの種類です。cpu
, memory
, storage
という3種類があります。
SELECT DATE(start_time, target_timezone) AS start_date, namespace, sku_id, SUM(usage.amount) AS usage_amount, ANY_VALUE(usage.unit) AS usage_unit, resource_name, cluster_name, cluster_location FROM `cluster-gcp-project.usage-metering-dataset.gke_cluster_resource_usage` WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND start_time >= start_time_limit AND end_time <= end_time_limit GROUP BY start_date, namespace, sku_id, resource_name, cluster_name, cluster_location
4つ目のブロックでは、GKEリソース使用量のテーブルから、SKDごとの使用量を取得しています。後の計算では、この使用量と名前空間ごとの使用量の比を用いて、名前空間ごとの課金額を算出します。
SELECT sku_id, SUM(usage.amount) AS usage_amount, ANY_VALUE(usage.unit) AS usage_unit FROM `cluster-gcp-project.usage-metering-dataset.gke_cluster_resource_usage` WHERE DATE(_PARTITIONTIME) >= partitiontime_start_limit AND DATE(_PARTITIONTIME) <= partitiontime_end_limit AND start_time >= start_time_limit AND end_time <= end_time_limit GROUP BY sku_id
SELECT句
メインである課金額の算出の部分について説明します。
まず、SKUの課金額に、SKUの割引額を足しています。
(billing_sku_costs.cost + IFNULL(billing_sku_credits.credit_amount, 0))
次に、GKEでのSKU合計使用量/課金データのSKU合計使用量をかけます。GKEのノードにはGCEが使われており、これをかけないと、同じGCPプロジェクト内に存在する、GKE以外で使用しているGCEインスタンスの分の課金額まで含めてしまいます。これをかけることで、GKEだけの、SKU単位での課金額が求められます。
(billing_sku_costs.cost + IFNULL(billing_sku_credits.credit_amount, 0)) * (gke_sku_usage.usage_amount / billing_sku_costs.usage_amount)
最後に、名前空間のSKU合計使用量/GKEのSKU合計使用量をかけることで、名前空間で利用した分の課金額が求められます。WITH句の3ブロック目と4ブロック目で、名前空間のSKU使用量を取得するときは、テーブルにgke_cluster_resource_consumption
ではなく、gke_cluster_resource_usage
を用いてください。Kubernetesは、全体のリクエスト量をもとにノードのオートスケーリングを行っており、課金額に関係するのは、リクエスト量を取得できるgke_cluster_resource_usage
であるためです。
(billing_sku_costs.cost + IFNULL(billing_sku_credits.credit_amount, 0)) * (gke_sku_usage.usage_amount / billing_sku_costs.usage_amount) * (gke_ns_sku_usage.usage_amount / gke_sku_usage.usage_amount)
もし、GKE以外にGCEを利用しておらず、GKE専用のプロジェクトを持っている場合は、より正確に課金額を算出することができます。名前空間のSKU合計使用量/GKEのSKU合計使用量のみを用いて、以下で算出します。
(billing_sku_costs.cost + IFNULL(billing_sku_credits.credit_amount, 0)) * (gke_ns_sku_usage.usage_amount / gke_sku_usage.usage_amount)
以上が、GKEの名前空間ごとの課金額を算出するクエリの解説です。
※前提がかけていたり、クエリが綺麗でないかもしれませんが、ご了承ください。
おわりに
非常に強力なツールであるKubernetesですが、そのコスト管理は詳細の把握ができないために難しいものになっています。しかし、本記事で紹介した方法であれば、その出力結果を分析したり可視化することで、名前空間という意味のある単位でコストをコントロールすることが可能になります。 Kubernetesは、数多くのコンテナを運用するためには必要不可欠です。そのため、把握が難しいコスト管理を諦めず、GCPの公式の機能を活用して挑戦してみましょう。