#도입 이유
프로젝트 도중 프론트팀으로부터 메시지를 받았다. 서버가 다운 됐다는 내용이다. Spring 서버 에러 내용은 Slack으로 수신 받게 설정을 해두어 EC2 서버 모니터링에 대해서는 안일했었다. 지금은 프론트팀이 먼저 발견하여 연락을 주었지만, 실서비스를 운영하게 된다면 사용자가 먼저 서버 다운을 경험하게 될 것이다. 이는 서비스 신뢰도에 대한 하락 및 사용자 불편함이 증가한다는 것을 의미한다.
만약 서버에 문제가 생겼다면 여러 사용자가 접하기 전에 먼저 서버팀에서 인지하여 해결하는 것이 중요하므로 서버 모니터링을 도입하게 되었다.
(위 문제에 대한 해결 내용은 아래 포스터에 적어두었다.)
[에러] Out of memory Error: java heap space
에러원인: Out of memory Error / java heap space //컨테이너 메모리 사용량 보기docker stats 위 명령어로 컨테이너 메모리 사용량을 모니터링했습니다. MEM USAGE / LIMIT 열 부분을 확인해 보면 1/2이나 메모
jsw5913.tistory.com
#모니터링 과정
CloudWatch에서 알람 발생 → SNS 푸시 서비스 호출 → Lambda 함수 트리거(환경변수를 위해 KMS 사용) → 연동된 Slack 채널로 알람 전송
- Slack: 채널 기반 메시징 플랫폼. Channel 고유의 WebHook url을 통해 알람 수신
- CloudWatch: AWS 리소스 및 실행 중인 어플리케이션 모니터링
- SNS: Publisher ↔ Subscriber 간 통신 채널. 지원되는 프로토콜을 이용해 클라이언트에 메시지 발송
- Lambda: 서버리스 컴퓨팅 서비스. 서버를 관리하지 않아도 코드를 실행할 수 있게 하는 컴퓨팅 서비스
- KMS: 데이터 암호화에서 사용되는 암호화 키. 인터넷 전송 시 암호화 통신에 사용 (사용 안 할 예정. 이유는 밑에서 설명하겠다.
#도입 방법
1.슬랙 Webhook 생성
본 글에서 Webhook 생성 과정은 생략하겠다.
2. Amzons SNS(Simple Notification Service)생성
3. Amzon Lambda 생성
3-1 블루프린트 설정
3-2 SNS 트리거 설정
3-3 환경 변수 설정
slackChannel: ec2 모니터링 알림 받을 슬랙 채널명 입력
kmsEncryptedHookUrl: 임시값 test 입력
3-4 WebhookURL 입력하기
위와 같이설정해주면 미리 정의된 블루 프린트를 사용했기 때문에 아래와 같이 코드가 미리 입력되어있을 것이다. Lamda는 아래 코드와 같이 작동된다.
기존코드
import boto3
import json
import logging
import os
from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
# The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
# The Slack channel to send a message to stored in the slackChannel environment variable
SLACK_CHANNEL = os.environ['slackChannel']
# 이부분 주목
HOOK_URL = "https://" + boto3.client('kms').decrypt(
CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL),
EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
)['Plaintext'].decode('utf-8')
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info("Event: " + str(event))
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Message: " + str(message))
alarm_name = message['AlarmName']
#old_state = message['OldStateValue']
new_state = message['NewStateValue']
reason = message['NewStateReason']
slack_message = {
'channel': SLACK_CHANNEL,
'text': "%s state is now %s: %s" % (alarm_name, new_state, reason)
}
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", slack_message['channel'])
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)
원래 같았으면 코드상 KMS로 인해 암호화된 WebhookURL을 환경변수에 넣어줘야한다. 그럼 코드에서 환경변수에서 인식된 WebhookURL을 디코딩해서 사용한다. lamda의 환경변수 설정이 유출된 위험이 없고 이러한 과정이 불필요하다 생각하여 코드를 수정하였다. 환경변수에 그대로 WebhookURL을 입력해 디코딩을 하지 않게 말이다.
코드를 아래와 같이 수정하고 Deploy버튼을 누르면 된다. 혹여나 슬랙에 보내는 메시지 형식을 바꾸고 싶다면 Lamda에 들어와 이 코드를 수정하면 된다.
수정코드
import json
import logging
import os
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from datetime import datetime, timezone, timedelta
# The Slack webhook URL stored directly in the environment variable
HOOK_URL = os.environ['slackWebhookUrl']
# The Slack channel to send a message to stored in the slackChannel environment variable
SLACK_CHANNEL = os.environ['slackChannel']
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info("Event: " + str(event))
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Message: " + str(message))
alarm_name = message['AlarmName']
new_state = message['NewStateValue']
reason = message['NewStateReason']
state_change_time = message['StateChangeTime']
# UTC 시간을 한국 시간으로 변환
utc_time = datetime.strptime(state_change_time, "%Y-%m-%dT%H:%M:%S.%fZ")
korea_time = utc_time.replace(tzinfo=timezone.utc).astimezone(timezone(timedelta(hours=9)))
korea_time_str = korea_time.strftime("%Y-%m-%d %H:%M:%S")
slack_message = {
'channel': SLACK_CHANNEL,
'text': (
"*알람 이름*: %s\n"
"*상태*: %s\n"
"*원인*: %s\n"
"*발생 시각*: %s (한국 시간)"
) % (alarm_name, new_state, reason, korea_time_str)
}
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", slack_message['channel'])
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)
코드를 바꿨으니 환경변수도 바꿔줘야 한다.
3-5 AWS Lamda 테스트
아래 테스트 탭에 들어가 이벤트 JSON 아래 코드를 복붙해주고 테스트버튼을 누르면 된다.
{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:eu-west-1:000000000000:cloudwatch-alarms:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Sns": {
"Type": "Notification",
"MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"TopicArn": "arn:aws:sns:eu-west-1:000000000000:cloudwatch-alarms",
"Subject": "ALARM: \"Example alarm name\" in EU - Ireland",
"Message": "{\"AlarmName\":\"Example alarm name\",\"AlarmDescription\":\"Example alarm description.\",\"AWSAccountId\":\"000000000000\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).\",\"StateChangeTime\":\"2017-01-12T16:30:42.236+0000\",\"Region\":\"EU - Ireland\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"DeliveryErrors\",\"Namespace\":\"ExampleNamespace\",\"Statistic\":\"SUM\",\"Unit\":null,\"Dimensions\":[],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":1.0}}",
"Timestamp": "2017-01-12T16:30:42.318Z",
"SignatureVersion": "1",
"Signature": "Cg==",
"SigningCertUrl": "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.pem",
"UnsubscribeUrl": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:000000000000:cloudwatch-alarms:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"MessageAttributes": {}
}
}
]
}
슬랙에 잘 전송된 것을 확인할 수 있다.
4. AWS CloudWatch 생성
Ec2에서 추적할 지표 선택
트리거할 기준선 설정.
난 1분 평균 cpu사용률리 80이 넘을 때 알림을 보내게 설정했다.
알림 보낼 AWS SNS 선택

