こんにちは、ゲームソリューション部のsoraです。
今回は、TiDB Cloud APIを使って、請求情報をEventBridge Scheduler+Lambda+SNSで定期通知してみたことについて書いていきます。
構成
EventBridge Schedulerで指定した間隔でLambdaを呼び出して、TiDB Cloud APIを使っての請求情報を取得して、SNS経由でSlack通知する構成です。
Lambda(Python)コード
今回の構成では、TiDB Cloud APIから情報取得する用とSlack通知用の2つがあります。
ディレクトリ構成は以下です。
.
├── lambda
│ ├── slack_notification.py
│ └── tidb-api
│ ├── dist
│ │ └── tidb_api.py
│ └── requirements.txt
├── main.tf
└── terraform.tfvars
TiDB Cloud APIから情報取得するLambdaコードは以下です。
月初と月中で情報を取得することを想定しています。
※テストのためTiDBのAPIキーをLambdaに書くようにしていますが、本来だと良くないためSecrets Managerなどで管理するようにしてください。
TiDB Cloud APIについては、以下をご参照ください。
Billing System OPENAPI (v1beta1)
tidb_api.py
import boto3
import json
import os
import requests
from requests.auth import HTTPDigestAuth
import datetime
from dateutil.relativedelta import relativedelta
from zoneinfo import ZoneInfo
SNSTopicArn = os.environ['SNSTopicArn']
TiDBAPIPublicKey = os.environ['TiDBPublicKey']
TiDBAPIPrivateKey = os.environ['TiDBPrivateKey']
def lambda_handler(event, context):
public_key = TiDBAPIPublicKey
private_key = TiDBAPIPrivateKey
# 実行する年月を取得
date_source = datetime.datetime.now(ZoneInfo("Asia/Tokyo")).date()
if date_source.day == 1:
date_source = date_source - relativedelta(months=1)
date = str(date_source)
year_month = date[0:7]
r_get = requests.get('https://billing.tidbapi.com/v1beta1/bills/' + year_month, auth=HTTPDigestAuth(public_key, private_key))
billing_source = r_get.json()
print(billing_source)
message = []
message.append("TiDB Cloudの利用料を通知します。")
message.append(f"利用年月: {billing_source['overview']['billedMonth']} ,利用料: {billing_source['overview']['runningTotal']}")
if billing_source['overview']['runningTotal'] != "0":
for project in billing_source['summaryByProject']['projects']:
message.append(f"プロジェクト名: {project['projectName']} ,プロジェクト利用料: {project['runningTotal']}")
print(message)
# メッセージをSNSに送信
# 通知を送信するSNSトピックのARNを指定
sns_client = boto3.client('sns')
sns_topic_arn = SNSTopicArn
sns_client.publish(
TopicArn=sns_topic_arn,
Message=json.dumps(message, indent=3, ensure_ascii=False)
)
Pythonのライブラリはrequirements.txtを使って取得します。
requirements.txt
requests==2.31.0
datetime==5.2
python-dateutil==2.8.2
urllib3==1.26.17
boto3を使うときに、urlib3 2.xを使っていると以下エラーが出たため、urlib3 1.xを指定しています。
cannot import name 'DEFAULT_CIPHERS' from 'urllib3.util.ssl_
参考:"cannot import name 'DEFAULT_CIPHERS' from 'urllib3.util.ssl_'"の原因
Slack通知用のLambdaは、色々なところで紹介されていると思いますので割愛します。
Terraformコード
Terraformを使って作成します。
main.tf
variable tidb_public_key {}
variable tidb_private_key {}
variable slack_webhook_url {}
variable slack_channel_name {}
terraform {
#AWSプロバイダーのバージョン指定
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.19.0"
}
}
#tfstateファイルをS3に配置する(配置先のS3は事前に作成済み)
backend s3 {
bucket = "<S3 bucket名>"
region = "ap-northeast-1"
key = "tidb-api.tfstate"
}
}
#AWSプロバイダーの定義
provider aws {
region = "ap-northeast-1"
}
#============ IAMロール作成 start ============
##EventBridge Sheduler
data aws_iam_policy_document events_assume_role {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["scheduler.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource aws_iam_role iam_for_events {
name = "EventBridge_TiDB_API_Role"
assume_role_policy = data.aws_iam_policy_document.events_assume_role.json
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSLambdaRole"]
}
##Lambda(TiDB APIの情報取得用)
data aws_iam_policy_document lambda_assume_role {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource aws_iam_role iam_for_lambda {
name = "Lambda_TiDB_API_Role"
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
inline_policy {
name = "my_inline_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = ["sns:Publish"]
Effect = "Allow"
Resource = "*"
},
]
})
}
}
#============ IAMロール作成 end ============
#============ リソース作成 start ============
##SNS(トピック)
###トピックの作成
resource aws_sns_topic tidb_api_topic {
name = "TiDB-API-topic"
}
data aws_iam_policy_document sns_assume_role {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = ["sns:Publish"]
resources = [aws_sns_topic.tidb_api_topic.arn]
}
}
resource aws_sns_topic_policy sns_topic_policy {
arn = aws_sns_topic.tidb_api_topic.arn
policy = data.aws_iam_policy_document.sns_assume_role.json
}
##Lambda(TiDB APIの情報取得用)
data archive_file lambda_tidbapi {
type = "zip"
source_dir = "lambda/tidb-api/dist"
output_path = "tidb_api.zip"
}
resource aws_lambda_function lambda_tidbapi {
filename = "tidb_api.zip"
function_name = "tidb-api-execution"
role = aws_iam_role.iam_for_lambda.arn
handler = "tidb_api.lambda_handler"
source_code_hash = data.archive_file.lambda_tidbapi.output_base64sha256
runtime = "python3.10"
timeout = 30
environment {
variables = {
SNSTopicArn = aws_sns_topic.tidb_api_topic.arn
TiDBPublicKey = var.tidb_public_key
TiDBPrivateKey = var.tidb_private_key
}
}
}
##Lambda(Slack通知用)
data archive_file lambda_slack {
type = "zip"
source_file = "lambda/slack_notification.py"
output_path = "slack_notification.zip"
}
resource aws_lambda_function lambda_slack {
filename = "slack_notification.zip"
function_name = "tidb-api-to-slack"
role = aws_iam_role.iam_for_lambda.arn
handler = "slack_notification.lambda_handler"
source_code_hash = data.archive_file.lambda_slack.output_base64sha256
runtime = "python3.9"
#slack通知先
environment {
variables = {
HookURL = var.slack_webhook_url
ChannelName = var.slack_channel_name
}
}
}
resource aws_lambda_permission lambda_slack_permission {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda_slack.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.tidb_api_topic.arn
}
##EventBridge Sheduler
resource aws_scheduler_schedule eventbridge {
name = "tidb-api-schedule"
description = "TiDB API Notification"
flexible_time_window {
mode = "OFF"
}
schedule_expression = "cron(0 8 1,15 * ? *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn = aws_lambda_function.lambda_tidbapi.arn
role_arn = aws_iam_role.iam_for_events.arn
}
}
##SNS(サブスクリプション)
resource aws_sns_topic_subscription slack_subscription {
topic_arn = aws_sns_topic.tidb_api_topic.arn
protocol = "lambda"
endpoint = aws_lambda_function.lambda_slack.arn
}
#============ リソース作成 end ============
デプロイ
Pythonのライブラリを取得した後に、Terraformを実行します。
$ pwd
/xxxx/xxxx/tidb-api-lambda/lambda/tidb-api
$ pip install -r requirements.txt --target \dist
$ pwd
/xxxx/xxxx/tidb-api-lambda
$ terraform init
$ terraform apply
動作確認
テストを実行するとSlackに通知が来ることが確認できました。
最後に
今回は、TiDB Cloud APIを使って、請求情報をEventBridge Scheduler+Lambda+SNSで定期通知してみたことを記事にしました。
どなたかの参考になると幸いです。