클라우드 2단계: CI(지속적 통합) 파이프라인 구축 설계 - 100-hours-a-week/16-Hot6-wiki GitHub Wiki

상위 문서: 클라우드 위키
관련 문서: 클라우드 WHY? 문서

CI 파이프라인 구축 설계

목차

개요

이 문서는 본 프로젝트의 CI(Continuous Integration) 파이프라인 설계와 구성에 대한 설명을 제공합니다.

CI는 코드가 GitHub 저장소에 push되었을 때, 자동으로 테스트 및 빌드를 수행하고, 결과물(artifact)을 업로드하여 배포 준비 상태로 만드는 자동화 절차입니다.

본 문서에서는 다음을 다룹니다:

  • CI를 도입하게 된 필요성과 효과
  • GitHub Actions를 CI 도구로 선택한 이유와 다른 도구와의 비교 분석
  • 전체 CI 아키텍처 다이어그램과 흐름도
  • Frontend(React), Backend(Spring Boot), AI(FastAPI) 각각에 대해 개별적으로 정의된 CI 구성과 동작 방식
  • GitHub Actions 워크플로 스크립트 명세

이 프로젝트는 CI와 CD를 분리하여, 빌드 결과물을 GitHub Actions의 artifact로 업로드한 뒤, Cloud Engineer가 수동 배포를 수행하는 구조를 채택하고 있습니다.

CI 필요성 및 도구 선택

CI 필요성

왜 CI과정이 필요한가요?

CI는 코드의 변경 사항을 자동으로 통합하는 과정입니다. 단순히 코드의 형상을 일치시키기 위한 목적이 아니라, 린팅, 테스팅 등을 포함하여 "정말 사용할 수 있는 코드" 상태로 통합하는 것을 말합니다.

1. 빅뱅 배포 실험에서 마주한 문제

프로젝트 초기, 저희 팀은 자동화된 빌드·배포 파이프라인 없이, EC2 인스턴스에 접속하여 git pull빌드실행을 직접 수행하는 수작업(Big Bang) 배포 방식을 선택했습니다.

이 방식은 학습 부담이 없고, 별도의 도구 없이 빠르게 결과를 확인할 수 있다는 장점이 있었습니다. 그러나 다음과 같은 실제 실패 경험을 통해 한계가 명확히 드러났습니다.

  • 문제 상황:

    • 백엔드 서버는 정상 동작 중이었으나, 배포 중 프론트엔드(React) npm run build 과정에서 **메모리 부족(OOM)**이 발생해 빌드가 실패함
    • 서비스 운영 자체는 수백 MB로 가능했지만, 빌드에는 2~4GB 이상의 메모리가 필요했던 것
    • EC2 t2.micro 환경에서는 이를 감당할 수 없어, 결국 swap 메모리를 수동 설정해 해결해야 했음
  • 그 외 겪었거나 예상되는 어려움:

    • 로컬(macOS)에서는 문제 없이 빌드되던 코드가, 서버(Ubuntu)에서는 패키지 호환성 문제로 실패
    • 팀원이 늘어나면서 누가, 어떤 환경에서, 어떤 명령어로 배포했는지 이력을 추적하기 어려워짐
    • 실수로 테스트하지 않은 상태에서 배포해, 프로덕션 장애가 발생할 가능성도 높아짐

이러한 경험은 단순히 "수동 배포가 귀찮다"는 수준의 문제가 아니라, 서비스 안정성과 팀 협업의 리스크로 이어질 수 있는 구조적 문제임을 보여주었습니다.

2. CI 도입이 왜 해답인가?

CI는 다음과 같은 방식으로 위 문제들을 해소해줍니다.

빅뱅 배포에서 겪은 문제 CI를 통한 해결
빌드 시 서버 메모리 부족 GitHub Actions에서 외부 빌드 → EC2에는 실행만
환경 간 패키지 호환 문제 통일된 OS/버전에서의 자동 빌드 및 테스트 수행
테스트 없이 배포되는 문제 push 시 자동 유닛 테스트 수행, 실패 시 배포 불가
배포 방식 일관성 부족 모든 빌드/테스트 작업이 기록되고 공유됨
실수 방지 어려움 Step-by-step으로 검증된 자동화 파이프라인 구성 가능

