Lambda 를 사용해서 CloudWatch Alarms 내용 커스텀해서 메일 보내기 (CW Alarms → Lambda → SNS)

Lambda 를 사용해서 CloudWatch Alarms 내용 커스텀해서 메일 보내기 (CW Alarms → Lambda → SNS)

2025.08.21

안녕하세요! 클라우드 사업본부의 임채정입니다.

오늘 블로그에서는 Amazon Lamdba를 사용해서 CloudWatch Alarms의 메일 내용을 원하는 정보만 전송되도록 하는 방법에 대해 정리하겠습니다.

1. 실습 전 확인

1.1. CloudWatch Alarms 의 기본 메일 내용 확인

그럼 메일 내용을 변경하는 방법을 설명하기 전에 CloudWatch Alarms 으로 알람을 전송할 때 기본적으로 어떤 내용으로 전송되는지를 먼저 확인하겠습니다.
CloudWatch Alarms 통해 전송되는 메일은 다음과 같습니다.

ll01

사실 이렇게 보면 가독성이 그렇게 좋지는 않습니다.
자세히 보면 알기는 하겠지만, 메일 내용도 다 영어로 되어있고 내가 원하는 정보를 바로 확인하는 것은 확인하기 어려워보입니다.
그래서 오늘은 이 메일을 내가 원하는 정보만 포함하도록 변경해보겠습니다.

1.2. 오늘 실습 내용

ll14

① CloudWatch Alarm 에서 상태 변화로 인해 알람이 발생합니다.
② 알람의 타겟을 람다 함수로 설정을 해서 이벤트를 람다 함수로 전송합니다.
③ 람다 함수에는 이벤트의 원하는 정보만을 파싱해서 메일에 어떤식으로 출력할건지 편집합니다.
④ 함수 실행이 완료되면 결과 값을 SNS 주제로 전달합니다.
⑤ SNS 주제에 구독하고 있는 이메일 주소로 메일을 전송합니다.

2. 리소스 생성

블로그에서 테스트를 하기 위해 필요한 리소스는 다음과 같습니다.

2.1. EC2 인스턴스

블로그에서는 EC2 인스턴스의 CPU 사용률을 통해 알람을 발생시킬 예정입니다.
그렇기 때문에 EC2 인스턴스를 1대 준비해줍니다.
이번 블로그에서 EC2 인스턴스의 스펙은 중요하지 않기때문에 생략하겠습니다.

2.2. SNS 주제

메일 전송을 위해 SNS 주제를 작성합니다.
작성 절차는 생략하지만 주제에 테스트에 사용할 이메일(구독)을 추가해둡니다.

2.3. Lambda 함수

Lambda 함수도 작성해줍니다.
블로그에서는 파이썬을 사용해서 코드를 작성하고 싶기 때문에 다음과 같은 설정한 후 작성해줍니다.

  • 함수명: alarm-messages-modify-lambda
  • 런타임: Python 3.13

다른 항목들은 기본 설정으로 두고 함수 생성을 합니다.

2.4. CloudWatch Alarms

테스트를 위해서는 CloudWatch Alarms 이 필요합니다.
CloudWatch Alarms을 생성은 다음 단계로 구성됩니다.

  • 단계 1 - 지표 및 조건 지정
  • 단계 2 - 작업 구성
  • 단계 3 - 경보 세부 정보 추가
  • 단계 4 - 미리 보기 및 생성

이번 블로그에 상관없는 단계 1, 3, 4 에 대한 설정 방법은 생략하고 [단계 2 - 작업 구성]에 대해서만 설명하겠습니다.
지표를 설정하고 다음 페이지로 넘어가면 알람이 발생했을 시 어떤 작업(Action)을 실행할건지 설정할 수 있는 선택할 수 있습니다.
보통이라면 알림을 설정해서 SNS 주제를 통해 메일로 전송을 하지만, 오늘은 SNS 주제로 바로 보내는 게 아니라 Lambda로 보낼 것이기 때문에 Lambda 작업 을 추가합니다.

ll02

작업을 추가하면 다음과 같이 선택해줍니다.

  • 경보 상태 트리거: 경보 상태
  • 함수 유형: 로그인한 계정에서 Lambda 함수 선택
  • 함수 선택: alarm-messages-modify-lambda (2.3. 에서 작성한 함수를 선택합니다.)

ll03

그 후에 단계도 임의로 설정을 해주고 생성을 하면 알람작성 완료입니다!

3. 람다 함수의 권한 설정

