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
145 changes: 145 additions & 0 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Build and Deploy

on:
workflow_dispatch:
push:
branches: ["main", "develop"]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
# 코드 체크아웃 (서브모듈 포함)
- name: Checkout With Submodule
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
fetch-depth: 0

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

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

# Gradle 빌드 (테스트 제외)
- name: Build with Gradle
run: ./gradlew clean build -x test

# Docker Hub 로그인
- name: DockerHub Login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_ID }}
password: ${{ secrets.DOCKER_PASSWORD }}

# Docker 이미지 빌드 & 푸시
- name: Docker build & Push
run: |
docker build -t ${{ secrets.DOCKER_ID }}/${{ secrets.DOCKER_REPOSITORY }}:${{ github.ref_name }}-${{ github.sha }} .
docker push ${{ secrets.DOCKER_ID }}/${{ secrets.DOCKER_REPOSITORY }}:${{ github.ref_name }}-${{ github.sha }}

deploy:
needs: build
runs-on: self-hosted
steps:
# 배포 환경 변수 설정
- name: Set deployment target
id: deploy-env
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "EC2_HOST=${{ secrets.EC2_PROD_HOST }}" >> $GITHUB_OUTPUT
echo "PROFILE=prod" >> $GITHUB_OUTPUT
echo "DB_URL=${{ secrets.DB_PROD_URL }}" >> $GITHUB_OUTPUT
echo "DB_USERNAME=${{ secrets.DB_PROD_USERNAME }}" >> $GITHUB_OUTPUT
echo "DB_PASSWORD=${{ secrets.DB_PROD_PASSWORD }}" >> $GITHUB_OUTPUT
echo "REDIS_PASSWORD=${{ secrets.REDIS_PROD_PASSWORD }}" >> $GITHUB_OUTPUT
else
echo "EC2_HOST=${{ secrets.EC2_DEV_HOST }}" >> $GITHUB_OUTPUT
echo "PROFILE=dev" >> $GITHUB_OUTPUT
echo "DB_URL=${{ secrets.DB_DEV_URL }}" >> $GITHUB_OUTPUT
echo "DB_USERNAME=${{ secrets.DB_DEV_USERNAME }}" >> $GITHUB_OUTPUT
echo "DB_PASSWORD=${{ secrets.DB_DEV_PASSWORD }}" >> $GITHUB_OUTPUT
echo "REDIS_PASSWORD=${{ secrets.REDIS_DEV_PASSWORD }}" >> $GITHUB_OUTPUT
fi

# docker-compose.server.yml 체크아웃
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: |
deploy/docker-compose.server.yml
deploy/docker-compose.infra.yml
deploy/server-deploy.sh
sparse-checkout-cone-mode: false

# SSH Key 설정
- name: Setup SSH Key
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "${{ secrets.EC2_PROD_PEM_KEY }}" > ~/.ssh/deploy_key.pem
else
echo "${{ secrets.EC2_DEV_PEM_KEY }}" > ~/.ssh/deploy_key.pem
fi
chmod 600 ~/.ssh/deploy_key.pem

# EC2로 docker-compose.server.yml, server-deploy.sh 복사
- name: Prepare remote directories and copy files
run: |
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key.pem ubuntu@${{ steps.deploy-env.outputs.EC2_HOST }} \
"mkdir -p /home/ubuntu/deploy"

