Amazon SNS의 구독 필터 정책으로 RDS 이벤트 알림 받기

Amazon SNS의 구독 필터 정책 기능과 Lambda를 활용하여 RDS의 필요한 메시지만 메일로 수신하도록 설정하는 방법을 작성한 글입니다.
2021.12.08

안녕하세요 클래스메소드의 수재입니다.
이번에 RDS 인스턴스의 일부 이벤트를 제외하고 전체 이벤트를 수신하도록 설정해야 할 일이 있었습니다.
짧고 간단한 내용이지만 SNS의 구독 필터를 설정하며 오해했던 내용도 있어서 블로그로 남기려합니다.

사용하는 서비스

  • Amazon SNS
    • 유저나 서비스를 대상으로 메시지를 보내기 위해 사용하는 서비스입니다.
    • 해당 글에선 SNS를 이용하였지만 EventBridge 등 다른 서비스를 이용해도 됩니다.
  • RDS
    • RDS 인스턴스의 이벤트를 추적하도록 이벤트 구독 기능을 이용합니다.
  • Lambda
    • SNS에 도착한 메시지의 파싱 및 속성 지정

설정해보기

작업 순서

  1. SNS의 주제 작성
  2. Lambda 작성
  3. SNS 구독 작성 및 구독 필터 정책 설정
  4. RDS의 이벤트 구독 작성

하나하나 따라가며 설명하도록 하겠습니다

SNS의 주제 작성

우선 SNS의 주제와 구독을 작성합니다.
RDS의 이벤트 구독용 주제 1개와 Lambda 에서 메시지를 수신할 주제 1개를 작성합니다. 주제를 작성할 때 이름 이외에 옵션은 필요에 따라 지정해주세요.
본 글에서는 sns_for_lambda, sns_for_email 이라는 이름으로 주제를 작성하였습니다.

왜 2개의 주제를 작성하는가?

제가 처음에 생각한 순서는

  • SNS 주제 작성
  • SNS 구독 및 필터 작성
  • RDS 이벤트 구독

으로 처음부터 끝까지 똑같은 메시지를 전달할 것이라 생각했습니다.
그래서 주제도 1개만 있으면 해결 될 것이라 생각해서 1개의 주제만 작성한 후 구독의 필터링 없이 RDS를 재실행해보니 이메일로 다음과 같은 내용이 도착했었습니다.

Event Source : db-instance

Identifier Link: https://console.aws.amazon.com/rds/home?region=ap-northeast-1#dbinstance:id=rds-test

SourceId: rds-test

Notification time : 2021-12-14 07:37:33.597

Message : DB instance restarted

Event ID : http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0006

제대로 도착했으니 "이 내용을 바탕으로 구독 필터를 설정하면 되겠구나" 라고 생각해 필터를 설정하고 테스트해보니 메일이 오지 않았습니다.
그래서 CloudWatch의 지표를 찾아보니 SNS의 [NumberOfNotificationsFilteredOut-NoMessageAttributes] 지표가 상승해 있었습니다.
해당 지표의 내용은 메시지가 해당 속성을 전혀 포함하지 않아 결과적으로 구독 필터 정책과 일치하지 않았기 때문에 필터링된 모든 메시지를 말합니다.

SNS에 수신되는 메시지의 내용도 확인해보니 다음과 같았습니다.

{
      "Records": [
        {
          "EventVersion": "1.0",
          "EventSubscriptionArn": "arn:aws:sns:us-east-2:123456789012:rds-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
          "EventSource": "aws:sns",
          "Sns": {
            "SignatureVersion": "1",
            "Timestamp": "2019-01-02T12:45:07.000Z",
            "Signature": "tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==",
            "SigningCertUrl": "https://sns.us-east-2.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem",
            "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
            "Message": "{\"Event Source\":\"db-instance\",\"Event Time\":\"2019-01-02 12:45:06.000\",\"Identifier Link\":\"https://console.aws.amazon.com/rds/home?region=eu-west-1#dbinstance:id=dbinstanceid\",\"Source ID\":\"dbinstanceid\",\"Event ID\":\"http://docs.amazonwebservices.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0002\",\"Event Message\":\"Finished DB Instance backup\"}",
            "MessageAttributes": {},
            "Type": "Notification",
            "UnsubscribeUrl": "https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
            "TopicArn":"arn:aws:sns:us-east-2:123456789012:sns-lambda",
            "Subject": "RDS Notification Message"
          }
        }
      ]
}

즉 필터링이 제대로 되지 않는 이유로는 구독 필터 정책이 메시지의 내용이 아닌 속성(attribute)을 기준으로 필터링하기 때문입니다.
그리고 SNS 구독 필터 정책은 속성 중 하나라도 일치하는 경우 Amazon SNS는 구독자에게 메시지를 보냅니다. 그렇지 않으면 Amazon SNS는 메시지를 보내지 않고 구독자를 건너뜁니다.
SNS 에 보내는 메시지에서 메일로 수신되는 내용도 문자(string)로 들어가있는데다가 외부에 드러나는 속성도 아닌게 원인이었습니다.

