본문 바로가기
IT & Tech 정보

ChatOps 워크플로우 끝판왕 가이드: Slack · Hubot · GitHub Webhook 완전 정복

by 지식과 지혜의 나무 2025. 5. 26.
반응형




1. 개요: 채널 하나로 끝내는 운영·개발 자동화

ChatOps는 실시간 채팅 인터페이스(Slack)를 통해 사람과 봇이 협업하며, 봇이 복잡한 CI/CD·인프라 명령을 대신 실행하게 하는 패러다임입니다.
• 컨텍스트 일원화: 이슈·PR·배포·모니터링 모두 한 채널에서
• 자동화 확장성: Slash Command·Interactive 버튼·Webhook으로 모든 시스템 연결
• 투명한 감사 로그: 누가 언제 무슨 명령을 내렸는지 채널 기록으로 보존

이 가이드에서는 Slack App 설정부터 Hubot 스크립팅, GitHub Webhook 연동, CI/CD 트리거, 고급 패턴까지 ‘끝판왕’ 수준으로 심층 정리합니다.



2. 전체 아키텍처

┌──────────┐    ┌───────────────┐    ┌─────────────────┐
│ Slack    │◀──▶│ Hubot         │◀──▶│ GitHub Webhook  │
│ Workspace│    │ (Node.js Bot) │    │ Endpoint        │
└──────────┘    └───────────────┘    └─────────────────┘
      ▲                ▲                    ▲
      │ Slash, Action  │ CI/CD API 호출     │ GitHub PR·Issue 이벤트
      │ Events         │ (Jenkins, GHA)      │
      ▼                ▼                    ▼
┌─────────────────────────────────────────────────────────┐
│ 종합 ChatOps Bot: Slash Command, Dialog, Buttons, REST │
└─────────────────────────────────────────────────────────┘

1. Slack App: Slash Command & Interactive Components
2. Hubot: Slack Adapter + Express 서버로 Webhook/Action 처리
3. GitHub Webhook: PR·Issue 이벤트 → Hubot 라우터
4. CI/CD 연동: Hubot이 Jenkins/GitHub Actions/ArgoCD API 호출



3. Slack App & OAuth 설정

3.1 OAuth & Permissions
• Bot Token Scopes
• commands, chat:write, chat:write.public
• channels:read, groups:read, users:read
• incoming-webhook (옵션)
• OAuth Redirect:
• 설치형 앱 사용 시 /slack/oauth_redirect 엔드포인트 구현
• Signing Secret:
• 모든 요청에서 X-Slack-Signature 검증

3.2 Slash Command & Interactivity
• Slash Command 설정:
• /deploy, /status, /rollback 등 Command 당 Request URL
• Usage Hint에 예제 작성(예: /deploy web-app staging)
• Interactive Components
• 버튼, 메뉴 선택, 모달(Dialog)
• Request URL: /hubot/slack/actions



4. Hubot 설치 & 기본 구조

4.1 프로젝트 초기화

npm init -y
npm install hubot hubot-slack express body-parser @slack/events-api
yo hubot --adapter slack

4.2 파일 구조

hubot/
├── bin/                # hubot 실행 스크립트
├── external-scripts.json
├── Procfile            # Heroku 등 배포용
├── scripts/
│   ├── deploy.js
│   ├── status.js
│   ├── github.js
│   └── utils.js
└── server.js           # Express 서버 + Hubot 통합

4.3 server.js: Hubot + Express 결합

const Hubot = require('hubot');
const { createEventAdapter } = require('@slack/events-api');
const express = require('express');
const bodyParser = require('body-parser');

const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackEvents = createEventAdapter(slackSigningSecret);
const app = express();

app.use('/hubot/slack/events', slackEvents.expressMiddleware());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.post('/hubot/slack/commands', require('./scripts/deploy').commandHandler);
app.post('/hubot/slack/actions', require('./scripts/actions').actionHandler);
app.post('/hubot/github', require('./scripts/github').webhookHandler);

// initialize Hubot
const robot = Hubot.Robot.run({
  adapter: 'slack',
  name: 'hubot',
  slackToken: process.env.SLACK_BOT_TOKEN,
});
robot.loadFile(__dirname, 'scripts/utils.js');
robot.loadFile(__dirname, 'scripts/deploy.js');
robot.loadFile(__dirname, 'scripts/status.js');
robot.loadFile(__dirname, 'scripts/github.js');




5. Hubot 스크립팅: 주요 예제

5.1 Slash Command: /deploy

// scripts/deploy.js
module.exports.commandHandler = (req, res) => {
  const text = req.body.text; // "web-app staging"
  const [service, env] = text.split(/\s+/);
  // 즉시 응답(200 OK)
  res.json({ text: `🚀 배포 요청: *${service}* → *${env}* 처리 중...`, response_type: 'in_channel' });
  // 백그라운드 명령 수행
  const robot = req.robot;
  robot.emit('deploy:start', { service, env, channel: req.body.channel_id });
};