끝!
#아쉬웠던 점
AWS CloudWath, SNS, Lamda, KMS 등 처음 접해보는 서비스였다. 그랬기에 시간비용을 생각하여 인터넷에 자료가 많이 나와있는 방식대로 진행하였다. 나와있는 자료중 대부분이
CloudWatch에서 알람 발생 → SNS 푸시 서비스 호출 → Lambda 함수 트리거(환경변수를 위해 KMS 사용) → 연동된 Slack 채널로 알람 전송
위와 같았다. 그러나 난 KMS 사용이 불필요하다 생각하여 Lamda코드를 수정하여 KMS 서비스 사용을 설정하지 않았다. KMS설정 비용을 단축시킨 것이다. 이 부분은 잘했다고 생각이 든다.
그런데 위 과정을 다 끝내고 나서 위 흐름과 각 서비스에 대한 이해도가 올라가 AWS SNS가 중간에 불필요하다는 것을 느꼈다. 다시 말하자면,
CloudWatch에서 알람 발생 → Lambda 함수 호출 → 연동된 Slack 채널로 알람 전송
위 과정으로 단축시킬 수 있었다는 것이었다. AWS SNS는 메신저 구독/발행 느낌으로 필터링 및 여러 구독자에게 전달 등과 같은 역할을 해줄 수 있지만, 우리 프로젝트에서는 아직까지 이런 세세한 메시지 송신 조절이 필요없었다. 더군다나 필터링 필요하다면 AWS Lamda에서 코드를 수정하면 된다.
이미 오버엔지어링을 하였기에 다음과 같이 간략한 과정으로 바꾸려면 또 오버엔지어링을 하는 것이다. 그래서 간략한 버전으로 수정하지는 않겠다. 다음에 AWS CloudWatch로 모니터링 도입 업무를 맡게 된다며 위와 같은 간략한 흐름으로 설정하겠다.
만약 간소화 버전으로 AWS CloudWatch를 설정하고 싶다면 아래 링크를 참조해봐도 좋을 것 같다.
Lambda와 친해지는 첫걸음 - 장애 탐지 만들기
들어가며: 처음 접하는 AWS Lambda많은 사람들이 AWS를 처음 접하는 서비스로 Lambda를 선택하지 않습니다.AWS Lambda는 서버를 준비(프로비저닝)하거나 관리할 필요 없이 사용할 수 있는 서비스 입니다.
www.smileshark.kr
'CICD' 카테고리의 다른 글
Gighub Actions,docker 배포(Linux Ec2) (1) | 2024.12.18 |
---|---|
nginx 설치 및 포트바인딩 (0) | 2024.10.24 |
[에러] Out of memory Error: java heap space (0) | 2024.09.05 |
컨테이너 로그 초기화 (0) | 2024.09.05 |
리눅스 Ec2 mysql 명령어 모음 (0) | 2024.02.06 |