그리하여 해당 RDS에서의 이벤트를 필터링하기 위해서는 lambda를 경유하여 속성 값을 설정할 필요가 있었습니다.

Lambda 작성

이어서 도착하는 메시지를 파싱하고 SNS에 보내는 Lambda를 작성하겠습니다.
코드는 다음과 같습니다. 트리거도 잊지말고 지정하여야 합니다.

import json
import boto3

def lambda_handler(event, context):
    sns = boto3.client("sns")
    
    topic_arn = "arn:aws:sns:ap-northeast-1:{어카운트 ID}:sns_for_email"
    
    subject = "RDS EVENT ALERT"
    origin_message = event['Records'][0]['Sns']['Message']
    message = json.loads(origin_message)
    event_message = message['Event Message']
    
    sns.publish(
        TopicArn=topic_arn,
        Subject=subject,
        Message=origin_message,
        MessageAttributes={
            "event_source": {
                "DataType": "String",
                "StringValue": message['Event Source']
            },
            "event_time": {
                "DataType": "String",
                "StringValue": message['Event Time'],
            },
            "identifier_link": {
                "DataType": "String",
                "StringValue": message['Identifier Link']
            },
            "source_id": {
                "DataType": "String",
                "StringValue": message['Source ID']
            },
            "event_id": {
                "DataType": "String",
                "StringValue": message['Event ID']
            },
            "event_message": {
                "DataType": "String",
                "StringValue": message['Event Message']
            }
    })

코드 작성 후 lambd의 IAM 정책에 sns_for_email에 대한 쓰기 정책을 추가합니다.

구독 설정

구독 필터를 설정하기 전에 간단하게 SNS의 구독 필터 정책 기능에 대하여 알아보자면
구독 필터 정책을 사용하면 속성 이름을 지정하고 각 속성 이름에 값 목록을 지정하여 메시지의 내용에 따라 필터링을 할 수 있습니다.
단순하게 문자열이나 숫자 값의 일치 뿐만 아니라 접두사 일치(prefix) 조건이나 속성 값을 하나도 포함하지 않는 필터링(anything-but) 즉 해당 값이 있으면 메시지를 보내지 않는 조건, 숫자 값의 범위 등의 조건을 사용할 수 있습니다.
자세한 내용은 Amazon SNS 구독 필터 정책의 공식 문서를 참고해주세요.

이어서 구독을 작성합니다. 이메일을 구독 대상으로 지정하면 등록한 이메일에 다음과 같이 승인 메일이 도착합니다. [Confirm subscription] 을 클릭하여 구독을 승인합니다.

본 글에서는 구독 대상으로 이메일을 지정하였지만 webhook을 이용하여 다양한 대상을 수신처로 설정할 수 있습니다.
설정 방법은 다음 글을 참고해주세요.

승인 메일의 처리까지 끝났다면 sns_for_lambda의 메시지 게시에 왜 2개의 주제를 작성하는가? 단계의 "Record"가 포함된 메시지를 게시하여 메일이 제대로 수신되는지 확인해봅니다.
아마 다음과 같은 메일이 도착 할 것 입니다.

{"Event Source":"db-instance","Event Time":"2021-12-16 12:15:32.494","Identifier Link":"https://console.aws.amazon.com/rds/home?region=ap-northeast-1#dbinstance:id=rds-test","Source ID":"rds-test","Source ARN":"arn:aws:rds:ap-northeast-1:{"계정 id"}:db:rds-test","Event ID":"http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0004","Event Message":"DB instance shutdown"}

이어서 구독의 필터 정책을 작성해보겠습니다.
본 글에서는 간단하게 RDS의 재부팅과 셧다운 이외의 모든 이벤트를 수신하는 필터 정책을 작성하겠습니다.

RDS의 이벤트 목록은 공식 문서를 참고해주세요.
단 문서의 Description이 이벤트의 Message를 뜻하는 것은 아니므로 정확한 Message는 따로 검색을 해야합니다.

anything-but을 이용하여 정책을 작성 후 저장합니다.

{
  "event_message": [
    {
      "anything-but": [
        "DB instance shutdown",
        "DB instance restarted"
      ]
    }
  ]
}

문자열로 필터링 값을 입력한다면 OR 로직으로 처리되어 두 이벤트 모두 필터링 됩니다.

RDS의 이벤트 구독 작성

마지막으로 RDS의 대시보드에서 이벤트 구독에 들어간 후 구독을 작성합니다.
ARN은 sns_for_lambda로 지정합니다.
설정 후 RDS의 정지 및 시작 이벤트에 대해서는 메일이 도착하지만 재부팅을 해보면 메일이 도착하지 않습니다.

마치며

본 글에서는 RDS의 이벤트를 대상으로 설정하였지만 Amazon SNS로 메시지를 보낼 수 있는 모든 서비스를 대상으로 사용할 수 있습니다.

긴 글 읽어주셔서 감사합니다. 오탈자 및 내용 피드백은 언제나 환영합니다. must01940 지메일로 보내주시면 감사합니다.