Cloud Infra Architecture (AWS)

AWS SNS + Slack

seongduck 2024. 2. 2. 14:49

1) Slack 설정

  1. 왼쪽 하단 앱 클릭
  2. incoming-webhook
  3. Slack에 추가 (방 선택)
  4. 웹 후크 URL 복사
https://hooks.slack.com/services/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

 

2) AWS SNS 설정

  1. SNS 주제 클릭 후 주제 생성
  2. 표준으로 생성 (이름 : test-sns-slack-alarm)

 

3) CloudWatch 경보 생성

  1. 좌측 "경보"의 "경보 상태" 후 "경보 생성"
  2. 지표선택
  3. 찾아보기에서 ec2 선택후 CPUtilization 클릭후 지표 선택
  4. "작업 구성"에서 "알림 작업 추가" 클릭
    1. 같은 리전에서 생성한 cloudwatch 선택
  5. 경보 이름 및 설명 작성 후 생성

 

4) AWS Lambda 생성

  1. Lambda 같은 지역에서 함수생성
  2. 이름적고, Node.js 16.x 완료
  3. 생성 후 IAM의 역할에 자동적으로 하나 생성됨
  4. "코드" 클릭 후 업로드
// 구성 -> 환경변수로 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();
});
}

 

  1. Depoly 클릭 (업데이트 완료 표시 확인)
  2. 환경변수 등록 ("구성" -> "환경 변수" -> "편집" -> "환경변수 추가")
  3. key : webhook, value : 아까 저장해둔 webhookURL 적고 저장

 

5. sns 트리거 추가

  1. +트리거 추가 클릭 후 생성한 SNS 선택 후 추가

 

6. Lambda 테스트

  1. "테스트" 클릭 후 제목작성
  2. 이벤트 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: \"RDS-CPUUtilization-high\" in Asia Pacific (Seoul)",
        "Message": "{\"AlarmName\":\"Aurora PostgreSQL CPU 알람 (60%이상시)\",\"AlarmDescription\":\"Aurora PostgreSQL CPU 알람 (60%이상시)\",\"AWSAccountId\":\"981604548033\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 3 out of the last 3 datapoints [8.891518474692088 (14/07/21 23:18:00), 9.72 (14/07/21 23:17:00), 9.18241509182415 (14/07/21 23:16:00)] were greater than or equal to the threshold (7.0) (minimum 3 datapoints for OK -> ALARM transition).\",\"StateChangeTime\":\"2021-07-14T23:20:50.708+0000\",\"Region\":\"Asia Pacific (Seoul)\",\"AlarmArn\":\"arn:aws:cloudwatch:ap-northeast-2:981604548033:alarm:Aurora PostgreSQL CPU 알람 (60%이상시)\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/RDS\",\"StatisticType\":\"Statistic\",\"Statistic\":\"MAXIMUM\",\"Unit\":null,\"Dimensions\":[{\"value\":\"aurora-postgresql\",\"name\":\"EngineName\"}],\"Period\":60,\"EvaluationPeriods\":3,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":7,\"TreatMissingData\":\"- TreatMissingData:                    ignore\",\"EvaluateLowSampleCountPercentile\":\"\"}}",
        "Timestamp": "2021-06-07T11:31:17.380Z",
        "SignatureVersion": "1",
        "MessageAttributes": {}
      }
    }
  ]
}

테스트 진행 완료

'Cloud Infra Architecture (AWS)' 카테고리의 다른 글

AWS Security Service 종류 및 비용  (0) 2024.06.30