Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name: Build and Deploy

# 조건
on:
workflow_dispatch:
workflow_dispatch: # 수동 실행 가능
push:
branches: ["main", "develop"]
branches: ["main", "develop"] # main, develop 브랜치 푸시

permissions:
contents: read
contents: read # 코드 읽기

jobs:
build:
Expand All @@ -27,7 +28,7 @@ jobs:
java-version: '21'
distribution: 'temurin'

# Gradle wrapper 실행 권한 부여
# Gradle Wrapper 실행 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x gradlew

Expand Down Expand Up @@ -72,7 +73,7 @@ jobs:
echo "REDIS_PASSWORD=${{ secrets.REDIS_DEV_PASSWORD }}" >> $GITHUB_OUTPUT
fi

# docker-compose.server.yml 체크아웃
# 배포 파일 체크아웃
- name: Checkout
uses: actions/checkout@v4
with:
Expand All @@ -94,7 +95,7 @@ jobs:
fi
chmod 600 ~/.ssh/deploy_key.pem

# EC2로 docker-compose.server.yml, server-deploy.sh 복사
# EC2로 배포 파일 전송
- name: Prepare remote directories and copy files
run: |
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key.pem ubuntu@${{ steps.deploy-env.outputs.EC2_HOST }} \
Expand Down Expand Up @@ -127,16 +128,16 @@ jobs:

# SSH Key 정리
- name: Cleanup SSH Key
if: always()
if: always() # 항상 실행
run: |
rm -f ~/.ssh/deploy_key.pem

# 결과 알림
- name: Notify Discord
if: always()
if: always() # 항상 실행
uses: Ilshidur/action-discord@master
env:
DISCORD_WEBHOOK: ${{ github.ref_name == 'main' && secrets.DISCORD_MAIN_WEBHOOK || secrets.DISCORD_DEV_WEBHOOK }}
DISCORD_WEBHOOK: ${{ github.ref_name == 'main' && secrets.DISCORD_PROD_WEBHOOK || secrets.DISCORD_DEV_WEBHOOK }}
with:
args: |
배포 결과: ${{ job.status == 'success' && '✅ 서버 배포 성공(Blue-Green 전환 완료)' || '❌ 서버 배포 실패 (기존 컨테이너 유지)' }}
Expand Down
31 changes: 17 additions & 14 deletions .github/workflows/TEST.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
name: Test

# 조건
on:
workflow_dispatch:
workflow_dispatch: # 수동 실행 가능
push:
branches-ignore:
branches-ignore: # main, develop 제외한 모든 브랜치 푸시
- main
- develop
pull_request:
pull_request: # main, develop으로 PR 생성
branches: ["main", "develop"]

permissions:
contents: read
checks: write
pull-requests: write
contents: read # 코드 읽기
checks: write # 테스트 결과 작성
pull-requests: write # PR 코멘트 작성

jobs:
build:
runs-on: ubuntu-latest
# 브랜치 생성 직후 실행 방지
if: github.event_name != 'push' || github.event.created == false

steps:
# 코드 체크아웃 (서브모듈 포함)
Expand All @@ -34,13 +37,13 @@ jobs:
java-version: '21'
distribution: 'temurin'

# Redis 서비스 시작
# 테스트용 Redis 실행
- name: Start Redis
uses: supercharge/redis-github-action@1.7.0
with:
redis-version: 7

# Gradle 캐시 설정
# Gradle 의존성 캐싱
- name: Cache Gradle
uses: actions/cache@v3
with:
Expand All @@ -51,25 +54,25 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-

# Gradle wrapper 실행 권한 부여
# Gradle Wrapper 실행 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x gradlew

# 빌드 및 테스트 실행
- name: Build and Test
run: ./gradlew clean build -Dspring.profiles.active=test
continue-on-error: true
continue-on-error: true # 테스트 실패해도 리포트 생성 위해

