BE 공부/Infrastructure

[GitHub] Webhook을 활용한 자동 배포 트리거 시스템 구축하기

꼬질꼬질두부 2025. 6. 19. 17:51
반응형

GitHub Webhook을 활용한 자동 배포 트리거 시스템 구축하기

1. 개요

CI/CD를 구성하면서 이런 생각 해본 적 있으신가요?

"GitHub에 push하면 자동으로 배포까지 이어지면 좋겠다!"

많은 조직에서는 Jenkins, GitHub Actions 등으로 배포를 자동화합니다. 하지만 Webhook만으로도 간단한 자동 배포 트리거 시스템을 구축할 수 있습니다.

이 글에서는 GitHub Webhook + AWS API Gateway + AWS Lambda를 활용하여 다음과 같은 기능을 구현합니다.

  • GitHub push 이벤트 발생 시
  • Webhook이 API Gateway를 통해 Lambda를 호출
  • Lambda는 서명을 검증한 뒤
  • 조건에 맞으면 배포를 트리거(Lambda invoke 또는 SQS 전송)

2. Webhook이란?

Webhook은 특정 이벤트가 발생했을 때 HTTP 요청을 외부로 보내는 기능입니다.

GitHub에서는 다음과 같은 이벤트에 대해 Webhook을 설정할 수 있습니다:

  • Push
  • Pull Request
  • Release
  • Issue 등

Webhook은 이벤트 정보를 JSON payload 형태로 전송합니다.


3. 시스템 구성도

GitHub (Push)
   ↓ Webhook
API Gateway (Webhook Endpoint)
   ↓
AWS Lambda (서명 검증 + 배포 트리거)
   ↓
AWS Lambda 또는 SQS로 배포 트리거

4. GitHub Webhook 설정

  1. GitHub 리포지토리 > Settings > Webhooks > Add webhook
  2. Payload URL: https://{your-api-gateway}/github-webhook
  3. Content type: application/json
  4. Secret: your-webhook-secret (Lambda에서 검증용으로 사용)
  5. Trigger: Just the push event

5. Lambda 함수 코드 (TypeScript)

5.1 모듈 및 환경변수 설정

import crypto from 'crypto';
import { APIGatewayProxyHandler } from 'aws-lambda';
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';

// GitHub Webhook에서 설정한 Secret을 환경변수로 가져옵니다.
const SECRET = process.env.GITHUB_WEBHOOK_SECRET!;
  • crypto는 GitHub의 서명 검증에 사용됩니다.
  • @aws-sdk/client-lambda는 AWS SDK v3로 Lambda를 호출할 수 있게 해줍니다.
  • SECRET은 GitHub Webhook 설정에서 입력한 값을 동일하게 사용해야 서명 검증이 통과됩니다.

5.2 핸들러 함수 전체 구조

export const githubWebhookHandler: APIGatewayProxyHandler = async (event) => {
  const signature = event.headers['x-hub-signature-256']; // GitHub에서 전송된 서명
  const payload = event.body ?? ''; // 본문(payload) 문자열

  // 1. 서명 검증
  const isValid = verifySignature(SECRET, payload, signature);
  if (!isValid) {
    return { statusCode: 401, body: 'Invalid signature' };
  }

  // 2. payload를 JSON으로 파싱
  const body = JSON.parse(payload);
  const branch = body.ref; // 예: 'refs/heads/main'

  // 3. main 브랜치에만 반응
  if (branch !== 'refs/heads/main') {
    return { statusCode: 200, body: 'Ignored: not main branch' };
  }

  // 4. 배포용 Lambda 호출
  const lambda = new LambdaClient({});
  const command = new InvokeCommand({
    FunctionName: 'deploy-my-service',
    InvocationType: 'Event', // 비동기 호출
    Payload: Buffer.from(JSON.stringify({ repo: body.repository.full_name })),
  });

  await lambda.send(command);

  return { statusCode: 200, body: 'Deployment triggered' };
};

동작 설명

  1. 서명 검증: Webhook을 가장한 외부 요청을 차단하기 위해 서명을 반드시 검증해야 합니다.
  2. 브랜치 필터링: push는 여러 브랜치에서 발생할 수 있으므로 main 브랜치로 한정합니다.
  3. Lambda 호출: 실제 배포를 실행하는 Lambda 함수를 비동기로 호출합니다.

예시 payload 구조 (일부)

{
  "ref": "refs/heads/main",
  "repository": {
    "full_name": "my-org/my-repo"
  },
  "pusher": {
    "name": "user123"
  }
}

5.3 서명 검증 함수

function verifySignature(secret: string, payload: string, signature: string | undefined): boolean {
  if (!signature) return false; // 서명이 없으면 무조건 실패
  const hmac = crypto.createHmac('sha256', secret);
  const digest = `sha256=${hmac.update(payload).digest('hex')}`;
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}

동작 설명

  • crypto.createHmac()을 통해 요청 payload에 대해 해시를 생성합니다.
  • GitHub에서 보낸 서명(x-hub-signature-256)과 해시값을 비교하여 검증합니다.
  • timingSafeEqual은 시간 기반의 공격을 방지하기 위해 사용합니다.

해시 알고리즘(sha256)과 x-hub-signature-256 헤더는 반드시 일치해야 합니다.

 

위 코드 예시는 전체적인 동작 방식을 간단히 설명하기 위한 것이며, 실무에 적용할 때에는 다양한 요소들을 고려하여 직접 구현해야합니다!


참고 자료

반응형