사내 메신저로 로켓챗을 사용하고 있다. 
이번 글에서는 로켓챗을 채택한 과정과 운영하면서 생긴 이슈와 그를 해결했던 경험을 다루려고 한다.




메신저 선정

메신저를 이용해야 할 인원이 많지 않다면 카카오톡도 좋은 메신저가 될 수 있다. 
하지만 규모가 커지고 다루어야할 주제(단톡방?)가 많아 진다면 한계가 오기 시작한다.
인원 관리도 힘들고 보안에도 좋지 않기 때문이다.

그래서 사내에서 사용할 메신저 서비스를 찾게 되었다.

필요한 조건은 2가지였다.


오픈소스
  • 유지비용이 가급적 들지 않으면 좋겠다.
  • 모두가 신뢰할 수 있는 구조여야 한다.

설치형
  • 데이터 이전이 자유로워야 한다.

처음에는 많이들 사용하는 슬랙이 거론 되었다.
하지만 유지비용과 보안 측면으로 인해 쉽게 선정 결정을 내리지 못했다.

후보로 나온것은 매터모스트, 로켓챗 이었다. 둘 다 슬랙의 기능을 대부분 제공하고 있었다.

이들의 특징으로는 오픈소스 기반에 설치형 버전을 제공한다. 
필요하다면 월사용료를 내고 호스팅과 기술지원을 받을수도 있는 유료모델도 있다.

그 중 로켓챗의 모바일앱이 좀 더 활용하기 편한것으로 보여서 최종적으로 로켓챗으로 선정하였다.
자체적으로 돌릴수 있는 서버가 있었기에 유료모델은 고려하지 않았고 설치형 버전을 사용하기로 하였다.




설치


설치는 리눅스 계열을 추천하는듯 하다. 
Debian(Ubuntu), CentOS 계열에 대한 설치방법을 안내하고 있다.
설치 자체는 심플한 편이다.  링크를 참조하여 진행하면 된다.




운영중 이슈 및 해결


앱에서 푸쉬는 어찌 받을 수 있나

로켓챗은 다양한 방법으로 푸쉬를 지원하고 있다. 푸쉬 관련해서 기본설정으로도 잘 되는 편이다.
하지만.. 로켓챗 푸시서버의 상태가 안좋을 때 푸시가 제대로 서비스 안 될 수도 있기에 다른 방법들에 대해서도 미리 검토를 해보았다.


기본설정 푸시

로켓챗의 푸시 게이트웨이(https://gateway.rocket.chat)를 통해 푸시를 전달한다.
이 푸시는 로켓챗 공식앱을 통해 사용자의 앱으로 푸시가 전달된다.
푸시 발생 후 앱에서 푸시를 받을때까지 보통 2~5초 정도로 응답속도는 준수한 편이다.


자체 게이트웨이 구축

공용으로 쓰는 로켓챗의 푸시 게이트웨이가 가끔 안될 때가 있다. 
지금까지 로켓챗을 1년 넘게 사용해본 결과… 
안되는 상황이, 빈도로 보면 1년에 몇번 정도로 잦은편은 아니다.

하지만 높은 신뢰도가 필요하다면 이것도 불안할 수 있다.
그때는 자체 게이트웨이를 사용하면 된다. 
이와 함께 앱에 들어가는 앱 푸시 정보(APNS, FCM 등)를 마켓 개발자 계정으로 새로 발급받고 앱 소스에 넣어서 새로 빌드 해야한다.

.....이건 쉽게 할 수 있는 작업이 아니다. 
안드로이드 앱은 그나마 개발자 계정 새로 만들어서 배포 하면 되겠지만,
iOS 앱은 개발자 계정으로 설치할 수 있는 기기수 제한도 있고 설치도 불편하다.

나는  자체구축을 포기하고 기본설정 푸시를 사용했다.
혹시 도전하고픈 분들이 계시다면 아래 링크가 도움이 될 것이다.




웹훅 API

레드마인, 젠킨스, 컨플루언스 등 다양한 서비스에서 특정 상황이 되면 
로켓챗에 메시지를 보내주었으면 요청이 있었다.

예를 들자면, 젠킨스에서 빌드가 완료되었을 때 빌드 정보를 로켓챗의 특정 채널 이나 사용자에게 전달해 주는 것이다.

대부분의 서비스에서는 이런 상황을 위해 슬랙에서 사용하는 웹훅API 를 지원하고 있다.
전달할 주소와 인증정보가 필요한데 이것을 슬랙에서 정의하고 서비스 설정에서 넣어서 연동하는 식이다.

로켓챗에서도 슬랙과 동일한 방식으로 웹훅API 연동이 가능하게 되어있다.

메뉴에서 Intergrations > New Integration > incomming webhook 이다.
이곳에서 정보를 등록하고 나온 URL 을 젠킨스 쪽에 넣어주면 된다.

등록해야하는 설정 중 주요 설정은 아래와 같다.

Post to Channel
메시지를 보낼 채널 혹은 사용자의 이름이다. ex) #channel_id , @user_id