특히, EC2 인스턴스는 서비스를 실행하는 데 필요한 최소 자원만 확보하면 되며, 빌드는 GitHub Actions 같은 외부에서 처리되므로 운영 안정성과 비용 측면 모두에서 효과적인 구조가 됩니다.

결론

"CI가 좋아서 도입했다"가 아니라, "CI 없이 실제로 해봤더니 한계가 명확했고, 직접 겪은 문제를 해결하려고 도입했다" 는 것이 저희 팀의 진짜 이유입니다.

이러한 경험을 바탕으로, 저희는 서비스 품질과 배포 안정성을 확보하기 위해 CI는 반드시 필요하다고 판단했습니다.

CI 도구 비교 표

항목 GitHub Actions Jenkins CircleCI Travis CI GitLab CI
실행 환경 GitHub 제공 서버 (Self-hosted 지원) Self-hosted Self-hosted 기반 (Cloud 지원) Self-hosted 기반 (Cloud 지원) GitLab.com 또는 Self-hosted 지원
유연성 중간 (Marketplace 확장 활용) 매우 높음 (플러그인 수백 개 지원) 높음 (Orbs 및 고급 config 지원) 낮음 중간 ~ 높음
설치 및 유지보수 없음 (GitHub 기반 자동 제공) 필요 (운영/보안 포함 유지보수 수반) 없음 (Cloud 환경), 있으면 Self-hosted 없음 (Cloud 환경 기준) 선택적 (Self-hosted 시 유지 필요)
커스터마이징 Matrix 빌드, 환경별 조건 분기 등 고도 커스터마이징 지원 Orbs 기반 확장 및 파라미터화 빌드 지원 기본 설정만 허용 Stage/Job/Parallel 등 구성 유연
시크릿 관리 GitHub Secrets 서버 설정 or OS 환경변수 프로젝트 수준에서 키 관리 제한적 GitLab Settings 또는 Vault 활용 가능
트리거 방식 Push, PR, CRON, Manual 등 다양 Webhook, Polling Push, PR, CRON Push, PR Push, PR, Merge Request, Manual 등
UI 및 로그 가시성 우수 (GitHub UI 내 확인) 부족 (플러그인 필수) 우수 (실시간 로그 제공) 보통 (간단한 로그 제공) 우수 (로그 세분화 지원)
무료 사용 범위 무료 (Public은 2,000분/월 제공) 없음 (직접 관리 필요) 제한적 (무료 요금제 존재) Public은 무료, Private는 유료 Public 무료, Private 일부 제한 존재

도구별 장단점 정리

✅ GitHub Actions

장점

  • GitHub 생태계에 최적화 (PR/Merge 기반 흐름에 자연스럽게 연결됨)
  • 다양한 이벤트 기반 트리거 (Push, PR, Issue, Release 등)
  • MarketPlace를 통한 수천 개의 Action 재사용 가능
  • macOS, Linux, Windows 등 다양한 실행 환경

단점

  • GitHub 외 저장소 사용 시 제약이 있음
  • Self-hosted runner 관리가 복잡할 수 있음
  • 로그 필터링, 세션 내 상태 공유 등에 제약 있음

정리

  • GitHub 생태계 통합 가능(PR, push, Issue, Branch 등과 유연한 통합)
  • 러닝 커브 낮음
  • 오픈소스 Actions로 다양한 동작 가능(Marketplace)
  • 퍼블릭의 경우 무료 제공 시간
  • 도입하기로 결정

❌ Jenkins

장점

  • 플러그인을 통한 무한한 커스터마이징
  • 온프레미스 환경에서 자유롭게 구성 가능
  • Groovy 기반 Pipeline Script 작성 가능
  • 병렬 처리, 멀티 노드, 조건 분기 등 복잡한 로직 가능

단점

  • 설치/운영에 높은 러닝 커브 존재
  • 보안, 백업, 버전 충돌 문제 자주 발생
  • 현대 DevOps 흐름에 비해 UI가 부족하고 불편함

