GCP Billing - Budget Alert

김민형·2022년 6월 28일
1

GCP - Infra

목록 보기
6/14

아키텍처

Billing 관리를 BigQuery 테이블에 push가 된 후에 거기서 메신저로 트리거하는 것은 사실상 비용 통계를 내기 위한 것이고 실제 비용을 모니터링하기 위한 파이프라인으로는 비효율적이다.

Budget과 Pub/Sub 주제를 만들어서 연결

원래 만들어놓은 예산이 있을 경우 목표 금액 및 예상 비용에 대한 어떤 작업이 지정되어 있는지 확인해야 한다.

Cloud Function

Pub/Sub이 트리거하는 Function 생성

  • 메모리: 128GB
  • 런타임: Node.js10

Slack

index.js

const request = require(`request`);

// Translate JSON message into Text
exports.notifySlack = async (pubsubEvent, context) => {
    const pubsubData = Buffer.from(pubsubEvent.data, 'base64').toString();
    const alert = JSON.parse(pubsubData);

    // Format of message can be found here: https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications#notification_format
    let title = "", message = ""
    // If there was an alert threshold breached
    if ("alertThresholdExceeded" in alert) {
        title = ":bangbang: GCP Budget Alert Threshold Exceeded :bangbang:"
        message = `*Budget Name:* ${alert.budgetDisplayName}` +
            `\n*Current Spend:* ${alert.costAmount}` +
            `\n*Budget Amount Exceeded:* ${alert.budgetAmount}` +
            `\n*Alert Threshold:* ${alert.alertThresholdExceeded * 100}%` +
            `\n*Currency Type:* ${alert.currencyCode}` +
            `\n*Budget Start Date:* ${alert.costIntervalStart}`
    } else if ("forecastThresholdExceeded" in alert) {
        title = ":warning: GCP Budget Forecast Threshold Exceeded :warning:"
        message = `*Budget Name:* ${alert.budgetDisplayName}` +
            `\n*Current Spend:* ${alert.costAmount}` +
            `\n*Forecast Amount Exceeded:* ${alert.budgetAmount}` +
            `\n*Forecast Threshold:* ${alert.forecastThresholdExceeded * 100}%` +
            `\n*Currency Type:* ${alert.currencyCode}` +
            `\n*Budget Start Date:* ${alert.costIntervalStart}`
    } else {
        // Uncomment the section below if you wish to get general updates about a budget. This will be noisy! Updates are sent every 20 to 30 minutes per budget.
        /*
        title = "Budget Update"
        message = `*Budget Name:* ${alert.budgetDisplayName}` +
            `\n*Current Spend:* ${alert.costAmount}` +
            `\n*Budget Amount:* ${alert.budgetAmount}` +
            `\n*Currency Type:* ${alert.currencyCode}` +
            `\n*Budget Start Date:* ${alert.costIntervalStart}`
        */
    }

    if (title != "" && message != "") {
        // Post message to the room
        request({
            uri: <Slack webhook URL 입력>,
            method: "POST",
            json: {
                "blocks": [
                    {
                        "type": "header",
                        "text": {
                            "type": "plain_text",
                            "text": title,
                            "emoji": true
                        }
                    },
                    {
                        "type": "divider"
                    },
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": message
                        }
                    },
                    {
                        "type": "divider"
                    },
                    {
                        "type": "actions",
                        "elements": [
                            {
                                "type": "button",
                                "text": {
                                    "type": "plain_text",
                                    "text": "View In GCP Billing Console",
                                    "emoji": true
                                },
                                "style": "primary",
                                "url": "https://console.cloud.google.com/billing"
                            }
                        ]
                    }
                ]
            }
        }, function (error, response, body) {
            if (error) {
                console.log(error);
            } else {
                console.log(response.statusCode, body);
            }
        });
    }
};

package.json

{
    "name": "cloud-functions-billing",
    "version": "0.0.1",
    "dependencies": {
      "request": "2.88.2"
  }
}

Google Chat

index.js

const request = require(`request`);