Post as
채팅창에 나오는 봇의 이름이다.

Script
일괄적으로 적용하고 싶은 스크립트가 있다면 적는다. 
일반적으로는 비워두고 웹훅을 보내는쪽에서 넣는편이다.

등록을 마치면 WebHook URL 과 Token 이 나온다.

Example 에 있는 예시를 이용하면 커맨드라인 상에서  CURL 을 이용해 메시지를 보낼 수도 있다.

curl -X POST -H 'Content-Type: application/json' --data '{"icon_url":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQnQFLCYQ-rg_iJFXBzazZjUqMXTHPmTQ-AVU_JymsxleUHI1Oe","text":"Example message","attachments":[{"title":"Rocket.Chat","title_link":"https://rocket.chat","text":"Rocket.Chat, the best open source chat","image_url":"/images/integration-attachment-example.png","color":"#764FA5"}]}' http://[로켓챗주소]/hooks/[토큰]

레드마인, 젠킨스, 컨플루언스 등에서는 각각 슬랙의 웹훅API 와 연동하는 방법이 각 공식사이트에 정리되어 있다.

슬랙과의 연동방법을 참고해서 웹훅API 정보에 위에서 나온 로켓챗 웹훅 API URL 을 넣으면 된다.



젠킨스에서 슬랙 플러그인 설치 후 설정화면




챗봇

특정 메시지에 반응하는 챗봇을 만들고 싶다는 요청이 있었다.

