1. 역할 분담: 일하는 곳(GitHub Runner)과 보여주는 곳(EC2)
가장 먼저 이해해야 할 핵심 개념은 역할의 분리입니다.
- 이전 방식 (EC2에서 빌드): EC2 서버가 **빌드/테스트(일하는 곳)**와 서비스 운영(보여주는 곳) 역할을 모두 담당했습니다. 이는 가게 주방과 손님 테이블이 합쳐진 것과 같아서, 요리(빌드) 중에 가게가 어수선해지거나(서버 성능 저하) 문제가 생길 수 있습니다.
- 표준 방식 (GitHub Actions에서 빌드): GitHub Actions가 제공하는 가상 머신(GitHub Runner)이 **빌드/테스트(일하는 곳)**를 전담합니다. EC2 서버는 Runner가 완성한 결과물(음식)을 받아 **서비스 운영(보여주는 곳)**에만 집중합니다. 주방과 홀이 완벽히 분리되어 각자의 역할에만 충실하는 효율적인 레스토랑과 같습니다.
2. 표준 CI/CD 파이프라인의 개념적 흐름
이 방식의 전체적인 흐름은 다음과 같이 진행됩니다.
1단계: 빌드와 테스트 (in GitHub Actions Runner)
- 트리거: 개발자가 코드를 git push 합니다.
- 가상 환경 준비: GitHub Actions가 ubuntu-latest 같은 깨끗한 가상 머신(Runner)을 즉시 생성하고 실행합니다.
- 코드 다운로드: Runner가 소스 코드를 git clone (checkout) 합니다.
- 환경 설정 및 테스트: Runner 내부에 Java, Node.js 등 프로젝트에 필요한 환경을 설치하고, 의존성을 다운로드한 뒤, 작성된 테스트 코드를 실행하여 코드의 정합성을 검증합니다.
- 빌드 실행: 모든 테스트가 통과하면, Runner가 소스 코드를 컴파일하고 압축하여 실행 가능한 파일 묶음(Build Artifact)을 생성합니다. (예: Java의 .jar 파일, 웹 프론트엔드의 build 또는 dist 폴더)
2단계: 결과물 전달 및 배포 (Runner → EC2)
- 아티팩트 패키징: 생성된 빌드 아티팩트(예: app.jar)를 압축하거나 GitHub Actions 내에 임시 저장합니다.
- EC2에 보안 접속 및 전달: Runner가 SSH를 통해 EC2 서버에 안전하게 접속한 뒤, 패키징된 아티팩트 파일을 EC2 서버로 전송합니다. (scp나 rsync 같은 명령어를 사용)
- 서버 애플리케이션 교체: EC2 서버에 접속된 상태에서 스크립트를 실행합니다.
- 기존에 실행 중이던 구버전 애플리케이션을 종료합니다.
- 전달받은 새 버전의 아티팩트 파일의 압축을 풀고 제자리로 옮깁니다.
- 새 버전의 애플리케이션을 실행합니다.
왜 이 방식이 더 좋을까요?
- 🚀 서버 안정성 확보: EC2 서버는 빌드 과정의 리소스 소모(CPU, 메모리 사용량 급증)로부터 자유로워집니다. 오직 안정적으로 서비스를 제공하는 데에만 집중할 수 있습니다.
- ✨ 깨끗하고 일관된 빌드 환경: 매번 깨끗한 가상 환경에서 빌드를 시작하므로, 이전에 남아있던 파일이나 설정 때문에 빌드가 실패하는 "내 컴퓨터에선 됐는데..." 같은 문제를 원천적으로 방지할 수 있습니다.
- 🔒 보안 강화: 소스 코드 전체나 .git 폴더를 운영 서버에 둘 필요가 없습니다. 오직 실행에 필요한 최소한의 빌드 결과물만 전달하므로 보안적으로 더 안전합니다.
- 🐳 확장성 (컨테이너화 연계): 이 방식은 추후 Docker 이미지를 빌드하고 레지스트리에 푸시한 뒤, EC2에서 해당 이미지를 내려받아 실행하는 컨테이너 기반 배포로 자연스럽게 확장하기 매우 용이합니다.
이 개념을 먼저 이해하시면, 앞으로 보게 될 다양한 CI/CD 워크플로우 .yml 파일들이 어떤 역할을 왜 수행하는지 훨씬 쉽게 파악하실 수 있을 겁니다.
3. 실습 - 표준 CI/CD 파이프라인 구축: 빌드와 배포 분리하기
(이거는 Gemini 부분 답변이라 정확하지 않을수도 있으므로 나중 유료 강의보고 틀린부분 있으면 수정 or 직접해보고 수정)
이제 개념을 넘어 실제 워크플로우 파일을 작성해 보겠습니다. 이 방식의 핵심은 두 개의 독립적인 작업(Job)을 만들어 체인처럼 연결하는 것입니다. build 작업이 성공해야만 deploy 작업이 실행됩니다.
전체 흐름:
- Build Job: GitHub Runner 환경에서 코드를 테스트하고 빌드하여 .jar 같은 결과물(Artifact)을 생성합니다.
- Upload Artifact: 생성된 결과물을 GitHub Actions의 임시 저장 공간에 업로드합니다.
- Deploy Job: build 작업이 끝나면 새로운 Runner 환경에서 시작됩니다.
- Download Artifact: 임시 저장 공간에서 빌드 결과물을 다운로드합니다.
- 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 파이프라인의 안정성과 확장성을 크게 높일 수 있습니다.
'CICD' 카테고리의 다른 글
| 2. 개인 프로젝트에 CI/CD 구축법(EC2에서 빌드/테스트) (0) | 2025.09.03 |
|---|---|
| 1. CI/CD의 개념,기본 문법 (0) | 2025.09.03 |