이번 블로그에서는

  1. CloudWatch Alarms 에서 람다로 메시지를 보내고
  2. 람다에서는 그 메시지를 파싱에서 SNS를 통해 메일로 전송합니다.

이 과정에서 각각 서비스에 대한 권한 설정을 필요로 합니다.

3.1. 리소스 기반 정책 추가

먼저 CloudWatch Alarms가 람다 함수에 대한 권한을 가질 수 있도록 설정을 하겠습니다.

먼저 람다 함수에서 [구성]-[권한] 탭에 들어가면 리소스 기반 정책 설명 항목이 있습니다.
리소스 기반 정책을 추가해줍니다.
ll04

설정하는 항목은 다음과 같습니다.
ll05

각 항목의 의미는 다음과 같습니다.

  • 문 ID: 권한 정책의 고유 식별자(임의로 입력)
  • 보안 주체: CloudWatch 알람 서비스의 보안 주체 (principal)
    • lambda.alarms.cloudwatch.amazonaws.com
  • 작업: 어떤 작업을 실행할건지 지정
    • lambda:InvokeFunction: Lambda 함수를 실행

입력이 끝났으면 저장을 해줍니다.
다음과 같이 리스트로 나타나면 이제 CloudWatch Alarms 는 Lambda 함수를 실행시킬 수 있는 권한을 가질 수 있습니다.

ll06

주의) 만약 리소스 기반 정책을 설정하지 않는다면

만약 리소스 기반 정책을 설정하지 않는다면 알람이 발생했을 경우에 CloudWatch Alarms의 기록에서는 다음과 같은 로그가 출력되고 람다까지 메시지를 전송하지 못합니다.

3.2. SNS 전송 권한 추가

다음으로 람다 실행 후에 결과를 SNS으로 전송하기 위한 권한을 추가하겠습니다.
람다 함수에서 [구성]->[권한] 탭의 역할 이름을 클릭하면 해당 역할의 페이지 창이 열립니다.
ll07

역할 페이지의 권한 정책 리스트에서 [권한 추가]->[인라인 정책 생성] 버튼을 클릭해서 다음 권한을 추가해줍니다.
ll08

SNS 권한 추가
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sns:Publish"
            ],
            "Resource": "arn:aws:sns:ap-northeast-1:111111111111:*"
        }
    ]
}

추가되면 다음과 같이 권한 정책에 새로운 정책이 추가됩니다.
ll09

4. 코드 작성

리소스의 작성, 권한 설정까지 문제 없으면 람다 함수에 코드를 작성하겠습니다.

4.1. CloudWatch Alarm 의 event 내용 전체 확인

먼저 내용은 작성하기 전에 CloudWatch Alarms 이 발생했을 때 람다 함수에는 어떤 이벤트 내용이 보내지는지 확인하겠습니다.

람다에 다음과 같은 코드를 입력해주고 Deploy를 눌러줍니다.

CloudWatch Alarm 의 event 전체 출력
import json
import boto3

def lambda_handler(event, context):

    # 1. 전체 event 구조 확인
    print(f"전체 이벤트: {event}")

    return {
        'statusCode': 200,
        'body': 'Debug complete'
    }

그 후에 람다 함수를 실행시키기 위해 AWS CloudShell 에서 아래 명령어를 실행해서 CloudWatch Alarm 의 알람을 발생시키겠습니다.
먼저 위의 AWS CloudShell 을 실행해줍니다. (AWS 콘솔 화면의 오른쪽 위의 아이콘을 클릭하면 실행할 수 있습니다.)
ll10

CloudWatch Alarm 의 상태 변화를 위해 다음 명령어를 입력합니다.

CloudWatch Alarm 의 상태 변경 명령어
aws cloudwatch set-alarm-state --alarm-name "alarm-messages-modify-alarm" --state-value ALARM --state-reason "test"

명령어 실행후 알람을 확인해보면 알람 상태로 변경됐습니다.
ll11

변경 기록을 살펴보면 상태가 알람으로 업데이트 되면서 람다가 실행되었다는 걸 확인할 수 있습니다.
ll12

람다에서 실행 결과를 살펴보면 다음과 같습니다.

