2026年7月5日

2026年7月5日

AWS SNSで通知システムを構築する方法|Slack連携・フィルターポリシー・配信失敗の検知まで

Amazon SNS で実用的な通知の仕組みを作るには、トピックに購読先を付けるだけでなく、「どの通知を誰に送るか」を絞り込み、「届かなかった通知」に気づける状態にしておくことが大切です。この記事では、WordPress サーバーの運用通知を例に、SNS から Lambda 経由で Slack に投稿する方法、サブスクリプションフィルターポリシーで重要な通知だけを別の宛先に送る方法、配信に失敗した通知をキューに退避して監視する方法を説明します。SNS トピックの作成やメール購読、CloudWatch アラームの通知先にトピックを指定する基本の操作は済んでいる前提です。

AWS のサービス名や画面の表記、機能の範囲は更新されることがあります。ここでは AWS の公開ドキュメント(2026年10月現在)に沿って説明しますが、細かな設定項目は公式ドキュメントでも確認してください。コマンド中のアカウント ID・ARN・URL は例なので、自分の環境の値に置き換えます。

作る構成

1つのトピック wp-ops-alerts に、CloudWatch アラームと WordPress から通知が集まる前提で、次の2つの購読先を付けます。

  • Slack 用の Lambda:すべての通知を受け取り、Slack のチャンネルに投稿します。運用担当者が流れを把握するための経路です。
  • 当番のメールアドレス:アラームが「異常」になったときと、WordPress から重要度が高いと指定された通知だけを受け取ります。

通知が1か所に集まり、受け取る側の条件だけを変えられるのが SNS の利点です。送る側(CloudWatch や WordPress)は宛先の追加や変更を知らなくて済みます。

なお、Slack への通知には、AWS が提供するチャット連携サービス(旧称 AWS Chatbot。現在は Amazon Q Developer の一機能として提供)を使う方法もあります。コードを書かずに済む一方、名称や設定手順が変わってきた分野なので、使う場合は最新の手順を確認してください。この記事では、文面を自由に整えられる Lambda の方法を扱います。

手順1:SlackへのLambda関数を作る

Slack 側で、投稿先のチャンネルに対する Incoming Webhook の URL を発行しておきます。この URL を知っていれば誰でもそのチャンネルに投稿できるため、パスワードと同じように扱い、コードやリポジトリには書かずに Lambda の環境変数(より厳しく管理するなら Secrets Manager)に置きます。

# lambda_function.py(関数名: sns-to-slack / ランタイム: Python)
import json
import os
import urllib.request

WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]


def format_text(sns):
    subject = sns.get("Subject") or "(件名なし)"
    message = sns.get("Message", "")
    try:
        data = json.loads(message)
    except json.JSONDecodeError:
        data = None

    # CloudWatch アラームの通知は Message が JSON 文字列で届く
    if isinstance(data, dict) and "AlarmName" in data:
        return "[{}] {}\n{}".format(
            data.get("NewStateValue", "?"),
            data["AlarmName"],
            data.get("NewStateReason", ""),
        )
    return "*{}*\n{}".format(subject, message[:3000])