# 테스트 결과 보고
# 테스트 결과 리포트 생성
- name: Report Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
if: always() # 항상 실행
with:
files: '**/build/test-results/test/TEST-*.xml'

# PR에 테스트 결과 코멘트 추가
# PR에 테스트 결과 코멘트
- name: Comment Test Summary on PR
uses: mikepenz/action-junit-report@v3
if: always()
if: always() # 항상 실행
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# Java 21 런타임 이미지
FROM eclipse-temurin:21-jre-jammy

# 작업 디렉토리
WORKDIR /app

# 빌드된 JAR 파일 복사
COPY build/libs/*.jar app.jar

# 보안을 위한 전용 사용자 생성
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
RUN chown -R appuser:appgroup /app
USER appuser

# 애플리케이션 포트 설정
EXPOSE 8080

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ tasks.named('test') {
useJUnitPlatform()
}

// config 폴더의 .properties 파일을 src/main/resources로 복사
processResources.dependsOn('copyPrivateProperties')
tasks.register("copyPrivateProperties", Copy) {
from './config'
Expand Down
12 changes: 7 additions & 5 deletions deploy/docker-compose.infra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ services:
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
ports:
- "6379:6379"
- "6379:6379" # 호스트:컨테이너
volumes:
- redis-data:/data
- redis-data:/data # 데이터 저장
networks:
- devops-study

Expand All @@ -16,16 +16,18 @@ services:
container_name: nginx_container
restart: unless-stopped
volumes:
- /home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /home/ubuntu/nginx/logs:/var/log/nginx
- /home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf:ro # 설정 파일 (읽기 전용)
- /home/ubuntu/nginx/logs:/var/log/nginx # 로그 저장
ports:
- "80:80"
- "80:80" # 호스트:컨테이너
networks:
- devops-study

# 외부 네트워크 (server.yml과 공유)
networks:
devops-study:
external: true

# 볼륨
volumes:
redis-data:
5 changes: 3 additions & 2 deletions deploy/docker-compose.server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
ports:
- "8081:8080"
- "8081:8080" # 호스트:컨테이너
networks:
- devops-study

Expand All @@ -31,10 +31,11 @@ services:
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
ports:
- "8082:8080"
- "8082:8080" # 호스트:컨테이너
networks:
- devops-study

# 외부 네트워크 (infra.yml과 공유)
networks:
devops-study:
external: true
28 changes: 16 additions & 12 deletions deploy/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
# 워커 프로세스 수 (CPU 코어 수에 맞춰 자동 설정)
worker_processes auto;

events {
# 워커당 최대 동시 연결 수
worker_connections 1024;
}

http {
# 활성 서버 설정
# 활성 서버 설정 (Blue-Green 배포 시 변경)
upstream backend {
server springboot_blue_container:8080;
}

server {
# HTTP 포트
listen 80;

# 메인 트래픽
# 메인 트래픽 (모든 요청을 백엔드로 프록시)
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://backend; # upstream backend로 요청 전달

proxy_set_header Host $host; # 원본 도메인 전달
proxy_set_header X-Real-IP $remote_addr; # 클라이언트 실제 IP 전달
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 프록시 체인 전체 IP 기록
proxy_set_header X-Forwarded-Proto $scheme; # 원본 프로토콜 전달 (http/https)
}

# 로그 설정
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log; # 접속 기록
error_log /var/log/nginx/error.log warn; # 에러 기록

# 타임아웃 설정
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
proxy_connect_timeout 5s; # 백엔드 연결 타임아웃
proxy_read_timeout 30s; # 응답 읽기 타임아웃
proxy_send_timeout 30s; # 요청 전송 타임아웃
}
}
24 changes: 13 additions & 11 deletions deploy/server-deploy.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/bin/bash
set -e

# 0. devops-study 네트워크 없으면 생성
# Docker 네트워크(devops-study) 확인 및 생성
if ! docker network ls | grep -q devops-study; then
echo "📦 Docker 네트워크 생성"
docker network create devops-study
fi

# 1. Redis, Nginx 미실행 시 실행
# Redis, Nginx 실행 확인 및 시작
for SERVICE in redis nginx; do
CONTAINER=$(docker ps --filter "name=${SERVICE}_container" --format "{{.Names}}")
if [ -z "$CONTAINER" ]; then
Expand All @@ -16,16 +16,18 @@ for SERVICE in redis nginx; do
fi
done

# 2. 실행 중인 컨테이너 확인
# 현재 실행 중인 Spring Boot 컨테이너 확인 (Blue or Green)
SPRINGBOOT_CONTAINER=$(docker ps --filter "name=springboot_" --format "{{.Names}}")

# Blue 실행 중 -> Green으로 배포
if [ "$SPRINGBOOT_CONTAINER" == "springboot_blue_container" ]; then
NEW_PORT=8082
NEW_SERVICE="app_green"
NEW_CONTAINER="springboot_green_container"
OLD_SERVICE="app_blue"
OLD_CONTAINER="springboot_blue_container"
echo "🟢 Green(8082) 배포 시작"
# Green 실행 중 또는 없음 -> Blue로 배포
else
NEW_PORT=8081
NEW_SERVICE="app_blue"
Expand All @@ -35,10 +37,10 @@ else
echo "🔵 Blue(8081) 배포 시작"
fi

# 3. 이미지 Pull
# 이미지 Pull
docker pull ${DOCKER_IMAGE}

# 4. 새 컨테이너 시작
# 새 컨테이너 시작
docker compose -f /home/ubuntu/deploy/docker-compose.server.yml up -d --no-deps ${NEW_SERVICE}

# 헬스체크 (최대 45초)
Expand All @@ -58,20 +60,20 @@ for i in {1..9}; do
sleep 5
done


# 새로운 컨테이너 헬스체크 성공한 경우만 이후 단계 진행
# 5. Nginx 설정 변경
# 새로운 컨테이너 헬스체크 성공 시에만 진행
# Nginx upstream 변경 (Blue <-> Green)
if [ -n "$SPRINGBOOT_CONTAINER" ]; then
# 임시파일에 새로운 설정 내용 작성한 뒤 기존 파일에 덮어씌우기 (i-node 유지)
sed "s/${OLD_CONTAINER}:8080/${NEW_CONTAINER}:8080/" /home/ubuntu/nginx/nginx.conf > /tmp/nginx_temp.conf
cat /tmp/nginx_temp.conf > /home/ubuntu/nginx/nginx.conf
rm /tmp/nginx_temp.conf

# 6. Nginx 리로드
# Nginx 설정 리로드
docker exec nginx_container nginx -s reload

# 7. 이전 컨테이너 중지
# 이전 컨테이너 중지
docker compose -f /home/ubuntu/deploy/docker-compose.server.yml stop ${OLD_SERVICE}
fi

# 8. 리소스 정리
# 미사용 리소스 정리
docker system prune -f > /dev/null 2>&1
13 changes: 7 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@ services:
MYSQL_PASSWORD: studentpw
TZ: Asia/Seoul
ports:
- "3306:3306"
- "3306:3306" # 호스트:컨테이너
volumes:
- ./db/mysql/data:/var/lib/mysql
- ./db/mysql/init:/docker-entrypoint-initdb.d
- ./db/mysql/data:/var/lib/mysql # 데이터 저장
# - ./db/mysql/init:/docker-entrypoint-initdb.d # 초기화 스크립트
networks:
- devops-study

redis:
image: redis:7.0
container_name: redis_container
restart: always
command: redis-server --requirepass studentpw
command: redis-server --requirepass studentpw --appendonly yes
ports:
- "6379:6379"
- "6379:6379" # 호스트:컨테이너
volumes:
- ./db/redis/data:/data
- ./db/redis/data:/data # 데이터 저장
networks:
- devops-study

# 네트워크 설정
networks:
devops-study:
driver: bridge