ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • AWS Elastic Beanstalk의 상태를 Slack으로 모니터링 하기
    핵인싸 개발자의 길/트레바리 활동(2019.8~2020.07) 2020. 3. 1. 01:29

     

    안녕하세요. 트레바리 테크 셀 크루 이뇽 입니다.

    현재 저희 트레바리 서비스는 AWS의 Elastic Beanstalk를 이용하여 서버를 운영하고 있습니다.

    Elastic Beanstalk

    Elastic Beanstalk는 무엇인가요?
    하나의 서버 환경을 쉽게 구성해주고, 서비스를 중간에 끊김 없이 한 번에 배포하고 관리할 수 있는 AWS 서비스입니다. 
    예를 들어, EC2를 직접 구성하여 서버를 운영하려면, 보안그룹 설정과 SSH 쉘로 직접 접속하여 새로운 서버 배포 및 관리를 해야 하지만, Elastic Beanstalk를 이용하면 이러한 요소를 모두 알아서 설정해주고, 서버 중지 없이 도중에 바로 배포가 가능합니다 또한 서버의 상태를 실시간으로 메시지 로그가 업데이트되어 모니터링도 간편합니다.

    Elastic Beanstalk를 이용하면 좋은 점이 많습니다. 따로 SSH 쉘에 접속하지 않고 로컬에서 바로 배포 명령어를 실행하면, 그 이후로부턴 중간에 서비스 끊김 없이 알아서 npm install을 하고 배포를 해주는 아주 편리한 서비스이죠.

    그러나 가끔 불편한 점도 있습니다. 

     

    Elastic Beanstalk의 불편한 점

    우선 배포 시간이 약 10~30분으로 상당히 길다는 것입니다. 그래서 배포가 완료된 후 기능 상 문제가 없는지 확인하려면 약 30분이라는 시간을 기다려야 합니다.

    배포가 완료되면 기능 상 문제가 없나 확인을 해야 하나, 쌓여있는 다른 업무들이 기다리고 있는 저희로선, 마냥 배포가 완료될 때까지 일일이 기다릴 수 없어 결국 배포가 완료되어도 놓칠 때가 많습니다.

     

    뿐만 아니라 현재 저희 트레바리 서비스를 운영하고 있는 Elastic Beanstalk이 가끔 말썽을 일으켜, 가끔 서버의 성능이 저하되면 원활한 서비스 운영이 되고 있을 때가 많습니다.

    일시적으로 나타나는 성능 저하 상태

     

    가끔 이렇게 성능 저하가 되면, API 요청이 계속 실패되어 원활한 서비스 운영이 되지 않을 때가 간간히 있습니다. 

    수시로 해당 서버의 서능 상태를 모니터링 하게되면 많은 번거로움이 생기기 마련입니다. 

    그래서 찾아본 결과, Elastic Beanstalk의 상태 변경이 있을 때마다 이벤트를 발생하여 상태 메시지를 받을 수 있는 AWS SNS(Simple Notification Service)의 서비스를 찾게 되었습니다.

     

    AWS SNS은 AWS에서 제공하는 여러 푸시 알림 서비스입니다. AWS에서 사용 중인 서비스에 대한 상태 알림을 받거나, 혹은 기능적으로 알림 기능이 필요로 할 때 사용할 수도 있습니다.

    이메일, SMS, SNS, HTTP 통신 등 알림 수단 역시 여러 가지로 제공을 하고 있습니다.

    이 SNS를 이용해서 Elastic Beanstalk의 상태 변화가 생길 때마다 이벤트를 발생시켜 슬랙으로 전송한다면, 수시로 AWS 웹 콘솔로 접속하지 않고도 Elastic Beanstalk의 상태를 모니터링할 수 있을 것입니다.

     

    아키텍처

     

    Elastic Beanstalk를 AWS SNS의 '주제'로 등록

     

     

    AWS SNS에는 주제와 구독이라는 개념이 있습니다.

    #주제
    SNS와 연결시킬 다른 AWS의 서비스를 등록하는 부분입니다. 저희가 연결하려는 Elastic Beanstalk가 될 수도 있고, 이 외에도 이벤트를 발생시킬 수 있는 다른 서비스가 될 수도 있습니다.

    #구독
    주제가 발생시킨 이벤트(또는 알림)를 전달받을 수단을 등록하는 부분입니다. Lambda의 트리거 발생이 될 수도 있고 HTTP Request 요청 또는, 기본 적인 이메일이나 문자 발송이 될 수도 있습니다. 

     

    #AWS SNS의 요금

    SNS의 요금 측정은 오른쪽과 같습니다.
    저희는 Lambda 트리거를 이용할 것이므로, Lambda 요금으로 측정이 될 것입니다.

     

     

     

    Elastic Beanstalk를 주제로 생성해야 SNS로 이벤트를 받을 수 있습니다.

    AWS SNS 주제 생성 화면

     

    직접 SNS 콘솔에서 주제를 생성하려 하니, Topic이니, IAM이니, ARN이니 설정할 내용들이 너무 많습니다.
    그러나 Elastic Beanstalk의 웹 콘솔에서 SNS의 주제를 더욱 쉽게 등록할 수 있는 방법이 있습니다.

     

    # Elastic Beanstalk에서 SNS의 주제 생성하기

    Elastic Beanstalk의 웹 콘솔로 접속하여 '구성-알림'으로 들어갑니다.

     

     

    해당 환경의 중요 이벤트가 발생할 때마다 알림을 받을 이메일을 등록하라고 나옵니다. 여기서 이메일을 등록하게 되면, 해당 Elastic Beanstalk의 주제와, 해당 주제에 대한 메시지를 이메일로 전송받을 수 있는 구독이 자동으로 생성됩니다.

    그러나 저희는 이메일이 아닌 Lambda로 이벤트 메시지를 전송받을 것이므로 주제만 이용하고 이메일에 대한 구독은 이따가 삭제하시면 됩니다.

     

    Lambda 함수 세팅

    Lambda를 AWS SNS에 구독으로 등록하기 위해서는, 사용할 해당 Lambda를 먼저 만들어야 합니다.

    람다 함수를 생성하는 법은 모두 아시리라 생각하고 생략하겠습니다.

     

    향후에 event 파라미터 변수를 통해서 Elastic Beanstalk의 메시지를 전달받을 것입니다.

    Elastic Beanstalk 메시지 구조는 아래와 같습니다.

    {
      "Records": [{
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-2:151095201970:ElasticBeanstalkNotifications-Environment-APP-ENV:1111-1111-1111-1111",
        "Sns": {
          "Type": "Notification",
          "MessageId": "1234-1234-1234-1234",
          "TopicArn": "arn:aws:sns:ap-northeast-2:111111:ElasticBeanstalkNotifications-Environment-TEST-ENV",
          "Subject": "AWS Elastic Beanstalk Notification - Environment health has transitioned from Ok to Info. Applica......",
          "Message": "Timestamp: Tue Sep 18 01:53:46 UTC 2018\nMessage: Environment health has transitioned from Ok to Info. Application update in progress on 1 instance. 0 out of 1 instance completed (running for 19 seconds).\n\nEnvironment: TEST-ENV\nApplication: APP\n\nEnvironment URL: http://TEST-ENV.ap-northeast-2.elasticbeanstalk.com\nNotificationProcessId: ed05c426-fe75-46c9-9ca2-c03d53d85e25",
          "Timestamp": "2018-09-18T01:54:04.516Z",
          "SignatureVersion": "1",
          "Signature": "VaxT10......",
          "SigningCertUrl": "https://sns.ap-northeast-2.amazonaws.com/SimpleNotificationService-111111.pem",
          "UnsubscribeUrl": "https://sns.ap-northeast-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-2:151095201970:ElasticBeanstalkNotifications-Environment-APP-ENV:......",
          "MessageAttributes": {}
        }
      }]
    }

     

    저희에게 필요한 부분은 Records[0].Sns.Message 부분 입니다.

    {
      "Records": [{
        "Sns": {
          "Message": "Timestamp: Tue Sep 18 01:53:46 UTC 2018\nMessage: Environment health has transitioned from Ok to Info. Application update in progress on 1 instance. 0 out of 1 instance completed (running for 19 seconds).\n\nEnvironment: APP-ENV\nApplication: APP\n\nEnvironment URL: http://APP-ENV.ap-northeast-2.elasticbeanstalk.com\nNotificationProcessId: ed05c426-fe75-46c9-9ca2-c03d53d85e25"
        }
      }]
    }

     

    Message의 값이 개행문자로 구분이 되어 있는데, 풀어보면 아래와 같습니다.

    "Timestamp": "Tue Sep 18 01:53:46 UTC 2018"
    "Message": "Environment health has transitioned from Ok to Info. Application update in progress on 1 instance. 0 out of 1 instance completed (running for 19 seconds)."
    "Environment": "TEST-ENV"
    "Application": "APP"
    "Environment URL": "http://TEST-ENV.ap-northeast-2.elasticbeanstalk.com"
    "NotificationProcessId": "ed05c426-fe75-46c9-9ca2-c03553285e25"

     

     

    위 메시지와 같이 Elastic Beasntalk의 이름과 이벤트가 발생한 시간, 상태 메시지가 담겨 있습니다.

    이 데이터들을 적절히 사용하여 슬랙으로 전송하면 될 것 같습니다.

    #Lambda 코드 설명 (TypeScript 사용)

    import { IncomingWebhook } from "@slack/client";
    
    interface IPayload {
      Records: Array<{
        Sns: {
          Message: string;
        };
      }>;
    }
    
    interface ISendMessage {
      attachments: Array<{
        author_name: string;
        color: string;
        text: string;
      }>;
      icon_emoji: string;
      username: string;
    }
    
    const handler: AWSLambda.Handler = async ( payload: IPayload, _, cb): Promise<any> => {
      const iconEmoji = process.env.ICON_EMOJI || ":robot_face:";
      const userName = process.env.USERNAME || "AWS";
      const url = process.env.SLACK_WEBHOOK_URL || "";
      
      const sendMessage: ISendMessage = {
        attachments: buildAttachments(payload),
        icon_emoji: iconEmoji,
        username: userName
      }; 
    }
    
    const webhook = new IncomingWebhook(url);
    webhook.send(message);

     

    IPaylead는 Lambda로 받아올 Event의 구조입니다. 
    위에서 설명한 대로 저희는 Records[0].Sns.Message의 부분만 받아서 사용할 것이므로 이 부분만 타입으로 정의하도록 합니다.

    ISendMessage는 Slack Webhook으로 메시지를 전송할 데이터 구조입니다.
    중간의 attachments는 Slack bot이 Slack channel에서 보여줄 메시지 형태입니다.

    buildAttachments함수를 통해 ISendMessage의 attachments 구조에 맞게 파싱을 할 것입니다.

    const buildAttachments = (payload: IPayload) => {
      const msgMap = parseSnsMsg(payload.Records[0].Sns.Message);
    
      if (msgMap && msgMap.Environment) {
        const { Message, Environment } = msgMap;
        return [
          {
            author_name: Environment,
            color: isErrorMessage(Message) ? "danger" : "good",
            text: highlightMessage(Message)
          }
        ];
      }
      return [
        {
          author_name: "author-error",
          color: "danger",
          text: "BuildAttachments Parsing Error"
        }
      ];
    };

    buildAttachments함수는 최종적으로 attachments의 구조에 맞는 데이터를 반환하도록 합니다.

     

    각 함수 설명

    # parseSnsMsg()

    const parseSnsMsg = (message: string) => {
      try {
        const parts = message.split("\n");
        const data = {
          Environment: "",
          Message: ""
        };
        parts.forEach((part: string) => {
          part = part.trim();
          if (!part) {
            return data;
          }
          if (!part.includes(":")) {
            return data;
          }
          let [key, value] = part.split(":");
          key = key.trim();
          value = value.trim();
          if (!key || !value) {
            return;
          }
          Object.assign(data, { [key]: value });
        });
        return data;
      } catch (error) {
        console.log("parseSnsMsg error: " + error);
        return null;
      }
    };

     

    Records[0].Sns.Message의 데이터에서, 이벤트를 발생시킨 환경 이름과 상태 메시지를 추출합니다.

    # isErrorMessage()

    const isErrorMessage = (message: string) => {
      const errorKeywords =
        process.env.ERROR_KEYWORDS || "Unsuccessful command, to Degraded, Failed";
      const arrErrorKeywords = errorKeywords.split(", ");
      for (const errorKeyword of arrErrorKeywords) {
        if (message.includes(errorKeyword)) {
          return true;
        }
      }
    };

    해당 메시지가 성능 저하, 배포 실패 등 부정적인 상태인 메시지인지 구분합니다.

     

    # highlightMessage()

    const highlightMessage = (msg: string) => {
      const sentences = msg.split(".");
      sentences[0] = `*${sentences[0]}*`;
      return sentences.join(".");
    };

     

    추출한 해당 메시지 중, 첫 문장에 글씨를 굵게 하여 하이라이트를 주도록 합니다.

     

    Lambda를 AWS SNS의 '구독'으로 등록

    람다 함수가 세팅이 되었으면, 이제 AWS SNS의 구독으로 등록합니다.

    Elastic Beanstalk의 구성-알림 으로 생성했던 주제를 선택 후, 프로토콜은 Lambda, 엔드포인트는 방금 작업한 Lambda의 엔드포인트로 선택한 후, 하단의 '구독 생성'을 클릭합니다.

    해당 Elastid Beanstalk와 주제와 작업했던 Lambda가 구독으로 설정된 것을 확인할 수 있습니다.

    이제는 설정한 Elastid Beanstalk의 상태가 변경이 될 때마다, SNS를 통해서 Lambda에게 이벤트 호출을 할 것입니다.

     

    기능 확인

    Elastid Beanstalk의 배포를 실행시켜 보면 상태 변화가 나타납니다.
    그때마다 슬랙으로 전송이 잘 되는지 확인해 볼 수 있습니다.

     

    마치며

    현재 이 기능을 배포하고 2,3일이 지났는데, 확실히 일시적으로 에러가 발생했는지, 배포가 완료되었는지 슬랙으로 전송해주니 훨씬 안정감이 있습니다.

    또한 이번 기능을 구현하면서 SNS(Simple Notification Service)라는 새로운 AWS 서비스를 알게 되어서 아주 좋았습니다. 
    앞으로도 더욱 AWS의 서비스를 잘 알아갈 수 있는 기능을 많이 구현해 보고 싶습니다.

    감사합니다.

    반응형

    댓글

Designed by Tistory.