-
EC2 CPU 상승 알림 (SNS + Lambda + CloudWatch)공부/AWS 2024. 7. 28. 21:18
서비스를 운영하다 보면 CPU가 상승하는 현상을 자주 확인할 수 있습니다. 제한된 리소스로 최대의 효율을 내고, 안정적으로 운영할 수 있도록 하는 것이 항상 생각하고 가져가야 할 의무라고 생각합니다.
오늘은 EC2의 CPU 상승을 확인하고, 특정 임계치를 넘는 경우 알림을 받을 수 있도록 하는 기능을 설명하려고 합니다.
(참고에 작성하였지만, 거의 이분 블로그 를 참고하였다.)
1. SNS 주제 생성
- 알림을 수집하고 보내기 위해서는 SNS의 주제를 생성해야 합니다.

- 표준으로 설정합니다.
- 이름, 표시이름 2가지 모두 작성합니다.
2. Lambda 함수 생성
- 람다는 Cloud Watch의 경보가 SNS로 전달이 된 후 Lambda를 실행하여 알림을 보낼 수 있도록 합니다.

2-1. Lambda 함수 코드 추가
// 구성 -> 환경변수 const ENV = process.env; const webhook = ENV.webhook; // slack webhook url을 등록 if(!ENV.webhook) throw new Error('Missing enviroment variable: webhook'); const https = require('https'); // CloudWatch 경보 상태 3가지 const statusCodeColorsAndMessage = { ALARM: {"color": "danger", "message":"위험"}, INSUFFICIENT_DATA: {"color": "warning", "message":"데이터 부족"}, OK: {"color": "good", "message":"정상"} } // CloudWatch 경보 임계치 설정 4가지 const comparisonOperator = { "GreaterThanOrEqualToThreshold": ">=", "GreaterThanThreshold": ">", "LowerThanOrEqualToThreshold": "<=", "LessThanThreshold": "<", } exports.handler = async (event) => { await exports.processEvent(event); } exports.processEvent = async (event) => { console.log('EVENT:',JSON.stringify(event)); const snsMessage = event.Records[0].Sns.Message; console.log('SNS Message:', snsMessage); const postData = exports.buildSlackMessage(JSON.parse(snsMessage)); await exports.postSlack(postData, webhook); } exports.buildSlackMessage= (data)=>{ const newState = statusCodeColorsAndMessage[data.NewStateValue]; const oldState = statusCodeColorsAndMessage[data.OldStateValue]; const executeTime = exports.toDateTemplate(data.StateChangeTime); const description = data.AlarmDescription; const cause = exports.getCause(data); return { attachments:[ { title: `[${data.AlarmName}]`, color: newState.color, fields:[ { title:'언제', value:executeTime }, { title:'설명', value:description }, { title:'원인', value:cause }, { title:'이전 상태', value:oldState.message }, { title:'현재 상태', value:`*${newState.message}*` }, { title:'바로가기', value: exports.createLink(data) } ] } ] } } // CloudWatch 알람 바로 가기 링크 exports.createLink = (data)=>{ const region = exports.exportRegionCode(data.AlarmArn); return `https://ap-northeast-2.console.aws.amazon.com/cloudwatch/home?region=${region}#alarm:alarmFilter=ANY;name=${encodeURIComponent(data.AlarmName)}`; } exports.exportRegionCode = (arn)=>{ return arn.replace("arn:aws:cloudwatch:","").split(":")[0]; } exports.getCause = (data) =>{ const trigger = data.Trigger; const evaluationPeriods = trigger.EvaluationPeriods; const minutes = Math.floor(trigger.Period / 60); if(data.Trigger.Metrics){ return exports.buildAnomalyDetectionBand(data, evaluationPeriods, minutes); } return exports.buildThresholdMessage(data, evaluationPeriods, minutes); } // 이상 지표 중 Band를 벗어난 경우 exports.buildAnomalyDetectionBand = (data, evaluationPeriods, minutes)=>{ const metrics = data.Trigger.Metrics; const metric = metrics.find(metric =>metric) } // 이상 지표 중 Threshold 벗어나는 경우 exports.buildThresholdMessage = (data, evaluationPeriods, minutes) => { const trigger = data.Trigger; const threshold = trigger.Threshold; const metric = trigger.MetricName; const operator = comparisonOperator[trigger.ComparisonOperator]; return `${evaluationPeriods * minutes} 분 동안 ${evaluationPeriods} 회 ${metric} ${operator} ${threshold}`; } exports.toDateTemplate = (timeString) => { if(!timeString){ return ''; } const kstDate = new Date(new Date(timeString).getTime() + 32400000); function pad2(n) { return n < 10 ? '0' + n : n } return kstDate.getFullYear().toString() + '-'+ pad2(kstDate.getMonth() + 1) + '-'+ pad2(kstDate.getDate()) + ' '+ pad2(kstDate.getHours()) + ':'+ pad2(kstDate.getMinutes()) + ':'+ pad2(kstDate.getSeconds()); } exports.postSlack = async (message, slackUrl)=>{ return await request(exports.option(slackUrl),message); } exports.option = (slackUrl)=>{ const {host, pathname} = new URL(slackUrl); return { hostname:host, path:pathname, method:'POST', headers:{ 'Content-Type':'application/json' } } } function request(options,data){ return new Promise((resolve,reject)=>{ const req = https.request(options, (res)=>{ res.setEncoding('utf8'); let responseBody = ''; res.on('data',(chunk)=>{ responseBody +=chunk; }); res.on('end',()=>{ resolve(responseBody); }) }) req.on('error',(err)=>{ console.error(err); reject(err); }) req.write(JSON.stringify(data)); req.end(); }) }2-2. webhook 환경변수 추가

