nendのSREエンジニア入門ブログ

nendのSREエンジニア入門ブログ

AZURE関数アプリで月額使用コストをslack通知するプログラムを開発する手順を解説!


ハンズオン

注意: Slack Webhook URLやAzureのAPIキーなどの秘密情報は公開しないようにしてください。AzureのKey Vaultや環境変数などのセキュアな方法を利用してこれらの情報を管理してください。

0. Azure関数アプリ開発のローカル環境セットアップ

Azure FunctionsのNode.js用のnpmパッケージをインストールします。

npm install azure-functions


1. azure portalでのAzure関数アプリにコード挿入+実行してみる。

以下はwebhookurlを入れたらそこにhelloworldが送られる。

const axios = require('axios');

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const slackWebhookUrl = "url";
    if (!slackWebhookUrl) {
        context.log.error("Environment variable 'SLACK_WEBHOOK_URL' is not set.");
        context.res = {
            status: 400,
            body: "Environment variable 'SLACK_WEBHOOK_URL' is not set."
        };
        return;
    }

    try {
        const message = "Hello, Slack!";
        context.log(`Sending message to Slack: ${message}`);

        const response = await axios.post(slackWebhookUrl, {
            text: message
        });

        context.log("Successfully sent message to Slack.");

        context.res = {
            body: "Message sent successfully!"
        };
    } catch (error) {
        context.log.error(`Failed to send message to Slack. Error: ${error}`);
        context.res = {
            status: 500,
            body: `Failed to send message to Slack. Error: ${error}`
        };
    }
};

2. 月額使用コストを取得する前準備:Azure AD トークンを取得するための準備

  1. Azure AD トークンを取得するための準備: Azure Portal で Azure Active Directory に移動します。 アプリの登録 に移動し、新しいアプリを登録します。 アプリを登録したら、概要 タブから アプリケーション (クライアント) ID と ディレクトリ (テナント) ID をメモします。 証明書とシークレット タブに移動し、新しいクライアントシークレットを追加します。このシークレット値もメモしておきます。 APIのアクセス許可 タブで Azure Service Management の user_impersonation のアクセス許可を追加します。

  2. Azure 関数アプリの環境変数設定: Azure Portal の Function App の設定で以下の環境変数を設定します:

TENANT_ID: 上でメモした ディレクトリ (テナント) ID CLIENT_ID: 上でメモした アプリケーション (クライアント) ID CLIENT_SECRET: 上でメモしたクライアントシークレットの値 SUBSCRIPTION_ID: あなたの Azure サブスクリプション ID RESOURCE_GROUP_ID: コスト情報を取得するリソースグループの ID

  1. コードの更新 (index.js):
const axios = require('axios');

async function getAzureToken(tenantId, clientId, clientSecret) {
    const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/token`;

    const response = await axios.post(tokenUrl, 'grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + clientSecret + '&resource=https://management.azure.com/');
    
    return response.data.access_token;
}

async function getCost(accessToken, subscriptionId, resourceGroupId) {
    const startDate = new Date();
    startDate.setMonth(startDate.getMonth() - 1);
    const endDate = new Date();

    const apiUrl = `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupId}/providers/Microsoft.CostManagement/query?api-version=2019-10-01`;

    const response = await axios.post(apiUrl, {
        type: 'ActualCost',
        timeframe: 'Custom',
        dataSet: {
            granularity: 'Monthly',
            aggregation: {
                totalCost: {
                    name: 'Cost',
                    function: 'Sum'
                }
            }
        },
        timePeriod: {
            from: startDate.toISOString().split('T')[0],
            to: endDate.toISOString().split('T')[0]
        }
    }, {
        headers: {
            'Authorization': `Bearer ${accessToken}`
        }
    });

    return response.data.properties.rows[0][0];
}

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const slackWebhookUrl = process.env["SLACK_WEBHOOK_URL"];
    const tenantId = process.env["TENANT_ID"];
    const clientId = process.env["CLIENT_ID"];
    const clientSecret = process.env["CLIENT_SECRET"];
    const subscriptionId = process.env["SUBSCRIPTION_ID"];
    const resourceGroupId = process.env["RESOURCE_GROUP_ID"];

    try {
        const accessToken = await getAzureToken(tenantId, clientId, clientSecret);
        const cost = await getCost(accessToken, subscriptionId, resourceGroupId);

        const message = `The total cost for the resource group in the past month is: $${cost}`;
        context.log(message);

        const response = await axios.post(slackWebhookUrl, {
            text: message
        });

        context.log("Successfully sent message to Slack.");

        context.res = {
            body: "Message sent successfully!"
        };
    } catch (error) {
        context.log.error(`Failed to retrieve cost or send message to Slack. Error: ${error}`);
        context.res = {
            status: 500,
            body: `Failed to retrieve cost or send message to Slack. Error: ${error}`
        };
    }
};