휴봇(https://hubot.github.com/) 이라는 오픈소스로 된 챗봇이 있다. 
이 챗봇에서는 로켓챗에서 적용할 수 있도록 만든 버전을 지원한다.

설치는 아래 링크에서 참고하자.

2가지 설정파일이 필요하다. 환경설정(.env)과 스크립트(.js)이다.

환경설정 파일에는 로켓챗 접속정보를 넣는다.
스크립트 파일에는 봇이 동작할 로직을 넣으면 된다.

특정 문자열이 입력되면 다른 문자열로 고치는 로직을 간단하게 구현한 스크립트 내용이다. 
'onenote://' 라는 내용이 포함된 문자열이 들어오면 그 내용을 'onenotedesktop:' 으로 변경하는 로직이다.

module.exports = (robot) => {
  robot.hear(/^onenote:\/\/\/(.*)/, (res) => {
    var parsedMsg = res.match[1].replace(/\\/gi, "/")
    parsedMsg = 'onenotedesktop:' + parsedMsg
    parsedMsg = parsedMsg.replace(/\s/g, "%20")
    parsedMsg = parsedMsg.replace(/{/g, "%7B")
    parsedMsg = parsedMsg.replace(/}/g, "%7D")
    res.send(`${parsedMsg}`)
  })
}

휴봇 매뉴얼을 참조하면 이것 외에도 다양한 활용이 가능하다.

추가로, PM2 를 이용하면 nodeJS로 만들어진 휴봇 프로세스 관리를 좀 더 수월하게 할 수 있다.





Posted by panpid

댓글을 달아 주세요

머신러닝 관련 코드를 작성해보기 위해 환경을 세팅해보자. 다른 방법들도 많겠지만 가장 많이 쓰는 주피터 노트북을 설치하고 파이썬 패키지를 어떻게 관리할 것인지를 살펴보도록 하자.

주피터 노트북

주피터 노트북(jupyter notebook)은 웹 브라우저에서 파이썬 코드를 작성하고 실행까지 해볼 수 있는 REPL방식의 개발도구이다. 머신러닝이나 데이터분석 용도로 파이썬을 사용하는 사람들이 사용하는 툴로써 가벼우며 코드를 실행하고 수정하기가 간편하다. 또한 notebook형태로 파일이 공유가 가능하다.

REPL(Read–Eval–Print Loop) : 명령어를 한줄씩 입력하며 실행 상황을 지켜보는 방식(대화형)

먼저 파이썬을 설치하자. 파이썬이 이미 설치가 되어있다면 생략 하자.

$ sudo apt-get update                // apt-get 항목 업데이트
$ sudo apt-cache search python3      // 패키지 검색
$ sudo apt install python3.6         // 파이썬 3.6버전 설치
$ sudo apt install python3-pip       // 파이썬 패키지 관리

파이썬 pip를 설치한 후 pip install jupyter 로 주피터 노트북을 설치 할 수 있지만 일반적으로 아나콘다(Anaconda)를 설치하면 Jupyter Notebook이 함께 설치되어 주피터를 사용할 수 있다. 물론 pip를 이용하면 원하는 패키지만 그떄 그때 설치하여 사용할 수는 있지만, 아나콘다를 설치하면 데이터 처리 및 분석에 필요한 패키지가 모두 포함되어 설치가 되기 때문에 약간 설치시간이 오래 걸리긴 하지만 아나콘다를 사용하는 것을 추천한다.

아나콘다(Anaconda) 설치

이 글에서는 Ubuntu18.04 기준으로 설명하도록 하겠다. 또한 최신버전을 위해 반드시 아나콘다 사이트에서 최신 URL을 확인하여 설치를 진행하길 바란다.

$ wget https://repo.continuum.io/archive/Anaconda3-5.1.0-Linux-x86_64.sh
$ bash Anaconda3-5.1.0-Linux-x86_64.sh

계속 엔터를 눌러 설치한다(bashrc등록 여부는 Y로 입력해준다 - 그렇지 않으면 수동설정) 
아나콘다(Anaconda) 공식홈 참고

설치 확인

$ source ~/.bashrc    // bashrc에 등록한것을 활성해 준다
$ conda --version     // 버전확인
$ conda update conda  // 아나콘다 업데이트

만약 삭제하길 원한다면…

$ rm -rf ~/anaconda3
$ vi ~/.bashrc // # added by Anaconda3 installer 제거
$ rm -rf ~/.condarc ~/.conda ~/.continuum

주피터 노트북 실행

$ jupyter notebook


화면에 출력된 http://localhost:8888/?token=***으로 접속하면 된다.
(jupyter notebook --generate-config 명령으로 jupyter_notebook_config.py 파일을 생성하면 해당 파일에서 세부설정이 가능하다.)

접속화면


우측 상단에 New > Python3를 선택하면 새로운 창이 생성된다.

이제 웹상에서 파이썬 코드를 작성하고 실행 할 수 있다.

matplotlib 패키지를 이용하여 가공된 데이터를 그래프로 표현 할 수 있다.


주피터 노트북은 개인 로컬환경에 설치하여 직접 띄울수도 있지만, Microsoft에서는 azure notebook, Google에서는 Colab에서 클라우드상의 주피터환경을 제공해주고 있으니 간단한 학습용로라면 이를 활용하는 방법도 좋을 것이다. 공짜다!

(주피터 노트북 사용법에 대한 부분은 다루지 않겠다.)

더 살펴보자…

아나콘다 가상환경

개발을 위한 파이썬 패키지 가상환경을 별도로 만들고 싶다면 conda에서 지원하는 명령어로 사용이 가능하다. 만약 아나콘다를 사용하지 않고 pip를 사용한다면 virtualenv로 가상환경을 관리 할 수 있다.

$ conda create --name [가상환경명] python=3.6    // 가상환경을 만든다
$ conda info --envs                             // 설치된 가상환경 리스트 확인
$ source activate [가상환경명]                   // 가상환경 활성화

가상환경을 선택하여 활성화를 하게 되면 (가상환경명) winuss@ubuntu:~$와 같이 프롬프트 앞에 해당 가상환경명이 표시가 된다. 이후 패키지를 설치하게 되면 활성화된 가상환경에만 패키지가 설치가 된다.

다시 가상환경을 비활성화하거나 삭제하려면,

$ source deactivate 가상환경명          // 가상환경 비활성화
$ conda remove --name 가상환경명 -- all // 가상환경 삭제

파이썬에서는 한 라이브러리에 대해 하나의 버전만 설치가 가능한데 이는 여러개의 프로젝트를 진행하게 되면 문제가 될 수 있다. 작업을 바꿀때마다 다른 버전의 라이브러리를 설치해야 해야 하는 번거러움을 방지하기 위해 독립된 가상환경이 필요한 것이다.


아나콘다에 패키지 설치

가상환경을 활성화 하지 않고 가상환경명을 지정하여 패키지를 설치 할 수도 있다. mytest라는 가상환경에 tensorflow를 설치 및 삭제를 해보자.

$ conda search tensorflow                 // 패키지검색
$ conda install tensorflow                // 패키지 설치(전역)
$ conda install --name mytest tensorflow  // 패키지 설치(mytest 가상환경)
$ conda list                              // 패키지 리스트
$ conda remove --name mytest tensorflow   // 패키지 제거

주피터 확장기능 설치

주피터 확장기능을 사용하면 좀더 편리한 기능들을 사용할 수 있다.

$ conda install -c conda-forge jupyter_contrib_nbextensions

확장을 활성화 하려면 Nbextensions 탭을 클릭하거나 http://localhost:8888/nbextensions에서 확인 할 수 있다.


참고


Posted by @위너스

댓글을 달아 주세요

[FCM] #1. 소개 및 메시지

[FCM] #2. Firebase 프로젝트 생성 및 Android 앱 FCM 설정

[FCM] #3. Firebase 메시지 전송

[FCM] #4. Android 메시지 처리


이번에는 Android에서 단순 메시지가 아닌 아이콘이나 이미지 메시지를 어떻게 처리하는지 알아본다. 

우선 안드로이드 관련 코드들은 제외하고, 메시지 관련된 부분만 설명한다. 


푸시 메시지는 FirebaseMessagingService 클래스를 상속받아 onMessageReceived 메소드를 오버라이딩 해서 커스텀하게 처리한다.

onMessageReceived 메소드에서 메시지 알림 처리를 하면 앱이 포그라운드, 백그라운드 상관없이 푸시 메시지가 도착한다. 

코드 관련 정보는 #2. Firebase 프로젝트 생성 및 Android 앱 FCM 설정 부분에 있다.

@Override public void onMessageReceived(RemoteMessage remoteMessage) { // ... // TODO(developer): Handle FCM messages here. // Not getting messages here? See why this may be: https://goo.gl/39bRNJ Log.d(TAG, "From: " + remoteMessage.getFrom()); // Check if message contains a data payload. if (remoteMessage.getData().size() > 0) { Log.d(TAG, "Message data payload: " + remoteMessage.getData()); if (/* Check if data needs to be processed by long running job */ true) { // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher. scheduleJob(); } else { // Handle message within 10 seconds handleNow(); } } // Check if message contains a notification payload. if (remoteMessage.getNotification() != null) { Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); } // Also if you intend on generating your own notifications as a result of a received FCM // message, here is where that should be initiated. See sendNotification method below. }

푸시 메시지 중 큰 아이콘 메시지, 이미지 메시지 등은 onMessageReceived 메서드에서 처리한다.

remoteMessage는 송신자 정보와 데이터 메시지 정보를 가지고 있다. 

보통 데이터 메시지 안에 제목, 내용, 이미지 정보등을 포함하여 맞춤형 알림 처리를 한다.


이전 HTTP API 기준으로 아이콘 메시지, 이미지 메시지 처리를 해보자.


아이콘 메시지

메시지 정보

아래는 FCM 접속 서버에 발송하는 데이터 메시지 정보이다.

여기서 데이터 메시지는 커스텀하게 key,value를 설정할 수 있다. [FCM] #1. 소개 및 메시지 참고

{
  "data":
    {
      "title":"😃제목😃",
      "body":"😃내용😃",
      "largeIcon":"https://t1.daumcdn.net/news/201902/13/ZDNetKorea/20190213133354225nmeh.jpg"
  },
  "to":"사용자 토큰"
}

onMessageReceived 

Oreo 버전 이상부터는 NotificationChannel 정보를 추가해준다. (추가하지 않으면 메시지 수신 안함)
largeIcon에 링크가 아닌 데이터 자체 정보를 데이터 메시지에 넣을 수 있으나 FCM 전체 페이로드가 4KB까지 지원하므로 권장하지는 않는다. (이미지 메시지도 마찬가지...)

private void onMessageReceived(RemoteMessage remoteMessage){
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 , intent,
                PendingIntent.FLAG_ONE_SHOT);

        String channelId = "one-channel";
        String channelName = "My Channel One1";
        String channelDescription = "My Channel One Description";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
            notificationChannel.setDescription("channel description");
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.GREEN);
            notificationChannel.enableVibration(true);
            notificationChannel.setVibrationPattern(new long[]{100, 200, 100, 200});
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            notificationManager.createNotificationChannel(notificationChannel);
        }

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                 //제목
                .setContentTitle(remoteMessage.getData().get("title"))
                 //내용
                .setContentText(remoteMessage.getData().get("body"))
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

        try {
            URL url = new URL(remoteMessage.getData().get("largeIcon"));
            //아이콘 처리 
            bigIcon = BitmapFactory.decodeStream(url.openConnection().getInputStream());
            notificationBuilder.setLargeIcon(bigIcon);
        } catch (IOException e) {
            e.printStackTrace();
        }

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(333 /* ID of notification */, notificationBuilder.build());
    }