// Translate JSON message into Text
exports.notifyHangouts = async (pubsubEvent, context) => {
    const pubsubData = Buffer.from(pubsubEvent.data, 'base64').toString();
    const alert = JSON.parse(pubsubData);
   
    // Check to see if the budget has been exceeded. (without this, budget updates will be sent every 30 minutes)
    if (alert.costAmount >= alert.budgetAmount) {

    // Post message to the room
    request({
      uri: "<Google Chat 스페이스 webhook URL 입력>",
      method: "POST",
      json: {
  "text": "One of your GCP Projects has exceeded its monthly budget.",
  "cards": [
    {
      "header": {
        "title": "GCP Billing Alert",
        "subtitle": "Automated Budget Notificaiton",
        "imageUrl": "https://goo.gl/aeDtrS"
      },
      "sections": [
        {
          "widgets": [
              {
                "keyValue": {
                  "topLabel": "Budget Exceeded",
                  "content": alert.budgetDisplayName
                  }
              },
              {
                "keyValue": {
                  "topLabel": "Budget Start Date",
                  "content": alert.costIntervalStart.toString()
                }
              },
              {
                "keyValue": {
                  "topLabel": "Budget Amount",
                  "content": alert.budgetAmount.toString()
                }
              },
              {
                "keyValue": {
                  "topLabel": "Alert Threshold",
                  "content": alert.alertThresholdExceeded.toString()
                }
              },
              {
                "keyValue": {
                  "topLabel": "Current Spend",
                  "content": alert.costAmount.toString()
                }
              },
              {
                "keyValue": {
                  "topLabel": "Currency Type",
                  "content": alert.currencyCode.toString()
                }
              }
          ]
        },
        {
          "widgets": [
              {
                  "buttons": [
                    {
                      "textButton": {
                        "text": "OPEN THE GCP CONSOLE",
                        "onClick": {
                          "openLink": {
                            "url": "https://console.cloud.google.com"
                          }
                        }
                      }
                    }
                  ]
              }
          ]
        }
      ]
    }
  ]
}               
    }, function(error, response, body){
    if(error) {
        console.log(error);
    } else {
        console.log(response.statusCode, body);
    }
});

}
};

package.json은 Slack과 동일

주의할 점!
index.js 내에서 아래의 코드는 Billing의 예산 및 알림에서 지정해준 예산의 예산 비율 및 금액(임계값)을 넘었을 시 발생한다.
전체 코드를 보면 const pubsubData에서 발생한 데이터들을 JSON으로 파싱해서 코드에 찍히는데 지정해준 예산의 예산 비율이나 금액을 초과하지 않았을 경우 alert.alertThresholdExceeded.toString()가 찍히지 않아서 알람이 제대로 오지 않는다.

{
                "keyValue": {
                  "topLabel": "Alert Threshold",
                  "content": alert.alertThresholdExceeded.toString()
                }
              },

이런 식으로 정해진 금액이 있을 경우 위의 코드를 빼거나, 저 기준을 모두 없애주면 테스트 가능할 것이다.

Scheduling

하지만 Budget자체의 설정으로 30분마다 한 번씩 Pub/Sub 메시지를 게시한다.
하루에 한 번씩 정해진 시간대에 알람이 오게끔 하고 싶은데 Cloud Scheduler를 쓴다고 해도 Budget자체에선 30분마다 메시지를 게시하니 소용이 없을 것 같았다.
Cloud Function자체에서 코드를 넣어서 설정해줄 것이다.

//변수 설정에 밑에 변수 추가
var now = new Date();	// 현재 날짜 및 시간

//현재 시간도 같이 뜨게 하려면 위젯 하나를 복사해서 content에 아래 코드를 추가
now.toString()

//메시지를 Post하는 조건에 밑에 조건 추가
if (now.getHours() == 9)	// 현재 시간이 9시일 때를 뜻함.

이러면 매일 오전 9~10시 사이에 2번 정도 알람이 올 것이다.

하지만 에러가 날 수 있다. UTC가 안 맞아서 새는 것 같다..

    // 1. 현재 시간(Locale)
    const curr = new Date();

    // 2. UTC 시간 계산
    const utc = 
          curr.getTime() + 
          (curr.getTimezoneOffset() * 60 * 1000);

    // 3. UTC to KST (UTC + 9시간)
    const KR_TIME_DIFF = 9 * 60 * 60 * 1000;
    const now = 
          new Date(utc + (KR_TIME_DIFF));
    
    // 그리고 if에 메시지를 Post하는 시간 조건을 넣어준다

3시로 설정해놨었을 때 새벽에 알람이 왔었다.

[Integrate GCP Budget Alerts 참고]

profile
Solutions Architect (rlaalsgud97@gmail.com)

0개의 댓글