5 minute read

result

회사에서 Slack Bot을 만들게 된 배경

최근에 회사 전체 Hadoop 클러스터를 AWS의 관리형 Hadoop 서비스인 AWS EMR로 이전하였습니다. 예전에는 클러스터를 24/7로 띄워놓고 있었는데 비해 AWS EMR을 도입하면서부터는 정기적인 배치 Job이 돌아갈 때, 그리고 개발용으로 필요할 때만 node를 올려서 쓸 수 있기 때문에 유지 비용이 줄어드는 효과가 있습니다. 또한 scale up/out이 아주 잘 되어있어서 수십 개의 task node를 늘리거나 줄이는 데 클릭 한 번 / 몇 분이면 되니, 아주 좋은 서비스라고 할 수 있겠습니다. (AWS EMR 홍보글은 아닙니다.)

그러나 24/7로 클러스터를 띄울 필요가 없어졌다는 의미는, 사용 중이지 않은 클러스터는 반드시 내려줘야 한다는 것을 의미합니다. 이를 위해서는 매번 AWS EMR console에 들어가서 클러스터 현황을 보고 내릴 것은 내리고 유지할 것은 그대로 둬야하는, 나름의 고통스러운 작업이 매일 몇 번 씩이나 일어나야 한다는 것을 뜻합니다. 이를 방지하기 위해 하루에도 수십 번 넘게 사용하는 회사 메신져인 Slack에 EMR 클러스터를 관리할 수 있는 bot user를 integration 하면 언제 어디서나 console에 들어갈 필요 없이 전체 클러스터들을 손쉽게 관리할 수 있겠다는 생각이 들었습니다. (제가 생각한 건 아니고 저희 팀장님께서…)

그리하여, 나만 쓸 것이 아니고 모두가 쓸 거니까 아주 잘 만들어야겠다는 생각과 함께 회사에서도 쓸만한 Slack Bot 만들기 프로젝트를 시작하게 되었습니다.

aws-emr-console

더 이상 이 화면을 보지 않아도 되는 것입니다.

요구 사항

Google에 검색해 보니 역시 수많은 선례들이 있더군요. 그러나 제가 원하는 만큼 상세하게 적혀진 자료는 없었습니다. (사실 이 글을 쓰는 이유가 바로…) 저는 다음과 같은 기능이 필요했습니다.

  • 클러스터 전원 관리 (Launch, Terminate, Resize)
  • n시간 이상 유휴 (Idle) 상태인 클러스터 감지
  • 특정 시간에 전체 클러스터 현황 알림

이 정도면 console에 들어가서 관리하는 것과 큰 차이 없는 수준의 기능입니다. 글을 이어나가면서 하나씩 구현하는 과정과 결과를 보여드리도록 하겠습니다.

Slack API

slack-api

일단 Slack과 연동하려면 반드시 Slack API를 사용해야 합니다. (너무 당연한 소리인가요?) Slack API를 이용하여 bot을 새로 만드는 과정은 다른 곳에도 너무 많으니 생략하겠습니다. bot을 만드셨다면, 이후에 우리가 사용할 기능들은 다음과 같습니다.

  • Incoming Webhooks
    • 특정 URL을 invoke 하는 것으로 Slack bot에 특정 명령을 내릴 수 있는 기능입니다. 주로 채팅방에 메시지를 보내기 위해서 사용합니다.
  • Interactive Components
    • 사용자가 메시지에 반응하는 것을 처리할 수 있는 기능입니다. 메시지에 달려있는 버튼이나 드롭다운 등을 사용자가 클릭했을 때 어떻게 반응할 지를 결정할 수 있습니다.
  • Slash Commands
    • 사용자가 /를 붙여 명령어를 입력해 Slack bot을 호출할 수 있게 해주는 기능입니다. 어떤 방식으로든 우리의 Slack bot을 호출하여야 하니, 이러한 종류의 인터페이스가 반드시 하나 이상은 존재해야 합니다. 물론 명령어 말고도 직접 bot 메뉴에 들어가서 호출하는 등의 다른 방식도 있습니다.

위 기능들을 정상적으로 이용하려면 각 기능 (webhook 빼고) 에 URL endpoint를 제공해줘야 합니다. 왜냐하면, bot이 작동하는 시나리오가 다음과 같기 때문입니다.

  • Slack에서 사용자의 특정 action을 받아 특정 서버의 endpoint로 보내준다.
  • 서버에서는 유저가 원하는 action (e.g. EMR 클러스터 켜기) 을 수행한다.
  • 수행 결과에 따라 사용자에게 메세지를 webhook을 통해 전달한다.

이를 위해 직접 서버를 hosting 해도 되지만, 지금은 Serverless라는 개념조차 생길 정도의 아주 좋은 세상이기 때문에, 서버를 hosting하지 않는 흐름을 따라가도록 하겠습니다.

AWS Lambda + AWS API Gateway 구축

위의 나와있는 기능 - URL endpoint로 전달된 요청을 받아 처리한다 - 에 대한 효과적인 솔루션은 바로 AWS LambdaAPI Gateway를 이용하는 것입니다. 두 개가 무엇인지는 여러 군데에 설명이 아주 많으니 핵심만 얘기하겠습니다. API Gateway에서 요청을 받아 Lambda를 호출하고, Lambda에서는 받은 요청에 따라 일련의 action을 수행하면 되는 것입니다. 이 두 개를 함께 구축하는 것도 역시 수많은 튜토리얼들이 있기 때문에, 제가 실제로 사용 중인 설정을 참고하실 수 있도록 보여드리겠습니다.