정리

  • 서버 설치 및 유지보수 필요
  • Groovy DSL 학습 필요로 러닝 커브 높음
  • 플러그인 호환성 관리가 번거롭고 보안 리스크 존재
  • 도입하지 않음

❌ CircleCI

장점

  • Docker 기반의 빠른 빌드 처리 및 캐싱 기능
  • 병렬 처리/매트릭스 빌드 등 성능 최적화 기능 탑재
  • Orbs(재사용 가능한 config) 제공
  • 다양한 언어 및 OS 지원

단점

  • 요금제가 복잡하고 무료 사용에 제약 존재
  • 비직관적인 문법과 설정 (초기 진입장벽 있음)
  • 비공식 리전의 느린 빌드 속도

정리

  • 강력한 기능은 유료 요금제에서 제공됨
  • 기본 제공 시간이 적어 비용 부담 발생 가능
  • 도입하지 않음

❌ Travis CI

장점

  • 단순한 .travis.yml 하나로 시작 가능
  • GitHub 연동이 간단하며 빠른 셋업
  • 오픈소스 프로젝트에 적합 (무료 지원)

단점

  • 상용 서비스에 적합하지 않음 (기능 제약 많음)
  • 최근 사용자 수 및 커뮤니티가 감소 추세
  • 일부 기능은 유료 전환 (private repo 기준)

정리

  • 상업적 변화 이후 무료 플랜 제한적
  • 커뮤니티 규모가 줄어들며 유지가 불안정
  • 도입하지 않음

❌ GitLab CI

장점

  • GitLab과 완전히 통합된 DevOps 툴체인
  • .gitlab-ci.yml 하나로 배포까지 연계 가능
  • 환경변수/시크릿 관리, 자동화 스크립트 작성 용이
  • Auto DevOps와 GitOps 기능 제공

단점

  • GitHub 사용자에겐 도입 장벽이 있음
  • CI/CD 외 GitLab 사용을 전제함 (전체 툴셋 필요)
  • Auto DevOps 자동 구성은 일부 제약이 따름

정리

  • 코드 형상 관리로 GitLab을 사용하지 않음
  • 따라서 GitHub 기반 프로젝트와 통합 어려움.
  • 도입하지 않음

CI 파이프라인 아키텍처 다이어그램

브랜치 전략

왜 브랜치 전략을 그렇게 수립했나요?

비교 분석