2-3. 테스트 json 등록
{ "Records": [ { "EventSource": "aws:sns", "EventVersion": "1.0", "EventSubscriptionArn": "arn:aws:sns:ap-northeast-2:981604548033:alarm-topic:test", "Sns": { "Type": "Notification", "MessageId": "test", "TopicArn": "arn:aws:sns:ap-northeast-2:123123:test-alarm-topic", "Subject": "ALARM: \"EC2-CPUUtilization-high\" in Asia Pacific (Seoul)", "Message": "{\"AlarmName\":\"TEST!!!\",\"AlarmDescription\":\"EC2 CPU 알람 (10% 이상 시)\",\"AlarmArn\":\"arn:aws:cloudwatch:ap-northeast-2:123123:alarm:ant-man-live-ALB-RequestCount-high\",\"AWSAccountId\":\"123412351\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).\",\"StateChangeTime\":\"2024-07-24T07:41:50.708+0000\",\"Region\":\"Asia Pacific (Seoul)\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/EC2\",\"StatisticType\":\"Statistic\",\"Statistic\":\"MAXIMUM\",\"Unit\":null,\"Dimensions\":[{\"value\":\"i-0e3e982bf1c7f0910\",\"name\":\"EngineName\"}],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":1.0}}", "Timestamp": "2024-07-24T06:20:39.536Z", "SignatureVersion": "1", "MessageAttributes": {} } } ] }3. Slack Webhook 생성
- 슬랙 로그인 후 Your apps 선택

기존에 앱이 있다면, 기존 앱을 선택하시고 없다면 생성하셔야 합니다. 저 같은 경우 기존에 생성했던 앱으로 실행하였습니다.
앱 생성 방법은 아래 참고블로그를 확인해주세요.
- Your Apps 접속하면, 생성했었던 앱을 확인할 수 있습니다.

- 좌측에서 Incoming Webhooks 선택합니다.

- 현재 webhook으로 사용 중인 채널들을 확인할 수 있습니다.
- 여기서 하단에 Add New Webhook to Workspace를 선택하여 알림 받을 채널을 추가합니다.

- 웹훅을 추가한 후 다시 Lambda의 환경변수에 Webhook URL을 등록합니다. 그 후 테스트를 시도해 봅니다.

4. Lambda + SNS 연결
4-1. 트리거 추가 선택합니다.

4-2. SNS 선택 후, SNS 주제에 이전에 생성한 SNS를 선택합니다.
(이미지에 보이면 안 되는 개인정보가 있어서 해당 부분을 제거하고 올렸습니다. 양해 부탁드립니다.)

5. SNS + CloudWatch 경보 연결
5-1. 경보 상태 or 모든 경보 선택 후 경보 생성 선택합니다.


5-2. 지표 선택 클릭 후 하단 지표에서 인스턴스 ID를 입력하면 더 빨리 찾을 수 있습니다.

5-3. 지표와 조건을 설정합니다.


- 위와 같이 설정하면, 15분 동안 CPUUtilization이 평균 20% 넘는 기준이 3번 발생하는 경우 알림이 전송되게 설정하였습니다.
5-4. 알림 설정
- 경보 상태 알람 설정합니다. 다음으로 알림 전송은 이전에 생성했던 SNS 주제를 선택합니다.

- 경보를 했으면, 정상으로 돌아왔을 때도 알림도 확인이 필요합니다.
- 동일하게 SNS를 선택합니다.

6. 경보 상태 시작하기
6-1. 모든 경보에서 내가 만든 경보 검색 후 클릭하기
6-2. 데이터 부족 상태임으로 정상(실행) 상태로 만들기 위해서 데이터 부족이라는 막대바를 클릭하여 정상으로 전환하기.
(초록색 바를 클릭하면 됩니다. 처음이라면 회색바일 것입니다.)

이제는 서버의 특이사항을 슬랙으로 확인할 수 있습니다.
검색하며 찾아보고 작성한 글입니다. 혹시라도 부정확한 정보를 전달드릴 수 있습니다. 틀린 부분이 있으면 댓글을 남겨주세요.
Ref.
https://velog.io/@king/slack-incoming-webhook
Slack Incoming Webhook 2가지 방법
Slack Incoming Webhook(인커밍웹훅) 설정에는 2가지 방법이 있습니다. 앱 생성(추천), 앱 추가(비추) 를 각각 알아봅니다.
velog.io
[AWS] Lambda + CloudWatch + SNS 로 Slack 자동알림 생성하기
개요 AWS Lambda를 이용해 CloudWatch에서 나오는 경보를 Slack으로 자동알림을 생성해줘보았다. CPU 사용률이 갑자기 튀어 서버가 먹통되는 경우, 신속히 처리하기 위해 알림을 주게 되었다. CPU 사용률
velog.io
https://jojoldu.tistory.com/586
CloudWatch 이상 지표를 슬랙 알람으로 받기 (feat. SNS, Lambda)
AWS 서비스를 이용하면 CloudWatch를 통해 서비스의 이상 지표를 손쉽게 확인할 수 있습니다. 이를테면 다음과 같은 경우인데요. 평소보다 로드밸런서로 들어오는 요청양이 2배이상 높다거나 RDS의 C
jojoldu.tistory.com
'공부 > AWS' 카테고리의 다른 글
AWS Session Manager 생성 (0) 2025.01.09 [RDS MariaDB] Error: Too many connections (0) 2024.09.16 [Route53] Route 53에 도메인 등록 과정 및 도메인 취소 요청 (0) 2023.05.28 [CloudWatch] django log를 Cloudwatch에 기록하는 방법 (0) 2023.05.28 ALB(https) - ECS Fargate(Nginx - Django) 배포하기 (0) 2023.04.12