람다 함수 실행 결과 (event 내용 확인)
전체 이벤트: {'source': 'aws.cloudwatch', 'alarmArn': 'arn:aws:cloudwatch:ap-northeast-1:111111111111:alarm:alarm-messages-modify-alarm', 'accountId': '111111111111', 'time': '2025-08-15T05:42:04.819+0000', 'region': 'ap-northeast-1', 'alarmData': {'alarmName': 'alarm-messages-modify-alarm', 'state': {'value': 'ALARM', 'reason': 'test', 'timestamp': '2025-08-15T05:42:04.819+0000'}, 'previousState': {'value': 'INSUFFICIENT_DATA', 'reason': 'Insufficient Data: 1 datapoint was unknown.', 'reasonData': '{"version":"1.0","queryDate":"2025-08-15T05:40:28.805+0000","statistic":"Maximum","period":300,"recentDatapoints":[],"threshold":90.0,"evaluatedDatapoints":[{"timestamp":"2025-08-15T05:35:00.000+0000"}]}', 'timestamp': '2025-08-15T05:40:28.811+0000'}, 'configuration': {'metrics': [{'id': 'becc4456-694c-9fd4-6a6c-60ac292c4447', 'metricStat': {'metric': {'namespace': 'AWS/EC2', 'name': 'CPUUtilization', 'dimensions': {'InstanceId': 'i-02af0ffbcbe4e8460'}}, 'period': 300, 'stat': 'Maximum'}, 'returnData': True}]}}}

JSON 결과를 이쁘게 정리하면 다음과 같습니다.

event 내용 정렬
{
   "source":"aws.cloudwatch",
   "alarmArn":"arn:aws:cloudwatch:ap-northeast-1:111111111111:alarm:alarm-messages-modify-alarm",
   "accountId":"111111111111",
   "time":"2025-08-15T05:42:04.819+0000",
   "region":"ap-northeast-1",
   "alarmData":{
      "alarmName":"alarm-messages-modify-alarm",
      "state":{
         "value":"ALARM",
         "reason":"test",
         "timestamp":"2025-08-15T05:42:04.819+0000"
      },
      "previousState":{
         "value":"INSUFFICIENT_DATA",
         "reason":"Insufficient Data: 1 datapoint was unknown.",
         "reasonData":"{\"version\":\"1.0\",\"queryDate\":\"2025-08-15T05:40:28.805+0000\",\"statistic\":\"Maximum\",\"period\":300,\"recentDatapoints\":[],\"threshold\":90.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2025-08-15T05:35:00.000+0000\"}]}",
         "timestamp":"2025-08-15T05:40:28.811+0000"
      },
      "configuration":{
         "metrics":[
            {
               "id":"becc4456-694c-9fd4-6a6c-60ac292c4447",
               "metricStat":{
                  "metric":{
                     "namespace":"AWS/EC2",
                     "name":"CPUUtilization",
                     "dimensions":{
                        "InstanceId":"i-02af0ffbcbe4e8460"
                     }
                  },
                  "period":300,
                  "stat":"Maximum"
               },
               "returnData":true
            }
         ]
      }
   }
}

event 에 어떤 정보들이 들어있는지 확인했으니 이제 이 정보들을 조합해서 내가 원하는 정보만이 담긴 이메일을 작성할 수 있습니다.

4.2. event 에서 확인할 수 있는 정보 정리

그럼 다음 단계로 어떤 정보들을 확인할 수 있는지 람다 함수에서 변수로 정의해서 출력해보겠습니다.

람다 함수의 코드를 다음과 같이 변경해주고 Deploy를 눌러줍니다.
4.1. 에서 출력한 event 에서 확인할 수 있는 정보들을 파싱해서 각각의 변수에 넣어 출력하는 내용입니다.

CloudWatch Alarm 의 event 전체 출력
import json
import boto3

