【Google Cloud】Google Cloudで発生したエラーもAWSのSNSからメール通知したい

【Google Cloud】Google Cloudで発生したエラーもAWSのSNSからメール通知したい

2025.10.22

データ事業本部の川中子(かわなご)です。

今回はGoogle Cloudのエラー通知方法についてです。

ネイティブで用意されているメール通知の方法も存在するのですが、
とてもシンプルな仕様のため、要件によっては別の方法が必要になってきます。

特殊なケースではありますが、AWSのSNSを利用したメール通知の構成を検証したので、
その内容を本記事ではご紹介したいと思います。

Google Cloudにおけるメール通知方法

公式ではCloud Monitoringを利用したメール通知方法や、
SendGridなどのサードパーティツールを利用したメール通知方法が紹介されています。

https://cloud.google.com/monitoring/alerts?hl=ja

https://cloud.google.com/composer/docs/composer-3/configure-email?hl=ja

しかしCloud Monitoringではメールのフォーマットが以下の形式に固定され、
エラーの内容などを柔軟に盛り込むことができない仕様になっています。

1761106823906

またサードパーティツールでは、そもそも利用が制限されるケースがあったり、
APIキーの管理が発生するなどの運用上のデメリットも存在します。

今回紹介する構成はある種特殊なケースではあるのですが、
AWSとGoogle Cloudのマルチクラウド環境がある場合には選択肢になると思います。

検証概要

今回構築した全体のアーキテクチャは以下の通りです。

1761113142935

処理の流れは次のようになります。

  1. HTTPリクエストでCloud Runのジョブを実行しエラー発生
  2. エラー情報をPub/Subトピックに送信
  3. Pub/SubトリガーでCloud Run Functionsが起動
  4. メタデータサーバーからID Tokenを取得
  5. AWS STSでIAMロールを引き受け
  6. 取得した一時認証情報を使ってAWSのSNSにメッセージをパブリッシュ
  7. SNS経由でメール通知

本構成で使用している認証の方法については、以前投稿したブログでも紹介しています。
詳細については以下の記事を参照してください。

https://dev.classmethod.jp/articles/bigquery-jwt-s3-tables-to-bigquery/

検証準備

使用したリソース

検証では以下のリソースを作成しています。

Google Cloud

リソース種別 リソース名 説明
サービスアカウント cm-kawanago-sa AWS認証用サービスアカウント
Pub/Subトピック cm-kawanago-error-topic エラー情報を受け取るトピック
Cloud Run Functions cm-kawanago-error-trigger エラー発生用関数
Cloud Run Functions cm-kawanago-bridge-sns SNSパブリッシュ用関数

AWS

リソース種別 リソース名 説明
IAMロール cm-kawanago-google-jwt-role Google Cloud認証用IAMロール
SNSトピック cm-kawanago-notification 通知用SNSトピック

上記リソースの作成に加えて、Google Cloudで作成したサービスアカウントを、
AWS側で作成したIAMロールの信頼ポリシーにOIDCプロバイダーとして記載しています。

またSNSトピックのサブスクリプションに対して、通知先のメールアドレスを登録しています。

エラー発生用のCloud Run Function

今回は検証用として、意図的にエラーを発生させる関数を作成しました。

Pub/Subトピックに対してエラーメッセージをパブリッシュしています。
なお各種変数は環境変数としてデプロイ時に設定しています。

import functions_framework
from google.cloud import pubsub_v1
import json
import os
from datetime import datetime, timezone

