메뉴 닫기

Slack을 이용하여 AWS 알림 받기

안녕하세요

(주)에이클라우드  김종호 매니저입니다.

오늘은  인스턴스 CPU 사용량이 올라가면 Slack으로 알림이 갈 수 있도록 서비스를 만들어 보도록 하겠습니다.

아키텍처는 이렇게 되겠습니다.

1. Slack 설정

Slack 찾아보기 클릭 후 앱을 선택을 해줍니다.

검색 창에 incoming-webhook을 선택을 해줍니다.

구성을 클릭을 해줍니다.

Slack에 추가를 눌러줍니다.

채널에 포스트에 알람 받기를 원하는 채널을 선택을 해줍니다.

수신 웹후크 통합 앱 추가를 클릭을 해줍니다.

저희가 필요한거는 웹후크 URL 입니다.

복사를 해서 메모장에 붙여넣어줍니다.

그리고 밑으로 내려서 설정저장을 해줍니다.

이러면 Slack 설정은 끝이 납니다.

 

2. Amazon SNS 설정

이제 Amazon SNS를 설정을 해주겠습니다.

주제 생성을 클릭을 해줍니다.

주제 생성을 눌러줍니다.

이렇게 만들어진 amazon SNS를 확인할 수 있습니다.

 

3. Lambda 설정

이번에는 Lambda를 생성하여 Amazon SNS와 Slack을 연결을 해보도록 하겠습니다.

함수 생성을 클릭을 해줍니다.

함수 생성을 클릭을 해줍니다.

이렇게 만들어진게 확인이 되실겁니다.

코드 쪽을 넣어보도록 하겠습니다.

코드는 https://velog.io/@wngud4950/AWS-Lambda로-Slack-자동알림-생성하기

해당 블로그 도움을 받았습니다.

// 구성 -> 환경변수로 webhook을 받도록 합니다.
const ENV = process.env
if (!ENV.webhook) throw new Error(‘Missing environment variable: webhook’)

const webhook = ENV.webhook;
const https = require(‘https’)

const statusColorsAndMessage = {
ALARM: {“color”: “danger”, “message”:”위험”},
INSUFFICIENT_DATA: {“color”: “warning”, “message”:”데이터 부족”},
OK: {“color”: “good”, “message”:”정상”}
}

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 = statusColorsAndMessage[data.NewStateValue];
const oldState = statusColorsAndMessage[data.OldStateValue];
const executeTime = exports.toYyyymmddhhmmss(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,
short: true
},
{
title: ‘현재 상태’,
value: `*${newState.message}*`,
short: true
},
{
title: ‘바로가기’,
value: exports.createLink(data)
}
]
}
]
}
}

// CloudWatch 알람 바로 가기 링크
exports.createLink = (data) => {
return `https://console.aws.amazon.com/cloudwatch/home?region=${exports.exportRegionCode(data.AlarmArn)}#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.Id === ‘m1’).MetricStat.Metric.MetricName;
const expression = metrics.find(metric => metric.Id === ‘ad1’).Expression;
const width = expression.split(‘,’)[1].replace(‘)’, ”).trim();

return `${evaluationPeriods * minutes} 분 동안 ${evaluationPeriods} 회 ${metric} 지표가 범위(약 ${width}배)를 벗어났습니다.`;
}

// 이상 지표 중 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}`;
}

// 타임존 UTC -> KST
exports.toYyyymmddhhmmss = (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.options(slackUrl), message);
}

exports.options = (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();
});
}

코드 소스 부분에 코드를 넣어준 후 Deploy를 클릭을 해줍니다.

업데이트가 완료되었다는 문구를 확인할 수 있습니다.

이제 해당 코드에 환경 변수를 등록을 해주어야합니다.

위에 목록에서 구성 → 환경 변수로 이동을 해줍니다.

편집을 눌러주고

키는 webhook을 넣어주고 값은 저희가 아까 메모장에 저장했던 webhookurl을 넣어줍니다.

저장을 눌러주시면 되겠습니다.

이제 webhook을 넣어줬으니 Lambda에서 slack으로 알람을 받을 수 있는 테스트를 해보겠습니다.

위에 목록에서 테스트로 이동을 해줍니다.

{
“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: \”RDS-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\”:\”683308520328\”,\”NewStateValue\”:\”ALARM\”,\”NewStateReason\”:\”Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).\”,\”StateChangeTime\”:\”2021-07-14T23:20: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”: “2021-06-07T10:51:39.536Z”,
“SignatureVersion”: “1”,
“MessageAttributes”: {}
}
}
]
}

이벤트 JSON 부분에 해당 코드를 넣어줍니다.

이렇게 코드를 넣어준 후 테스트 버튼을 누르면 테스트 알람이 가겠습니다.

함수 실행이 성공적인 것을 확인할 수 있습니다.

slack에도 테스트 알람이 온 것을 확인할 수 있습니다.

 

4. Amazon SNS + Lambda 연결하기

자 이제 Amazon SNS를 Lambda와 연결을 해보겠습니다.

트리거 추가를 클릭을 해줍니다.

트리거 구성을 해주고 추가를 눌러줍니다.

이제 연결은 끝이났습니다.

 

5. Cloud Watch 경보 생성

마지막으로 CloudWatch 경보를 생성 해보도록 하겠습니다.

Cloud Watch로 이동을 하여 왼쪽 목록에서 모든 경보를 클릭을 해줍니다.

경보 생성을 클릭을 해줍니다.

지표 선택을 클릭을 해줍니다.

Ec2를 선택을 해주고 CPUUtilzation 부분을 선택을 해줍니다.

저희는 CPU 양에 따른 알람 발생이기에 CPUUtilzation을 선택을 해주었습니다.

저희는 평균적으로 CPU값이 1분동안 수치를 유지하게 되면 알람을 발생 하도록 하겠습니다.

CPU 사용량이 10보다 크거나 같으면 알람을 발생 시켜보겠습니다.(테스트 이기 떄문에 10으로 했습니다.)

다음을 눌러줍니다.

경보상태 트리거를 경보상태, 정상 두가지를 만들어 줍니다.

그리고 저희가 만들었던 Amazon SNS을 선택을 해주고 다음을 눌러줍니다.

경보 이름을 작성해주고 다음을 누르고 생성을 해줍니다.

이제 최종 테스트를 진행 해보도록 하겠습니다.

Ec2 인스턴스 CPU 사용량을 올려서 진짜로 Slack으로 알람이 가는지 테스트를 해보겠습니다.

 

6. 최종 테스트

현재 ec2 서버에는 stress tool 설치가 되어있습니다.

창을 두개를 열어주도록 하겠습니다.

top 명령을 입력하여 한군데서 cpu 사용률을 확인을 해줍니다.

stress –cpu 2 –timeout 60s #1분동안 2코에 부하를 주는 명

해당 명령어를 서버에 입력을 해줍니다.

그러면 서버에 cpu 사용률이 크게 올라가는 것을 확인할 수 있습니다.

이제 Slack에 알람이 오는지 확인을해보도록 하겠습니다.

이렇게 Slack에서 알람이 온 것을 확인할 수 있습니다.

또한 정상으로 바뀌면 한번 더 알람이 오는 것을 확인할 수 있습니다.

봐주셔서 감사합니다.

 

코드와 참고는 밑에 블로그를  참고를 했습니다.

https://velog.io/@wngud4950/AWS-Lambda로-Slack-자동알림-생성하기