【2020/08/17 Update】Oracle Cloud Infrastructureの新しいコスト取得の手段「UsageAPI」が公開されたので使ってみた

皆さんこんにちは。うちにあった古いノートパソコンを分解して昔取り付けた2.5HDDとメモリとBluetoothを回収したid:k-furusawa--gです。Bluetoothを付けなおした理由がいまだに思い出せません。

今回はひっそりと公開されたOracle Cloud Infrastructure(以下OCI)の新API「UsageAPI」を使って、コスト取得を行ってみたいと思います。

はじめに

OCIのコスト取得と本ブログの歴史は長く(?)、2019年4月からあらゆる手段を用いてOCIからコスト情報を取得してきました。

そんなOCIに新しくコストを取得する手段が仲間入りしたのですから、使わない手はありません。

ただし先に書いておきますが、当APIでコスト情報を取得して分析に利用するのはオススメいたしかねますので、そのつもりで読んでください。理由はAPIを呼び出しながらご説明します。

もしも詳細なコスト情報を取得して分析などを行いたい場合は、MeteringAPIがオススメです(※)。今のところ一番正確なコスト情報を返却するAPIです。UsageReportはコスト情報が実際と違ってしまうバグを抱えており、正確性に欠けます(2020/07段階。詳しくは当ブログ記事をお読みください)。

※ MyServiceAPI(旧OCIClassic画面で利用されたAPI)ですので、利用停止や保守終了などが発生する可能性もあるので、オススメはしますが継続して利用できるかの保証は致しかねますのでご了承ください。

APIを見ていこう

まずドキュメントを確認します。

docs.cloud.oracle.com

ほかのAPIと違ってなぜかエンドポイントのホスト名が oci.oraclecloud.com になっていますので注意しましょう。リリース直後は他のAPI同様 oraclecloud.com でしたが途中で変更になったようです。

また、どこにも書いてませんが、ホストのリージョンはホームリージョン以外を選択することはできません。なのでお使いの環境のホームリージョンをあらかじめご確認ください。ドキュメントは各リージョンのホストが書かれていますが、全リージョンで使えるという意味ではないのでご注意ください。

APIは2つしかない

UsageAPIは2つしかありません。

  • RequestSummarizedConfigurations

    リクエストコンフィグ一覧の取得。公式コンソールのコスト分析画面で表示されるドロップボックスと同じ内容を返す。

  • RequestSummarizedUsages

    コスト統計情報を取得。リクエストされた内容に沿ってコスト統計情報を返す。統計情報なので注意

RequestSummarizedConfigurations API

このAPIは統計情報を取得する際のリクエストに指定できる検索内容を返します。公式コンソールのコスト分析画面にあるドロップダウンに表示される内容と同じです。

特に語るべきところはないのですが、レスポンス内容がキーとバリューの関係で件数不明で返るので、パースする際は気を付けましょう。

RequestSummarizedUsages API

このAPIは指定されたリクエスト内容に沿って、コストの統計情報を返してくれます。

リクエストパラメータの内容は複雑で、どういう組み合わせをしたかによって返ってくる内容が変化します。どういう組み合わせをするとどう返るのかの案内はありません。そのため、各環境で実行してみないと分からない部分が多いです。

問題点が多いのですが、それは後でまとめて記載します。

実行してみよう

とりあえず実行してみましょう。実行方法はお任せしますが、今回は以前ブログで書かせていただいたBashによるAPIの実行を使ってみます。一通りの説明は記事をお読みください。

cloudii.atomitech.jp

RequestSummarizedConfigurations API

まずは疎通確認もかねて RequestSummarizedConfigurations を実行してみましょう。

指定できるリクエストパラメータは一つだけで、tenantId のみです。ここにはテナントのOCIDを指定しましょう。

コマンドでは以下のようになります。

oci-curl usageapi.<ホームリージョン>.oci.oraclecloud.com get "/20200107/configuration?tenantId=<テナントOCID>"

先にも書きましたが、ホストのリージョンはホームリージョン以外にしないでください。エラーになります。

RequestSummarizedUsages API

