Slack 챗봇으로 배포 자동화하기: Bolt와 Socket Mode 실전 구축기 (Node.js)

개발팀의 생산성을 가장 크게 저해하는 요소는 무엇일까요? 복잡한 알고리즘이나 레거시 코드보다 더 무서운 것은 바로 '컨텍스트 스위칭(Context Switching)'입니다. 하루에도 수십 번씩 Jira, GitHub, Jenkins, 그리고 모니터링 대시보드를 오가며 흐름이 끊기는 경험은 누구나 해보셨을 겁니다. 최근 저희 팀은 이러한 비효율을 제거하기 위해 단순한 메신저로만 사용하던 slack을 업무의 '중앙 운영체제(OS)'로 격상시키기로 결정했습니다. 이 글에서는 단순한 알림 발송을 넘어, chatbot을 통해 프로덕션 배포와 장애 대응을 슬랙 내부에서 원스톱으로 처리하게 된 기술적 과정을 공유합니다.

Context Switching 비용과 레거시 봇의 한계

제가 담당하던 플랫폼 엔지니어링 팀은 약 50여 개의 마이크로서비스를 운영하고 있었습니다. 초기에는 배포가 필요할 때마다 VPN을 켜고, 사내망에 접속하여 Jenkins UI를 클릭해야 했습니다. 단순한 핫픽스 배포 하나에도 '로그인 -> 2FA 인증 -> 잡 검색 -> 파라미터 입력'이라는 4단계의 불필요한 뎁스가 존재했죠.

처음에는 간단한 Incoming Webhook을 사용하여 빌드 결과를 통보받는 방식을 취했습니다. 하지만 이는 '단방향(One-way)' 소통이라는 치명적인 한계가 있었습니다. 봇은 말을 걸지만, 우리는 봇에게 명령할 수 없었죠. 결과적으로 알림을 보고 다시 브라우저를 켜야 하는 상황은 변하지 않았습니다. 우리는 "슬랙 안에서 버튼 하나로 롤백(Rollback)까지 가능해야 한다"는 목표를 세웠습니다.

Legacy Error: 과거 Webhook 방식은 HTTP 429 (Too Many Requests) 에러에 취약했으며, 사용자 상호작용(Interactive Components)을 구현하려면 별도의 퍼블릭 IP와 SSL 인증서가 필요해 사내 보안 규정상 도입이 불가능했습니다.

이 문제를 해결하기 위해 우리는 Socket Mode와 Slack의 차세대 프레임워크인 Bolt를 도입하기로 했습니다. Socket Mode는 방화벽 인바운드 포트를 열 필요 없이 WebSocket을 통해 안전하게 슬랙 서버와 양방향 통신을 가능하게 합니다.

RTM(Real Time Messaging) API의 함정

초기 조사 단계에서 과거 많이 쓰이던 RTM API를 고려했었습니다. 하지만 RTM은 새로운 최신 기능(Block Kit, 모달 창 등)을 지원하지 않으며 슬랙 측에서도 점진적으로 사용을 중단(Deprecated)시키고 있다는 사실을 알게 되었습니다. 만약 레거시 튜토리얼만 보고 RTM으로 개발을 시작했다면, 화려한 UI 요소를 전혀 사용할 수 없는 텍스트 기반의 CLI 봇을 만드는 데 그쳤을 것입니다. 현대적인 chatbot은 단순 텍스트 파싱이 아닌, 풍부한 UI 경험(Block Kit)을 제공해야 합니다.

Bolt JS와 Socket Mode를 활용한 솔루션

다음은 Node.js 환경에서 `@slack/bolt` 라이브러리를 사용하여 배포 명령어를 처리하는 핵심 로직입니다. 사용자가 `/deploy` 명령어를 입력하면, 봇은 모달 창을 띄워 배포할 서비스와 버전을 선택하게 합니다. 이 모든 과정이 사내망 내부의 서버에서 안전하게 실행됩니다.

// app.js - Bolt Framework 초기화 및 Socket Mode 설정
const { App } = require('@slack/bolt');

// 환경 변수에서 토큰 로드 (반드시 .env 등으로 관리)
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode: true, // 핵심: 웹소켓 모드 활성화
  appToken: process.env.SLACK_APP_TOKEN,
  // 포트 포워딩 없이 로컬/사내망에서도 동작 가능
});