def lambda_handler(event, context):

    # 2. 테이터 파싱 및 확인하기
    # 2-1. 알람의 상태 정보
    alarm_name = event['alarmData']['alarmName']
    current_state = event['alarmData']['state']['value']
    current_reason = event['alarmData']['state']['reason']
    current_timestamp = event['alarmData']['state']['timestamp']

    # 2-2. 이전 상태 정보
    previous_state = event['alarmData']['previousState']['value']
    previous_reason = event['alarmData']['previousState']['reason']

    # 2-3. AWS 정보
    account_id = event['accountId']
    region = event['region']
    alarm_arn = event['alarmArn']

    # 2-4. 메트릭 정보
    metric_info = event['alarmData']['configuration']['metrics'][0]
    metric_name = metric_info['metricStat']['metric']['name']
    namespace = metric_info['metricStat']['metric']['namespace']
    instance_id = metric_info['metricStat']['metric']['dimensions']['InstanceId']
    period = metric_info['metricStat']['period']
    stat = metric_info['metricStat']['stat']

    # 2-5. 출력
    print(f"파싱 결과:")
    print(f"- 알람명: {alarm_name}")
    print(f"- 알람 상태: {current_state}")
    print(f"- 알람 발생 이유: {current_reason}")
    print(f"- 알람 발생 시간: {current_timestamp}")

    print(f"- 이전 알람 상태: {previous_state}")
    print(f"- 이전 알람 상태의 이유: {previous_reason}")

    print(f"- AWS 계정: {account_id}")
    print(f"- 실행 리전: {region}")
    print(f"- 알람 ARN: {alarm_arn}")

    print(f"- 알람 ARN: {metric_info}")
    print(f"- 알람 설정한 지표 이름: {metric_name}")
    print(f"- 네임스페이스: {namespace}")
    print(f"- 인스턴스ID: {instance_id}")
    print(f"- 지표 데이터의 수집 기간: {period}")
    print(f"- 알람의 통계값: {stat}")

    return {
        'statusCode': 200,
        'body': 'Debug complete'
    }

결과를 확인하면 파싱된 정보가 잘 출력됩니다.

람다 함수 실행 결과
Status: Succeeded
Test Event Name: messages1

Response:
{
  "statusCode": 200,
  "body": "Debug complete"
}

Function Logs:
START RequestId: ce17beb7-62f0-41f0-914f-cb5c675d6f21 Version: $LATEST
파싱 결과:
- 알람명: alarm-messages-modify-alarm
- 알람 상태: ALARM
- 알람 발생 이유: test
- 알람 발생 시간: 2025-08-15T05:42:04.819+0000
- 이전 알람 상태: INSUFFICIENT_DATA
- 이전 알람 상태의 이유: Insufficient Data: 1 datapoint was unknown.
- AWS 계정: 111111111111
- 실행 리전: ap-northeast-1
- 알람 ARN: arn:aws:cloudwatch:ap-northeast-1:111111111111:alarm:alarm-messages-modify-alarm
- 알람 ARN: {'id': 'becc4456-694c-9fd4-6a6c-60ac292c4447', 'metricStat': {'metric': {'namespace': 'AWS/EC2', 'name': 'CPUUtilization', 'dimensions': {'InstanceId': 'i-02af0ffbcbe4e8460'}}, 'period': 300, 'stat': 'Maximum'}, 'returnData': True}
- 알람 설정한 지표 이름: CPUUtilization
- 네임스페이스: AWS/EC2
- 인스턴스ID: i-02af0ffbcbe4e8460
- 지표 데이터의 수집 기간: 300
- 알람의 통계값: Maximum
END RequestId: ce17beb7-62f0-41f0-914f-cb5c675d6f21
REPORT RequestId: ce17beb7-62f0-41f0-914f-cb5c675d6f21	Duration: 10.66 ms	Billed Duration: 11 ms	Memory Size: 128 MB	Max Memory Used: 64 MB	Init Duration: 345.58 ms

Request ID: ce17beb7-62f0-41f0-914f-cb5c675d6f21
A function update is still in progress so the invocation went to the previously deployed code and configuration.

4.3. 원하는 정보만 넣은 이메일 전송하기

마지막으로 람다 함수의 코드를 다음과 같이 변경해주고 Deploy를 눌러줍니다.
원하는 정보로만 이메일로 내용을 작성하고 SNS 주제으 보내서 메일로 전송되게 하는 코드입니다.

CloudWatch Alarm 의 event 전체 출력
import json
import boto3