결과

이미지 메시지

메시지 정보

이미지 정보도 아이콘과 동일한 이미지로 테스트 해본다. 
{
  "data":
    {
      "title":"😃제목😃",
      "body":"😃내용😃",
      "imgUrl":"https://t1.daumcdn.net/news/201902/13/ZDNetKorea/20190213133354225nmeh.jpg"
  },
  "to":"사용자 토큰"
}

onMessageReceived 

푸시 메시지에 이미지를 적용하기 위해서는 setStyle()에 NotificationCompat.BigPictureStyle() 을 넣어서 사용한다. 
수신된 이미지 메시지는 펼쳐진 상태와 줄여진 상태에서 제목과 내용을 표현하는 방법이 다르다. 

펼쳐진 상태


 제목

 NotificationCompat.Builder.setContentTitle()

 내용

 NotificationCompat.Builder.setContentText()

 줄여진 상태


 제목

 NotificationCompat.BigPictureStyle().setBigContentTitle()

 내용

 NotificationCompat.BigPictureStyle()..setSummaryText()

private void onMessageReceived(RemoteMessage remoteMessage){
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 , intent,
                PendingIntent.FLAG_ONE_SHOT);

        String channelId = "one-channel";
        String channelName = "My Channel One1";
        String channelDescription = "My Channel One Description";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
            notificationChannel.setDescription("channel description");
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.GREEN);
            notificationChannel.enableVibration(true);
            notificationChannel.setVibrationPattern(new long[]{100, 200, 100, 200});
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            notificationManager.createNotificationChannel(notificationChannel);
        }

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(remoteMessage.getData().get("title"))
                .setContentText(remoteMessage.getData().get("body"))
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

        try {
            URL url = new URL(remoteMessage.getData().get("imgUrl"));
            //이미지 처리 
            bigPicture = BitmapFactory.decodeStream(url.openConnection().getInputStream());
            notificationBuilder.setStyle(
                    new NotificationCompat.BigPictureStyle()
                            .bigPicture(bigPicture)
                            .setBigContentTitle(remoteMessage.getData().get("title"))
                            .setSummaryText(remoteMessage.getData().get("body")));
        } catch (IOException e) {
            e.printStackTrace();
        }

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(333 /* ID of notification */, notificationBuilder.build());
    }

