일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- R
- Stack
- Algorithm
- 이스케이프 문자
- datastructure
- list 컬렉션
- stream
- 알기쉬운 알고리즘
- Selection Sorting
- C programming
- Graph
- s
- coding test
- 메모리구조
- 혼자 공부하는 C언어
- 윤성우 열혈자료구조
- Serialization
- buffer
- C 언어 코딩 도장
- JSON
- insertion sort
- 윤성우의 열혈 자료구조
- 이것이 자바다
- Today
- Total
Engineering Note
[SW Engineering] GitHub Actions + Docker를 활용한 Spring Boot 자동 배포 파이프라인 구축 과정 본문
[SW Engineering] GitHub Actions + Docker를 활용한 Spring Boot 자동 배포 파이프라인 구축 과정
Software Engineer Kim 2025. 9. 28. 12:16Spring Boot 프로젝트에서 수동 배포의 번거로움을 해결하고 개발 생산성을 높이기 위한 완전 자동화 CI/CD 파이프라인을 구축하는 실용적 가이드이자 개발 과정에서 실제 적용한 방법을 기록하고 공유하기 위한 글입니다.
수동 배포의 한계점
- 반복적인 수작업: SSH 접속 → 코드 pull → 빌드 → 실행의 반복
- 배포 시간 소요: 매번 7분 이상의 수동 작업 필요
- 휴먼 에러: 수동 과정에서 발생하는 실수
- 개발 집중도 저하: 배포 과정에 신경 써야 하는 부담
목표 설정
- PR 병합 시 자동 배포 실현
- 배포 시간 대폭 단축
- 100% 자동화로 휴먼 에러 제거
- 개발자가 기능 개발에만 집중할 수 있는 환경 구축
전체 아키텍처
플로우:
- 개발자가 PR 생성 및 병합
- GitHub Actions가 자동 빌드 시작
- Docker 이미지 생성 및 GCP 서버 배포
- Nginx를 통한 서비스 제공
기술 선택 과정
GCP vs AWS
기준 | GCP | AWS |
프리티어 정책 | 3개월 크레딧 한도 내 무제한 | 사용량별 과금 |
비용 예측 | 크레딧 소진까지 안전 | 갑작스런 요금 발생 위험 |
데모 프로젝트 적합성 | 높음 | 낮음 (비용 부담) |
24시간 서비스 운영 | 크레딧 내에서 가능 | 비용 부담 |
선택: GCP (예측 가능한 비용과 안정적인 24시간 서비스 운영)
GitHub Actions vs Jenkins
기준 | GitHub Actions | Jenkins |
비용 | 무료 (퍼블릭 repo) | 별도 서버 비용 |
설정 복잡도 | 낮음 | 높음 |
유지보수 | GitHub에서 관리 | 직접 관리 필요 |
확장성 | 클라우드 기반 | 서버 리소스 제한 |
선택: GitHub Actions (간편함과 비용 효율성)
Docker 도입 이유
- 환경 일관성: 로컬, 개발, 운영 환경 동일화
- 배포 간소화: 이미지 하나로 모든 의존성 포함
- 확장성: 새 서버 추가 시 Docker만 설치하면 즉시 배포 가능
- 롤백 용이성: 이전 이미지로 즉시 복구 가능
서버 배포 연습을 하면서 클라우드를 인스턴스를 만들면서 jdk 설치, MySQL 설치 등 프로젝트를 위한 환경을 구축하면서 실수가 일어나기도 하고 서버마다 동일한 환경을 구축하는데 불편함을 겪어서 Docker를 통해 환경을 통일하고 배포를 간소화하기 위해 도입하기로 했습니다.
단계별 구현 가이드
1단계: 환경 설정
SSH 키 생성 및 등록
# 로컬에서 SSH 키 생성
ssh-keygen -t rsa -f ~/.ssh/gcp-deploy-key -C "ubuntu"
# 공개키를 GCP 서버에 등록
cat ~/.ssh/gcp-deploy-key.pub >> ~/.ssh/authorized_keys
공개키 등록 방법은 CLI 통한 방법 말고 GCP를 사용한다면 웹 환경에서 Compute Engine > 메타데이터를 통해서도 SSH 공개키를 등록할 수 있습니다.
GitHub Secrets 등록
GitHub Repository > Settings → Secrets and variables → Actions에서 등록:
- SERVER_HOST: Cloud 서버 IP 주소
- SERVER_USER: 서버 접속 사용자명 (예: ubuntu)
- SERVER_KEY: 생성한 SSH 개인키 전체 내용
2단계: Docker 설정
Dockerfile 작성
FROM openjdk:17-jdk-slim
# 앱 디렉토리 생성
WORKDIR /app
# JAR 파일 복사
COPY target/shop-0.0.1-SNAPSHOT.jar app.jar
# 포트 노출
EXPOSE 8080
# 애플리케이션 실행
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
docker-compose.yml 작성
version: "3.8"
services:
mysql:
image: mysql:9.0
container_name: shop-mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
MYSQL_DATABASE: shop
MYSQL_USER: shopuser
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-shoppassword}
ports:
- "3307:3306"
volumes:
- mysql-data:/var/lib/mysql
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 10s
retries: 5
app:
build: .
container_name: shop-app
depends_on:
mysql:
condition: service_healthy
ports:
- "8081:8080"
volumes:
- ./uploads:/app/uploads
environment:
SPRING_PROFILES_ACTIVE: prod
JAVA_OPTS: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
nginx:
image: nginx:alpine
container_name: shop-nginx
depends_on:
app:
condition: service_healthy
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./uploads:/usr/share/nginx/html/uploads:ro
restart: unless-stopped
volumes:
mysql-data:
3단계: GitHub Actions 워크플로우 구성
.github/workflows/deploy.yml
name: Deploy to GCP on PR Merge
on:
pull_request:
types: [closed]
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build application
run: |
mvn clean package -DskipTests
echo "JAR size: $(du -h target/shop-0.0.1-SNAPSHOT.jar)"
- name: Copy files to server
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_KEY }}
source: "target/shop-0.0.1-SNAPSHOT.jar,docker-compose.yml,Dockerfile,nginx/"
target: "/home/${{ secrets.SERVER_USER }}/shop-app/"
- name: Deploy application
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_KEY }}
script: |
set -e
cd /home/${{ secrets.SERVER_USER }}/shop-app/
echo "=== Starting deployment for PR #${{ github.event.pull_request.number }} ==="
# 필요한 디렉토리 생성
mkdir -p /home/${{ secrets.SERVER_USER }}/shop-app/uploads/item
# 기존 컨테이너 정리
docker-compose down --remove-orphans
docker image prune -f
# 새 버전 배포
docker-compose up -d --build
# 헬스체크 대기
echo "Waiting for application to start..."
for i in {1..12}; do
if curl -f http://localhost/actuator/health >/dev/null 2>&1; then
echo "✅ Application is healthy!"
exit 0
fi
echo "Attempt $i/12: Waiting 10 seconds..."
sleep 10
done
echo "❌ Application failed to start"
docker-compose logs app
exit 1
- name: Notify deployment status
if: always()
run: |
if [ "${{ job.status }}" == "success" ]; then
echo "✅ Deployment successful for PR #${{ github.event.pull_request.number }}"
else
echo "❌ Deployment failed for PR #${{ github.event.pull_request.number }}"
fi
4단계: Nginx 설정
nginx/nginx.conf
events {
worker_connections 1024;
}
http {
upstream app {
server app:8080;
}
server {
listen 80;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 정적 파일 서빙 (이미지 등)
location /images/ {
alias /usr/share/nginx/html/uploads/;
}
}
}
리버스 프록시로 Nginx를 도입했습니다. 프로젝트에서 사용하는 이미지 파일을 서빙하기 위한 경로도 설정해주었습니다.
모니터링 및 로그 관리
Docker 애플리케이션 서비스 컨테이너 로그 확인
# 애플리케이션 로그
docker-compose logs app -f
# 모든 서비스 로그
docker-compose logs -f
# 특정 시간 이후 로그
docker-compose logs --since 2h app
시스템 모니터링
# 컨테이너 상태 확인
docker-compose ps
# 리소스 사용량 확인
docker stats
# 디스크 사용량 확인
df -h
du -sh /var/lib/docker/
트러블슈팅 가이드
일반적인 문제들
1. 빌드 실패
# 해결: 캐시 정리 후 재빌드
mvn clean package -DskipTests
docker-compose down && docker system prune -f
docker-compose up -d --build
성과 및 효과
정량적 성과
- 배포 시간: 7분 → 2분 20초 (67% 단축)
- 빌드 성공률: 불안정 → 100% 안정화
- 개발자 개입 시간: 완전 자동화로 0분
정성적 효과
- 개발자가 배포 걱정 없이 기능 개발에 집중
- 빠른 피드백 루프로 개발 속도 향상
- 휴먼 에러 제거로 서비스 안정성 확보
결론
GitHub Actions와 Docker를 활용한 자동 배포 파이프라인 구축을 통해 개발 생산성을 크게 향상시킬 수 있었습니다. 특히 수동 작업 제거와 배포 시간 단축은 개발 집중도를 높이는 데 큰 도움이 되었습니다.
이 가이드를 통해 비슷한 환경에서 개발하시는 분들이 효율적인 배포 파이프라인을 구축하는 데 도움이 되기를 바랍니다.
'SW Engineering' 카테고리의 다른 글
[SW Engineering] 게시판 댓글 좋아요 기능 DB 설계 - UNIQUE 제약조건 활용하기 (0) | 2025.10.04 |
---|---|
[SW Engineering] 쇼핑몰 도메인 모델 설계를 통해 알아보는 JPA 엔티티 설계 의사결정 과정 (0) | 2025.09.30 |
[SW Engineering] 구체적인 사례로 보는 JavaScript Stack Trace 읽는 법 (0) | 2025.09.27 |
[SW Engineering] AJAX 응답 타입 불일치로 인한 TypeError 해결기 (0) | 2025.09.27 |
[SW Engineering] 이커머스 장바구니 조회 기능 구현: 복잡한 연관관계를 단일 쿼리로 해결하고, 복합 인덱스로 쿼리 최적화 (0) | 2025.09.24 |