Today Keys : aws, cost, explorer, 비용, finops, telegram, 텔레그램, bot, 핀옵스
이번 포스팅에서는 AWS 비용을 Telegram으로 받아보기 위한 방법에 대한 내용입니다.
Telegram Bot을 만들고, Lambda를 이용해서 AWS 비용을 정기적으로 받아 볼 수 있도록 구성되어 있습니다.
추가적으로 Budget 설정을 통해서 정해진 Budget이 초과되는 경우에도 유사하게 구성을 할 수가 있으며,
Code를 수정해서 필요한 비용과 관련된 내용을 받아 볼 수 있도록 하실 수도 있습니다.
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에서 가볍게 쫓아내겠습니다.
이제 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에 대한 권한을 허용하도록 설정합니다.
이제 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