RDS 이벤트 로그를 사용하여 웹훅으로 상태 알람 받기 (AWS 모니터링 시리즈)
RDS의 이벤트 로그를 구독하는 SNS와 람다를 연결하여 웹훅으로 알림 자동화 하기
문제상황
RDS에서 용량이 부족해서 성능이 떨어지는 상황이었는데, 인지를 하지 못했다.
원인
RDS의 경우 알람을 cpu와 메모리 그리고 커넥션 위주로만 구성하다보니 다른 요소들에 대해서는 신경을 못썼다.
해결방법
AWS RDS에서는 중요한 이벤트들에 대해 알림을 설정할 수 있습니다.
AWS의 SNS와 함께 작동합니다. 이후 Lambda를 사용하여 Slack에 webhook을 보내도록 설정할 수 있습니다.
- AWS Management Console에 로그인 한 후, 서비스 메뉴에서 “RDS”를 찾아 클릭
- RDS 대시보드의 왼쪽 패널에서 “이벤트 구독”을 클릭
- “이벤트 구독 만들기” 버튼을 클릭
- “이름” 입력란에 이벤트 구독의 이름입력
“이벤트 유형”에서 원하는 이벤트 유형을 선택.
- 예를 들어, “Configuration Change”, “Failure”, “Maintenance”, “Notification”, “Restoration” 등의 이벤트 유형을 선택 가능
- “SNS 토픽”에서 이벤트 알림을 받을 SNS 토픽을 선택하거나 새 토픽을 생성
- “소스 유형”에서 알림을 받을 RDS 리소스 유형을 선택
- “이벤트 구독 만들기”를 클릭하여 이벤트 구독을 생성
이렇게 설정하면 선택한 이벤트 유형이 발생할 때마다 SNS를 통해 알림을 받게 됩니다.
하지만 슬랙에는 직접 SNS를 통해 메세지를 보낼 수 없습니다. 그래서 람다를 SNS에 연결해주어야 합니다.
Lambda 함수는 SNS 알림을 받아 Slack의 Incoming WebHook URL로 HTTP POST 요청을 보내 Slack 채널에 메시지를 전송합니다.
다음은 이 과정을 설정하는 단계입니다
- Slack의 Incoming Webhook URL 생성: 먼저, Slack 앱에서 Incoming WebHook을 설정하고 Webhook URL을 생성해야 합니다. 이 URL은 Lambda 함수에서 사용되며, 이 URL로 HTTP POST 요청을 보내면 해당 Slack 채널에 메시지가 전송됩니다.
- Lambda 함수 생성: AWS Management Console에서 Lambda 서비스를 열고, 새로운 함수를 생성
- Lambda 함수 코드 작성: Lambda 함수 코드에서는 SNS 알림을 받아 Slack의 Webhook URL로 메시지를 보내는 로직을 구현해야 합니다.
- SNS 토픽과 Lambda 함수 연결: 생성한 Lambda 함수를 SNS 토픽에 연결해야 합니다. SNS 콘솔에서 해당 토픽을 선택하고, “구독 추가” 버튼을 클릭한 후, “프로토콜”로 Lambda를 선택하고, “엔드포인트”로 Lambda 함수를 선택합니다.
이렇게 하면 AWS RDS에서 발생하는 이벤트에 대해 SNS 알림을 받고, 이 알림을 Slack에 전달하는 시스템이 구성됩니다.
RDS 이벤트의 내용을 받기 위한 람다 코드는 밑과 같습니다.
import json
def lambda_handler(event, context):
print("Received event:", json.dumps(event))
람다에 위 코드를 넣고, 구독을 한 인스턴스에 대해 재시작을 해보았습니다.
위와 같이 Url로 받아진 로그에서 Event ID를 사용해 봅시다.
위 공식문서에서 RDS 이벤트ID 확인 가능합니다.
- 설정한 이벤트가 발생했을 때 알람오도록 자유롭게 수정 가능합니다.
- 모든 이벤트를 구독하면 람다 비용과 슬랙에 메세지 오는 피로도 때문에 각자 서비스에 맞는 알림만 구독하도록 추천드립니다.
RDS 이벤트를 구독하기 위한 람다 코드 예시는 밑과 같습니다.
import json
import os
import urllib.request
from datetime import datetime, timedelta
from urllib.parse import urlparse
# 환경 변수 확인
webhook = os.environ.get('webhook')
if not webhook:
raise ValueError('Missing environment variable: webhook')
allowed_event_ids = {"RDS-EVENT-0004", "RDS-EVENT-0069", "RDS-EVENT-0353", "RDS-EVENT-0019", "RDS-EVENT-0071", "RDS-EVENT-0073", "RDS-EVENT-0003", "RDS-EVENT-0005", "RDS-EVENT-0087", "RDS-EVENT-0088" }
def lambda_handler(event, context):
return process_event(event)
def process_event(event):
print('Event:', json.dumps(event))
sns_message = event['Records'][0]['Sns']['Message']
print('SNS Message:', sns_message)
data = json.loads(sns_message)
# Event ID 추출 및 분석
event_id_url = data.get("Event ID")
event_id = event_id_url.split('#')[-1] # URL 마지막 부분을 추출
if event_id not in allowed_event_ids:
print(f"Ignoring event with ID: {event_id}")
return
postData = build_slack_message(data)
post_slack(postData, webhook)
def build_slack_message(data):
print('DATA:', data)
eventSource = data.get("Event Source")
eventTime = data.get("Event Time").split(".")[0]
sourceId = data.get("Source ID")
eventMessage = data.get("Event Message")
return json.dumps({
"attachments": [
{
"title": "[" + eventMessage + "]",
"color": "warning",
"fields": [
{
"title": "이벤트 소스",
"value": eventSource
},
{
"title": "이벤트 시간",
"value": to_yyyymmddhhmmss(eventTime)
},
{
"title": "해당 리소스",
"value": sourceId
},
{
"title": "메시지",
"value": eventMessage
},
{
"title": "데이터베이스 목록",
"value": f"<https://ap-northeast-2.console.aws.amazon.com/rds/home?region=ap-northeast-2#databases: |클릭 후 데이터베이스 상태 확인 가능>"
},
{
"title": "데이터베이스 이벤트로그",
"value": f"<https://ap-northeast-2.console.aws.amazon.com/rds/home?region=ap-northeast-2#event-list: |클릭 후 로그와 이벤트 확인 가능>"
}
]
}
]
})
def to_yyyymmddhhmmss(time_string):
if not time_string:
return ''
kst_date = datetime.strptime(time_string, '%Y-%m-%d %H:%M:%S') + timedelta(hours=9)
return kst_date.strftime('%Y-%m-%d %H:%M:%S')
def post_slack(message, slack_url):
req = urllib.request.Request(slack_url, data=message.encode('utf-8'), headers={'Content-Type': 'application/json'}, method='POST')
with urllib.request.urlopen(req) as response:
response_body = response.read().decode('utf-8')
print(response_body)
event_id = event_id_url.split(‘#’)[-1] 를 사용해서 제일 마지막에 RDS-EVENT-0004를 추출해서 이벤트 아이디로 저장하고
허용한 이벤트들에 따라서만 메세지를 보낼 수 있도록 로직을 추가해서 피로감을 최대한 없애려고 시도를 했습니다.
실제 RDS 이벤트 기반 알람 예시
이렇게 해당 RDS에 대해서 슬랙에서 이벤트 메시지와 알람을 받게 됩니다.
참고