def lambda_handler(event, context):
    for record in event.get("Records", []):
        text = format_text(record["Sns"])
        req = urllib.request.Request(
            WEBHOOK_URL,
            data=json.dumps({"text": text}).encode("utf-8"),
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        # 失敗時は例外を投げる。Lambda の再試行と失敗時の退避に任せる
        with urllib.request.urlopen(req, timeout=5) as res:
            res.read()

Slack への送信に失敗したとき、例外を握りつぶさずにそのまま投げているのは意図的です。SNS から Lambda への呼び出しは非同期なので、関数がエラーで終わると Lambda 側で再試行され、それでも失敗したものは後で設定する退避先に送られます。例外を握りつぶすと、失敗が「成功」として扱われて記録に残りません。

手順2:Lambdaをトピックに購読させる

SNS が Lambda を呼び出せるように許可を付け、購読を作ります。Lambda の購読はメールと違い、確認の手順はありません。

TOPIC_ARN=arn:aws:sns:ap-northeast-1:123456789012:wp-ops-alerts
FUNC_ARN=arn:aws:lambda:ap-northeast-1:123456789012:function:sns-to-slack

aws lambda add-permission --function-name sns-to-slack \
  --statement-id sns-wp-ops-alerts --action lambda:InvokeFunction \
  --principal sns.amazonaws.com --source-arn "$TOPIC_ARN"

aws sns subscribe --topic-arn "$TOPIC_ARN" \
  --protocol lambda --notification-endpoint "$FUNC_ARN"

aws sns publish --topic-arn "$TOPIC_ARN" \
  --subject "SNS test" --message "Slack 連携のテストです"

Slack に投稿されなければ、Lambda の CloudWatch Logs(/aws/lambda/sns-to-slack)を確認します。ログ自体が無いなら呼び出しの許可か購読の設定、ログにエラーがあるなら Webhook の URL や関数のコードを疑います。

手順3:フィルターポリシーで宛先ごとに絞り込む

メールの購読にフィルターポリシーを付けて、重要な通知だけを受け取るようにします。フィルターの対象には2種類あり、購読ごとにどちらか一方を選びます。

  • メッセージ属性(既定):送る側がメッセージとは別に付けた「属性」で判定します。
  • メッセージ本文:本文が JSON の場合に、その中の値で判定します。

ここでつまずきやすいのが、CloudWatch アラームの通知にはメッセージ属性が付かない 点です。属性でフィルターすると、アラームの通知はすべて除外されてしまいます。アラームの通知は本文が JSON なので、本文を対象にして状態が ALARM のものだけを通します。

aws sns subscribe --topic-arn "$TOPIC_ARN" \
  --protocol email --notification-endpoint oncall@example.com \
  --attributes '{"FilterPolicyScope":"MessageBody","FilterPolicy":"{\"NewStateValue\":[\"ALARM\"]}"}'

これで、アラームが異常になったときはメールにも届き、正常に戻ったときの通知(OK)は Slack にだけ届きます。メールの購読なので、届いた確認メールのリンクを開いて購読を有効にしてください。

WordPress から送る通知も当番に届けたい場合は、本文を JSON にして同じキーを入れるか、WordPress 用に別の購読(属性でフィルター)を作ります。後者の場合、WordPress 側では発行時に属性を付けます。AWS SDK for PHP を使う例です。

// $client は Aws\Sns\SnsClient のインスタンス
$client->publish( array(
	'TopicArn'          => 'arn:aws:sns:ap-northeast-1:123456789012:wp-ops-alerts',
	'Subject'           => 'WordPress: 管理者アカウントが追加されました',
	'Message'           => 'サイト example.com で管理者権限のユーザーが追加されました。',
	'MessageAttributes' => array(
		'severity' => array(
			'DataType'    => 'String',
			'StringValue' => 'critical',
		),
	),
) );

この通知を受ける購読には {"severity":["critical"]} というフィルターポリシーを付けます。フィルターポリシーの変更は、反映までに数分かかることがあるため、設定直後のテストで結果が変わらなくても少し待ってから試してください。

通知の本文には、利用者のメールアドレスなどの個人情報を入れないようにします。Slack やメールに流れた内容は、そのまま多くの人の目に触れ、記録にも残るからです。必要なら「ユーザー ID」程度にとどめ、詳細は管理画面で確認する運用にします。

手順4:届かなかった通知を退避する

通知の失敗には2つの段階があり、それぞれ退避先の設定場所が違います。

  1. SNS が購読先に届けられなかった:Lambda が削除されている、呼び出しの許可が無い、などの場合です。サブスクリプションの再処理ポリシー(RedrivePolicy)で SQS のキューを指定すると、SNS が再試行を終えても届けられなかったメッセージがそこに残ります。
  2. Lambda は呼び出されたが、処理に失敗した:Slack が一時的に応答しないなどの場合です。Lambda の非同期呼び出しの設定で、失敗時の送り先を指定します。
DLQ_ARN=arn:aws:sqs:ap-northeast-1:123456789012:wp-ops-alerts-dlq
# Lambda 購読の ARN(aws sns list-subscriptions-by-topic で確認できる)
SUB_ARN=arn:aws:sns:ap-northeast-1:123456789012:wp-ops-alerts:your-subscription-id

# 1. SNS から届けられなかったものを退避する
aws sns set-subscription-attributes --subscription-arn "$SUB_ARN" \
  --attribute-name RedrivePolicy \
  --attribute-value "{\"deadLetterTargetArn\":\"$DLQ_ARN\"}"

# 2. Lambda の処理に失敗したものを退避する(再試行は2回まで)
aws lambda put-function-event-invoke-config --function-name sns-to-slack \
  --maximum-retry-attempts 2 \
  --destination-config "{\"OnFailure\":{\"Destination\":\"$DLQ_ARN\"}}"

1 の場合、キューのアクセスポリシーで、このトピックからの sqs:SendMessage を許可しておく必要があります。条件に aws:SourceArn としてトピックの ARN を指定し、ほかのトピックからは書き込めないようにします。2 の場合は、Lambda の実行ロールにキューへの sqs:SendMessage 権限が必要です。キューを KMS で暗号化しているなら、そのキーの利用許可も必要になります。キューは購読と同じアカウント・同じリージョンに作ります。

手順5:通知の仕組みそのものを監視する

退避先のキューにメッセージが溜まっても、誰も見なければ意味がありません。次の値に CloudWatch アラームを設定します。

  • SQS の ApproximateNumberOfMessagesVisible(退避キュー):1件以上になったら通知
  • SNS の NumberOfNotificationsFailed(トピック単位):配信の失敗
  • Lambda の Errors(sns-to-slack):関数のエラー

ここで注意したいのは、これらのアラームの通知先を **監視対象と同じ wp-ops-alerts にしない** ことです。通知の経路が壊れているときに、その経路で知らせようとしても届きません。別のトピックを作り、メールで直接受け取るようにしておきます。

フィルターで意図せず除外されていないかは、SNS の NumberOfNotificationsFilteredOut 系のメトリクスで確認できます。設定を変えた直後に、届くはずの通知が届かない場合はここを見ます。より詳しく追いたい場合は、配信ステータスのログ記録を有効にすると、購読先ごとの成功・失敗を CloudWatch Logs に残せます(IAM ロールの設定が必要です)。

まとめ

  • Slack 連携は Lambda で文面を整えて送る。Webhook の URL は秘密の値として環境変数などで管理する。
  • Lambda 内で例外を握りつぶさず、再試行と失敗時の退避に任せる。
  • フィルターポリシーで宛先ごとに通知を絞る。CloudWatch アラームには属性が無いので、本文を対象にフィルターする。
  • 届かなかった通知は、SNS の再処理ポリシーと Lambda の失敗時の送り先の両方で SQS に退避する。
  • 退避キューや配信失敗のアラームは、監視対象とは別のトピックで通知する。通知に個人情報を載せない。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