次にコスト情報を得るために RequestSummarizedUsages を実行してみましょう。いきなり複雑なリクエストはしたくないので最低限の設定で呼び出します。

最低限の設定で実行する

リクエストパラメータをJSONで書き、ファイルに保存します。名前は適当でいいです。今回は post_body.json としました。

とりあえず標準的なコスト情報として、1日分の情報を得てみようと思います。

{
        "granularity": "DAILY",
        "tenantId": "ocid1.tenancy.oc1..aaaaaaa....",
        "timeUsageStarted": "2020-04-17T00:00:00Z",
        "timeUsageEnded": "2020-04-18T00:00:00Z"
}

granularity に取得するデータのタイプを指定します。HOURLY DAILY MONTHLY TOTAL が指定可能です。ただ HOURLY を指定した場合は後述する日付指定が重要になるのでご留意ください。

tenantId にテナントのOCIDを指定します。

timeUsageStartedtimeUsageEnded には日付を指定します。ドキュメントには記載がありませんが、フォーマットが決まっており yyyy-MM-ddTHH:mm:ssZ の形式でないとリクエストエラーになります。また、granularityHOURLY を指定した場合は24時間以内に設定しないとリクエストエラーになります。

最低限これだけ設定しておけばリクエストが可能です。実行してみましょう。

oci-curl usageapi.<ホームリージョン>.oci.oraclecloud.com post ./post_body.json "/20200107/usage"

実行してみると、以下のような形で返ります。