lambda-config Lambda는 다음 세 가지만 설정해주면 됩니다.

  • Runtime - 어떤 runtime에서 실행될 것인지를 골라야 합니다. 저는 Python을 이용할 것이기에 Python 3.7을 runtime으로 설정해주었습니다.
  • Trigger - 누가 Lambda를 호출할 수 있는지를 정하는 부분입니다. 일단 당연히 API Gateway가 있어야 하고 (옆에서 버튼을 누르면 자동으로 만들어줍니다!) 나중에 알람 등의 기능을 구현하기 위해서는 CloudWatch 또한 필요합니다. 하지만 이 부분에 대한 설정은 나중에 해주도록 하겠습니다.
  • Role - EMR 클러스터를 관리하기 위해선 우리의 Lambda 함수가 반드시 EMR 관련 권한을 가지고 있어야 합니다. 즉, Lambda로 AWS 내의 어떤 것을 제어하고 싶다면 반드시 해당 role을 통해 권한을 부여해줘야 합니다. 저는 EMR 권한을 포함하는 role을 AWS IAM을 이용해 만들어준 상태입니다.

api-gateway-config API Gateway는 다음 두 가지만 설정해주면 됩니다.

  • Resources - Slack API는 POST로 요청을 보내기 때문에, POST를 제외한 요청은 모두 필요 없습니다.
  • Integration Request - Integration type을 Lambda function으로 지정해주면 됩니다. 경험상 이렇게 하는 것이 제일 편했습니다.

나머지는 굳이 건드리지 않아도 Slack bot을 구성하는 데에는 아무런 문제 없습니다. 여기서 한 가지 궁금증 - 보안 설정은 따로 안 해주나요? 같은 의문을 떠올리신 분들이 많을 텐데, 아쉽게도 Slack bot이 API key같은 것을 요청에 실어보내도록 하는 기능이 없기 때문에 API Gateway에 보안 설정은 따로 해줄 수 없습니다. (제가 알기론 그렇습니다) 다만 해당 요청이 진짜 Slack에서 보낸 건지 validation 하는 방법이 있는데, 나중에 구현해보도록 하겠습니다.

설정이 완료되면 Actions - Deploy API를 통해 deploy 해주고, Stages에 가보면 endpoint가 나와있습니다. 해당 endpoint + methods에서 정해준 name을 조합하면 우리가 방금 만든 API에 대한 전체 endpoint를 얻을 수 있습니다. 이 endpoint를 Slack API 곳곳에 입력해주면 됩니다.

Slack API에 API Gateway endpoint 연동

endpoint가 필요한 API 기능은 Interactive componentsSlash commands 입니다. 두 기능의 설정 화면에 가보면 다음과 같이 endpoint를 입력해주는 부분이 있습니다. interactive-components-endpoint Slash command의 경우 명령어를 만드는 순간에 endpoint를 요구합니다. 테스트용 커맨드를 하나 만들어서 endpoint를 넣어보세요.

그 다음, Slack API의 Install Apps 탭을 통해 지금 만든 Slack bot을 특정 workspace에 설치할 수 있습니다. 원하는 workspace에 설치해 보세요. 그럼 다음과 같이 우리의 bot이 workspace에 등장하게 됩니다. 저의 경우 EMR Cluster Manager로 만들었습니다. installed-app

Slash command 호출해보기

위에서 만든 Slash command를 한 번 호출해 볼까요? 아무 채널에나 가서 아까 등록한 명령어를 쳐보세요. 그럼 당연하게도 아무 일도 일어나지 않을 겁니다. 아직 해당 명령어가 호출되었을 때 무엇을 해줄지 아무 것도 정한 게 없기 때문입니다. Slack bot이 등록된 기념으로 Hello World 한 번 출력해 볼까요?

기본적으로 Slash command는 response code가 200 일 때, body에 있는 응답을 그대로 Slack에 다시 뿌려줍니다. 단, body에 있는 데이터 형식은 JSON 형식이어야 합니다. 그리고 200이 아닌 response가 오면 은밀한 에러 메시지를 출력합니다. 일단 Lambda의 내용을 다음과 같이 한 번 해봅시다.

import json

def handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps("Hello World!")
    }

명령어를 다시 호출해보면, 우리가 딱 예상했던 결과를 볼 수 있습니다. 이로써 모든 기능들을 구현하기 위한 토대를 마련하는 일이 비로소 끝이 났습니다. 다음 편에서 이 모든 것을 조합하여 AWS EMR을 제어하는 기능을 속속들이 구현해보도록 하겠습니다.

다음 편에서 다룰 내용은 다음과 같습니다. (다음 편이 본편인 것 같은 느낌이 드신다구요? 그렇다면 이 글을 제대로 읽으신 게 맞습니다.)

  • Trigger (Slash Commands, Slack Action, AWS CloudWatch Event) 에 따른 Lambda 함수 구현
  • boto3를 이용한 AWS EMR 제어 (Launch, Terminate, Resize)
  • Slack Message API를 이용한 formatting + button 등 action 추가 및 message update
  • HTTP Response를 이용하는 것이 아닌, Webhook을 통해 채널에 메시지 전송하기
  • Slack 채널별 권한 관리하기

slack-bot-test

생생했던 삽질 현장 사진을 마지막으로 글을 마무리합니다. :)

Comments