시작하기전에

Slack은 기업의 팀 구성원들이 내부 커뮤니케이션하는데 최적화된 협업용 메신저입니다.
전체 채팅방, 프로젝트별 멤버 대화방, 1:1대화방 등 상황에 맞는 구성원들과 편리하고 빠르게 소통할 수 있으며, 이외에도
Slack App 다양한 어플리케이션을 연동하여 필요한 요소를 맞춤구성 할 수 있어 매우 유용합니다.

Slack의 App예를 들면 App중 개발자들이 상당 수 사용하는 Github를 사용하게 되면, 새로운 코드가 저장소에 Push될떄마다 개발팀 채널에
있는 모든 팀원들이 코드 업데이트 알림을 받을 수 있습니다. 이렇듯 커뮤니케이션 외에도 서드파티 앱을 통해 전반적인 프로젝트의 상황 인식이
향상되는 효과를 기대할 수 있습니다.

이번 기고에서는 Ncloud의 Open API 기반하여 VM생성시 이에 대한 알림을 받아보는 NodeJS기반 Slack Bot을 만드는 과정을 기술해 보겠습니다.


Ncloud APIs

Ncloud에 대한 OpenAPI 호출 방법은 Ncloud APIs의 공통가이드에서 확인할 수 있습니다.
호출하기절에서 API 사용을 위한 값들을 확인 할 수 있습니다.

API 호출을 위해서는 다음의 요소가 필요합니다.

  • API URL
  • 액션
  • 액션파라미터
  • 응답 포맷
  • Auth parameter
    • x-ncp-apigw-api-key
    • x-ncp-apigw-timestamp
    • x-ncp-iam-access-key
    • x-ncp-apigw-signature-v1

Auth parameter값 중 x-ncp-apigw-timestamp는 Ncloud API Gateway 서버와 시간차를 확인해 보기위한 변수로, 호출하고자하는 서버 혹은 클라이언트에서
서버의 현재 timestamp값을 사용하면 됩니다.
x-ncp-apigw-signature-v1 값은 HmacSHA256 알고리즘을 이용해 만들어지는 값입니다. 위 auth parameter에 기술된 값외에도
비밀키(secretKey)가 필요합니다. 정리하자면 x-ncp-iam-access-key, secretKey, x-ncp-apigw-api-key가 필요하며 이 값은 각각 다음의 과정에서 획득할 수 있습니다.

x-ncp-iam-access-key, secretKey

x-ncp-iam-access-keysecretKey는 ncloud Portal에서 확인할 수 있습니다.
마이페이지 > 계정관리 > 인증키 관리로 이동합니다.

현재 사용 중 상태에 있는 Access Key ID와 Secret Key를 확인합니다.
Access Key ID가 x-ncp-iam-access-key이며 Secret Key가 secretKey입니다.

x-ncp-apigw-api-key

x-ncp-apigw-api-key는 Ncloud API Gateway 상품에서 생성 할 수 있습니다.

API Gateway > API Keys 메뉴에서 API Key 생성 버튼을 클릭합니다.

생성된 API중에서 Primary Key가 secretKey가 됩니다.

API 호출 함수 만들기

helpers/ncloudApi/fetchClient.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const CryptoJS = require("crypto-js");
const Base64 = require('crypto-js/enc-base64');
const axios = require('axios');

const {
accessKey,
secretKey,
apiKey,
} = process.env;

const baseURL = 'https://ncloud.apigw.ntruss.com/';
const space = " ";
const newLine = "\n";

// const basePath = '/server/v1/';
// const action = 'getZoneList';

module.exports = ({ method, basePath, action, actionParams={} }) => {
const timestamp = Date.now();
const message = [];
let params = [];

actionParams = {...actionParams, responseFormatType: 'json'};

for( let key in actionParams ) {
params.push(`${key}=${actionParams[key]}`);
} // end for loop

const paramsString = params.join('&');
const url = basePath + action + '?' + paramsString;

message.push(method);
message.push(space);
message.push(url);
message.push(newLine);
message.push(timestamp);
message.push(newLine);
message.push(apiKey);
message.push(newLine);
message.push(accessKey);

const authSignature = Base64.stringify(CryptoJS.HmacSHA256(message.join(''), secretKey));

return axios.request({
url,
baseURL,
method,
headers: {
"x-ncp-apigw-timestamp" : timestamp,
"x-ncp-apigw-api-key" : apiKey,
"x-ncp-iam-access-key" : accessKey,
"x-ncp-apigw-signature-v1" : authSignature,
}
});
};

API 요청시 메시지 구성은 Ncloud APIs 공통가이드의 호출하기 부분에서 확인 할 수 있습니다.
아래와 같이 구성되도록 합니다.

1
{API URL}/{액션} + 액션 파라미터 + 응답 포맷 + AUTHPARMS(header)

AUTHPARMS 또한 일련의 순서와 규칙을 가집니다. AUTHPARAMS 생성하기 절의 샘플 코드를 참조하여 해당 방식으로 생성해줍니다.
이를 위해서 message array에 해당 값들을 push후 이후 join 메서드를 통해 하나의 string으로 합치는 방식을 택하였습니다.

x-ncp-apigw-signature-v1 생성하기 위해서는 HmacSHA256알고리즘을 이용해야합니다. 이를 위해서 crypto-js모듈을 이용하였습니다.

이렇게 생성한 값을 가지고 axio 모듈을 이용하여 Ncloud Open API를 호출할 수 있는 RESTful Message를 생성합니다.