전략 특징 장점 단점
GitHub Flow main 브랜치 기준으로 feature → PR → merge 방식 간단하고 빠름 운영 코드와 개발 코드 분리가 어려움
Git Flow main, develop, feature, release, hotfix 구분 명확한 역할 분담, 안정적인 릴리즈 관리 가능 브랜치가 많아지고 관리가 복잡해질 수 있음
현재 전략 main, dev, feat/*, hotfix/*로 구성 단순성과 안전성 절충, CI/CD 흐름과 맞음 브랜치 병합 규칙을 초기에 명확히 해야 함

GitHub Flow를 단독으로 사용하지 않은 이유
→ 기능 개발과 운영 코드의 분리가 어렵고, 긴급 수정 대응에도 제약이 있기 때문.

Git Flow 전체를 도입하지 않은 이유
release 브랜치까지 운용하는 구조는 팀 규모나 프로젝트 단계에 비해 과도하게 복잡하다고 판단.

현재 전략을 채택한 이유
GitHub Flow의 간결함과 Git Flow의 안정성을 절충하여, 운영과 개발 모두를 유연하게 관리할 수 있기 때문.

기본 구조

이 프로젝트에서는 다음과 같은 브랜치 전략을 사용합니다.

브랜치 용도
main 운영용 브랜치. 항상 배포 가능한 안정 상태를 유지합니다.
dev 개발 통합 브랜치. 여러 기능을 병합하여 테스트하는 공간입니다.
feat/* 기능 단위 작업 브랜치. 단일 기능 개발을 위한 브랜치입니다.
hotfix/* 운영 중 긴급 수정 브랜치. main에 바로 반영되며 이후 dev에 병합됩니다.
브랜치 흐름도
[feat/기능A] → [dev] → [main]
  • 기능 개발은 feat/* 브랜치에서 시작됩니다.
  • 기능이 완료되면 dev 브랜치에 병합되어 통합 테스트를 진행합니다.
  • dev 브랜치가 충분히 안정적일 경우 main에 병합하여 배포합니다.
[hotfix/버그수정] → [main] → [dev]
  • 운영 중 발생한 긴급 이슈는 hotfix/* 브랜치에서 빠르게 수정하여 main에 먼저 반영합니다.
  • 이후 main 브랜치의 변경사항을 dev에 병합하여 일관성을 유지합니다.

왜 이러한 전략을 사용했는가?

프로젝트 초기에는 단순한 GitHub Flow만으로 충분했지만, 다음과 같은 요구사항이 추가되면서 전략을 확장할 필요가 있었습니다:

  • 개발 중인 기능과 운영 코드의 분리 필요
    maindev 브랜치를 구분함으로써, 배포 가능한 안정 코드와 통합 개발 코드를 나눌 수 있습니다.

  • 기능별 분리 작업 및 병렬 진행
    feat/* 브랜치를 통해 팀원들이 동시에 여러 기능을 개발하고, 독립적으로 코드 리뷰를 받을 수 있습니다.

  • 긴급한 운영 이슈 대응
    hotfix/* 브랜치를 통해 기능 개발 진행과 무관하게 빠르게 main에 반영하고, 이후 dev에도 되돌려 병합함으로써 코드 이력의 일관성을 유지할 수 있습니다.

  • 풀리퀘스트 기준 병합 구조 정립

    • feat/*dev: 기능 개발 완료 후 병합
    • devmain: 릴리즈 시 병합
    • hotfix/*maindev: 운영 긴급 수정 후 병합

결론

이 전략은 GitHub Flow의 간결함과 Git Flow의 안전성을 절충한 구조로, 기능 개발의 유연성과 운영 안정성, 긴급 대응력을 모두 확보할 수 있는 실용적인 브랜치 전략입니다.

CI 파이프라인 다이어그램

image

아키텍처 설명 포함 링크

아키텍처 다이어그램

image

왜 Nginx를 사용하나요?

파이프라인 구성 명세

Frontend

항목 내용
빌드 도구 npm
산출물 build/ 디렉터리 (정적 파일)
CI 단계 작업 유닛 테스트, 정적 빌드, artifact 업로드
테스트 방법 npm test -- --watchAll=false (Jest 기반 유닛 테스트)
CD 방식 수동 복사 후 GCE에서 Nginx에 연동
Artifact 위치 GitHub Actions Artifact
추가 사항 feat/* push 시 [no-ci] 커밋으로 테스트 생략 가능, PR 시 유닛 테스트 필수

[Push] → (조건: no [no-ci]) → [npm install]
      → [npm test]
      → [npm run build]
      → [build/ 디렉터리 Artifact 업로드]

Backend

항목 내용
빌드 도구 Gradle
산출물 .jar 파일
CI 단계 작업 유닛/통합 테스트, Gradle 빌드, artifact 업로드
테스트 방법 ./gradlew test, ./gradlew integrationTest (Spring 기반)
CD 방식 수동으로 JAR 파일을 서버에 전송하여 실행
환경변수 주입 SPRING_PROFILES_ACTIVE, DB credentials 등
Artifact 위치 GitHub Actions Artifact
추가 사항 통합 테스트는 feat → dev, hotfix → main에서는 백그라운드로 실행됨

[Push] → (조건: no [no-ci]) → [./gradlew test]
      → [./gradlew build]
      → [build/libs/*.jar Artifact 업로드]

AI

항목 내용
빌드 도구 없음
산출물 .py 소스, requirements.txt, 모델 파일 등
CI 단계 작업 유닛 테스트, 의존성 검증, 소스 패키징 및 artifact 업로드
테스트 방법 pytest 기반 유닛 테스트 및 (선택적) 통합 테스트
CD 방식 수동 복사 후 서버에서 FastAPI 실행
Artifact 위치 GitHub Actions Artifact
추가 사항 통합 테스트는 feat → dev, hotfix → main에서는 백그라운드로 실행됨

[Push] → (조건: no [no-ci]) → [pytest 실행]
      → [requirements.txt + 소스 패키징]
      → [artifact/ 디렉터리 업로드]

설정 및 스크립트 명세

Frontend

Github Actions

name: Frontend CI

on:
  pull_request:
    branches:
      - develop
      - main
  push:
    branches:
      - 'feat/**'
      - 'hotfix/**'

jobs:
  unit-test-on-push:
    if: |
      github.event_name == 'push' &&
      !contains(github.event.head_commit.message, '[no-ci]')
    name: Unit Test on feat/* push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install Dependencies
        run: npm ci

      - name: Run Unit Test
        run: npm run test

      - name: Notify Failure
        if: failure()
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ❌ [feat/* push] 유닛 테스트 실패!
            브랜치: ${{ github.ref_name }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

  pr-workflow:
    if: github.event_name == 'pull_request'
    name: Pull Request CI
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install Dependencies
        run: npm ci

      - name: Run Unit Test
        run: npm run test

      - name: Notify Failure - Unit Test
        if: failure()
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ❌ [PR] 유닛 테스트 실패!
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

      - name: Run Integration Test (only dev -> main)
        if: github.base_ref == 'main' && github.head_ref == 'develop'
        run: npm run test:integration

      - name: Run Integration Test (background for feat → dev or hotfix → main)
        if: github.base_ref == 'develop' || (startsWith(github.head_ref, 'hotfix/') && github.base_ref == 'main')
        continue-on-error: true
        run: npm run test:integration

      - name: Notify Integration Test Failure (background)
        if: failure() && (github.base_ref == 'develop' || startsWith(github.head_ref, 'hotfix/'))
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ⚠️ [PR] 통합 테스트 실패 (백그라운드)
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

      - name: Notify Success
        if: success() && github.base_ref == 'main'
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ✅ 모든 테스트 통과! 이제 머지 가능합니다.
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

다운로드 및 전송

scp -i ~/.ssh/your-key.pem frontend-build.zip your-user@your-fe-server:/home/ubuntu/onthetop-frontend

배포

#!/bin/bash

# 1. 아티팩트 압축 해제
unzip -o frontend-build.zip -d frontend-build

# 2. 환경 변수 설정
cp .env.production .env

# 3. 정적 파일을 Nginx 서비스 디렉토리에 복사
sudo rm -rf /var/www/onthetop/*
sudo cp -r frontend-build/* /var/www/onthetop/

# 4. 권한 설정 및 Nginx 재시작 (필요시)
# sudo chown -R www-data:www-data /var/www/onthetop
# sudo systemctl restart nginx

Backend

Github Actions

name: Backend CI/CD

on:
  push:
    branches:
      - 'feat/**'
      - 'hotfix/**'
  pull_request:
    branches:
      - dev
      - main

jobs:
  unit-test-on-push:
    if: |
      github.event_name == 'push' &&
      !contains(github.event.head_commit.message, '[no-ci]')
    name: Backend Unit Test on feat/* push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Run unit tests
        run: ./gradlew test

      - name: Notify Failure
        if: failure()
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ❌ [feat/* push] 백엔드 유닛 테스트 실패!
            브랜치: ${{ github.ref_name }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

  pr-workflow:
    if: github.event_name == 'pull_request'
    name: Backend Pull Request CI
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Run unit tests
        run: ./gradlew test

      - name: Notify Failure - Unit Test
        if: failure()
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ❌ [PR] 백엔드 유닛 테스트 실패!
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

      - name: Run Integration Test (only dev → main)
        if: github.base_ref == 'main' && github.head_ref == 'dev'
        run: ./gradlew integrationTest

      - name: Run Integration Test (background for feat → dev or hotfix → main)
        if: github.base_ref == 'dev' || (startsWith(github.head_ref, 'hotfix/') && github.base_ref == 'main')
        continue-on-error: true
        run: ./gradlew integrationTest

      - name: Notify Integration Test Failure (background)
        if: failure() && (github.base_ref == 'dev' || startsWith(github.head_ref, 'hotfix/'))
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ⚠️ [PR] 백엔드 통합 테스트 실패 (백그라운드)
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

      - name: Notify Success
        if: success() && github.base_ref == 'main'
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ✅ 모든 백엔드 테스트 통과! 이제 머지 가능합니다.
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

  build-artifact:
    if: github.base_ref == 'main' || github.base_ref == 'dev'
    name: Build Backend and Upload Artifact
    runs-on: ubuntu-latest
    needs: pr-workflow
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build

      - name: Upload jar as artifact
        uses: actions/upload-artifact@v3
        with:
          name: backend-jar
          path: build/libs/*.jar

다운로드 및 전송
```bash
scp -i ~/.ssh/your-key.pem backend-jar.zip your-user@your-be-server:/home/ubuntu/onthetop-backend

배포

#!/bin/bash

# 1. 아티팩트 압축 해제
unzip -o backend-jar.zip -d backend-jar

# 2. 실행 중인 백엔드 종료
pkill -f 'onthetop-backend'

# 3. JAR 실행 (Spring profile 설정 포함)
nohup java -jar backend-jar/*.jar --spring.profiles.active=prod > app.log 2>&1 &

AI

Github Actions


name: AI Server CI/CD

on:
  push:
    branches:
      - 'feat/**'
      - 'hotfix/**'
  pull_request:
    branches:
      - dev
      - main

jobs:
  unit-test-on-push:
    if: |
      github.event_name == 'push' &&
      !contains(github.event.head_commit.message, '[no-ci]')
    name: AI Unit Test on feat/* push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source code
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run unit tests
        run: |
          if [ -d "tests" ]; then
            pytest tests
          else
            echo "No test directory found. Skipping tests."
          fi

      - name: Notify Failure
        if: failure()
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ❌ [feat/* push] AI 유닛 테스트 실패!
            브랜치: ${{ github.ref_name }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

  pr-workflow:
    if: github.event_name == 'pull_request'
    name: AI Pull Request CI
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source code
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run unit tests
        run: |
          if [ -d "tests" ]; then
            pytest tests
          else
            echo "No test directory found. Skipping tests."
          fi

      - name: Notify Failure - Unit Test
        if: failure()
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ❌ [PR] AI 유닛 테스트 실패!
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

      - name: Run Integration Test (only dev → main)
        if: github.base_ref == 'main' && github.head_ref == 'dev'
        run: pytest integration_tests || echo "No integration tests."

      - name: Run Integration Test (background for feat → dev or hotfix → main)
        if: github.base_ref == 'dev' || (startsWith(github.head_ref, 'hotfix/') && github.base_ref == 'main')
        continue-on-error: true
        run: pytest integration_tests || echo "No integration tests."

      - name: Notify Integration Test Failure (background)
        if: failure() && (github.base_ref == 'dev' || startsWith(github.head_ref, 'hotfix/'))
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ⚠️ [PR] AI 통합 테스트 실패 (백그라운드)
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

      - name: Notify Success
        if: success() && github.base_ref == 'main'
        uses: Ilshidur/action-discord@master
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: |
            ✅ 모든 AI 테스트 통과! 이제 머지 가능합니다.
            브랜치: ${{ github.head_ref }} → ${{ github.base_ref }}
            [자세히 보기](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

  package-artifact:
    if: github.base_ref == 'main' || github.base_ref == 'dev'
    name: Package AI Application
    runs-on: ubuntu-latest
    needs: pr-workflow
    steps:
      - name: Checkout source code
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Package AI application
        run: |
          mkdir -p artifact
          cp -r ai_app/* artifact/
          cp requirements.txt artifact/

      - name: Upload AI artifact
        uses: actions/upload-artifact@v3
        with:
          name: ai-server
          path: artifact/

다운로드 및 전송

scp -i ~/.ssh/your-key.pem ai-server.zip your-user@your-ai-server:/home/ubuntu/onthetop-ai

배포

#!/bin/bash

# 1. 기존 서버 종료
pkill -f 'ai_app'

# 2. 아티팩트 압축 해제
unzip -o ai-server.zip -d ai-server

# 3. 가상환경 또는 pip로 의존성 설치
cd ai-server || exit

python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt

# 4. 서버 실행
nohup python3 main.py > app.log 2>&1 &