// '/deploy' 슬래시 커맨드 리스너
app.command('/deploy', async ({ command, ack, client }) => {
  // 1. 슬랙에게 명령어를 잘 받았음을 3초 내에 응답 (필수)
  await ack();

  try {
    // 2. 대화형 모달(Modal) 띄우기
    const result = await client.views.open({
      trigger_id: command.trigger_id,
      view: {
        type: 'modal',
        callback_id: 'deploy_modal_view',
        title: {
          type: 'plain_text',
          text: '🚀 서비스 배포'
        },
        blocks: [
          {
            type: 'section',
            text: {
              type: 'mrkdwn',
              text: `*${command.user_name}*님, 배포할 서비스를 선택해주세요.`
            }
          },
          {
            type: 'input',
            block_id: 'service_select_block',
            element: {
              type: 'static_select',
              action_id: 'service_select_action',
              placeholder: {
                type: 'plain_text',
                text: '서비스 선택'
              },
              options: [
                {
                  text: { type: 'plain_text', text: 'Auth Service' },
                  value: 'auth-svc'
                },
                {
                  text: { type: 'plain_text', text: 'Payment Service' },
                  value: 'payment-svc'
                }
              ]
            },
            label: {
              type: 'plain_text',
              text: 'Target Service'
            }
          }
        ],
        submit: {
          type: 'plain_text',
          text: '배포 시작'
        }
      }
    });
    console.log(result);
  } catch (error) {
    console.error('Modal Open Error:', error);
  }
});

(async () => {
  await app.start();
  console.log('⚡️ Slack Bolt App is running using Socket Mode!');
})();

위 코드에서 가장 중요한 부분은 socketMode: true 설정과 ack() 함수의 호출입니다. 슬랙은 사용자의 인터랙션 발생 후 3초 이내에 서버로부터 ack() 응답이 없으면 타임아웃 에러를 사용자에게 표시합니다. 따라서 비즈니스 로직(예: Jenkins API 호출)은 ack() 이후 비동기로 처리해야 사용자 경험(UX)을 해치지 않습니다.

도입 효과 및 성능 비교

단순한 알림 봇을 대화형 chatbot으로 고도화한 후, 우리 팀의 배포 프로세스는 혁신적으로 단축되었습니다. Jenkins 대시보드 로딩과 검색 시간을 완전히 제거했기 때문입니다. 다음은 실제 운영 환경에서 측정한 핫픽스 배포 소요 시간 비교입니다.

단계 기존 방식 (GUI) Slack Chatbot (CUI)
접근 및 로그인 45초 (VPN + 2FA) 0초 (로그인 상태 유지)
배포 잡 탐색 30초 5초 (/deploy 입력)
파라미터 입력 20초 5초 (Select Box 선택)
총 소요 시간 95초 10초

수치상으로 약 90%의 시간 단축 효과가 있었지만, 정량적인 수치보다 더 중요한 것은 '심리적 장벽'의 해소였습니다. 배포가 쉬워지니 개발자들이 더 자주, 더 작게 배포(Micro-deployment)하게 되었고, 이는 결과적으로 서비스 안정성을 높이는 선순환 구조를 만들었습니다.

Slack Bolt 공식 문서 확인하기

주의사항 및 엣지 케이스

하지만 slack 봇을 운영 체제처럼 사용할 때 몇 가지 주의해야 할 엣지 케이스가 있습니다.

  1. Rate Limiting (속도 제한): 슬랙 API는 티어별로 속도 제한이 엄격합니다. 만약 봇이 대량의 로그를 채널에 쏟아내게 되면, API 호출이 차단될 수 있습니다. 중요한 알림은 thread_ts를 사용하여 스레드로 묶거나, 별도의 로깅 전용 채널로 분산시켜야 합니다.
  2. 보안 (Security): 봇이 강력한 권한(배포, DB 조회 등)을 가질수록 보안은 취약해집니다. 반드시 슬랙 내의 사용자 ID(User ID)를 화이트리스트로 관리하여, 인가된 엔지니어만 /deploy 명령어를 실행할 수 있도록 미들웨어를 구현해야 합니다.
  3. Socket Mode 연결 끊김: 드물지만 네트워크 플랩(Flap)으로 소켓 연결이 끊길 수 있습니다. Bolt 프레임워크는 자동 재접속을 지원하지만, process.on('SIGTERM') 시그널을 핸들링하여 우아한 종료(Graceful Shutdown)를 보장해야 데이터 유실을 막을 수 있습니다.
Best Practice: 프로덕션 환경에서는 pm2나 Kubernetes의 Liveness Probe를 활용해 봇 프로세스의 상태를 주기적으로 체크하는 것이 필수적입니다.

결론

슬랙을 단순한 메신저로만 사용하는 것은 마치 최신 스마트폰을 전화기로만 쓰는 것과 같습니다. 적절한 chatbot의 도입은 흩어진 도구들을 하나의 인터페이스로 통합하여, 팀의 집중력을 유지하고 컨텍스트 스위칭 비용을 획기적으로 줄여줍니다. 우리가 구축한 Bolt 기반의 소켓 모드 봇은 보안성과 편의성이라는 두 마리 토끼를 잡은 성공적인 사례였습니다. 지금 당장 여러분의 반복적인 업무 중 봇에게 위임할 수 있는 것이 무엇인지 고민해보세요. 작은 명령어 하나가 팀의 문화를 바꿀 수 있습니다.

Post a Comment