scp -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key.pem -r deploy/* ubuntu@${{ steps.deploy-env.outputs.EC2_HOST }}:/home/ubuntu/deploy/

# EC2에 배포
- name: Deploy to EC2
run: |
ssh -o StrictHostKeyChecking=no \
-i ~/.ssh/deploy_key.pem \
ubuntu@${{ steps.deploy-env.outputs.EC2_HOST }} << ENDSSH

set -e
cd /home/ubuntu

# 환경변수 설정
export DOCKER_IMAGE="${{ secrets.DOCKER_ID }}/${{ secrets.DOCKER_REPOSITORY }}:${{ github.ref_name }}-${{ github.sha }}"
export SPRING_PROFILE="${{ steps.deploy-env.outputs.PROFILE }}"
export DB_URL="${{ steps.deploy-env.outputs.DB_URL }}"
export DB_USERNAME="${{ steps.deploy-env.outputs.DB_USERNAME }}"
export DB_PASSWORD="${{ steps.deploy-env.outputs.DB_PASSWORD }}"
export REDIS_PASSWORD="${{ steps.deploy-env.outputs.REDIS_PASSWORD }}"

# Blue-Green 배포 스크립트 실행
chmod +x /home/ubuntu/deploy/server-deploy.sh
/home/ubuntu/deploy/server-deploy.sh
ENDSSH

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

# 결과 알림
- name: Notify Discord
if: always()
uses: Ilshidur/action-discord@master
env:
DISCORD_WEBHOOK: ${{ github.ref_name == 'main' && secrets.DISCORD_MAIN_WEBHOOK || secrets.DISCORD_DEV_WEBHOOK }}
with:
args: |
배포 결과: ${{ job.status == 'success' && '✅ 서버 배포 성공(Blue-Green 전환 완료)' || '❌ 서버 배포 실패 (기존 컨테이너 유지)' }}
- 브랜치: ${{ github.ref_name }}
- 서버: ${{ steps.deploy-env.outputs.EC2_HOST }}
- 로그: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
75 changes: 75 additions & 0 deletions .github/workflows/TEST.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Test

on:
workflow_dispatch:
push:
branches-ignore:
- main
- develop
pull_request:
branches: ["main", "develop"]

permissions:
contents: read
checks: write
pull-requests: write

jobs:
build:
runs-on: ubuntu-latest

steps:
# 코드 체크아웃 (서브모듈 포함)
- name: Checkout With Submodule
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
fetch-depth: 0

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

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

# Gradle 캐시 설정
- name: Cache Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

# 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

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

# PR에 테스트 결과 코멘트 추가
- name: Comment Test Summary on PR
uses: mikepenz/action-junit-report@v3
if: always()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ out/

### VS Code ###
.vscode/

# 설정 파일
src/main/resources/*.properties

# Docker 로컬 볼륨 데이터
db/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "config"]
path = config
url = https://github.com/JS-DevOps-Study/docker-compose-deploy-config.git
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM eclipse-temurin:21-jre-jammy

WORKDIR /app

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"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Spring Boot 기반 프로젝트 템플릿
- 형식: `타입/ISSUE번호-설명`
- 타입: feature, bugfix, refactor, chore, test, hotfix, docs
- ex)
- feature/#114-auth-login
- feature/114-auth-login
- release/v1.2.0 (예외)

---
Expand Down
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// Spring Security
// implementation 'org.springframework.boot:spring-boot-starter-security'
Expand All @@ -53,3 +54,10 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}

processResources.dependsOn('copyPrivateProperties')
tasks.register("copyPrivateProperties", Copy) {
from './config'
include "*.properties"
into 'src/main/resources'
}
1 change: 1 addition & 0 deletions config
Submodule config added at 76aff0
31 changes: 31 additions & 0 deletions deploy/docker-compose.infra.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
services:
redis:
image: redis:7.0
container_name: redis_container
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- devops-study

nginx:
image: nginx:alpine
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
ports:
- "80:80"
networks:
- devops-study

networks:
devops-study:
external: true

volumes:
redis-data:
40 changes: 40 additions & 0 deletions deploy/docker-compose.server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
services:
app_blue:
image: ${DOCKER_IMAGE}
container_name: springboot_blue_container
restart: unless-stopped
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILE}
- TZ=Asia/Seoul
- DB_URL=${DB_URL}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_HOST=redis_container
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
ports:
- "8081:8080"
networks:
- devops-study

app_green:
image: ${DOCKER_IMAGE}
container_name: springboot_green_container
restart: unless-stopped
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILE}
- TZ=Asia/Seoul
- DB_URL=${DB_URL}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_HOST=redis_container
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
ports:
- "8082:8080"
networks:
- devops-study

networks:
devops-study:
external: true
34 changes: 34 additions & 0 deletions deploy/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
worker_processes auto;

events {
worker_connections 1024;
}

http {
# 활성 서버 설정
upstream backend {
server springboot_blue_container:8080;
}

server {
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;
}

# 로그 설정
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;
}
}
Loading