Cloud/AWS

Telegram Bot으로 AWS 비용 확인하기

지기(ZIGI) 2022. 11. 15. 23:43

Today Keys : aws, cost, explorer, 비용, finops, telegram, 텔레그램, bot, 핀옵스 


이번 포스팅에서는 AWS 비용을 Telegram으로 받아보기 위한 방법에 대한 내용입니다.

Telegram Bot을 만들고, Lambda를 이용해서 AWS 비용을 정기적으로 받아 볼 수 있도록 구성되어 있습니다.

추가적으로 Budget 설정을 통해서 정해진 Budget이 초과되는 경우에도 유사하게 구성을 할 수가 있으며,

Code를 수정해서 필요한 비용과 관련된 내용을 받아 볼 수 있도록 하실 수도 있습니다.


 
Telegram으로 Bot 및 Group 만들기
 

Telegram으로 Bot을 만듭니다.

Telegram에서 BotFather를 검색 후, 대화창을 엽니다.

'/newbot' 을 입력하면, 새로운 bot을 만들 수 있습니다.

Bot name과 Bot의 username을 차례로 입력합니다.

본 포스팅에서는 'zigi_finops'와 'zigi_finops_bot'으로 name과 username을 입력했습니다.

참고로, username의 마지막은 'bot'으로 끝나야 합니다.

Bot이 성공적으로 생성 되면, Bot에 대한 Token 값이 발급 됩니다.

zigi_finops bot은 '56084~'로 시작하는 token 값을 발급 받았습니다.

 
 
 

Bot이 만들어졌으며, AWS 비용을 받아 볼 Telegram Group을 만듭니다.

저는 ZIGI-FinOps라는 이름으로 Telegram Group을 만들었습니다. 

 
 
 

Telegram Group에 방금 전에 만들었던 bot인 zigi_finops를 Member로 추가합니다. 

 
 
 

그리고, 'get id bot'도 Group에 추가합니다.

get id bot은 Group에 대한 'Chat ID' 값을 확인하기 위해 필요한 bot입니다.

 
 
 

이제 ZIGI-FinOps Group에서,

@get_id_bot 을 호출하면 아래와 같이 'group Chat ID' 값을 get id bot이 응답합니다.

ZIGI-FinOps Group의 group Chat ID 값은 '-75~'로 시작합니다.

group Chat ID 값은 앞에 '-'시작하는 값이며, Chat ID와는 다르니 이를 확인하셔야 합니다.

 

 

 

 

이제 group Chat ID 값을 확인 했으면, get id bot은 본인의 소명을 다했기 때문에

ZIGI-FinOps Group에서 가볍게 쫓아내겠습니다.

 
 
 
 
Cost Explorer에서 비용을 가져오는 Lambda 만들기
 

이제 Telegram에 Bot과 Group을 모두 준비했으니,

Telegram Bot으로 메시지를 전달 할 Code를 짜보겠습니다.

어떤 것으로 Code를 짜도 상관 없으나, 원하는 시점에만 비용을 Telegram으로 보내면 되기 때문에

별도의 EC2를 만들지는 않고, Lambda로 구성해보겠습니다.

 

새로운 Lambda Function을 만듭니다.

저는 ZIGI-Daily-Cost-Report라는 이름으로 Report를 만들겠습니다.

Runtime은 Python 3.7으로 Architecture는 기본 값인 x86_64로 설정하고 Lambda Function을 만듭니다.

 
 

 

이제 Labmda가 만들어 졌으니, Telegram으로 비용을 보낼 코드를 짜보겠습니다.

코드를 짜기 전에, 코드에서 사용 할 변수 값을 입력합니다.

 
 
 

코드에서 사용 할 변수 값은 2개입니다.

하나는 Bot을 생성했을 때의 'Token' 값이고, 나머지 하나는 Telegram Group의 'Group Chat ID' 입니다.

원하는 변수 명과, 앞서 확인한 Token과 Group Chat ID 값으로 변수를 생성합니다.

저는 TOKEN과 GROUP_ID라는 이름으로 변수를 생성했습니다.

 
 

 

이제 Cost Explorer에서 현재 비용을 가져오는 Code를 작성합니다.

현재 날짜와 함께 현재 환율, 그리고 AWS 비용을 $(달러)와 \(원화)로 함께 출력하는 코드입니다.

 
 
 

다음은 전체 코드 내용입니다.

import datetime
import os

import boto3
from botocore.vendored import requests
from dateutil import relativedelta

#환
import urllib.request 
import urllib.parse as parse



TOKEN = os.environ['TOKEN']
GROUP_ID = os.environ['GROUP_ID']
TELEGRAM_URL = "https://api.telegram.org/bot{}/sendMessage".format(TOKEN)