{
  "groupBy" : null,
  "items" : [ {
    "tenantId" : null,
    "tenantName" : null,
    "compartmentId" : null,
    "compartmentPath" : null,
    "compartmentName" : null,
    "service" : null,
    "resourceName" : null,
    "resourceId" : null,
    "region" : null,
    "ad" : null,
    "weight" : null,
    "shape" : null,
    "skuPartNumber" : null,
    "skuName" : null,
    "unit" : "GB_SEC",
    "discount" : null,
    "listRate" : null,
    "platform" : null,
    "timeUsageStarted" : "2020-04-17T00:00:00.000Z",
    "timeUsageEnded" : "2020-04-18T00:00:00.000Z",
    "computedAmount" : null,
    "computedQuantity" : 0,
    "overagesFlag" : null,
    "unitPrice" : null,
    "currency" : " ",
    "subscriptionId" : null,
    "overage" : null,
    "tags" : [ {
      "namespace" : null,
      "key" : null,
      "value" : null
    } ]
  }, {
    "tenantId" : null,
    "tenantName" : null,
    "compartmentId" : null,
    "compartmentPath" : null,
    "compartmentName" : null,
    "service" : null,
    "resourceName" : null,
    "resourceId" : null,
    "region" : null,
    "ad" : null,
    "weight" : null,
    "shape" : null,
    "skuPartNumber" : null,
    "skuName" : null,
    "unit" : " ",
    "discount" : null,
    "listRate" : null,
    "platform" : null,
    "timeUsageStarted" : "2020-04-17T00:00:00.000Z",
    "timeUsageEnded" : "2020-04-18T00:00:00.000Z",
    "computedAmount" : 9485.749599031789,
    "computedQuantity" : 32246419592682.281495428556,
    "overagesFlag" : null,
    "unitPrice" : null,
    "currency" : "JPY",
    "subscriptionId" : null,
    "overage" : null,
    "tags" : [ {
      "namespace" : null,
      "key" : null,
      "value" : null
    } ]
  }
~略~

結果はほとんどの値がnullで返ります。これはバグではなく仕様です。

取得できるパラメータはリクエストの仕方で決まる

当APIはMeteringAPIやpublicにされていないusagecostAPIとは違って、リクエストされて返す内容に詳細なコスト情報を載せません。リクエストする際にどのパラメータでグルーピングしたかで記載する情報を変化させます。

試しに groupBy["service"] を指定してみましょう。

{
        "granularity": "DAILY",
        "queryType":"USAGE",
        "groupBy":["service"],
        "tenantId": "ocid1.tenancy.oc1..aaaaa....",
        "timeUsageStarted": "2020-04-17T00:00:00Z",
        "timeUsageEnded": "2020-04-18T00:00:00Z"
}

レスポンスは以下のようになります。

{
  "groupBy" : [ "service" ],
  "items" : [ {
    "tenantId" : null,
    "tenantName" : null,
    "compartmentId" : null,
    "compartmentPath" : null,
    "compartmentName" : null,
    "service" : "Virtual Cloud Network",
    "resourceName" : null,
    "resourceId" : null,
    "region" : null,
    "ad" : null,
    "weight" : null,
    "shape" : null,
    "skuPartNumber" : null,
    "skuName" : null,
    "unit" : " ",
    "discount" : null,
    "listRate" : null,
    "platform" : null,
    "timeUsageStarted" : "2020-04-17T00:00:00.000Z",
    "timeUsageEnded" : "2020-04-18T00:00:00.000Z",
    "computedAmount" : 0,
    "computedQuantity" : 3111486847,
    "overagesFlag" : null,
    "unitPrice" : null,
    "currency" : null,
    "subscriptionId" : null,
    "overage" : null,
    "tags" : [ {
      "namespace" : null,
      "key" : null,
      "value" : null
    } ]
  },
~略~

グルーピングに利用した service に値が入りました。これを region にすれば region に値が入ります。["service","region"] にすれば二つの要素でグルーピングされ、二つの要素に値が入ります。

コスト分析でオススメしない理由はこの仕様にあります。1回のリクエストで詳細な内容を得られれば、あとは分析するだけです。当APIはあらかじめ取得したい詳細なパターンを洗い出してから、全パターンでAPIを呼び出さなければ、どのサービス、どのリージョン、どのコンパートメントでコストがいくら発生しているのかを知ることができません。ここにタグの組み合わせが入るので場合によっては数百回以上のリクエストが必要です。

さらに言えば tenantIdresourceName はグルーピングに指定するとリクエストエラーになります。ドキュメントにはこの辺りの記載が一切ないので、どの値がグルーピングに利用できるのか不明です。試すしかありません。

当APIは詳細を取得するためのAPIではない

一通り呼び出してみて、何に使うためのAPIなのか非常に疑問だったのですが、どうやら当APIは公式コンソールのコスト分析画面で利用されているAPIを、ただ単に外部のエンジニアも使えるようにした、というただそれだけのようです。RequestSummarizedConfigurations API がコンソールと同じドロップダウンの内容を返すのはそのためですね。

つまり、OCIコンソールのコスト分析画面のように検索をしたい要素に対するコスト情報を返す、というものであり、他のAPIのように詳細を提供して検索条件によって絞り込んでいくというタイプのものではありません。

かなり限定的な利用を想定したAPIということになりますね。

問題点まとめ

もともとコスト分析画面用に作成したAPIを外部公開しただけのものなので、使い心地は正直よくないAPIです。というかほとんど使う機会がないと言っていいでしょう。コンソール画面でできることしか提供しないAPIなのですからコンソールを使えばいいだけの話です。

ドキュメントもどこか曖昧で、どのパラメータに何を設定すればどうなるのかの説明はありません。groupByfilter の内容が大きな意味を持ってくるAPIであるにも関わらず、その旨の説明もなければサンプルも単純なものしか記載がありません。組み合わせ次第でリクエストエラーにもなるのですが、その組み合わせも正直よくわからないです。検証するにはパターンが多すぎます(サポートでドキュメントが不十分すぎると伝えてあるので、そのうち改善されるかもしれません)。

また、異常なほど InternalServerError を返します。5回試すと多い時で3回は InternalServerError になります。レスポンスも遅いため内部でタイムアウトでもしているのかもしれません。これはコンソールのコスト分析画面にも影響しているらしく、よくエラーが表示されます。もしかしたら弊社のホームリージョンがUS-ASHBURNであるのも関係しているかもしれません(契約がAP-TOKYO立ち上げ前)。

ちなみに「コストおよび使用状況レポートの作成」と、このAPIの内部で行われているロジックは同一なので(サポートで確認済み)、このAPIで発生した問題はレポート側にも発生します。弊社で確認が取れているバグとしては、金額が少額で表示されてしまうものがあります(サポートによれば2020/08/03に修正される予定です)。

f:id:k-furusawa--g:20200730091238p:plain
警告とエラーが目立つコスト分析画面

【2020/08/17Update】APIの詳細な仕様を確認しました

サービスリクエストを利用して、APIの詳細な仕様を確認したところ、以下のような回答を得ましたので、原文のまま紹介させていただきます。

UsageAPIを利用してコスト情報を取得する際に、パラメータ設定についてご確認したいとのリクエストを見受けております。

ご存じのように、UsageAPIを利用して、使用料及びコストを取得することができます。これは、SQLクエリのような動作しておりますが、ディメンション/タグによるフィルターし、ディメンションによるGroup Byでサポートします。

フィルターがNullの場合、以下の例ご参照ください。

{
"tenantId": "ocid1.tenancy.oc1..aaaaaaaaeaqea****************************",
"timeUsageStarted": "2020-04-01T00:00:00.000Z",
"timeUsageEnded": "2020-07-01T00:00:00.000Z",
"granularity": "MONTHLY",
"queryType": "COST",
"groupBy": [
"tagNamespace",
"tagKey",
"tagValue",
"service",
"compartmentPath"
],
"compartmentDepth": 2,
"filter": null
}


各パラメータについて、説明いたします。

******************************************************
Granularity:現時点でMONTLY, DAILY, HOURLYをサポートします。

queryType:USAGE または COSTをサポートします。

groupBy: APIレスポンスでは、groupByのディメンションのみが表示されます。 例えば、「service」がgroupByにない場合、応答の「service」フィールドは空になります。
また、groupByが空の場合、またはqueryTypeが空の場合は、「通貨」がgroupByに追加されます。従って、「computedAmount」は、多くの場合に応答して表示されます。
ただし、queryTypeをUSAGEに設定するか、groupByに「unit」を追加しない限り、集約された「computedQuality」は、あまり意味がありません。
以下は有効な値となります:
"tagNamespace", "tagKey", "tagValue", "service", "skuName", "skuPartNumber", "unit",
"compartmentName", "compartmentPath", "compartmentId", "platform", "region", "logicalAd",
"resourceId", "tenantId", "tenantName"
ご注意:"tenantId", "tenantName" は現在まだ対応しておりません。

Tag: "tagNamespace", "tagKey", "tagValue"をサポートします。

filter dimension: "service", "skuName", "skuPartNumber", "unit", "compartmentName", "compartmentPath", "compartmentId", "platform", "region", "logicalAd", "resourceId", "tenantId", "tenantName"
ご注意:"tenantId", "tenantName" は現在まだ対応しておりません。

******************************************************

私が個人的に調査した内容通りの回答が返りました。私の利用している環境ではどうやってもtenantIdやtenantNameがnullで返るのですが、回答を見る限りまだ開発段階のAPIに見えますので、仕様かもしれません。

細かな設定についても説明がありますが、ドキュメントに記載がないのは開発途上だからという認識でしょうか? 疑問はまだありますが、ある程度分かったので基本的に扱いには問題ないと思います。

なお、回答は2020/08/17時点のものですので、今後改善・改修される可能性は十分ありますのでご了承ください。

おしまいに

いかがだったでしょうか。

コストを取得する手段がいささか多すぎるように思えるOCIですが、どれも微妙にやれることが違うので役割分担ができているとも言えるかもしれません。

実は記事内でしれっと触れたpublicになっていないusagecostAPIというものがあるのですが、こちらはまだ記事にしたことがございません。publicでないものを記事にするのはどうかと思うのですが、機会があったらそちらも記事にしてみてもいいかなと思います。

宣伝

今まで記事にしてきたREST APIの呼び出しや、コスト取得の技術を用いて弊社では独自にコスト分析を提供するツールを開発いたしました。

詳しくは「Cloudii Cloud Manager Portal」で検索、もしくは下記のリンクをご利用ください。

cloudii.jp