결과



아이콘과 이미지를 같이 처리하면 아래와 같은 결과를 얻을 수 있다. 


마치며

지금까지 푸시 메시지 발신, 수신을 확인하기 위해서 Firebase 프로젝스 생성 및 설정, AndroidStudio로 앱 생성 및 설정 작업, 메시지 전송 작업을 하였다. 
푸시 메시지에 수신 처리은 그렇게 어렵지 않다. 
사용자에게 메시지를 어떻게 발송할지에 대한 니즈에 따라 메시지를 발송하는 담당하는 앱서버에서 작업이 달라진다. 
(대량 메시지 발송, 특정 앱의 키워드에 대한 알림 수신, 주문을 했을 때 주문 및 배송상태에 대한 수신, 게임 설치 중이나 설치 완료에 대한 수신 등등)



Posted by 사용자 피랑이

댓글을 달아 주세요


스카우터에서는 성능 모니터링 중 생긴 특정 상황에 대해 얼럿 기능이 들어가 있다.
스카우터 클라이언트의 얼럿항목에서 확인 및 조회가 가능하다.
하지만 항상 보고 있을수는 없는 상황에서, 얼럿을 스카우터 외부에서 받기를 원한다면 어떻게 해야 할까.

스카우터의 커스텀 플러그인으로 해결이 가능하다.
이런 상황에 맞추어 사용자가 직접 플러그인을 개발해서 넣을수 있도록 내부 API를 가지고 있고, 
이를 활용할 수 있는 커스텀 플러그인 구조를 가지고 있다.

