[DevOps] Hubot 적용기
안녕하세요? 정리하는 개발자 워니즈입니다. 이번시간에는 hubot 적용기에 대해서 정리를 해보도록 하겠습니다. 필자는 DevOps업무를 하다보니, 주로 여러개의 개발팀으로부터의 요청들 (CI/CD설정, 네트워크, 인프라, 모니터링 등)에 대해서 처리를 해주는 업무를 합니다.
요청양도 최근들어 꽤나 늘어났고, 요청의 범주도 굉장히 다양하기 때문에 Slack W/F를 통해서 모든것으르 제어하기는 어려운 시점이 왔습니다.
JIRA의 티켓을 수동으로 등록하여 관리를 하고있었는데, 이부분을 자동화 시키고 슬랙 Thread상에서 일감을 처리하면 자동 종료까지 되는 부분으로 업무의 프로세스를 개선하고 싶었습니다.
1. hubot 소개
- Hubot 은 깃헙의 사내용으로 제작된 챗봇이지만, 많은 발전을 거듭하여 현재 오픈소스로 공개되어있습니다. Node 기반이며, Slack과 친화적입니다.
- 휴봇의 가장 큰 장점은 간단한 스크립트(CoffeeScript, JavaScript) 작성을 통해 강력한 기능을 추가할 수 있다는 점입니다.
- 특정 단어 혹은 문장에 따라 프로세스를 정의할 수 있습니다. 이미 구축된 스크립트들도 많이 공개 되어있어 손쉽게 스크립트를 추가하여 구현할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 |
enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you'] leaveReplies = ['Are you still there?', 'Target lost', 'Searching'] module.exports = (robot) -> robot.enter (res) -> res.send res.random enterReplies robot.leave (res) -> res.send res.random leaveReplies [출처] https://blog.hax0r.info/2017-05-14/slack-developer-kit-for-hubot/ [Hax0r blog] |
스크립트를 통해 Local 혹은 Heroku를 통해 배포하여 슬랙과 연동할 수 있습니다.
- hubot-diagnostics: 간단한 기본기능들이 들어있다. 위에서 사용했던
ping
을 이 모듈이 응답한 것이다. 그 외time
과echo
도 있다. - hubot-help: 현재 hubot의 명령어들을 표시해준다. script들의 # Commands 들을 가져와서 뿌려주는 역할을 한다.
- hubot-pugme: 설명을 보면은 가장 중요한 휴봇 스크립트라고 적혀있다. 기능은 퍼그 이미지 url을 랜덤으로 가져오는 것이다. 하지만 2년이 지나서 그런지 url이 유효하지 않다.
- hubot-rules: hubot의 룰을 설명한다.
> hubot rules
으로 볼 수 있다. - hubot-shipit: 가지고 있는 이미지URL중 랜덤으로 하나를 보내준다.
hubot-pugme
와 마찬가지로 유효한 URL이 별로 없다. - hubot-heroku-keepalive: 무료 heroku를 사용할 경우 하루 사용시간 제한이 있기 때문에 필요한 것 같다.
- hubot-redis-brain: hubot의 brain기능을 redis로 이용하는 것이다.
hubot-google-images와 hubot-google-translate는 이름에서도 알 수 있듯이 구글의 API키를 받아서 구글 서비스를 사용할 때 필요하다.
hubot-maps도 구글의 맵서비스를 이용하는 것이다.
2. hubot 설치 및 설정
hubot은 기본적으로 node기반으로 수행되는 어플리케이션입니다. 따라서 npm과 node가 설치되어있어야 설치가 가능합니다.
- node version : v16.17.0
- npm version : 8.15.0
2-1. hubot 설치
1 2 |
$ npm install -g yo generator-hubot |
여기서 yoman 이라는것을 같이 설치하게 되는데, 간단하게 말하면 구조를 어플리케이션의 구조를 잡아주는 도구라고 보시면 됩니다.
1 2 3 4 |
$ mkdir -p ~/apps/devops $ cd ~/apps/devops $ yo hubot --adapter=slack |
여기서 몇가지 Interactive Question을 받게 되는데, 간단하게 입력을 하면됩니다.
2-2. hubot 설정
이제 간단하게 hubot 설치는 마쳤습니다. Hubot 기능중에 데이터 유지를 위해서 Redis module이 자동적으로 들어가있는데 이부분을 제거해야 합니다.
1 2 3 |
# external-script.json [] |
external-script.json에는 hubot에 필요한 모듈들을 탑재할 수 있는데, 별도의 서버에서 구성을 진행하기 때문에 모든 내용들을 삭제해줘도 무방합니다. 밑의 내용은 필수적으로 삭제를 진행합니다.
- hubot-heroku-keepalive
- hubot-redis-brain
1 2 3 4 5 |
# ~/apps/devops/node_modules/hubot/src/hubot.js ... const port = process.env.EXPRESS_PORT || process.env.PORT || 8083 ... |
port도 겹치지 않게 custom port로 변경을 해줍니다.
2-3. Slack 설정
Hubot 사용의 가장 큰 목적은 Slack을 통해서 ChatOps를 구현하기 위함입니다. 따라서 Slack에 앱을 추가하고 연동을 하는 작업이 필요합니다.
Slack 앱을 추가하면 Hubot 설정 페이지가 나오게 되고, 그곳에서 Hubot의 Token값을 얻을 수 있습니다.
2-4. Hubot 실행
1 2 |
$ HUBOT_SLACK_TOKEN=xoxb-... ./bin/hubot --adapter slack |
hubot을 실행하게 되면, Slack상에 Hubot이 연결이 되면서 연결중으로 접속이 표시가 됩니다. 이렇게 되면 설치 & 설정이 마무리 된것이고, Hubot에 대한 Script를 작성해서 기능을 추가하면 됩니다.
3. hubot script 가이드
hubot은 script를 통해서 기능 확장이 가능하고, 현재 open source로 나와있는 여러가지 Script들을 참고 할 수도 있습니다.
- Hubot Script Guide
- .coffee 혹은 .js 파일로 작성이 가능합니다.
3-1. send/ reply / emote
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 경로에 script 작성 /apps/devops/scripts # send : room으로 왔으면 room으로 전달, DM으로 오면 DM으로 전달 # reply : thread에 댓글 형식으로 메시지 전달 # emote : room으로 메시지 전달 module.exports = (robot) -> robot.hear /badger/i, (res) -> res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS" robot.respond /open the pod bay doors/i, (res) -> res.reply "I'm afraid I can't let you do that." robot.hear /I like pie/i, (res) -> res.emote "makes a freshly baked pie" |
3-2. messageRoom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# messageRoom 기능을 이용하여 지정된 방이나 사용자에게 메시지를 보낼 수 있습니다. module.exports = (robot) -> robot.hear /green eggs/i, (res) -> room = "mytestroom" robot.messageRoom room, "I do not like green eggs and ham. I do not like them sam-I-am." robot.respond /I don't like Sam-I-am/i, (res) -> room = 'joemanager' robot.messageRoom room, "Someone does not like Dr. Seus" res.reply "That Sam-I-am\nThat Sam-I-am\nI do not like\nthat Sam-I-am" robot.hear /Sam-I-am/i, (res) -> room = res.envelope.user.name robot.messageRoom room, "That Sam-I-am\nThat Sam-I-am\nI do not like\nthat Sam-I-am" |
3-3. Capturing
1 2 3 4 5 6 7 8 |
# 정규식에 대해 들어오는 메시지를 처리할 수 있습니다. robot.respond /open the (.*) doors/i, (res) -> doorType = res.match[1] if doorType is "pod bay" res.reply "I'm afraid I can't let you do that." else res.reply "Opening #{doorType} doors" |
3-4. HTTP 호출하기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# Hubot은 3rd API들과 연계하기 위해서 HTTP 호출을 할 수 있습니다. data = JSON.stringify({ foo: 'bar' }) robot.http("https://midnight-train") .header('Content-Type', 'application/json') .post(data) (err, res, body) -> # your code here if err res.send "Encountered an error :( #{err}" return # your code here, knowing it was successful if res.statusCode isnt 200 res.send "Request didn't come back HTTP 200 :(" return # RateLimit을 이용하여 호출량을 조절 rateLimitRemaining = parseInt res.getHeader('X-RateLimit-Limit') if res.getHeader('X-RateLimit-Limit') if rateLimitRemaining and rateLimitRemaining < 1 res.send "Rate Limit hit, stop believing for awhile" |
3-5. HTTP 수신기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Hubot은 HTTP 요청을 처리하기 위한 익스프레스 웹 프레임워크에 대한 지원을 포함합니다. # 해당 포트로 정적파일 제공도 가능합니다. module.exports = (robot) -> # the expected value of :room is going to vary by adapter, it might be a numeric id, name, token, or some other value robot.router.post '/hubot/chatsecrets/:room', (req, res) -> room = req.params.room data = if req.body.payload? then JSON.parse req.body.payload else req.body secret = data.secret robot.messageRoom room, "I have a secret: #{secret}" res.send 'OK' # Curl을 이용하여 테스트 // raw json, must specify Content-Type: application/json curl -X POST -H "Content-Type: application/json" -d '{"secret":"C-TECH Astronomy"}' http://127.0.0.1:8080/hubot/chatsecrets/general // defaults Content-Type: application/x-www-form-urlencoded, must st payload=... curl -d 'payload=%7B%22secret%22%3A%22C-TECH+Astronomy%22%7D' http://127.0.0.1:8080/hubot/chatsecrets/general |
3-6. 기타 기능
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 |
# Randomize lulz = ['lol', 'rofl', 'lmao'] res.send res.random lulz # Detect Enter & Exit enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you'] leaveReplies = ['Are you still there?', 'Target lost', 'Searching'] module.exports = (robot) -> robot.enter (res) -> res.send res.random enterReplies robot.leave (res) -> res.send res.random leaveReplies # Brain robot.respond /have a soda/i, (res) -> # Get number of sodas had (coerced to a number). sodasHad = robot.brain.get('totalSodas') * 1 or 0 if sodasHad > 4 res.reply "I'm too fizzy.." else res.reply 'Sure!' robot.brain.set 'totalSodas', sodasHad+1 robot.respond /sleep it off/i, (res) -> robot.brain.set 'totalSodas', 0 msg.reply 'zzzzz' |
4. hubot 기능 구현
필자가 최초 생각했던 것처럼, Slack W/F와 연계하여 각 개발팀에서 DevOps를 통한 문의 및 요청들이 접수되면, 해당 내용을 기반으로 Hubot이 JIRA에 Ticket을 생성하고 해당 Ticket을 링크로 응답 합니다.
또한, 작업이 모두 완료된 이후로는 emoji를 설정하였을때, 작업을 종료하도록 구성하고자 합니다.
4-1. JIRA 자동 등록
JIRA에 자동 등록을 하기 위해서는 JIRA의 API를 활용해야 합니다.
간단하게 JSON 구조를 만들어서 http request를 한 뒤, 결과를 parsing하여 massage로 다시 return해주는 구조입니다. 그렇게 되면, Slack의 Thread 상에서 티켓의 링크를 확인할 수 있습니다.
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 |
module.exports = (robot) -> robot.respond /create TICKET(.*)/i, (msg) -> threadId = getThreadId(msg); title = '' for line in msg.message.text.split(/\r?\n/) console.log (line) if line.indexOf("*Summary :*") != -1 title = line.replace /\*Summary \:\*/, "" if title == '' msg.send 'Faild to get the subject' return json = fields: project: key: "LNDO" summary: title, description: msg.message.text, issuetype: name: "_Task" labels: ["help_devops_thread"] json = JSON.stringify(json) create_query = btsBaseUrl + "/rest/api/2/issue" auth = btoa("#{user}:#{password}") msg.http(create_query) .headers(Authorization: "Basic #{auth}", 'Content-Type': 'application/json') .post(json) (err, res, body) -> issueName = undefined if body returnJson = JSON.parse(body) if returnJson.hasOwnProperty('key') issueName = returnJson.key if err console.log 'Error!' console.log err if issueName == undefined console.log res msg.send 'Error on creation issue on BTS' else link = '<https://jira.test.com/browse/' + issueName + '|' + issueName + '>' msg.send 'Create TICKET at ' + link threadCache[threadId] = {'lastUpdate': Date.now(), 'fsUpdate': Date.now(), 'BTS': issueName} fs.writeFileSync(threadDir + threadId, JSON.stringify(threadCache[threadId])) |
4-2. JIRA 상태 업데이트
Slack을 통해서 일감 처리가 완료되면, 이모지(DONE)를 통해서 해당 요청이 종료되었다는 것을 표기하였습니다. 그러다보니 명확하게 티켓과 동기화가 되지 않았었습니다. 이러한 부분을 이모지를 인식해서 티켓의 상태를 업데이트(Resolve)처리를 하도록 구성했습니다.
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 |
robot.respond /(.*)resolve TICKET(.*)/i, (msg) -> threadId = getThreadId(msg); getThreadCache(threadId); json = transition: id: "21" fields: resolution: name: "Done" json = JSON.stringify(json) BTS = threadCache[threadId]['BTS'] resolve_query = btsBaseUrl + '/rest/api/2/issue/' + BTS + '/transitions?expand=transitions.fields&transitionId=21' auth = btoa("#{user}:#{password}") msg.http(resolve_query) .headers(Authorization: "Basic #{auth}", 'Content-Type': 'application/json') .post(json) (err, res, body) -> if body returnJson = JSON.parse(body) console.log(returnJson) if err console.log err msg.send 'There is an error reolsve ticket!' else link = '<https://jira.test.com/browse/' + BTS + '|' + BTS + '>' msg.send 'Cloase BTS at ' + link threadCache[threadId] = {'lastUpdate': Date.now(), 'fsUpdate': Date.now(), 'BTS': BTS} fs.writeFileSync(threadDir + threadId, JSON.stringify(threadCache[threadId])) |
5. hubot 구성도
전체적인 구성도는 위와 같습니다.
- Request Layer : Slack W/F를 통해서 요청하는 영역
- Slack – Hubot Layer : Slack의 W/F를 분석하여 Hubot 스크립트를 수행하는 영역
- InfraStructure Layer : Hubot과 연계되는 Tools가 위치하는 영역
이번에 정리된 기준으로는 Jira의 Ticket을 생성하고 종료하는 내용이였습니다. 추후에는 기능을 좀더 강화 할 예정입니다.
6. 마치며..
이번시간에는 Slack W/F와 Hubot을 연계하여 업무를 자동화했던 내용을 정리해보았습니다. DevOps업무를 하면서 자동화 처리를 통하여 좀더 효율적으로 일하는 문화를 배울 수 있었고, 특히나 업무 자체를 코드화 시키고, 프로세스화 시키니까 처리하기가 좀더 수월했던 것 같습니다.
다음시간에는 Hubot의 좀더 강화된 기능을 사용하는 내용으로 찾아뵙겠습니다.
좋은 내용 감사합니다.
그런데 code 에서 ‘->’ 가 모두 escape 처리되어
->
로 출력되고있는거 같습니다.앗, 초안을 먼저 다른프로그램으로 작성하고 복사/붙여넣기 하면서 검토를 못했네요!! 감사합니다 건님!