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
126 changes: 126 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: Deploy Prod Backend

concurrency:
group: be-prod
cancel-in-progress: true # Deploy 작업은 한번에 하나만 실행하도록 제한

on:
push:
tags:
- 'v*.*.*-be'

jobs:
build-and-push:
runs-on: ubuntu-latest

outputs:
version: ${{ steps.get_version.outputs.VERSION }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # 태그 검증을 위해 전체 Git 히스토리 필요

- name: tag가 be/prod branch에 있는지 검증
run: |
git fetch origin be/prod
if git merge-base --is-ancestor ${{ github.sha }} origin/be/prod; then
echo "이 태그는 be/prod branch에 포함되어 있습니다. 배포를 진행합니다."
else
echo "Error: 이 tag는 be/prod branch에 없습니다."
exit 1
fi

- name: Git tag에서 버전 정보 추출
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"

- name: Docker Hub 로그인
run: echo "${{ secrets.DOCKER_ACCESS_TOKEN }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

- name: QEMU 설정
uses: docker/setup-qemu-action@v3

- name: Docker Buildx 설정
uses: docker/setup-buildx-action@v3

- name: Docker Image Build & Push
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile
push: true
platforms: linux/arm64
tags: |
${{ secrets.PROD_IMAGE_NAME }}:${{ steps.get_version.outputs.VERSION }}
${{ secrets.PROD_IMAGE_NAME }}:latest

deploy:
runs-on: [ self-hosted, recycle-study-server-prod ]
needs: build-and-push

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: .env file 생성
run: |
cat <<EOF > .env
DOCKER_IMAGE=${{ secrets.PROD_IMAGE_NAME }}
DOCKER_TAG=${{ needs.build-and-push.outputs.version }}
${{ secrets.ENV_PROD }}
EOF

- name: log directory 준비
run: |
sudo mkdir -p /app/log
sudo chown -R 1001:1001 /app/log

- name: 배포 시작
run: |
docker compose -f docker-compose.prod.yaml pull
docker compose -f docker-compose.prod.yaml up -d

- name: 신규 deploy에 대한 health check
run: |
echo "=========================================="
echo "Health Check 시작"
echo "=========================================="

echo "애플리케이션 초기화 대기 중... (10초)"
sleep 10

# 최대 12번 시도 (총 60초)
for i in {1..12}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/health || echo "000")

if [ "$STATUS" -eq 200 ]; then
echo "=========================================="
echo "배포 성공!"
echo "=========================================="
docker ps --filter "name=recycle-study-server"
exit 0
fi

echo "[$i/12] 헬스체크 재시도... (응답 코드: $STATUS)"
sleep 5
done

# Health Check 실패 시
echo "=========================================="
echo "Health Check 실패"
echo "=========================================="
echo "컨테이너 상태:"
docker ps -a --filter "name=recycle-study-server"
echo ""
echo "컨테이너 로그:"
docker logs recycle-study-server --tail 100 || true

exit 1

- name: Docker resource 정리
if: always()
run: |
docker image prune -a -f
docker builder prune -a -f
34 changes: 34 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Build stage
FROM amazoncorretto:21-alpine3.19-jdk AS builder

WORKDIR /app

COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src

RUN chmod +x ./gradlew
RUN ./gradlew bootJar --no-daemon

# Runtime stage
FROM amazoncorretto:21-alpine3.19

WORKDIR /app

RUN apk add --no-cache curl tzdata && \
cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
echo "Asia/Seoul" > /etc/timezone && \
apk del tzdata

RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -D appuser
RUN mkdir -p /app/log && chown -R appuser:appgroup /app

COPY --from=builder --chown=appuser:appgroup /app/build/libs/*.jar app.jar

USER appuser

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ dependencies {
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'

// monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured'
testRuntimeOnly 'com.h2database:h2'
Expand Down Expand Up @@ -112,6 +116,10 @@ tasks.register('copySwaggerDocument', Copy) {
into file("src/main/resources/static/docs")
}

jar {
enabled = false
}

bootJar {
dependsOn copySwaggerDocument
}
11 changes: 11 additions & 0 deletions docker-compose.prod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
app:
container_name: recycle-study-server
image: ${DOCKER_IMAGE}:${DOCKER_TAG}
restart: unless-stopped
ports:
- "8080:8080"
env_file:
- .env
volumes:
- /app/log:/app/log
18 changes: 18 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
services:
app:
container_name: recycle-study-server
build: .
restart: unless-stopped
ports:
- "8080:8080"
env_file:
- .env
depends_on:
mysql:
condition: service_healthy

mysql:
container_name: recycle-study-mysql
image: mysql:8.4
volumes:
- mysql_volume:/var/lib/mysql
Expand All @@ -11,6 +24,11 @@ services:
- MYSQL_DATABASE=${DB_DATABASE}
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ]
interval: 10s
timeout: 5s
retries: 5

volumes:
mysql_volume:
6 changes: 6 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@ spring:
enable: true
auth: true

management:
endpoints:
web:
exposure:
include: prometheus, health

auth:
base-url: ${BASE_URL}
30 changes: 15 additions & 15 deletions src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

<property name="LOG_PATH" value="./logs"/>
<property name="LOG_PATH" value="/app/log"/>
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%blue(%X{traceId:-NO_TRACE_ID})] %magenta(%-40.40logger{39}) : %msg%n"/>
<property name="FILE_LOG_PATTERN"
Expand All @@ -15,26 +15,26 @@
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>180</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>

<springProfile name="local | default">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>

<springProfile name="prod | dev">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>180</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
Expand Down