Cloud Run Functions + SendGrid で Cloud SQLの監視メールを自動送信する
1. はじめに
クラウドエース安田です。
システム運用においては、Cloud SQL などのリソースの稼働状況や異常をいち早く把握し、迅速に対応することが重要です。そのため、監視結果を定期的にメールで受け取ることで、運用担当者が異常や傾向変化にすぐ気付き、障害の予防や早期対応につなげることができます。
しかし、こうした監視メールを人力で送る運用では、手間やミスが発生しやすく、運用負荷も高まります。こうした課題を解決するために、監視メールの自動化が有効な手段となります。
この記事では、Google Cloud の Cloud Run Functions を使用して Cloud SQL のメトリクスを監視し、SendGrid API を活用してメールを自動送信するシステムの実装方法を紹介します。
この記事の目的
この記事では、以下の目的を達成するための実装方法を紹介します。
- Cloud SQL の監視を自動化し、運用負荷を軽減する
- 定期的なメール通知により、システムの状態を継続的に把握する
- 異常の早期発見と迅速な対応を可能にする
- 運用担当者の作業効率を向上させる
これらの目的を達成するために、Cloud Run Functions と SendGrid API を組み合わせた実装方法を詳しく解説します。
システムの概要
実装するシステムは以下のような流れで動作します
- Cloud Scheduler が指定された時間にトリガー
- Cloud Run Functions が実行され、以下の処理を実施
- Cloud SQL のメトリクスを取得
- 現在値とその日の最高値を計算
- SendGrid API を使用してメールを送信
- 受信者は定期的にシステムの状態を確認可能
このシステムにより、Cloud SQL の状態を定期的に把握し、必要に応じて対応することが可能になります。
2. システム構成
使用する Google Cloud サービス
このシステムでは、以下の Google Cloud サービスを使用します
-
Cloud Run Functions
- Python 3.9 ランタイム
- HTTP トリガー
- メトリクス取得とメール送信の処理を実装
-
Cloud SQL
- 監視対象のデータベース
- CPU、メモリ、ディスク使用率のメトリクスを提供
-
Cloud Scheduler
- 1 日 3 回(6 時、12 時、18 時)の定期実行
- HTTP リクエストによる Cloud Run Functions のトリガー
- 日本時間(Asia/Tokyo)でのスケジュール管理
-
Cloud Monitoring
- Cloud SQL インスタンスのメトリクス(CPU、メモリ、ディスク使用率など)を取得
-
Secret Manager
- SendGrid API キーなどのシークレット情報を安全に管理・取得
アーキテクチャ図
3. 事前準備
Cloud Run Functions の実行に必要なリソースを作成します。
3.1 必要なリソース
- Google Cloud プロジェクト
- サービスアカウント(以下のロールが必要)
- Cloud Run 起動権限(
roles/cloudrun.invoker
) - Cloud Monitoring 閲覧権限(
roles/monitoring.viewer
) - Cloud SQL 閲覧権限(
roles/cloudsql.viewer
)
- Cloud Run 起動権限(
- Secret Manager に登録した SendGrid API キー
- Cloud Scheduler の作成
- Cloud SQL の作成(監視対象)
3.2 サービスアカウントの作成と権限設定
このシステムで使用するサービスアカウントと必要な権限を、Terraform で定義していきます。
resource "google_service_account" "main" {
account_id = "sql-monitoring-sa"
display_name = "Cloud SQL Monitoring Service Account"
description = "Cloud SQLの監視メールを送信するためのサービスアカウント"
}
# 必要な権限の付与
resource "google_project_iam_member" "cloud_functions_invoker" {
project = var.project_id
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:${google_service_account.main.email}"
}
resource "google_project_iam_member" "monitoring_viewer" {
project = var.project_id
role = "roles/monitoring.viewer"
member = "serviceAccount:${google_service_account.main.email}"
}
resource "google_project_iam_member" "cloudsql_viewer" {
project = var.project_id
role = "roles/cloudsql.viewer"
member = "serviceAccount:${google_service_account.main.email}"
}
3.3 Secret Manager の設定
resource "google_secret_manager_secret" "sendgrid_api_key" {
secret_id = "{your-secret-name}"
replication {
automatic = true
}
}
resource "google_secret_manager_secret_version" "sendgrid_api_key" {
secret = google_secret_manager_secret.sendgrid_api_key.id
secret_data = "{your-sendgrid-api-key}" # SendGrid の API キーを設定
}
3.4 Cloud Scheduler の作成
Cloud Scheduler を作成して、Cloud Run Functions を定期的に実行するように設定します。
resource "google_cloud_scheduler_job" "sql_monitoring" {
name = "sql-monitoring-job"
description = "Cloud SQLの監視メールを送信するジョブ"
schedule = "0 6,12,18 * * *" # 6時、12時、18時に実行
time_zone = "Asia/Tokyo"
http_target {
http_method = "POST"
uri = "<URL>"
oidc_token {
service_account_email = google_service_account.main.email
}
}
}
3.5 Cloud SQL の作成
監視対象の Cloud SQL インスタンスを作成します。
resource "google_sql_database_instance" "test_instance_1" {
name = "test-instance-1"
database_version = "MYSQL_8_0"
region = "asia-northeast1"
settings {
tier = "db-f1-micro"
}
deletion_protection = false
}
resource "google_sql_database_instance" "test_instance_2" {
name = "test-instance-2"
database_version = "MYSQL_8_0"
region = "asia-northeast1"
settings {
tier = "db-f1-micro"
}
deletion_protection = false
}
3.6 SendGrid の登録と API キーの取得
新規登録
今回は API キーの取得方法のみを紹介します。新規登録の仕方は以下を参考にしてください。
API キーの取得
1.「Create API Key」を選択
ダッシュボードから「Settings > API Keys」を選択して、画面右上の「Create API Key」を選択します。
2.名前とアクセス許可
API キーの名前と、アクセスレベルを選択します。今回は[ Full Access ] で問題ありません。
3.API キーの発行
「Create & View」ボタンを選択すると、API キーが発行され、画面に表示されます。
この API キーは 1 度しか表示されず、再確認することはできないため適切な場所に保存してください。この後の環境変数の設定の際に使用します。
4. Cloud Run Functions のデプロイ
4.1 デプロイ用ソースコード
main.py
Cloud Run Functions で Cloud SQL のメトリクスを取得し、SendGrid 経由で監視メールを送信するメインの実装ファイルです。
import os
import time
import requests
import functions_framework
from datetime import datetime, timedelta, timezone
from google.cloud import monitoring_v3
from google.protobuf import timestamp_pb2
PROJECT_ID = os.environ['PROJECT_ID']
SENDGRID_API_KEY = os.environ['SENDGRID_API_KEY']
EMAIL_FROM = os.environ['EMAIL_FROM']
EMAIL_TO = os.environ['EMAIL_TO']
CLOUDSQL_INSTANCES = os.environ.get('CLOUDSQL_INSTANCES', '').split(',')
INFRA_PROJECT_ID = os.environ.get('INFRA_PROJECT_ID', PROJECT_ID)
infra_client = monitoring_v3.MetricServiceClient()
def get_metric_value(filter_str, project_id_override=None):
try:
target_project = project_id_override if project_id_override else INFRA_PROJECT_ID
end_time = timestamp_pb2.Timestamp(seconds=int(time.time()))
start_time = timestamp_pb2.Timestamp(seconds=int(time.time()) - 300)
interval = monitoring_v3.TimeInterval(start_time=start_time, end_time=end_time)
series = infra_client.list_time_series(
request={
"name": f"projects/{target_project}",
"filter": filter_str,
"interval": interval,
"view": monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL,
}
)
for s in series:
if s.points: return s.points[0].value.double_value
return None
except Exception: return None
def get_daily_peak_metric_value(filter_str, project_id_override=None):
try:
target_project = project_id_override if project_id_override else INFRA_PROJECT_ID
jst = timezone(timedelta(hours=9))
now_jst = datetime.now(jst)
midnight_jst = now_jst.replace(hour=0, minute=0, second=0, microsecond=0)
end_time = timestamp_pb2.Timestamp(seconds=int(now_jst.timestamp()))
start_time = timestamp_pb2.Timestamp(seconds=int(midnight_jst.timestamp()))
interval = monitoring_v3.TimeInterval(start_time=start_time, end_time=end_time)
request_params = {
"name": f"projects/{target_project}",
"filter": filter_str,
"interval": interval,
"view": monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL,
"aggregation": {
"alignment_period": {"seconds": 300},
"per_series_aligner": monitoring_v3.Aggregation.Aligner.ALIGN_MAX
}
}
series = infra_client.list_time_series(request=request_params)
max_value, max_time_str = None, None
for s in series:
if not s.points: continue
for point in s.points:
value = point.value.double_value
ts_seconds = getattr(point.interval.end_time, 'seconds', None) or \
(int(point.interval.end_time.timestamp()) if hasattr(point.interval.end_time, 'timestamp') else None)
if ts_seconds is None: continue
point_time_dt = datetime.fromtimestamp(ts_seconds, tz=timezone.utc).astimezone(jst)
if max_value is None or value > max_value:
max_value, max_time_str = value, point_time_dt.strftime('%H:%M')
return max_value, max_time_str
except Exception: return None, None
def format_metric_html(label, current_val, peak_val, peak_time_str, current_time_str):
curr_str = f"{current_val * 100:.2f}%" if current_val is not None else "N/A"
peak_str = f"{peak_val * 100:.2f}%" if peak_val is not None and peak_time_str else "N/A"
peak_t_str = peak_time_str if peak_time_str else "N/A"
return (
f"<h3>{label}</h3>"
f"<table style='margin-bottom:10px;border-collapse:collapse;'><tr>"
f"<td style='padding-right:5px;'>現在値</td><td style='padding-right:5px;text-align:right;'>({current_time_str})</td><td style='text-align:right;'>{curr_str}</td>"
f"</tr><tr>"
f"<td style='padding-right:5px;'>今日の最高値</td><td style='padding-right:5px;text-align:right;'>({peak_t_str})</td><td style='text-align:right;'>{peak_str}</td>"
f"</tr></table>"
)
@functions_framework.http
def main(request):
jst = timezone(timedelta(hours=9))
current_H_M = datetime.now(jst).strftime('%H:%M')
email_body_parts = []
metrics_config = [
{"label": "CPU使用率", "type": "cloudsql.googleapis.com/database/cpu/utilization"},
{"label": "メモリ使用率", "type": "cloudsql.googleapis.com/database/memory/utilization"},
{"label": "ディスク使用量", "type": "cloudsql.googleapis.com/database/disk/utilization"}
]
first_instance = True
for instance_name in filter(None, CLOUDSQL_INSTANCES):
if not first_instance:
email_body_parts.append("<hr style='margin:15px 0;border-top:1px solid #ccc;'>")
first_instance = False
instance_display_name = f"{INFRA_PROJECT_ID}:{instance_name}"
email_body_parts.append(f"<h2>{instance_display_name}</h2>")
for metric in metrics_config:
metric_filter = f'metric.type = "{metric["type"]}" AND resource.labels.database_id = "{INFRA_PROJECT_ID}:{instance_name}"'
current_value = get_metric_value(metric_filter)
peak_value, peak_time = get_daily_peak_metric_value(metric_filter)
email_body_parts.append(format_metric_html(metric["label"], current_value, peak_value, peak_time, current_H_M))
subject = "Cloud SQL システム稼働状況"
html_content = (
f"<!DOCTYPE html><html><head><meta charset='UTF-8'><title>{subject}</title></head>"
f"<body style='font-family:sans-serif;margin:15px;'>{''.join(email_body_parts)}</body></html>"
)
email_to_list = [email.strip() for email in EMAIL_TO.split(',') if email.strip()]
response = requests.post(
"https://api.sendgrid.com/v3/mail/send",
headers={"Authorization": f"Bearer {SENDGRID_API_KEY}", "Content-Type": "application/json"},
json={
"personalizations": [{"to": [{"email": addr} for addr in email_to_list]}],
"from": {"email": EMAIL_FROM},
"subject": subject,
"content": [{"type": "text/html", "value": html_content}]
}
)
response.raise_for_status()
return "Email sent successfully.", 200
main.py の主な機能と流れ
-
初期設定
スクリプトは実行に必要な API キー、プロジェクト ID、メールアドレス、監視対象の Cloud SQL インスタンス名などを環境変数から読み込みます。これにより、コードを変更せずに設定を管理できます。
-
メトリクスデータの収集
Google Cloud Monitoring API を利用して、指定された Cloud SQL インスタンスの主要なパフォーマンスメトリクスを取得します。
- get_metric_value 関数:CPU 使用率、メモリ使用率、ディスク使用量といったメトリクスの「現在の値」(直近 5 分間)を取得します。
- get_daily_peak_metric_value 関数:日本標準時(JST)における「今日の最高値」とその発生時刻を、各メトリクスごとに午前 0 時から現在までの範囲で取得します。
-
HTML メールの生成
- format_metric_html 関数:収集したメトリクスデータ(現在の値、今日の最高値、発生時刻など)を見やすい HTML 形式のテーブルに整形します。これにより、メール受信者は各メトリクスの状況を一目で把握できます。
- 監視対象のインスタンスごとに、これらの情報がまとめられ、インスタンスが複数ある場合は区切り線で区切られます。
-
メール送信
生成された HTML コンテンツをメール本文とし、SendGrid API を利用して指定されたメールアドレス(複数指定可能)に送信します。件名は「Cloud SQL システム稼働状況」となります。
requirements.txt
このプロジェクトで必要な Python パッケージを定義するファイルです。
functions-framework>=3.0.0
requests>=2.20.0
google-cloud-monitoring>=2.0.0
env.yaml
Cloud Run Functions の環境変数(メール送信先やインスタンス名など)を設定するファイルです。
EMAIL_FROM: "{your-sender-email@example.com}"
EMAIL_TO: "{recipient1@example.com,recipient2@example.com}"
CLOUDSQL_INSTANCES: "{your-instance-1,your-instance-2}"
INFRA_PROJECT_ID: "{your-cloudsql-project-id}"
4.2 デプロイ手順
1. 作業ディレクトリの準備
# 作業用ディレクトリを作成
mkdir {your-project-name}
cd {your-project-name}
# ソースコードを配置するディレクトリを作成
mkdir src
2. 必要なファイルの配置
ファイルを以下のように配置します
-
src/main.py
: メインの Python コード -
src/requirements.txt
: 依存パッケージの定義 -
env.yaml
: 環境変数の設定
3. デプロイ用シェル変数の設定
Cloud Run Functions のデプロイ時に必要な各種変数(プロジェクト ID やサービスアカウント、シークレット名など)をシェルで設定する手順です。
# Cloud FunctionをデプロイするプロジェクトID
export PROJECT_ID="{your-project-id}"
# プロジェクト番号を自動取得
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
# 関数をデプロイするリージョン
export REGION="{your-region}"
# 関数名
export FUNCTION_NAME="{your-function-name}"
# 関数が使用するサービスアカウントのメールアドレス
export SERVICE_ACCOUNT_EMAIL="{your-service-account@your-project.iam.gserviceaccount.com}"
# Secret Manager に登録した SendGrid API キーのシークレット名
export SECRET_NAME_SENDGRID="{your-secret-name}"
4. Cloud Run Function のデプロイ
ここでは Cloud Run Functions を実際にデプロイするためのコマンド例を示します。
gcloud functions deploy ${FUNCTION_NAME} \
--region=${REGION} \
--project=${PROJECT_ID} \
--runtime=python39 \
--trigger-http \
--no-allow-unauthenticated \
--entry-point=main \
--source=src/. \
--env-vars-file=env.yaml \
--set-secrets="SENDGRID_API_KEY=projects/${PROJECT_NUMBER}/secrets/${SECRET_NAME_SENDGRID}:latest" \
--memory=256MB \
--timeout=180s \
--service-account=${SERVICE_ACCOUNT_EMAIL}
デプロイコマンドのオプション説明
-
--runtime=python39
: Python 3.9 ランタイムを使用 -
--trigger-http
: HTTP リクエストによるトリガー -
--no-allow-unauthenticated
: 認証が必要 -
--entry-point=main
: main.py 内の main 関数をエントリーポイントとして指定 -
--source=src/.
: src ディレクトリをソースコードのルートとして指定 -
--env-vars-file=env.yaml
: 環境変数の設定ファイル -
--set-secrets
: Secret Manager からシークレットを取得 -
--memory=256MB
: メモリ割り当て -
--timeout=180s
: タイムアウト時間(3 分) -
--service-account
: 実行時に使用するサービスアカウント
5. システムの実行
Cloud Run Functions のデプロイが完了すると、Cloud Scheduler が設定したスケジュール(6 時、12 時、18 時)に従って自動的に実行されます。
実行の流れ
- Cloud Scheduler が指定された時間に HTTP リクエストを送信
- Cloud Run Functions が起動し、以下の処理を実行
- Cloud SQL インスタンスのメトリクスを取得
- 現在値とその日の最高値を計算
- メールを送信
送信されるメールの形式
送信されるメールは以下のような形式になります。
Cloud SQL のシステム稼働状況
・インスタンス: {project-id}:test-instance-1
・CPU使用率
[現在値] (12:00) 15.23%
[今日の最高値] (10:30) 45.67%
・メモリ使用率
[現在値] (12:00) 25.45%
[今日の最高値] (09:15) 35.89%
・ディスク使用量
[現在値] (12:00) 45.67%
[今日の最高値] (11:45) 48.90%
・インスタンス: {project-id}:test-instance-2
・CPU使用率
[現在値] (12:00) 12.34%
[今日の最高値] (08:30) 23.45%
・メモリ使用率
[現在値] (12:00) 34.56%
[今日の最高値] (10:15) 45.67%
・ディスク使用量
[現在値] (12:00) 56.78%
[今日の最高値] (11:30) 58.90%
実際に送られてきたメールの様子
6. まとめ
本記事では、Cloud Run Functions と SendGrid API を組み合わせて、Cloud SQL の監視メールを自動送信するシステムの実装方法を紹介しました。このシステムにより、Cloud SQL の状態を定期的に把握し、必要に応じて対応することが可能になります。
実装のポイントは、メトリクス取得の効率化、運用性の向上にあります。また、今後の改善点として、監視項目の拡充、運用面の強化、セキュリティの強化なども考えられます。
Discussion