APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Azureの利用料金をSlackに通知させる【後編】

f:id:thanaism:20210609112945p:plain

はじめに

Azureコンテナソリューショングループの髙井です。

私の部署ではAzureのクラウドネイティブ内製化支援サービスを提供しています。そして、その一環として、私はAKSを中心としたAppアーキテクトを担当しています。

さて、前回の記事では、Azureの料金をSlackに通知させるためにAPI用のトークンを取得するところまでやったのでした。

今日は実際にSlackに通知を投げるところまでやってしまいましょう。

後半の流れ

残りの工程は2つです!

  1. 前回:認証用のサービスプリンシパルを作る
  2. 前回:サービスプリンシパルを使ってAPI用のtokenを取得する
  3. Cost Management APIを叩いて課金データのJSONを取得する
  4. JSONをエエ感じに加工してSlackに投げる

JSONをエエ感じに加工してSlackに投げる部分などは、Azureに限らずネット上に同様の記事が有象無象にありますので端折りながら説明していきます。

1.Cost Management APIを叩いて課金データのJSONを取得する

本日のメイン、Cost Management APIです。

対象となる期間や種別を指定するJSON形式のクエリを投げてあげると、おなじくJSON形式で料金情報を返してくれます。

APIへのPOSTに必要なのは、以下の3つです。

  • ①前編で取得したtoken
  • ②サブスクリプションID
  • ③クエリ用JSON

このうち後ろの2つは、皆様のお手元にあるtokenとは別に、新たに用意する必要があります。

1-1. ②サブスクリプションIDを取得する

さっそく②サブスクリプションIDを取得していきましょう。

これはPortalのサブスクリプションのページにいけばトップに表示がありますので、そちらをコピーしていただいても問題ありません。

f:id:thanaism:20210525160335p:plain
Azure Portal サブスクリプションのトップページ

しかし、せっかくなのでコマンドラインで取得する方法も覚えておいて損はないでしょう。

たとえば、誰もが通る「無料試用版」という名前のサブスクリプションで実行した場合は下記のようになります。

$ az account subscription list -o tsv --query '[*][@.displayName,@.subscriptionId]'
無料試用版      XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

--query以降の文字列が見慣れないかもしれませんが、これはJSONをパースするためのクエリです。

Azure CLIにはJMESPathというJSONパーサーが組み込まれているため、不要な情報を取り払ってスッキリ見ることができます。

1-2. ③クエリ用JSONを作成する

次に、Cost Management APIに対して「いつの、どんなデータを返してほしいか」を指定するためのクエリを用意していきましょう。

今回は一例として「リソースの種類ごと」に「過去一週間」のデータを要求することを考えます。

以下のようなクエリになるでしょう。

{
    "dataset": {
        "aggregation": {
            "totalCost": {
                "function": "Sum",
                "name": "PreTaxCost"
            }
        },
        "granularity": "Daily",
        "grouping": [
            {
                "name": "ResourceType",
                "type": "Dimension"
            }
        ]
    },
    "timeframe": "WeekToDate",
    "type": "Usage"
}

もっと柔軟な日時指定をしたい場合は、"timeframe": "WeekToDate",の部分を変更すれば対応可能です。

"timeframe": "Custom",
"timePeriod": {
    "from": "2021-05-18T00:00:00.000Z",
    "to": "2021-05-25T00:00:00.000Z"
},

また、リソースの種類ではなく「リソースグループごと」にほしい場合は、"grouping": []を変更します。

"grouping": [
    {
        "name": "ResourceGroup",
        "type": "Dimension"
    }
]

クエリに関するより詳細な情報はMicrosoft Docsに記載がありますので、気になったら覗いてみてください。

1-3. クエリを投げる!

必要なものが出揃いましたので、さっそくクエリをぶん投げて参りましょう!

$ token=<①前編で取得したtoken>
$ subId=<②サブスクリプションID>
$ json=<③クエリ用JSON>
$ url=https://management.azure.com/subscriptions/${subId}/providers/Microsoft.CostManagement/query?api-version=2019-11-01
$ curl -s -X POST $url -H "Authorization: Bearer ${token}" -H 'content-Type: application/json' -d $json | jq .properties.rows[]
[
  XXXX.XXXXXXX,
  20210523,
  "microsoft.compute/virtualmachines",
  "JPY"
]
[
  XXXX.XXXXXXX,
  20210523,
  "microsoft.compute/virtualmachinescalesets",
  "JPY"
]
...

