본문 바로가기
CICD

3. 일반 프로젝트에 CI/CD 구축법(깃허브 액션에서 빌드/테스트)

by shulk 2025. 9. 3.

1. 역할 분담: 일하는 곳(GitHub Runner)과 보여주는 곳(EC2)

가장 먼저 이해해야 할 핵심 개념은 역할의 분리입니다.

  • 이전 방식 (EC2에서 빌드): EC2 서버가 **빌드/테스트(일하는 곳)**와 서비스 운영(보여주는 곳) 역할을 모두 담당했습니다. 이는 가게 주방과 손님 테이블이 합쳐진 것과 같아서, 요리(빌드) 중에 가게가 어수선해지거나(서버 성능 저하) 문제가 생길 수 있습니다.
  • 표준 방식 (GitHub Actions에서 빌드): GitHub Actions가 제공하는 가상 머신(GitHub Runner)이 **빌드/테스트(일하는 곳)**를 전담합니다. EC2 서버는 Runner가 완성한 결과물(음식)을 받아 **서비스 운영(보여주는 곳)**에만 집중합니다. 주방과 홀이 완벽히 분리되어 각자의 역할에만 충실하는 효율적인 레스토랑과 같습니다.

2. 표준 CI/CD 파이프라인의 개념적 흐름

이 방식의 전체적인 흐름은 다음과 같이 진행됩니다.

1단계: 빌드와 테스트 (in GitHub Actions Runner)

  1. 트리거: 개발자가 코드를 git push 합니다.
  2. 가상 환경 준비: GitHub Actions가 ubuntu-latest 같은 깨끗한 가상 머신(Runner)을 즉시 생성하고 실행합니다.
  3. 코드 다운로드: Runner가 소스 코드를 git clone (checkout) 합니다.
  4. 환경 설정 및 테스트: Runner 내부에 Java, Node.js 등 프로젝트에 필요한 환경을 설치하고, 의존성을 다운로드한 뒤, 작성된 테스트 코드를 실행하여 코드의 정합성을 검증합니다.
  5. 빌드 실행: 모든 테스트가 통과하면, Runner가 소스 코드를 컴파일하고 압축하여 실행 가능한 파일 묶음(Build Artifact)을 생성합니다. (예: Java의 .jar 파일, 웹 프론트엔드의 build 또는 dist 폴더)

2단계: 결과물 전달 및 배포 (Runner → EC2)

  1. 아티팩트 패키징: 생성된 빌드 아티팩트(예: app.jar)를 압축하거나 GitHub Actions 내에 임시 저장합니다.
  2. EC2에 보안 접속 및 전달: Runner가 SSH를 통해 EC2 서버에 안전하게 접속한 뒤, 패키징된 아티팩트 파일을 EC2 서버로 전송합니다. (scp나 rsync 같은 명령어를 사용)
  3. 서버 애플리케이션 교체: EC2 서버에 접속된 상태에서 스크립트를 실행합니다.
    • 기존에 실행 중이던 구버전 애플리케이션을 종료합니다.
    • 전달받은 새 버전의 아티팩트 파일의 압축을 풀고 제자리로 옮깁니다.
    • 새 버전의 애플리케이션을 실행합니다.

 왜 이 방식이 더 좋을까요?

  • 🚀 서버 안정성 확보: EC2 서버는 빌드 과정의 리소스 소모(CPU, 메모리 사용량 급증)로부터 자유로워집니다. 오직 안정적으로 서비스를 제공하는 데에만 집중할 수 있습니다.
  • ✨ 깨끗하고 일관된 빌드 환경: 매번 깨끗한 가상 환경에서 빌드를 시작하므로, 이전에 남아있던 파일이나 설정 때문에 빌드가 실패하는 "내 컴퓨터에선 됐는데..." 같은 문제를 원천적으로 방지할 수 있습니다.
  • 🔒 보안 강화: 소스 코드 전체나 .git 폴더를 운영 서버에 둘 필요가 없습니다. 오직 실행에 필요한 최소한의 빌드 결과물만 전달하므로 보안적으로 더 안전합니다.
  • 🐳 확장성 (컨테이너화 연계): 이 방식은 추후 Docker 이미지를 빌드하고 레지스트리에 푸시한 뒤, EC2에서 해당 이미지를 내려받아 실행하는 컨테이너 기반 배포로 자연스럽게 확장하기 매우 용이합니다.

이 개념을 먼저 이해하시면, 앞으로 보게 될 다양한 CI/CD 워크플로우 .yml 파일들이 어떤 역할을 왜 수행하는지 훨씬 쉽게 파악하실 수 있을 겁니다.

 

 

3. 실습 - 표준 CI/CD 파이프라인 구축: 빌드와 배포 분리하기 

(이거는 Gemini 부분 답변이라 정확하지 않을수도 있으므로 나중 유료 강의보고 틀린부분 있으면 수정 or 직접해보고 수정)

