ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CloudWatch 경보 Slack에서 알림 받아보기. (SNS, Lambda)
    AWS 2023. 7. 25. 16:10

    1) Slack Webhook 생성

     

    Slack → 워크스페이스 메뉴 → 설정 및 관리 → 앱 관리로 이동

     

     

    앱 디렉터로리에서 web hook 를 검색해서 Slack 에 추가해준다.

     

     

    슬랙에 추가버튼을 누르면 웹 후크 통합 앱 추가하기 

     

     

    URL 복사해두기

     

     


     

    2) SNS 생성하기 . 

     

    - 표준 SNS 로 생성해주고 이름을 넣어준뒤 나머지 설정 값들은 모두 디폴트 값으로 넣어준다. 

     

     


    3) Lambda 생성

     

     

    3-1 ) SNS를 구독할 Lambda를 생성해주기. 

     

     


     

     

    3-2 ) 함수 생성. 

     

    함수 생성해주면 함수 코드를 추가해주자.

    Slack으로 메세지를 보내는 코드는 아래 더보기 란에서 확인이 가능하다.

    위에서 생성한 Slack Webhook URL을 사용하여 람다 함수를 생성한 후 SNS 트리거를 추가해준다. 

    ▼ 람다 함수 코드 더보기

     

    더보기

     

     

    람다함수 코드 출처 : 

    https://jojoldu.tistory.com/586

     

    CloudWatch 이상 지표를 슬랙 알람으로 받기 (feat. SNS, Lambda)

    AWS 서비스를 이용하면 CloudWatch를 통해 서비스의 이상 지표를 손쉽게 확인할 수 있습니다. 이를테면 다음과 같은 경우인데요. 평소보다 로드밸런서로 들어오는 요청양이 2배이상 높다거나 RDS의 C

    jojoldu.tistory.com

     

     

    // 구성 -> 환경변수로 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();
        });
    }

     

     


     

    3-3 ) Lambda 환경변수 추가해주기

     

    아까 webhook 저장해둔 값을 넣어준다.

     

     

     


    3-4 ) Lambda 테스트

    - 해당 코드를 

    {
      "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\":\"Lambda Function 알람테스트 \",\"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": {}
          }
        }
      ]
    }

     

     

     


    4. Lambda와 SNS연동하기

    나는 이미 트리거를 연결해놔서 추가된 것으로 보이지만, 

    함수 개요에서 트리거 추가를 선택해 위에서 만든 SNS를 선택해주면 된다. 

     


    5. SNS와 CloudWatch 경보 연동시키기 

    CloudWatch 페이지로 넘어가서 경보를 생성해주자. 

    내가 원하는 특정 지표에 맞는 경보가 오도록 설정해주면 된다.

    나는 IVS 시청자수에 대한 경보를 설정해주었다.

    조건에서는 선택한 지표가 언제 경보를 발생시킬지에 대해 설정해주면 된다.

    (동시 접속자 수 테스트를 위해 임계값을 2명으로 설정해보았다.)

     

     

    알람 트리거를 등록하고서 발생시 활성화 될 SNS를 선택해준다. 

    트리거는 총 2개를 생성해주었다. 

    - 정상 → 경보

    - 경보/데이터 부족 → 정상

     

    둘 다 넣어야지 비정상 상태에서 정상으로 돌아왔음을 알람으로 인지할 수 있음. 

    정상 알람을 걸어두지 않으면 장애가 해소되었는지 아직도 장애 상태로 지속되고 있는지를 직접 확인할 수 밖에 없게 되기 때문이다. 사전에 장애에 대비하기 위해서, 그리고 효율적인 모니터링을 위해서는 꼭 정상 알람을 설치하는 것을 권장한다!!

     

     

    경보를 생성한 후, IVS서비스의 시청자 수를 올리면, 아래와 같은 알림을 슬랙으로 확인받아 볼 수 있게 된다. 

     

     

     


     

    참고 블로그

     

    https://jojoldu.tistory.com/586

     

     

    CloudWatch 이상 지표를 슬랙 알람으로 받기 (feat. SNS, Lambda)

    AWS 서비스를 이용하면 CloudWatch를 통해 서비스의 이상 지표를 손쉽게 확인할 수 있습니다. 이를테면 다음과 같은 경우인데요. 평소보다 로드밸런서로 들어오는 요청양이 2배이상 높다거나 RDS의 C

    jojoldu.tistory.com

    https://yuntreee.github.io/aws/aws_CloudWatch_Slack/

     

    [AWS] CloudWatch와 Slack 연동

    1. CloudWatch Alarm과 Slack 연동

    yuntreee.github.io

     

    'AWS' 카테고리의 다른 글

    [ AWS ] aws 에서 엔드포인트란?  (0) 2023.09.12
    [ AWS ] SQS란?  (0) 2023.08.13
    [ AWS ] Secrets Manager  (0) 2023.08.12
    [ AWS ] IVS란?  (0) 2023.07.30
    AWS route53에 한글 도메인 등록하기  (0) 2023.07.30

    댓글

SSOONTORY Blog.