Azure Cost Management の Benefit Recommendations API を使って Savings Plan の推奨を自動取得したところ、totalCost や benefitCost が実際の請求額より桁違いに大きく「ダミーデータでは?」と感じたことはないでしょうか。本記事では、その原因となる scope 設定(Shared / Single)の挙動と、実サブスクリプションのコストに即した推奨値を得るための具体的な設定・検証方法を詳しく解説します。
Azure Benefit Recommendations API とは
Azure Benefit Recommendations API(REST リソース名: benefitRecommendations)は、Azure Cost Management が提供する「Savings Plan(コンピュートセービングプラン)購入の推奨値」を取得するための API です。指定したスコープの利用状況を一定期間さかのぼって分析し、「どれくらいのコミット額で Savings Plan を購入すると、どれくらいコスト削減できるか」を数値で返してくれます。
公式ドキュメント上のエンドポイントは次のように定義されています。
GET https://management.azure.com/{billingScope}/providers/Microsoft.CostManagement/benefitRecommendations
?api-version=2025-03-01
{billingScope} には、以下のような Billing スコープやサブスクリプション スコープを指定できます。
| スコープ種別 | 例 | 典型的な用途 |
|---|---|---|
| 課金アカウント | /providers/Microsoft.Billing/billingAccounts/{billingAccountId} | 課金アカウント配下の全サブスクリプションをまとめて Savings Plan を購入したい場合 |
| サブスクリプション | /subscriptions/{subscriptionId} | 特定サブスクリプション単位でコミット額を検討したい場合 |
| リソース グループ | /subscriptions/{subscriptionId}/resourceGroups/{rgName} | あるワークロード(RG 単位)だけを Savings Plan でカバーしたい場合 |
Benefit Recommendations API は、Azure ポータルの「コスト管理 + 請求」で見られる推奨値をプログラムから機械的に取得したいときに便利です。FinOps ツールや社内コスト可視化ダッシュボードから Savings Plan の購入候補を自動提示する、といったユースケースで多用されます。
API が返す主なコスト関連項目
レスポンスの properties.recommendationDetails には、Savings Plan を購入した場合のコスト構造を示すさまざまな数値が含まれます。代表的なものを簡単に整理すると次のとおりです。
| プロパティ名 | 意味 |
|---|---|
totalCost | 推奨 Savings Plan を購入した場合の合計コスト(benefitCost + overageCost) |
benefitCost | コミット額でカバーされる部分のコスト(Savings Plan によって割引が適用される部分) |
overageCost | コミットを超過した利用に対し、従量課金で発生するコスト(totalCost から benefitCost を引いた残り) |
wastageCost | コミットしたのに使い切れなかった「余り」に相当するコスト |
savingsAmount | Savings Plan を購入した結果、従量課金と比較して削減できる金額 |
savingsPercentage | 同じく削減率(%) |
さらに、properties.costWithoutBenefit というプロパティも返却されます。これは「Savings Plan を一切買っていない場合のコスト(対象期間分)」を表しており、API ドキュメントでは「look-back 期間における totalHours に対応する、現在の benefit なしのコスト」と説明されています。
つまり、Benefit Recommendations API が返す数値は本来、実利用に基づいた“生きたコスト情報”であり、ダミー値であることは想定されていません。それにもかかわらず「totalCost が実請求額の 100 倍以上」といった現象が起きるのは、別の要因があるということです。
「totalCost が実コストの 100 倍以上」に見える典型パターン
よくある相談シナリオは次のようなものです。
- API バージョン:
2025-03-01 - エンドポイント:
/providers/Microsoft.Billing/billingAccounts/{billingAccountId}配下で呼び出し - クエリに特定サブスクリプション ID を指定しているつもりなのに、レスポンスの
totalCost/benefitCost/overageCost/costWithoutBenefitが、実サブスクリプションの請求額より 100 倍以上大きい
レスポンスの JSON 構造そのものは正常に見えるため、「これはテスト用のダミーデータなのでは?」と疑いたくなる状況です。
実際、2025 年 9 月には Microsoft Q&A コミュニティでほぼ同じ内容の質問が投稿され、Microsoft のモデレーターから以下のような回答がされています。
scopeを Shared にして Billing Account スコープで呼び出していると、同じ課金アカウント配下のすべてのサブスクリプションの利用が合算される。- そのため、単一サブスクリプションの実コストと照合すると「とんでもなく大きな数値」に見える。
- 単一サブスクリプションの利用に基づく推奨値を得るには、
$filterでproperties/scopeを'Single'に指定し、さらにproperties/subscriptionIdで対象サブスクリプションを明示する必要がある。
ここで重要になるのが、Benefit Recommendations API の scope(Single / Shared) です。
scope=’Shared’ と ‘Single’ の根本的な違い
Benefit Recommendations API の $filter パラメーターでは、以下のような条件で結果を絞り込むことができます。
properties/scope…'Single'または'Shared'(既定値は'Shared')properties/lookBackPeriod…'Last7Days'/'Last30Days'/'Last60Days'(既定値は'Last60Days')properties/term…'P1Y'/'P3Y'(既定値は'P3Y')properties/subscriptionIdproperties/resourceGroup
ここでいう scope は「Savings Plan を適用する対象範囲」を意味し、次のように解釈できます。
| scope の値 | 意味 | 典型的な用途 |
|---|---|---|
'Shared'(既定) | 課金アカウント配下の複数サブスクリプションにまたがって Savings Plan を共有する | 組織全体でまとめてコミット額を決めたい場合(大きなボリューム割引を狙う) |
'Single' | 単一のサブスクリプションまたはリソース グループに限定して Savings Plan を適用する | 「このサブスクリプションだけ」「このワークロードだけ」でコミット額を最適化したい場合 |
Azure Cost Management の「スコープ」概念そのものも、Billing スコープや RBAC スコープといった単位で構成されており、課金アカウント スコープを指定すると複数サブスクリプションがまとめて集計されるのが基本的な動作です。
Shared スコープの挙動:課金アカウント全体の合算
properties/scope の既定値は 'Shared' です。そのため、filter を何も指定せずに Billing Account スコープで API を叩くと、課金アカウント配下の全サブスクリプションをまとめて評価する動きになります。
例えば、以下のような状況を想像してください。
- Billing Account A 配下に 10 個のサブスクリプションがある。
- 各サブスクリプションの月次利用額(Savings Plan 対象分)が 100 USD ずつ。
- 全体では 1,000 USD / 月 の利用が発生している。
このとき、Billing Account スコープで scope='Shared' のまま Benefit Recommendations API を呼ぶと、10 サブスクリプション合計の 1,000 USD をベースに、Savings Plan の推奨コミット額とコストが計算されます。ところが、その結果を「特定サブスクリプションの請求額(100 USD)」と 1:1 で比較すると、
totalCost ≒ 1,000 USD- 実コスト(特定サブスクリプション) ≒ 100 USD
となり、「10 倍も差がある」「テスト用のダミー値では?」という印象になってしまいます。サブスクリプション数がさらに多かったり、Savings Plan 対象サービスが偏っていたりすると、その差は 100 倍以上に膨らむこともあります。
Single スコープの挙動:サブスクリプション単体の評価
一方で、properties/scope を 'Single' に設定し、かつ properties/subscriptionId で対象のサブスクリプション ID を指定すると、そのサブスクリプションだけを対象にした推奨値が返ってきます。
先ほどと同じ例で、1 つのサブスクリプション(利用額 100 USD)のみを Single スコープで評価した場合、
costWithoutBenefit≒ 100 USD(Savings Plan がない場合の現在のコスト)totalCost≒ Savings Plan を購入したうえでの想定トータルコスト(従量課金より少し安くなる)savingsAmount≒ 数十 USD 程度(期間や利用状況による)
といった、実サブスクリプションの請求額と整合するオーダー感の値になります。
$filter で scope と subscriptionId を正しく指定する
実サブスクリプションのコストに即した推奨値を取得するには、$filter パラメーターで以下の 2 点を必ず指定します。
properties/scope eq 'Single'properties/subscriptionId eq '{subscriptionId}'
これにより、Billing Account スコープで API を呼び出していても、評価対象は単一サブスクリプションに限定され、合算による「桁違いの数値」が返ってくることを防げます。
HTTP リクエスト例(Billing Account スコープ)
質問でよく出てくるのが、次のような Billing Account スコープでの呼び出しです。
GET https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/providers/Microsoft.CostManagement/benefitRecommendations
?api-version=2025-03-01
&$filter=properties/scope eq 'Single'
AND properties/subscriptionId eq '{subscriptionId}'
AND properties/lookBackPeriod eq 'Last30Days'
AND properties/term eq 'P1Y'
{billingAccountId}と{subscriptionId}は実際の ID に置き換えます。- クエリの空白は URL エンコードされるため、実際の HTTP リクエストでは
%20になります。 - 必要に応じて
$expand=properties/usage,properties/allRecommendationDetailsも付与すると、詳細な利用明細や候補パターンが取得できます。
サブスクリプション スコープで直接呼び出す例
単一サブスクリプションごとに定期的にバッチ実行する場合は、Billing Account スコープではなく、サブスクリプション スコープを直接 {billingScope} に指定するのも良い選択肢です。
GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/benefitRecommendations
?api-version=2025-03-01
&$filter=properties/scope eq 'Single'
AND properties/lookBackPeriod eq 'Last30Days'
AND properties/term eq 'P1Y'
この場合でも scope を 'Single' にしておけば、サブスクリプションをまたいだ合算は行われません。Billing Account スコープとどちらを選ぶかは、組織の課金運用ポリシーや実装のしやすさ次第です。
$filter で指定できる主な項目まとめ
| フィールド | 例 | 説明 |
|---|---|---|
properties/scope | 'Single', 'Shared' | 推奨値の評価スコープ。既定は 'Shared'。 |
properties/subscriptionId | '00000000-0000-0000-0000-000000000000' | Single スコープで対象となるサブスクリプション ID。 |
properties/resourceGroup | 'my-workload-rg' | Single スコープで対象となるリソース グループ名(任意)。 |
properties/lookBackPeriod | 'Last7Days', 'Last30Days', 'Last60Days' | 推奨値計算の「振り返り期間」。既定は 60 日。 |
properties/term | 'P1Y', 'P3Y' | Savings Plan の期間(1 年 or 3 年)。既定は 3 年。 |
totalCost / benefitCost / costWithoutBenefit をどう読み解くか
scope の指定を正しく行ったうえで、代表的なコスト項目をどのように解釈するかを整理しておきます。
| 項目 | 概念的な意味 | 実コストとの付き合わせ方 |
|---|---|---|
costWithoutBenefit | look-back 期間における、Savings Plan を使わない場合のオンデマンドコスト(現在の世界線) | Azure ポータルの「コスト分析」で同期間・同通貨・同スコープを指定し、Savings Plan や RI 割引を除いた実コストと比較する。 |
totalCost | 推奨 Savings Plan を購入した場合の合計コスト(benefitCost + overageCost) | costWithoutBenefit と比較して、Savings Plan 導入後の想定コストを把握する。 |
benefitCost | コミット額でカバーされる部分のコスト(Savings Plan で「前払い+割引」が効いている領域) | コミット額(commitmentAmount × totalHours)と概ね一致するかを確認する。 |
overageCost | コミットを超えた利用に対するオンデマンド部分のコスト | コミット額より利用が大きい場合にのみ発生することを確認する。 |
savingsAmount | Savings Plan 導入によって削減できる金額 | costWithoutBenefit - totalCost に近い値になっているかを確認する。 |
savingsPercentage | 削減率(%) | 社内の「どれくらいお得なら購入するか」という判断基準に使う。 |
特に costWithoutBenefit は「現在の実コスト(Savings Plan 未購入時)」に相当するため、ポータルのコスト分析と比較するときの基準値として理解しておくと便利です。
実コストと付き合わせるときのチェックポイント
「scope を Single にしたのに、まだポータルの金額と微妙に合わない」というときは、以下のポイントを確認してみてください。
同一期間で比較しているか
Benefit Recommendations API では properties/lookBackPeriod で 7 / 30 / 60 日のいずれかを指定します。一方、ポータルの「コスト分析」ではカスタムの日付範囲を自由に指定できるため、期間がズレていると簡単に数十%レベルの差が出ます。
- API 側:
Last30Days - ポータル側:「当月」「先月」など別の期間を見ている
というケースは非常によくあるので、まずは「同一期間」になっているかを確認しましょう。
通貨・税込/税抜の前提を合わせる
Benefit Recommendations API の各コストは、currencyCode(ISO 4217 コード)で示される通貨単位で返ってきます。また、請求書の金額は税金やその他の手数料を含んだ値になる場合があります。
- API のコスト値 … 通常は税抜ベース(国や契約形態によって異なる)
- 請求書の合計 … 税金や Marketplace 課金なども含んだ最終金額
そのため、API の値と請求書の値を直接 1:1 で比較するのではなく、コスト分析のグラフで「税金除外」「Savings Plan 対象サービスのみ」といったフィルターをかけたうえで比較するのがおすすめです。
Billing スコープと Azure RBAC スコープを取り違えていないか
Billing Account スコープ(たとえば MCA の課金アカウント)と、サブスクリプション スコープ / リソース グループ スコープは別物です。前者は「課金単位」、後者は「リソース管理単位」であり、Cost Management の API では両者を明確に使い分ける必要があります。
Billing Account スコープをベースにしつつ、scope='Single' と subscriptionId を組み合わせれば、課金アカウント全体の中から特定サブスクリプションだけを切り出した推奨値を得られます。
検証・トラブルシュート手順(チェックリスト)
ここまでの内容を、実際のトラブルシュート手順として整理します。
- 対象期間を決める
まず、ポータルの「コスト分析」で確認したい期間(例:当月、前月、直近 30 日など)を決め、その期間に合わせてlookBackPeriodを選びます。 - Billing スコープと Azure スコープを確認する
現在の API 呼び出しが Billing Account スコープなのか、Subscription スコープなのかを整理します。Billing Account スコープの場合は、scope を Single にしない限り複数サブスクリプションが合算されることを念頭に置きます。 $filterで scope=’Single’ と subscriptionId を指定する
先述の例のように、$filter=properties/scope eq 'Single' AND properties/subscriptionId eq '{subscriptionId}'を付与して再度実行します。- ポータルと costWithoutBenefit を比較する
API レスポンスのcostWithoutBenefitを取り出し、ポータルのコスト分析で同期間・同通貨・同スコープの金額と付き合わせます。オーダー感が一致していれば、推奨値は実利用に概ね即していると判断できます。 - 見積もりコストと savingsAmount / savingsPercentage を確認する
totalCostとsavingsAmount/savingsPercentageを確認し、「Savings Plan を導入した世界線」のコスト差分を評価します。 - Shared スコープの必要性を再検討する
組織全体で 1 つの Savings Plan を共有する運用方針なら Shared スコープの推奨値も参考になりますが、「サブスクリプション単位で判断したい」場合は Single スコープをベースに運用するのが安全です。
よくある疑問と落とし穴
Q. subscriptionId だけ指定していれば、scope が Shared でも単一サブスクリプションだけが対象では?
A. いいえ。Billing Account スコープ + scope='Shared' の組み合わせでは、同一課金アカウント配下のサブスクリプション全体をまとめて評価したうえで、結果の一部として subscriptionId を返しているだけと理解した方が安全です。実際に Microsoft Q&A の議論でも、scope を Single に変えた途端にコストが大きく下がり、実コストに近づいたことが報告されています。
Q. それでも数値が「多め」に出ることはある?
A. あります。Benefit Recommendations API は将来の Savings Plan 購入を前提とした「モデル上の世界線」を算出しているため、以下のような要素で請求額とぴったり一致しないことがあります。
- look-back 期間の利用パターンをベースにしているため、今後の利用変動までは織り込めない。
- Savings Plan の対象にならないサービスがある。
- 為替レートの変動や、税金・手数料の扱いが違う。
そのため、Benefit Recommendations API の値は「ピタリ一致」をめざすよりも、オーダー感と削減率を評価するための指標として扱うのが実務上は現実的です。
Q. Savings Plan ではなく Reserved VM Instance の推奨も欲しい
A. Benefit Recommendations API 自体は Savings Plan 向けの推奨に特化していますが、Azure には他にも Reserved VM Instances や Azure Hybrid Benefit など、コスト最適化の仕組みが存在します。Reserved Instance の推奨は Advisor など別の仕組みで提供されているため、「Savings Plan の推奨」と「RI の推奨」を組み合わせて全体最適を図る構成が望ましいでしょう。
サンプル:CLI / PowerShell / アプリコードからの呼び出しイメージ
Azure CLI(az rest)でのサンプル
az rest \
--method get \
--url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}/providers/Microsoft.CostManagement/benefitRecommendations?api-version=2025-03-01&\$filter=properties/scope%20eq%20'Single'%20AND%20properties/subscriptionId%20eq%20'${SUBSCRIPTION_ID}'%20AND%20properties/lookBackPeriod%20eq%20'Last30Days'%20AND%20properties/term%20eq%20'P1Y'" \
--query "value[0].properties"
PowerShell でのサンプル(簡易版)
$billingAccountId = "xxxxxxxx"
$subscriptionId = "00000000-0000-0000-0000-000000000000"
$filter = "properties/scope eq 'Single' AND properties/subscriptionId eq '$subscriptionId' " +
"AND properties/lookBackPeriod eq 'Last30Days' AND properties/term eq 'P1Y'"
$uri = "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/$billingAccountId" +
"/providers/Microsoft.CostManagement/benefitRecommendations" +
"?api-version=2025-03-01&`$filter=$([uri]::EscapeDataString($filter))"
$token = (Get-AzAccessToken).Token
$result = Invoke-RestMethod -Method Get -Uri $uri -Headers @{ Authorization = "Bearer $token" }
$result.value.properties
アプリケーションコードで扱う際のポイント
- SDK(.NET / Python / JavaScript など)の
BenefitRecommendationPropertiesクラスやインターフェースには、scope/costWithoutBenefit/recommendationDetailsなどがマッピングされています。 - 既定値だと scope=Shared になるケースがあるため、必ず Single を明示するように実装するのが安全です。
- 大量のサブスクリプションに対して一括実行する場合は、API のレートリミット(429 / 503 など)にも注意し、リトライ制御を入れておきましょう。
運用でのベストプラクティス
最後に、実際に Benefit Recommendations API を運用に組み込む際のベストプラクティスをいくつか挙げておきます。
- Single スコープをデフォルトとする
「まずはサブスクリプション単位で Savings Plan を検討し、必要に応じて Shared スコープも見る」というスタイルを基本とし、Shared による合算値をいきなり採用しないようにします。 - lookBackPeriod は 30 日 or 60 日で固定する
日々の利用変動をならす意味でも、Last30Days か Last60Days のどちらかに統一しておくと、推奨値の比較がしやすくなります。 - ポータルと API の二重チェック
定期的に「ポータルの Savings Plan 推奨」と「Benefit Recommendations API の結果」を付き合わせ、実装ミスや権限不足がないかを検証します。 - FinOps ダッシュボードとの連携
API から取得したsavingsAmountやsavingsPercentageをもとに、部署ごと・プロジェクトごとの「購入候補一覧」をダッシュボード化すると、Savings Plan 購入の意思決定がスムーズになります。 - Shared スコープの値は「全体最適」の議論用
Shared スコープの推奨値は、CSP パートナーや中央 IT 部門が「全社でまとめ買いした方がトクか?」を議論する材料として活用すると効果的です。
まとめ
Azure Benefit Recommendations API で totalCost や benefitCost が実際の請求額より桁違いに大きく見える場合、その多くは「scope=’Shared’ のまま Billing Account スコープで呼び出している」ことが原因です。これは API がダミー値を返しているわけではなく、課金アカウント配下の複数サブスクリプション分が合算されているためです。
単一サブスクリプションの実利用に即した推奨値を得るには、次の 2 点を必ず押さえておきましょう。
$filterでproperties/scope eq 'Single'を指定する。$filterでproperties/subscriptionId eq '{subscriptionId}'を併せて指定する。
これにより、Benefit Recommendations API の返す costWithoutBenefit や totalCost / benefitCost などが、対象サブスクリプション単体の実利用に近い値となり、「ダミーに見える」問題は解消されます。Savings Plan の購入判断を自動化・定量化していくうえで、scope とスコープ階層(Billing / Subscription / Resource Group)の正しい理解は欠かせません。本記事をもとに、自組織のコスト最適化ツールに Benefit Recommendations API を安心して組み込んでいただければ幸いです。

コメント