이제 개념을 넘어 실제 워크플로우 파일을 작성해 보겠습니다. 이 방식의 핵심은 두 개의 독립적인 작업(Job)을 만들어 체인처럼 연결하는 것입니다. build 작업이 성공해야만 deploy 작업이 실행됩니다.

전체 흐름:

  1. Build Job: GitHub Runner 환경에서 코드를 테스트하고 빌드하여 .jar 같은 결과물(Artifact)을 생성합니다.
  2. Upload Artifact: 생성된 결과물을 GitHub Actions의 임시 저장 공간에 업로드합니다.
  3. Deploy Job: build 작업이 끝나면 새로운 Runner 환경에서 시작됩니다.
  4. Download Artifact: 임시 저장 공간에서 빌드 결과물을 다운로드합니다.
  5. Deploy to EC2: 다운로드한 결과물을 EC2 서버로 전송(scp)하고, 서버를 재시작하는 스크립트를 실행합니다.

### ⚙️ GitHub Actions 워크플로우 (.yml) 전문

.github/workflows/deploy.yml

name: CI/CD Pipeline with separated Build and Deploy

on:
  push:
    branches: [ "main" ]

jobs:
  #-----------------
  #  1. Build Job
  #-----------------
  build:
    runs-on: ubuntu-latest
    
    steps:
    # (1) 기본 체크아웃
    - name: Checkout
      uses: actions/checkout@v4

    # (2) JDK 17 설치
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

    # (3) Gradle 권한 부여
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    # (4) Gradle 빌드 (테스트 포함)
    - name: Build with Gradle
      run: ./gradlew build

    # (5) 빌드 결과물(Artifact) 업로드
    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: my-app-jar  # 아티팩트 이름
        path: build/libs/*.jar # 업로드할 파일 경로

  #-----------------
  #  2. Deploy Job
  #-----------------
  deploy:
    # build 작업이 성공해야만 실행됨
    needs: build
    runs-on: ubuntu-latest

    steps:
    # (6) 빌드 결과물(Artifact) 다운로드
    - name: Download artifact
      uses: actions/download-artifact@v4
      with:
        name: my-app-jar # 다운로드할 아티팩트 이름 (build에서 사용한 이름과 동일)
        
    # (7) EC2에 빌드 결과물 전송
    - name: SCP to EC2
      uses: appleboy/scp-action@v1.0.3
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        key: ${{ secrets.EC2_PRIVATE_KEY }}
        source: "*.jar"  # 다운로드 받은 모든 .jar 파일을 전송
        target: "/home/ubuntu/app" # EC2 서버의 어느 경로에 저장할지 지정

    # (8) EC2에 접속하여 배포 스크립트 실행
    - name: Deploy to EC2
      uses: appleboy/ssh-action@v1.0.3
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        key: ${{ secrets.EC2_PRIVATE_KEY }}
        script: |
          # 기존 서버 종료 (포트 사용 기준)
          sudo fuser -k -n tcp 8080 || true
          # 새 버전 서버 실행
          cd /home/ubuntu/app
          nohup java -jar *.jar > ./output.log 2>&1 &

단계별 상세 설명

build 작업:

  • (1) ~ (4): GitHub Runner라는 가상 머신에 우리의 소스 코드를 받고, Java와 Gradle을 설치하여 빌드하는 과정입니다. 이전 방식에서는 이 과정을 EC2 서버에서 직접 했습니다.
  • (5) actions/upload-artifact: build 작업의 가장 중요한 부분입니다. ./gradlew build 명령어로 build/libs/ 경로에 생성된 .jar 파일을 my-app-jar라는 이름으로 GitHub Actions의 임시 저장 공간에 업로드합니다. 이 결과물은 deploy 작업에서 사용됩니다.

deploy 작업:

  • needs: build : 이 설정 덕분에 build 작업이 실패하면 deploy는 아예 시작조차 하지 않아 안전합니다.
  • (6) actions/download-artifact: build 작업에서 업로드했던 my-app-jar를 현재 Runner 환경으로 다운로드합니다. 이제 Runner는 EC2로 전송할 .jar 파일을 갖게 됩니다.
  • (7) appleboy/scp-action: scp는 SSH를 통해 파일을 안전하게 복사하는 명령어입니다. 다운로드한 .jar 파일을 EC2 서버의 /home/ubuntu/app 디렉토리로 전송합니다. 소스 코드 전체가 아닌, 실행에 필요한 파일만 전달하는 것이 핵심입니다.
  • (8) appleboy/ssh-action: EC2에 접속하여 마지막 배포 스크립트를 실행합니다. 이전 서버를 종료하고, scp로 전달받은 새로운 .jar 파일을 실행시켜 배포를 마무리합니다.

이처럼 빌드와 배포의 역할을 명확히 나누면, 운영 서버의 부담을 줄이고 CI/CD 파이프라인의 안정성과 확장성을 크게 높일 수 있습니다.