def lambda_handler(event, context):

    # 2. 테이터 파싱 및 확인하기
    # 2-1. 알람의 상태 정보
    alarm_name = event['alarmData']['alarmName']
    current_state = event['alarmData']['state']['value']
    current_reason = event['alarmData']['state']['reason']
    current_timestamp = event['alarmData']['state']['timestamp']

    # 2-2. 이전 상태 정보
    previous_state = event['alarmData']['previousState']['value']
    previous_reason = event['alarmData']['previousState']['reason']

    # 2-3. AWS 정보
    account_id = event['accountId']
    region = event['region']
    alarm_arn = event['alarmArn']

    # 2-4. 메트릭 정보
    metric_info = event['alarmData']['configuration']['metrics'][0]
    metric_name = metric_info['metricStat']['metric']['name']
    namespace = metric_info['metricStat']['metric']['namespace']
    instance_id = metric_info['metricStat']['metric']['dimensions']['InstanceId']
    period = metric_info['metricStat']['period']
    stat = metric_info['metricStat']['stat']

    # 3.커스텀 메시지 생성
    if current_state == 'ALARM':
        subject = f"🚨 [CRITICAL] {alarm_name} 알람 발생"
        emoji = "🚨"
        color = "#FF0000"
    elif current_state == 'OK':
        subject = f"✅ [RESOLVED] {alarm_name} 알람 해결"
        emoji = "✅"
        color = "#00FF00"
    else:
        subject = f"⚠️ [WARNING] {alarm_name} 상태 변경"
        emoji = "⚠️"
        color = "#FFA500"

    # HTML 형식의 이메일 본문
    message = f"""
{emoji} CloudWatch 알람 알림

📋 알람 정보:
- 알람명: {alarm_name}
- 현재 상태: {current_state}
- 이전 상태: {previous_state}
- 변경 시간: {current_timestamp}

💻 리소스 정보:
- 인스턴스 ID: {instance_id}
- 메트릭: {metric_name}
- 리전: {region}

📝 상세 사유:
{current_reason}

🔗 CloudWatch 콘솔:
https://console.aws.amazon.com/cloudwatch/home?region={region}#alarmsV2:alarm/{alarm_name}
    """

    # SNS 클라이언트 생성
    sns_client = boto3.client('sns', region_name=region)

    try:
        # SNS로 메시지 전송
        response = sns_client.publish(
            TopicArn='arn:aws:sns:ap-northeast-1:111111111111:ets',  # SNS의 ARN 입력
            Subject=subject,
            Message=message
        )

        print(f"SNS 메시지 전송 성공: {response['MessageId']}")

        return {
            'statusCode': 200,
            'body': json.dumps('SNS 메시지 전송 완료!')
        }

    except Exception as e:
        print(f"SNS 메시지 전송 실패: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps(f'SNS 전송 실패: {str(e)}')
        }

함수를 실행해보면 결과는 다음과 같이 성공적으로 실행되었습니다.

람다 함수 실행 결과
Status: Succeeded
Test Event Name: messages1

Response:
{
  "statusCode": 200,
  "body": "\"SNS \\uba54\\uc2dc\\uc9c0 \\uc804\\uc1a1 \\uc644\\ub8cc!\""
}

Function Logs:
START RequestId: 3ab8f8ef-8357-46cb-97eb-ca2e2752f9b1 Version: $LATEST
SNS 메시지 전송 성공: 9abba9c2-1b4c-56f8-a9f8-c8dac3091088
END RequestId: 3ab8f8ef-8357-46cb-97eb-ca2e2752f9b1
REPORT RequestId: 3ab8f8ef-8357-46cb-97eb-ca2e2752f9b1	Duration: 2032.55 ms	Billed Duration: 2033 ms	Memory Size: 128 MB	Max Memory Used: 84 MB	Init Duration: 339.24 ms

Request ID: 3ab8f8ef-8357-46cb-97eb-ca2e2752f9b1

이메일도 확인해보면 원하는 정보만 있는 상태로 잘 전송되었습니다.
처음에 확인한 기본 이메일 내용과 비교해보면 가독성은 확실히 높아진걸 확인할 수 있습니다.
ll13

※ 참고) 상세 사유 항목은 실제로는 아래와 같은 형식으로 출력됩니다.

Thresholds Crossed: 1 out of the last 1 datapoints [22.23512980326258 (16/07/25 03:22:00)] was less than the lower thresholds [0.8691294330009517] or greater than the upper thresholds [4.5977855104716365] (minimum 1 datapoint for OK -> ALARM transition).

5. 마무리

이상으로 Lambda 를 사용해서 CloudWatch Alarms 내용 변경해서 메일로 전송하는 방법이었습니다.
이 방법을 사용해서 알람이 발생했을 대 원하는 정보만 메일로 전송받아, 알람 발생 원인을 빠르게 파악하는 데 도움이 되었으면 좋겠습니다.

문의 사항은 클래스메소드코리아로!

클래스메소드코리아에서는 다양한 세미나 및 이벤트를 진행하고 있습니다.
진행중인 이벤트는 아래 페이지를 참고해주세요.

https://classmethod.kr/board/library

AWS에 대한 상담 및 클래스메소드코리아 멤버스에 관한 문의사항은 아래 메일로 연락주시면 감사드립니다!
Info@classmethod.kr

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.