curlのレスポンスをパイプで渡してjqでクエリしていますが、propertiesrowsにお目当てのデータが入っていることがわかります。

ここまで出来ればあとはSlackに投げたい形で加工してあげるだけです!ちなみにcurlでなくGASでJSONを受け取りたい場合は、以下のような実装ができるでしょう。

function fetchCostManagementRawJSON(token, query) {
    var options = {
        muteHttpExceptions: true,
        contentType: 'application/json',
        headers: { Authorization: "Bearer " + token },
        method: 'post',
        payload: JSON.stringify(query)
    };
    var url = 'https://management.azure.com/subscriptions/' +
        SUBSCRIPTION_ID +
        '/providers/Microsoft.CostManagement/query?api-version=2019-11-01';
    var res = UrlFetchApp.fetch(url, options).getContentText();
    return JSON.parse(res);
}

引数にトークンとクエリを渡してあげれば、APIから返却されたJSONをオブジェクトに変換して返してくれます。

2. JSONをエエ感じに加工してSlackに投げる

ここまでくればウイニングランです!

あとは加工して投げるだけですし、このあたりはネットにも情報が多いので、サラッと書いていきます。

まず、Slackのページでアプリを新規に作成して、bot用のAPIトークン(xoxb-から始まるもの)を用意しましょう。

次に、Cost Management APIから返却されたJSONデータを加工して、Slackにpostするpayloadを作成します。

Block Kit Builderなどを活用するとカッチョイイ感じに仕上げやすいと思います。

最後に、Slackに投げる部分を実装します。

function genPayload() {
    return {
        channel: 'XXXXXXXXXXX', // 投稿先のチャンネルID
        blocks: [
            // Block Kit Builderで作成
        ]
    }
}

function postToSlack() {
    var url = 'https://slack.com/api/chat.postMessage';
    var payload = genPayload();
    var options = {
        method: 'post',
        headers: {
            Authorization: "Bearer " + SLACK_TOKEN,
            'Content-Type': 'application/json'
        },
        payload: JSON.stringify(payload)
    };
    UrlFetchApp.fetch(url, options);
}

私が試しに作ってみたのが以下の画像です。え?デザインセンスがない?えーい!わかればええんや!

f:id:thanaism:20210525160426p:plain
Slackへの投稿イメージ

使いたい人がいるかはわかりませんが、一応、参考までにテンプレートも貼っておきましょう。

デザインセンス抜群のコードはこちら

{
    "blocks": [
        {
            "type": "context",
            "elements": [
                {
                    "text": "*2021/05/25*  |  Your Group Name",
                    "type": "mrkdwn"
                }
            ]
        },
        {
            "type": "divider"
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": " :loud_sound: *DAILY COST* :loud_sound:"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "2021/05/18: *0,000 yen* \n2021/05/19: *0,000 yen* \n2021/05/20: *0,000 yen* \n2021/05/21: *0,000 yen* \n2021/05/22: *0,000 yen* \n2021/05/23: *0,000 yen* \n2021/05/24: *0,000 yen* (in process)"
            },
            "accessory": {
                "type": "button",
                "text": {
                    "type": "plain_text",
                    "text": "View in Cost Management",
                    "emoji": true
                },
                "url": "https://portal.azure.com/xxxxxx"
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "*TOP 3 RESOURCE TYPE* (2021/05/23)"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": ":one: dbforpostgresql/servers: *0,000 yen* \n:two: compute/virtualmachinescalesets: *0,000 yen* \n:three: compute/virtualmachines: *0,000 yen* \n",
                "verbatim": false
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "*TOP 3 RESOURCE GROUP* (2021/05/23)"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": ":one: resource_group_foo: *0,000 yen* \n:two: resource_group_bar: *0,000 yen* \n:three: resource_group_baz: *0,000 yen* \n",
                "verbatim": false
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "context",
            "elements": [
                {
                    "type": "mrkdwn",
                    "text": ":pushpin: Do you have any request? Please send a mesage to me!"
                }
            ]
        }
    ]
}

おわりに

はい、無事にSlackに投稿することができました!

1から調べてやるにはちょっと面倒かもしれませんが、手順を見れば思ったより簡単そうではないでしょうか?

チームメンバーがAzureの料金を毎日Portalに見に行く手間を削減したり、1発としては大きくないけどチリツモで無駄遣いになっているリソースなどに気付きやすくなったりと、それなりにいいことはある気がしますのでぜひトライしてみてくださいね。

それではあなたのAzureライフが楽しくなるように!Stay Azure!バイバイ!