@functions_framework.http
def error_trigger(request):
    """
    HTTPリクエストを受け取り、意図的にエラー情報をPub/Subに送信する関数
    """
    project_id = os.environ.get('GCP_PROJECT', '{projectID}')
    topic_id = os.environ.get('PUBSUB_TOPIC', 'cm-kawanago-error-topic')

    # Pub/Sub Publisher クライアントを初期化
    publisher = pubsub_v1.PublisherClient()
    topic_path = publisher.topic_path(project_id, topic_id)

    # エラー情報を作成
    error_message = {
        'error_type': 'TestError',
        'message': 'これはマルチクラウドエラー通知のテストです',
        'severity': 'ERROR',
        'source': 'cm-kawanago-error-trigger',
        'timestamp': datetime.now(timezone.utc).isoformat()
    }

    # メッセージをJSON形式にエンコード
    message_data = json.dumps(error_message, ensure_ascii=False).encode('utf-8')

    try:
        # Pub/Subにメッセージを送信
        future = publisher.publish(topic_path, message_data)
        message_id = future.result()

        return {
            'status': 'success',
            'message': 'エラー情報をPub/Subに送信しました',
            'message_id': message_id,
            'topic': topic_path
        }, 200

    except Exception as e:
        return {
            'status': 'error',
            'message': f'Pub/Subへの送信に失敗しました: {str(e)}'
        }, 500

requirements.txtには以下の2行だけ記載しています。

functions-framework==3.*
google-cloud-pubsub==2.*

SNSパブリッシュ用Cloud Run Functions

続いてPub/Subからメッセージを受け取り、SNSに転送する関数を作成しました。

get_google_identity_token関数でメタデータサーバーからID Tokenを取得し、
assume_role_with_web_identityでAWSのIAMロールをアシュームしています。

import functions_framework
import base64
import json
import os
import boto3
import urllib.request
import urllib.parse

def get_google_identity_token():
    """Google Identity Token取得"""
    try:
        metadata_server = 'http://metadata.google.internal/computeMetadata/v1/'
        token_request_url = metadata_server + 'instance/service-accounts/default/identity'
        token_request_headers = {'Metadata-Flavor': 'Google'}

        audience = 'sts.amazonaws.com'
        params = {'audience': audience, 'format': 'full', 'include_email': 'true'}
        url = f"{token_request_url}?{urllib.parse.urlencode(params)}"

        req = urllib.request.Request(url, headers=token_request_headers)
        with urllib.request.urlopen(req) as response:
            identity_token = response.read().decode('utf-8')
        return identity_token
    except Exception as e:
        raise Exception(f"JWT取得エラー: {e}")

@functions_framework.cloud_event
def bridge_to_sns(cloud_event):
    """
    Pub/Subからメッセージを受け取り、JWTを使ってAWS SNSに転送する関数
    """
    # 環境変数から設定を取得
    aws_role_arn = os.environ.get('AWS_ROLE_ARN')
    sns_topic_arn = os.environ.get('SNS_TOPIC_ARN')

    if not aws_role_arn or not sns_topic_arn:
        print("ERROR: AWS_ROLE_ARN または SNS_TOPIC_ARN が設定されていません")
        return

    # Pub/Subメッセージをデコード
    pubsub_message = base64.b64decode(cloud_event.data["message"]["data"]).decode('utf-8')
    print(f"受信したメッセージ: {pubsub_message}")

    try:
        # Google CloudのService AccountからID Tokenを取得
        print("Google CloudのID Tokenを取得中...")
        id_token = get_google_identity_token()
        print("ID Token取得成功")

        # AWS STSクライアントを作成
        sts_client = boto3.client('sts', region_name='us-east-1')

        # AssumeRoleWithWebIdentityでIAMロールを引き受け
        print(f"AWS IAMロール {aws_role_arn} を引き受け中...")
        assume_role_response = sts_client.assume_role_with_web_identity(
            RoleArn=aws_role_arn,
            RoleSessionName='cloud-function-session',
            WebIdentityToken=id_token
        )
        print("IAMロール引き受け成功")

        # 一時認証情報を取得
        credentials = assume_role_response['Credentials']

        # 一時認証情報を使ってSNSクライアントを作成
        sns_client = boto3.client(
            'sns',
            region_name='us-east-1',
            aws_access_key_id=credentials['AccessKeyId'],
            aws_secret_access_key=credentials['SecretAccessKey'],
            aws_session_token=credentials['SessionToken']
        )

        # SNSにメッセージを送信
        print(f"SNSトピック {sns_topic_arn} にメッセージを送信中...")

        # メッセージをパース
        try:
            message_data = json.loads(pubsub_message)
            subject = f"[{message_data.get('severity', 'ERROR')}] {message_data.get('error_type', 'Error')}"
            message_body = json.dumps(message_data, ensure_ascii=False, indent=2)
        except json.JSONDecodeError:
            subject = "Google Cloud エラー通知"
            message_body = pubsub_message

        response = sns_client.publish(
            TopicArn=sns_topic_arn,
            Subject=subject,
            Message=message_body
        )

        message_id = response['MessageId']
        print(f"SNS送信成功: MessageId={message_id}")

    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        import traceback
        traceback.print_exc()
        raise

