Lambda를 통해 Teams에 AWS 비용 통지
45918 단어 AWSLambdaBotMicrosoft Teamstech
개시하다
람바다로 AWS 비용을 알리기 때문에 메모입니다.
슬랙 알림에는 정보가 가득하지만, 인터넷의 바다에서는 팀스를 알리는 사람을 보지 못했다.
사용할 수 있는 환경이 아니라는 제약도 있고sam은 사용하지 않았다.
Teams로 incoming-webhook을 준비합니다.
공식 서류에 따라 진행하다.웹훅 URL 제어하는 거 잊지 마세요.
람다만의 IAM Role이 함께 만들어졌습니다.
매니저부터 lambda 페이지까지 함수를 만듭니다.
함수의 이름은
aws_billing
등 임의의 이름으로 만듭니다.기본 실행 캐릭터가 생성되었기 때문에,iam 페이지에 부속된 정책을 변경하여, 생성된 캐릭터가 비용을 받을 수 있도록 합니다.
우선 다음 두 가지 AWS 관리 정책을 첨부합니다.
정책을 작성하려면 라이센스
Cost Explorer Service
의 ReadOnly를 사용합니다.CostExplorerRead
등 임의의 명칭으로 저장하고 첨부한다.Lambda 코드에 대한 설명 (python)
나는 학급 방법의 슬랙 알림용 글의 코드를 기초 위에 썼다.
Incomming-Webhook URL은 환경 변수로 만들어 숨기려 했지만 시간이 없어 포기했습니다.
시간 있을 때 해.
TEAMS_WEBHOOK_URL
에 이전 장에서 만든 Teams의 Incoming-Webhook URL을 입력합니다.import os
import boto3
import json
import requests
from datetime import datetime, timedelta, date
# TODO: 環境変数化
# SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
TEAMS_WEBHOOK_URL = "ここにWebhookURLを入力"
def lambda_handler(event, context) -> None:
client = boto3.client('ce', region_name='ap-northeast-1')
# 合計とサービス毎の請求額を取得する
total_billing = get_total_billing(client)
service_billings = get_service_billings(client)
# Slack用のメッセージを作成して投げる
(title, detail) = get_message(total_billing, service_billings)
print(f'title: {title}')
print(f'detail: {detail}')
# post_slack(title, detail)
post_teams(title, detail, service_billings)
def get_total_billing(client) -> dict:
(start_date, end_date) = get_total_cost_date_range()
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ce.html#CostExplorer.Client.get_cost_and_usage
response = client.get_cost_and_usage(
TimePeriod={
'Start': start_date,
'End': end_date
},
Granularity='MONTHLY',
Metrics=[
'AmortizedCost'
]
)
return {
'start': response['ResultsByTime'][0]['TimePeriod']['Start'],
'end': response['ResultsByTime'][0]['TimePeriod']['End'],
'billing': response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount'],
}
def get_service_billings(client) -> list:
(start_date, end_date) = get_total_cost_date_range()
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ce.html#CostExplorer.Client.get_cost_and_usage
response = client.get_cost_and_usage(
TimePeriod={
'Start': start_date,
'End': end_date
},
Granularity='MONTHLY',
Metrics=[
'AmortizedCost'
],
GroupBy=[
{
'Type': 'DIMENSION',
'Key': 'SERVICE'
}
]
)
billings = []
for item in response['ResultsByTime'][0]['Groups']:
billings.append({
'service_name': item['Keys'][0],
'billing': item['Metrics']['AmortizedCost']['Amount']
})
return billings
def get_message(total_billing: dict, service_billings: list) -> (str, str):
start = datetime.strptime(total_billing['start'], '%Y-%m-%d').strftime('%m/%d')
# Endの日付は結果に含まないため、表示上は前日にしておく
end_today = datetime.strptime(total_billing['end'], '%Y-%m-%d')
end_yesterday = (end_today - timedelta(days=1)).strftime('%m/%d')
total = round(float(total_billing['billing']), 2)
title = f'{start}~{end_yesterday}の請求額は、{total:.2f} USDです。'
details = []
for item in service_billings:
service_name = item['service_name']
billing = round(float(item['billing']), 2)
if billing == 0.0:
# 請求無し(0.0 USD)の場合は、内訳を表示しない
continue
details.append(f' ・{service_name}: {billing:.2f} USD')
return title, '\n'.join(details)
def post_slack(title: str, detail: str) -> None:
# https://api.slack.com/incoming-webhooks
# https://api.slack.com/docs/message-formatting
# https://api.slack.com/docs/messages/builder
payload = {
'attachments': [
{
'color': '#36a64f',
'pretext': title,
'text': detail
}
]
}
# http://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/
try:
response = requests.post(SLACK_WEBHOOK_URL, data=json.dumps(payload))
except requests.exceptions.RequestException as e:
print(e)
else:
print(response.status_code)
def post_teams(title: str, detail: str, service_billings: list) -> None:
# https://docs.microsoft.com/ja-jp/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL
# payload = {
# 'title': title,
# 'text': detail
# }
facts = []
for item in service_billings:
service_name = item['service_name']
billing = round(float(item['billing']), 2)
if billing == 0.0:
# 請求無し(0.0 USD)の場合は、内訳を表示しない
continue
dict_tmp = {'name': f'{billing:.2f} USD', 'value':service_name, 'billing':billing}
facts.append(dict_tmp)
facts_sorted_by_billing = sorted(facts, key=lambda x:x['billing'], reverse=True)
# ソート用に保持していたbilling要素を削除
for item in facts_sorted_by_billing:
del item['billing']
payload = {
'@type': 'MessageCard',
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"summary": title,
"sections": [{
"activityTitle": title,
"activitySubtitle": "サービス別利用金額(金額降順)",
"activityImage": "https://img.icons8.com/color/50/000000/amazon-web-services.png",
"facts": facts_sorted_by_billing,
"markdown": 'true',
"potentialAction": [{
"@type": "OpenUri",
"name": "Cost Management Console",
"targets": [{
"os": "default",
"uri": "https://console.aws.amazon.com/cost-management/home?region=ap-northeast-1#/dashboard"
}]
}]
}]
}
try:
response = requests.post(TEAMS_WEBHOOK_URL, data=json.dumps(payload))
except requests.exceptions.RequestException as e:
print(e)
else:
print(response.status_code)
def get_total_cost_date_range() -> (str, str):
start_date = get_begin_of_month()
end_date = get_today()
# get_cost_and_usage()のstartとendに同じ日付は指定不可のため、
# 「今日が1日」なら、「先月1日から今月1日(今日)」までの範囲にする
if start_date == end_date:
end_of_month = datetime.strptime(start_date, '%Y-%m-%d') + timedelta(days=-1)
begin_of_month = end_of_month.replace(day=1)
return begin_of_month.date().isoformat(), end_date
return start_date, end_date
def get_begin_of_month() -> str:
return date.today().replace(day=1).isoformat()
def get_prev_day(prev: int) -> str:
return (date.today() - timedelta(days=prev)).isoformat()
def get_today() -> str:
return date.today().isoformat()
Lambda 코드의 변경점
크라메소의 글 코드와 차이가 있는 부분은
post_teams()
부분을 추가했다.서비스 분류에 따른 비용은 내림차순으로 표시하는 방식으로 투고한다.
슬랙의 정보도 다양한 맞춤 제작이 가능해 흥미롭고, 팀스도 어느 정도 맞춤 제작이 가능하다.
연결기 메시지 규격의 공식 문서는 다음과 같다.
def post_teams(title: str, detail: str, service_billings: list) -> None:
# https://docs.microsoft.com/ja-jp/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL
# payload = {
# 'title': title,
# 'text': detail
# }
facts = []
for item in service_billings:
service_name = item['service_name']
billing = round(float(item['billing']), 2)
if billing == 0.0:
# 請求無し(0.0 USD)の場合は、内訳を表示しない
continue
dict_tmp = {'name': f'{billing:.2f} USD', 'value':service_name, 'billing':billing}
facts.append(dict_tmp)
facts_sorted_by_billing = sorted(facts, key=lambda x:x['billing'], reverse=True)
# ソート用に保持していたbilling要素を削除
for item in facts_sorted_by_billing:
del item['billing']
payload = {
'@type': 'MessageCard',
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"summary": title,
"sections": [{
"activityTitle": title,
"activitySubtitle": "サービス別利用金額(金額降順)",
"activityImage": "https://img.icons8.com/color/50/000000/amazon-web-services.png",
"facts": facts_sorted_by_billing,
"markdown": 'true',
"potentialAction": [{
"@type": "OpenUri",
"name": "Cost Management Console",
"targets": [{
"os": "default",
"uri": "https://console.aws.amazon.com/cost-management/home?region=ap-northeast-1#/dashboard"
}]
}]
}]
}
try:
response = requests.post(TEAMS_WEBHOOK_URL, data=json.dumps(payload))
except requests.exceptions.RequestException as e:
print(e)
else:
print(response.status_code)
Teams 발언 아이콘
이번 코드는 투고한 정보함에 aws 아이콘이 포함된 것인데 배경이 투명한 아이콘만 발견됐기 때문에 Teamas의 어두운 패턴으로 보면 뭔가 미묘한 느낌이 든다...
만약 배경에 흰색을 칠한aws 아이콘 URL을 아는 사람이 있다면 댓글로 저에게 알려주세요.
주기적으로 수행되는 트리거 설정
트리거 추가
EventBridge(Cloud Watch Events)
를 선택한 다음cron으로 씁니다.이번에는 직원들이 근무일 오전 10시에 통지할 수 있도록
cron(0 1 ? * MON-FRI *)
결정했다.(UTC이므로 마이너스 9시간)만들어진 물건
끝맺다
일이 힘들 때 bot을 하면 휴식 시간이 좋아지기 때문에 Teams를 이용하는 분들은 꼭 해보세요.
Reference
이 문제에 관하여(Lambda를 통해 Teams에 AWS 비용 통지), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/antyuntyun/articles/aws_cost_notification_to_teams_by_lambda텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)