// 로컬 이벤트 핸들러
module.exports = (robot) => {
  robot.on('deploy:start', async (msg) => {
    const { service, env, channel } = msg;
    try {
      const result = await utils.triggerBuild(service, env);
      robot.adapter.client.chat.postMessage({ channel, text: `✅ 배포 성공: ${result.buildId}` });
    } catch (e) {
      robot.adapter.client.chat.postMessage({ channel, text: `❌ 배포 실패: ${e.message}` });
    }
  });
};

5.2 Interactive Button: 승인 프로세스

// scripts/actions.js
module.exports.actionHandler = (req, res) => {
  const payload = JSON.parse(req.body.payload);
  const action = payload.actions[0];
  if (action.name === 'approve') {
    res.send({ text: `👍 승인 완료: ${action.value}`, replace_original: false });
    req.robot.emit('deploy:approve', { service: action.value, user: payload.user.id });
  } else {
    res.sendStatus(200);
  }
};

// events in deploy.js
robot.on('deploy:needsApproval', ({ service, channel }) => {
  robot.adapter.client.chat.postMessage({
    channel,
    text: `배포 승인 필요: ${service}`,
    attachments: [{
      text: "지금 승인하시겠습니까?",
      fallback: "Approve deployment",
      callback_id: "deploy_approve",
      actions: [
        { name: "approve", text: "승인", type: "button", value: service }
      ]
    }]
  });
});




6. GitHub Webhook 처리

6.1 시그니처 검증 미들웨어

// scripts/github.js
const crypto = require('crypto');

function verifySignature(req) {
  const signature = req.headers['x-hub-signature-256'];
  const hmac = crypto.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET);
  hmac.update(JSON.stringify(req.body));
  return signature === `sha256=${hmac.digest('hex')}`;
}

module.exports.webhookHandler = (req, res) => {
  if (!verifySignature(req)) return res.sendStatus(400);
  const event = req.headers['x-github-event'], payload = req.body;
  if (event === 'pull_request' && payload.action === 'opened') {
    const channel = 'dev-team';
    const msg = `🔔 새 PR by *${payload.sender.login}*:\n>*${payload.pull_request.title}*\n${payload.pull_request.html_url}`;
    req.robot.adapter.client.chat.postMessage({ channel, text: msg });
  }
  res.sendStatus(200);
};




7. CI/CD 연동

7.1 Jenkins 빌드 트리거

// scripts/utils.js
const axios = require('axios');

async function triggerBuild(service, env) {
  const resp = await axios.post(process.env.JENKINS_URL, null, {
    params: { token: process.env.JENKINS_TOKEN, SERVICE: service, ENV: env }
  });
  return { buildId: resp.headers['x-jenkins-build-number'] };
}
module.exports = { triggerBuild };

7.2 GitHub Actions 호출

async function triggerGHA(service, env) {
  await axios.post(
    `https://api.github.com/repos/${process.env.GH_REPO}/actions/workflows/deploy.yml/dispatches`,
    { ref: 'main', inputs: { service, env } },
    { headers: { Authorization: `token ${process.env.GH_TOKEN}` } }
  );
}




8. 고급 패턴 & 팁
1. Ephemeral Response

res.json({ response_type: 'ephemeral', text: '개인 DM: 처리 중...' });


2. Command Queueing
• async.queue로 동시성 제어: 서비스별 최대 1개 큐
3. Rate Limiting
• express-rate-limit 미들웨어로 슬래시 커맨드 남용 방지
4. OAuth Installed App
• 사용자 액션 별 권한 분리: /deploy는 Admin만 가능
5. Metrics & Tracing
• prom-client 로 명령 처리 시간·성공률 집계 → Grafana 대시보드
• OpenTelemetry Node SDK로 분산 트레이싱 연동
6. Self-Healing
• 실패시 자동 재시도(retry 로직) + 알림
7. Dynamic Help

robot.respond(/help/i, (res) => {
  const cmds = ['/deploy', '/status', '/rollback'];
  res.send(`사용 명령: ${cmds.join(', ')}`);
});





9. 보안 고려사항
• Slack Signing Secret: 모든 요청 검증
• GitHub Webhook Secret: sha256 시그니처 체크
• 환경변수 관리: Vault·KMS·Kubernetes Secrets
• HTTPS 강제: 봇 서버와 Slack/GitHub 통신 암호화



10. 결론
• Slack App: Slash + Interactive
• Hubot: 스크립팅 + Express + Adapter
• GitHub Webhook: 이벤트 수신 + 서명 검증
• CI/CD API: Jenkins·GitHub Actions 등 호출
• 고급 패턴: Rate Limit, OAuth, Metrics, Self-Healing

“교과서론 절대 알려주지 않는, 실리콘밸리·심천 IT기업 최정예 개발자급 ChatOps”을 직접 구현해 보세요. Slack 하나로 모든 작업이 실행되는 혁신적 생산성 향상을 경험할 수 있습니다!

반응형