requirements.txtには以下の2行を記載しています。

functions-framework==3.*
boto3==1.*

検証結果

エラーを発生させる

デプロイした関数のURLにHTTPリクエストを送信します。

curl -X POST https://asia-northeast1-{projectID}.cloudfunctions.net/cm-kawanago-error-trigger

次にPub/Subトリガーで起動したcm-kawanago-bridge-snsのログを確認します。

受信したメッセージ: {"error_type": "TestError", "message": "これはマルチクラウドエラー通知のテストです", "severity": "ERROR", "source": "cm-kawanago-error-trigger", "timestamp": "2025-10-21T02:12:53.193984+00:00"}
Google CloudのID Tokenを取得中...
ID Token取得成功
AWS IAMロール arn:aws:iam::{accountID}:role/cm-kawanago-google-jwt-role を引き受け中...
IAMロール引き受け成功
SNSトピック arn:aws:sns:us-east-1:{accountID}:cm-kawanago-notification にメッセージを送信中...
SNS送信成功: MessageId=dda0975c-1b37-530c-9ce0-f62cf5509e4f

ログから、以下の一連の処理が正常に完了していることが分かりました。

  • Pub/Subメッセージの受信
  • Google CloudのID Token取得
  • AWS IAMロールの引き受け
  • SNSへのメッセージ送信

受信したメール

SNSトピックのサブスクリプションに登録していたアドレスに対して、
以下のような形式でエラー通知のメールが届きました。

# 件名
[ERROR] TestError

# 本文
{
  "error_type": "TestError",
  "message": "これはマルチクラウドエラー通知のテストです",
  "severity": "ERROR",
  "source": "cm-kawanago-error-trigger",
  "timestamp": "2025-10-21T02:12:53.193984+00:00"
}

エラー発生元から送信しているメッセージをパースして、
想定したjson形式でメッセージが構成されています。

なおPub/SubもSNSも形式はある程度自由に設定することができるので、
もっと人間が読みやすい形でテキストを構成することも可能です。

例としては以下のようなフォーマットに整理したり、
ログを確認するコンソールのURLを記載するとより利用しやすいですね。

{ジョブ名}でエラーが発生しました。

■ 重要度
ERROR

■ エラー内容
これはマルチクラウドエラー通知のテストです

■ 詳細
・エラー種別: TestError
・発生日時: 2025-10-21 02:12:53 (UTC)
・https://ログが確認できるコンソールURL

さいごに

今回はGoogle Cloud環境で発生したエラーの通知を、
Cloud Run Functionsを経由してAWSのSNSから配信する検証をやってみました。

もしAWS環境がすでに稼働している状態であれば、
エラー通知の仕組みを使い慣れたSNSに統一して利用できるのはメリットだと思います。

複数のアカウントやプロジェクトを利用するような大規模な環境ではなおさら、
エラー通知用の環境を共通利用するメリットが大きくなりそうですが、
共有用アカウントへのアクセス制御が追加になるのでそこは注意が必要です。

またSNSへのサブスクリプション登録の際は以下の手順を踏まないと、
簡単にメール配信登録を解除できてしまうので、運用面ではこちらも注意が必要です。

https://dev.classmethod.jp/articles/prevent-unsubscribe-all-sns-topic/

少しでも参考になれば幸いです。
最後まで記事を閲覧いただきありがとうございました。

この記事をシェアする

FacebookHatena blogX

関連記事