우선 스카우터의 플러그인에 대해 알아보도록 하겠다.



스카우터의 플러그인

스카우터의 서버 플러그인은 크게 2가지 종류이다.
스크립트 형식으로 된 스크립팅 플러그인과 java의 jar 파일 형식으로 된 빌트인 플러그인이다.

스크립팅 플러그인은 파일에 java 문법으로 이루어진 스크립트를 넣어서 적용할 수 있다.
빌트인 플러그인의 경우 jar 파일로 이루어진 자바 프로젝트이다.
정해진 형식에 맞게 프로젝트를 만들고 내부 로직은 자유롭게 개발해서 사용할 수 있다.

이번 글에서는 2가지 방식 중 빌트인 플러그인 방식에 대해서 알아보려고 한다.
2가지 방식의 스카우터 플러그인 외에 에이전트에 적용하는 플러그인도 있다. 더 자세한 정보는 아래 링크에서 확인 가능하다.

처음 접하는 입장에서 플러그인을 처음부터 만드는것은 쉽지 않다.
그래서 스카우터를 사용하는 능력자들께서 손수 만드신 플러그인들을 공유하고 있다.
https://github.com/scouter-project 이곳에서 scouter-plugin 으로 시작하는 프로젝트 들이다.

확인해보고 필요한 것이 있다면 학습해보는 것도 좋을것이다.



빌트인 플러그인 적용방법

  1. 플러그인 프로젝트를 jar 파일로 만든다(빌드 방법은 아래에서 다룬다.)
  2. ./server/lib 폴더 아래에 jar 파일을 넣어준다.
  3. 스카우터 수집기 서버를 재기동 한다. (./server.startup.sh)



얼럿 플러그인

