처음에는 아주 단순한 이유로 출발했다.
AWS EC2 Instance 는 유동 아이피 이기 때문에, 서버가 꺼졌다가 켜지면 IP가 변한다.
그래서 서버의 IP를 확인하고자, 서버 목록을 slack 에서 출력하기로 했다.
중요 정보가 많아서 다 블라인드 처리;;
해당 기능을 가볍게(?) 개발하기 시작한다.
기존에 만들어둔 slackBot 에다가 !서버
를 추가하고, 해당 할 일을 작성하기 시작했다.
- 먼저 AWS CLI 를 설치 하고, Config 설정한 후 ec2 describe-instances 가지고 Query를 만든다.
aws ec2 describe-instances --query "Reservations[].Instances[].[Tags[?Key=='Name'] | [0].Value, PublicIpAddress, InstanceId, State.Name]
Example Result
[
[
"InstanceName",
"54.xxx.xxx.xxx",
"i-1234567890",
"running"
],
[
...
반복
...
]
]
해당 내용을 가지고, SlackBot을 구현. Botkit을 가지고 구현했는데, nodejs 를 사용하다보니, nodejs 안에서 다른 process를 실행하는 것을 찾다가 child_process 를 발견하고, 아래 코드로 진행했다.
var Botkit = require('botkit');
var controller = Botkit.slackbot();
var bot = controller.spawn({ token: "TOKENVALUE" })
const { exec } = require('child_process');
....
....
....
controller.hears(["!서버"],["direct_message","direct_mention","mention","ambient"],function(bot,message) {
exec('aws ec2 describe-instances --query "Reservations[].Instances[].[Tags[?Key==\'Name\'] | [0].Value, PublicIpAddress, InstanceId, State.Name]"', (err, stdout, stderr) => {
if (err) {
return;
}
if (stderr) {
bot.reply(message, stderr);
return;
}
if (stdout) {
var json = JSON.parse(stdout);
// json parsing
// 추가 처리 진행
var reply_format = {
"text": txt,
"mrkdwn" : true
};
bot.reply(message, reply_format);
}
});
});
이렇게 !서버
는 구현을 완료했다.
여기서 더 나아가서 요구사항이 하나 더 생겼다. 특정 팀에서 GPU 서버를 사용하게 해달라고 하는데, 비싼 서버라서 on/off 하면서 사용하겠다는 내용이었다. aws console login을 하게 해서 사용 방법을 알려주면 그만인 것인데, IAM 설정해야 하고, 권한 확인하고, 그리고 일단 이것저것 설명해야 하는 것이 귀찮았다.
생각난김에 !서버
를 완성했으니 서버의 on/off
도 만들 수 있을 것 같아서 도전해보았다.
아니 이렇게 만들어서 사용 방법을 알려주는게 더 귀찮은 것 아닌가??
botkit에서 hears 를 이용하여 chatbot 만드는 기능으로도 기능을 만들 수 있을 것 같았는데, 뭔가 arguments 를 받는 내용이 아직인 것 같아서 빠르게 개발을 진행하려고 Slack Command 기능을 이용하기로 결정했다. slack command를 이용하면 arguments 를 받아서 쓸 수 있다.
/start-instance
와 /stop-instances
를 slack command에 등록하고,
그 해당 slack command 의 Request가 들어오면 실행되고 있는 API 서버
에서 받아서 Response를 날려주는 기능으로 개발했다.
해당 기능도 slackBot 개발한 것처럼 빠르게 만드려고 nodejs를 가지고 기본 http 기능을 이용해서 빠르게 만들었다.
const util = require('util');
const http = require('http');
var uri = require('url');
const { exec } = require('child_process');
const commandToken = { start : "TOKENVALUE" , stop : "TOKENVALUE" };
var Botkit = require('botkit');
var controller = Botkit.slackbot();
var bot = controller.spawn({
token: "TOKENVALUE"
})
http.createServer((request, response) => {
const { headers, method, url } = request;
request.params = params(request);
let body = [];
request.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
response.on('error', (err) => {
console.error(err);
});
response.statusCode = 200;
// Empty value pass
if (!Object.keys(request.params).length) {
response.write ("start!");
response.end();
return;
}
//console.log(request.params);
var req_command = request.params.command || '';
var req_text = request.params.text || '';
// Valid Token
var flag = isValidToken(request.params.token || '');
if (!flag) {
console.error("bad request");
response.write("bad request");
response.end();
}
if (req_command.length && req_text.length)
{
// Execute Command
var execCommand = util.format('aws ec2 %s --instance-id %s' , req_command.replace('%2F', ''), req_text);
console.log("execCommand ", execCommand);
exec(execCommand, (err, stdout, stderr) => {
if (err) {
console.error(err);
bot.say({text : err.toString(), channel: '@'+ request.params.user_id });
return;
}
if (stderr) {
console.error(stderr);
bot.say({text : stderr.toString(), channel: '@'+ request.params.user_id });
return;
}
if (stdout) {
var json = JSON.parse(stdout);
var firstKeyName = Object.keys(json)[0];
var explains = json[firstKeyName][0];
var msg = {
text : " ",
attachments: [
{
"color": firstKeyName.indexOf('Stop') > -1 ? "warning" : "good",
"title": firstKeyName,
"text": "InstanceId : "+ explains.InstanceId,
"fields": [
{
"title": "CurrentState",
"value": explains.CurrentState.Name,
"short": true
},
{
"title": "PreviousState",
"value": explains.PreviousState.Name,
"short": true
}
],
"footer": util.format('[%s] %s' , request.params.channel_name, request.params.user_name)
}
],
channel : '@'+ request.params.user_id
}
bot.say(msg);
console.log(stdout);
}
});
response.write ("Okay. wait a second..");
response.end();
}
});
}).listen(8080);
var isValidToken = function(token) {
if (!token) return false;
var isValid = false;
for( var key in commandToken)
{
isValid = token == commandToken[key];
if (isValid) break;
}
return isValid;
};
var params=function(req){
let q=req.url.split('?'),result={};
if(q.length>=2){
q[1].split('&').forEach((item)=>{
try {
result[item.split('=')[0]]=item.split('=')[1];
} catch (e) {
result[item.split('=')[0]]='';
}
})
}
return result;
}
역시 slack은 UI 만드는게 가장 힘들고 어려운 작업이다. -_-a
이렇게 만들고나니까, 누가
on/off를 하는지 모른다는 단점이 있길래, slack command log를 남기는 #channel 을 만들어서 명령어가 들어올 때마다 해당 #channel 로 포스팅 하도록 log append 해버렸다.
만들고 나니까 별건 아닌데, 서버에 대한 개념이 없는 사람이 편하게 사용할 수 있고, 요청한 분도 아주 쉽게 이용하고 계시니까 만족스러운 작업이었다.