def lambda_handler(event, context):
    def monthly_bill(start, end):
        response = client.get_cost_and_usage(
            TimePeriod={"Start": start, "End": end},
            Granularity="MONTHLY",
            Metrics=["UnblendedCost"],
            Filter={
                'Dimensions': {
                  'Key': 'PAYMENT_OPTION',
                  'Values': [
                     'USAGE',
                  ],
                'MatchOptions': [
                    'EQUALS',
                ]
               }
            }
        )
        amount = response["ResultsByTime"][0]["Total"]["UnblendedCost"]["Amount"]
        output = "%.3f" % float(amount)

        return str(output)

    this_month = datetime.datetime.today()
    next_month = this_month + relativedelta.relativedelta(months=1)

    this_month_1st = this_month.strftime("%Y-%m-01")
    next_month_1st = next_month.strftime("%Y-%m-01")

    client = boto3.client("ce")
    this_month_bill = monthly_bill(this_month_1st, next_month_1st)
    
    #print(type(this_month_bill))
    
    #### 환율 조회 ####
    url = "https://quotation-api-cdn.dunamu.com/v1/forex/recent?codes=FRX.KRWUSD"
    ex_html = urllib.request.urlopen(url)
    encoding = ex_html.info().get_content_charset(failobj='utf-8')

    ex_html = ex_html.read().decode(encoding)

    ex_html = ex_html[1:len(ex_html)-1]
    ex_html = ex_html.replace("null","'null'")
    ex_dict = eval(ex_html)
    exchange = ex_dict['basePrice']
    ##############
    this_month_bill_dollor = int((float(this_month_bill)))
    this_month_bill_dollor = format(this_month_bill_dollor,",")
    
    this_month_bill_won =  int((float(this_month_bill))*exchange)
    this_month_bill_won = format(this_month_bill_won,",")
    
    
    ### telegram 메시지 ###
    
    message = "[ AWS 운용 비용(Daily) ]\n" \
    + this_month.strftime("▶ 기준일 : %Y년 %m월 %d일") + "\n"  \
    + "▶ 계정 : ZIGI Account (012345678912)\n" \
    + "▶ 환율 : " +  str(exchange) +  "원\n" \
    + "▶ 비용(원) : " + str(this_month_bill_won) + "원 (월 누적)\n" \
    + "▶ 비용( $) : " + str(this_month_bill_dollor) + "달러(월 누적)"
    #######################
    
    
    # Basic exception handling. If anything goes wrong, logging the exception    
    try:


        # Payload to be set via POST method to Telegram Bot API
        payload = {
            "text": message.encode("utf8"),
            "chat_id": GROUP_ID
        }

        # Posting the payload to Telegram Bot API
        requests.post(TELEGRAM_URL, payload)

    except Exception as e:
        raise e
 
 

이제 Lambda가 Cost Explorer에서 비용을 가져와야 하기 때문에

Lambda에 Cost Explorer의 비용을 조회 할 수 있는 권한을 추가하겠습니다.

Configuration-Permissions에서 Execution role을 선택합니다.

 
 
 

Lambda를 실행할 Role에서 inline policy를 추가하겠습니다.

 
 
 

Policy에서 아래와 같이 ce:GetCostAndUsage에 대한 권한을 허용하도록 설정합니다.

 

 

 
inline policy에 대한 이름을 입력하는 데,
본 포스팅에서는 'zigi_Get_Cost_Usage'라고 설정하고, Inline Policy를 생성을 합니다.
 

 

 
이후, Lambda를 Depoly 한 후 Test로 Lambda를 실행합니다.
Lambda를 실행하면 아래와 같이 ZIGI-FinOps Group에 zigi_finops Bot가 AWS 비용을 출력해주는 것을 볼 수 있습니다.
 
 
간혹, Lambda 호출 시에 실행 시간이 3초를 초과하여 Timeout이 걸려서
정상적으로 Telegram에 비용을 보내지 못하는 경우가 있는 데,
이를 방지하기 위해서 Lambda의 Timeout을 10초 정도로 수정합니다.
실제 수행 시간은 대체로 2~3초정도입니다.
 
 
 
 
Lambda를 정기적으로 호출 할 EventBridge 만들기
 
이제 Lambda를 이용해서, Cost Explorer에서 AWS 비용을 가져와서
Telegram Group 보내서 Bot이 비용 값을 출력하는 것까지 확인했습니다.
다음으로는 매일 아침마다 Bot이 AWS 비용을 Telgram Group에 알려줄 수 있도록 설정 하겠습니다.
정기적으로 Lambda를 호출하기 위해서 Amazon EventBridge를 사용하겠습니다.
EventBridge에서 새로운 Rule을 추가합니다.
Rule Name을 입력하고,
매일 아침마다 정기적으로 호출하도록 설정할 것이기 때문에  Rule Type을 Schedule로 설정합니다.
 
 
 
본 포스팅에서는 매일 아침 업무를 시작하는 9시에 비용을 Telegram으로 받아보도록 설정하기 위해서
Schedule pattern에서 위에 있는 패턴을 선택하고, 
하단의 Cron expression은 매일 9시로 설정하기 위해서
0 0 * * ? * 으로 설정합니다.
 
 
 

이제 Target을 선택합니다.

Target은 앞서 만든 'ZIGI-Daily-Cost-Report' Lambda Function을 지정합니다.

이후 원하는 Tag 값을 설정하고 설정한 값을 리뷰한 뒤에 Rule 생성을 완료합니다.

 
 
 

Lambda에서 확인해 보면,

ZIGI-Daily-Cost-Report라는 Lambda Function의 Trigger에 방금 전에 만든 EventBridge Rule이 추가 된 것을 볼 수 있습니다.

 
 
 

이제 오전 9시가 되면, 아래와 같이

Telegram Group에 zigi_finops Bot이 AWS 비용을 출력해주는 것을 볼 수 있습니다.

 
 

 

※ 참고

https://bezdelev.com/hacking/aws-sns-to-telegram-bot/

https://aws.plainenglish.io/get-aws-billing-everyday-with-eventbridge-lambda-and-sns-8013b181e28c