얼럿을 외부로 보내기 위한 플러그인은 매체에 따라 구분된다. email, slack, line, telegram, teamup(?) 등이 있다.
매체가 다른 플러그인이지만, 대부분 로직은 비슷하고 발송 단계의 로직만 다르기 때문에 원하는 발송 매체가 있다면 커스터마이징도 가능하다.

이번 글에서는 다양한 발송 매체 중에 슬랙을 이용하는 플러그인을 가지고 설명하려고 한다.
해당 플러그인의 깃헙 주소는 아래와 같다.

이 얼럿 플러그인으로 받을수 있는 알림은 아래와 같다.
  • CPU of Agent (warning / fatal)
  • Memory of Agent (warning / fatal)
  • Disk of Agent (warning / fatal)
  • connected new Agent
  • disconnected Agent
  • reconnect Agent



얼럿 플러그인 적용하기


1.  플러그인 빌드

적용하기 위해서는 얼럿 플러그인 프로젝트의 jar 파일이 필요하다.
클론을 받고, jar 파일로 빌드하자(macos 기준..)
git clone https://github.com/scouter-project/scouter-plugin-server-alert-slack.git
cd scouter-plugin-server-alert-slack/
mvn package

문제없이 빌드가 완료되었다면,
target 디렉토리에 scouter-plugin-server-alert-slack-1.0.1-SNAPSHOT.jar 라는 파일이 생성되어 있을 것이다.
이 파일을 스카우터 수집기 서버 디렉토리 아래 lib 폴더에 복사한다. (스카우터를 설치한 디렉토리에서 ./server/lib 이다)


2. 플러그인 설정

스카우터 수집기 서버의 설정 파일을 수정해야 한다.
별도의 변경이 없다면 경로는 아래와 같다.
./server/conf/scouter.conf
얼럿 플러그인을 사용하기 위해 필요한 설정값은 아래와 같다.
내용을 참고해서 환경에 맞게 내용을 추가해 준다.