작성한 fetchClient를 재사용하여 여러 action들을 호출할 수 있는 함수를 작성할 수 있습니다.

helpers/ncloudApi/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const fetchClient = require('./fetchClient');

module.exports = {
getZoneList: () => {
return fetchClient({
method: 'GET',
action: 'getZoneList',
basePath: '/server/v1/',
}).then(response => response.data)
.catch(error => error.response.data);
},
getServerInstanceList: () => {
return fetchClient({
method: 'GET',
action: 'getServerInstanceList',
basePath: '/server/v1/',
}).then(response => response.data)
.catch(error => error.response.data);
}
};


Slack Bot 만들기

Slack Bot 생성 & 토큰 받기

봇 사용자가 없는 경우 아래의 링크로 이동하여 봇 사용자를 새로 생성합니다.

https://my.slack.com/services/new/bot

bot의 이름을 입력후 Add bot Integration을 입력합니다.

봇이 생성되면 화면과 같은 API Token을 확인할 수 있습니다. 해당키를 이용하여 채널이나 사용자에게 메시지를 보낼 수 있습니다.
해당키가 노출되지 않도록 주의합니다.

Bot 코드 작성

특정 유저에게 앞서 생성한 ncloud open API 클라이언트를 이용하여 서버의 상태가 변경되었을 시 알람을 전달하는 코드를 작성합니다.

helpers/slackBot/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const SlackBot = require('slackbots');
const { token, botname: name } = process.env;

// create a bot
let bot = null;

module.exports = {
connect: () => {
return Promise.race([
new Promise((resolve, reject) => {
bot = new SlackBot({ token, name });
bot.on('start', () => resolve());
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('TIMEOUT')), 5000);
})
])
},
postMessageToUser: ({ username, message }) => {
return new Promise((resolve, reject) => {
bot.postMessageToUser(username, message, { icon_emoji: ':cat:' }, ( data ) => {
if ( ! data.ok ) {
return reject(data);
}

resolve( data );
});

}) // end new Promise
}
};

nametoken은 앞서 slack bot생성시 입력하고, 획득한 값을 사용해야합니다.

Slackbot 작성을 위해서 npm slackbot 패키지를 이용하였습니다.

SlackBot 사용을 위해서 new SlackBot()를 통해 객체를 생성해줍니다. Bot사용이 가능하면 start이벤트가 발생합니다.
이후에 postMessageToUser메서드를 통해 특정 사용자에게 메시지를 전송할 수 있습니다.

이 프로젝트에서는 VM의 상태 변화를 감지하고 변화가 있을 시 이를 특정 사용자에게 알림을 주는것이 목적입니다.
따라서 Ncloud Open API를 통해 획득한 서버리스트와 과거의 서버리스트의 차이를 확인해야합니다. 이를 위한 간단한 store를 작성하면 다음과 같습니다.

helpers/stateStore/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const { findIndex } = require('lodash');
let store = [];

module.exports = {
set: async ( promiseFn ) => {
const responseServerList = await promiseFn;
let diffList = [];

responseServerList.forEach( serverInfo => {
const idx = findIndex(store, serverInfo);

if ( idx === -1 ) {
diffList.push( serverInfo );
} // end if
});

store = responseServerList;

return diffList;
},
get: () => {
return store;
},
clear: () => {
store = [];
}
};

Store의 입력값으로는 Promise를 입력받는 것을 가정하였습니다. set메서드에서는 Promise 수행에서 반환받은 값을 이전에 저장되어 있는
store 배열의 값과 비교하고 차이가 있는 값을 반환합니다. 이 차이를 slack사용자에게 알려주도록 합니다.

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require('dotenv').config();
const slackBot = require('./helpers/slackBot');
const stateStore = require('./helpers/stateStore');
const ncloudApi = require('./helpers/ncloudApi');

(async () => {
try {
await slackBot.connect();

setInterval(async () => {
const changedServerList = await stateStore.set(
ncloudApi.getServerInstanceList()
.then(response => response.getServerInstanceListResponse.serverInstanceList)
);

changedServerList.forEach(async (serverInfo) => {
const message = '[서버 상태가 변경되었습니다]\n' +
`서버이름: ${ serverInfo.serverName }, 서버상태: ${ serverInfo.serverInstanceStatusName } \n` +
`서버이미지: ${ serverInfo.serverImageName }`;

await slackBot.postMessageToUser({ username: 'mosfet1kg', message});
})

}, 5000);

} catch(e) {
console.log( e );
} // end try ~ catch

})();

앞서 기술한 open API호출 함수와 store를 이용하여 서버 변화를 확인합니다. 서버리스트에 변화가 있을 시 slackbot의 postMessageToUser 메서드를
통해 특정 사용자에게 이를 알려줍니다.

위와 같이 SlackBot을 통해 신규 서버를 생성했을 떄 서버의 상태에 변화가 있을때마다 이에 대한 알람을 통보 받을 수 있습니다.

Slack을 통해 팀단위로 협업할때 이러한 서드파티 어플리케이션을 작성하게 되면 공통의 서버자원의 상태를 편리하게 공유할 수 있게 되어, 프로젝트의 상황 인식에 큰 도움을 가져올 수 있습니다.

작성된 코드는 아래의 Github 저장소에서 확인할 수 있습니다.

https://github.com/mosfet1kg/ncloud-bot

본 기고에 도움을 주신 임대진, 김진용님께 감사의 말씀을 전합니다.