ext_plugin_slack_send_alert : 발송기능을 사용할지 여부. true/false
ext_plugin_slack_debug : 메시지를 로깅 할지 여부. true/false
ext_plugin_slack_level : 로깅 레벨. (0=info, 1=warn, 2=error, 3=fatal)
ext_plugin_slack_webhook_url : 슬랙 웹훅 url
ext_plugin_slack_channel : 채널명 (ex. #test1) 혹은 사용자명(ex. @user_id)
ext_plugin_slack_botName : 알림을 보낼 봇 이름
ext_plugin_slack_icon_emoji : 봇 아이콘
ext_plugin_slack_xlog_enabled : xlog 얼럿 활성화 여부 true/false
ext_plugin_elapsed_time_threshold : 응답시간의 임계치. 이 값 보다 큰 응답시간에 반응한다
ext_plugin_gc_time_threshold : gc 시간 임계치. 이 값보다 큰 gc 시간이 걸리면 반응한다
ext_plugin_thread_count_threshold : 쓰레드 갯수 임계치. 쓰레드 갯수가 임계치 보다 커지면 반응한다.

적용해 사용하고 있는 설정은 아래와 같다.
# External Interface (Slack)
ext_plugin_slack_send_alert=true
ext_plugin_slack_debug=true
ext_plugin_slack_level=0
ext_plugin_slack_webhook_url=http://mydonain.com/hooks/1234CCSReKgN23wiw/dtCGozaNPAA41234yiCnhnpcNQCA7BSrkjqmFmsy3nMuKk95
ext_plugin_slack_channel=#alert_channel
ext_plugin_slack_botName=scouter
ext_plugin_slack_icon_emoji=:computer:
ext_plugin_slack_icon_url=https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQnQFLCYQ-rg_iJFXBzazZjUqMXTHPmTQ-AVU_JymsxleUHI1Oe
ext_plugin_slack_xlog_enabled=true
ext_plugin_elapsed_time_threshold=3000
ext_plugin_gc_time_threshold=5000
ext_plugin_thread_count_threshold=300

3. 적용하기

서버를 재기동 하면 적용된다.
./server/startup.sh



플러그인 커스터마이징

얼럿 플러그인을 사용하면서, 기능상 변경이 필요한 상황이 종종 찾아왔다.

예를들어,
스카우터에서 모니터링 하고 있는 서비스 중, 일부 서비스의 요청은 기본 응답시간이 1분이 넘어갈 정도로 오래 걸리게 만들어져 있었다.
응답시간 임계치 보다 크다보니 이 요청이 호출 될 때마다 응답시간 초과로 얼럿이 오게 되었다.
스카우터 전체 응답시간 임계치를 늘리면 기존에 10ms 이내로 처리되는 요청들의 응답이 느려질 때 얼럿을 받지 못하게 되어서 문제가 되었다.

이런식으로 얼럿 조건이 특수한 상황에 맞게 설정 되어야 할 때, 소스를 직접 수정하여 해결 할 수 있다.

대부분의 로직은 SlackPlugin.java 에 있다.

이 클래스의 메소드는 아래와 같은 용도를 가진다.

  • alert : 얼럿 발송
  • object : 에이전트 연결/연결끊김/재연결 얼럿 처리
  • xlog : xlog 에 에러로 표시된 건에 대한 얼럿 처리
  • counter : gc 타임 임계치 값에 대한 얼럿 처리
나의 경우 xlog 에서 응답시간 임계 초과 때 오는 얼럿을 서비스 마다 따로 설정하고 싶었다.
xlog 메소드 쪽에  분기를 추가해서 처리했다. 로직을 만들 때 사용한 내용은 아래와 같다.

  • elapsedThreshold 변수에는 아까 설정한 ext_plugin_elapsed_time_threshold 값이 들어있다. 기본 3초이다.
  • XlogPack 객체로 이루어진 pack 변수에는 xlog의 각 요청을 정보가 들어있다.
  • pack 변수에는 http 요청에 대한 엔드포인트 URL 정보와  지연된 응답시간 정보가 있다.

기존 코드의 264라인 근처에 수정을 하였다.
아래는 특정 엔드포인트에 대해서만 지연 응답시간 기준을 60초로 설정하는  분기를 추가한 코드이다.
try {
      int elapsedThreshold = conf.getInt("ext_plugin_elapsed_time_threshold", 0);
      if (elapsedThreshold != 0 && pack.elapsed > elapsedThreshold) {
         String serviceName = TextRD.getString(DateUtil.yyyymmdd(pack.endTime), TextTypes.SERVICE, pack.service);
         if (
                 AgentManager.getAgentName(pack.objHash).equals("/192.168.123.111/endpoint1")
                 || AgentManager.getAgentName(pack.objHash).equals("/192.168.123.111/endpoint2")
               && pack.elapsed < 60000) {
           //얼럿을 보내지 않음.
         } else {
            AlertPack ap = new AlertPack();
            ap.level = AlertLevel.WARN;
            ap.objHash = pack.objHash;
            ap.title = "Elapsed time exceed a threshold.";
            ap.message = "[" + AgentManager.getAgentName(pack.objHash) + "] "
                  + pack.service + "(" + serviceName + ") "
                  + "elapsed time(" + pack.elapsed + " ms) exceed a threshold.";
            ap.time = System.currentTimeMillis();
            ap.objType = AgentManager.getAgent(pack.objHash).objType;
            alert(ap);
         }
      }

수정을 마치게 되면 위에 설명한 방식으로 빌드를 다시 하고,
스카우터 수집기 서버 lib 폴더에 jar 파일을 덮어쓴 뒤 재기동 하면 바로 적용이 된다.

스카우터에서는 플러그인 개발을 위해 내부 API를 잘 정리하여 공유하고 있다.

자신만의 로직이 필요한 상황이라면 내부 API 문서를 참고하여 기존의 플러그인에 수정을 해서 필요에 맞게 사용할 수 있다.

혹은 새로운 플러그인을 새로 만들 수 있을것이다. 결과가 좋다면 커뮤니티에 공유해서 좋은 피드백도 받을 수도 있지 않을까 한다. 


Posted by panpid

댓글을 달아 주세요