Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Binary file added .github/.DS_Store
Binary file not shown.
83 changes: 83 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CD

on:
workflow_run:
workflows: ['CI']
types: [completed]

jobs:
# ============================================
# 서버 배포
# ============================================
deploy:
name: Deploy to Wisoft Server
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' }}

steps:
- name: SSH 키 설정
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.RASPI_SSH_PRIVATE_KEY }}

- name: Known Hosts 등록
env:
JUMP_PORT: ${{ secrets.RASPI_SSH_PORT }}
JUMP_HOST: ${{ secrets.RASPI_HOST }}
run: |
mkdir -p ~/.ssh
ssh-keyscan -p $JUMP_PORT -H $JUMP_HOST >> ~/.ssh/known_hosts

- name: 서버에 배포
env:
JUMP_PORT: ${{ secrets.RASPI_SSH_PORT }}
JUMP_HOST: ${{ secrets.RASPI_HOST }}
JUMP_USER: ${{ secrets.RASPI_USER }}
TARGET_HOST: ${{ secrets.RASPI_TARGET_HOST }}
TARGET_USER: ${{ secrets.RASPI_TARGET_USER }}
DEPLOY_PATH: ${{ secrets.RASPI_DEPLOY_PATH_JAVA }}
GHCR_PAT: ${{ secrets.GHCR_PAT }}
GITHUB_ACTOR: ${{ github.actor }}
run: |
ssh -o StrictHostKeyChecking=no -o ProxyJump=$JUMP_USER@$JUMP_HOST:$JUMP_PORT $TARGET_USER@$TARGET_HOST /bin/bash << ENDSSH
set -e

echo "=== 배포 디렉토리 이동 ==="
cd $DEPLOY_PATH

echo "=== GHCR 로그인 ==="
echo "$GHCR_PAT" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin

echo "=== 최신 이미지 Pull ==="
docker compose -f docker/docker-compose.prod.yml --env-file .env pull app

echo "=== 앱 컨테이너 재시작 ==="
docker compose -f docker/docker-compose.prod.yml --env-file .env up -d app

echo "=== Health Check (최대 60초) ==="
for i in \$(seq 1 12); do
if curl -sf http://localhost:7300/health > /dev/null; then
echo "Health check 통과"
exit 0
fi
echo "재시도 \$i/12"
sleep 5
done

echo "Health check 실패"
docker compose -f docker/docker-compose.prod.yml logs --tail=50 app
exit 1
ENDSSH

- name: 오래된 Docker 이미지 정리
if: success()
env:
JUMP_PORT: ${{ secrets.RASPI_SSH_PORT }}
JUMP_HOST: ${{ secrets.RASPI_HOST }}
JUMP_USER: ${{ secrets.RASPI_USER }}
TARGET_HOST: ${{ secrets.RASPI_TARGET_HOST }}
TARGET_USER: ${{ secrets.RASPI_TARGET_USER }}
run: |
ssh -o StrictHostKeyChecking=no -o ProxyJump=$JUMP_USER@$JUMP_HOST:$JUMP_PORT $TARGET_USER@$TARGET_HOST /bin/bash << ENDSSH
docker image prune -af --filter "until=72h" || true
ENDSSH
106 changes: 106 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: CI

on:
push:
branches:
- "*/**"
- main
pull_request:
branches:
- "*/**"
- main

jobs:
# ============================================
# Job 1: 테스트
# ============================================
test:
name: Test
runs-on: ubuntu-latest

services:
postgres:
image: postgres:17
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: interview_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- name: 코드 체크아웃
uses: actions/checkout@v4

- name: JDK 21 설정
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

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

- name: 테스트 실행
env:
SPRING_PROFILES_ACTIVE: test
run: ./gradlew test --no-daemon

# ============================================
# Job 2: Docker 이미지 빌드 + GHCR 푸시
# ============================================
build:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

permissions:
contents: read
packages: write

steps:
- name: 코드 체크아웃
uses: actions/checkout@v4

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

- name: GHCR 로그인
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: 이미지 메타데이터 설정
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest
type=sha,prefix=

- name: Docker 이미지 빌드 & 푸시
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ FROM mcr.microsoft.com/playwright/java:v1.49.0-noble

WORKDIR /app

RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y ffmpeg curl && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/build/libs/*.jar app.jar

# 포트 문서화
EXPOSE 7300

# 컨테이너 레벨 헬스체크 (Docker daemon의 status 표시용)
HEALTHCHECK --interval=24h --timeout=10s --start-period=40s --retries=3 \
CMD curl -sf http://localhost:7300/health || exit 1
Comment on lines +36 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

HEALTHCHECK의 interval이 24시간(24h)으로 설정되어 있습니다. 이는 컨테이너의 상태 변화를 감지하기에 너무 긴 시간입니다. 일반적으로 30초(30s) 또는 1분(1m) 정도로 설정하는 것이 적절합니다. 또한, Spring Boot Actuator를 의존성에 추가하셨으므로 기본 헬스체크 경로인 /actuator/health를 사용하는 것이 좋습니다.

HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -sf http://localhost:7300/actuator/health || exit 1


# 실행
ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "app.jar"]
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ dependencies {
implementation 'software.amazon.awssdk:s3:2.26.12'
runtimeOnly 'org.postgresql:postgresql'

implementation 'org.springframework.boot:spring-boot-starter-actuator'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml → docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ services:
app:
build:
context: .
dockerfile: Dockerfile
dockerfile: Dockerfile.prev
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Dockerfile.prev 파일이 프로젝트에 존재하지 않는 것으로 보입니다. 기존의 Dockerfile을 사용하도록 수정하거나, 해당 파일이 의도적으로 생성된 것인지 확인이 필요합니다.

      dockerfile: Dockerfile

container_name: interview-api
ports:
- "7300:7300"
Expand Down
70 changes: 70 additions & 0 deletions docker-compose-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
services:
fluent-bit:
image: fluent/fluent-bit:3.2
container_name: interview-fluent-bit
volumes:
- ./fluentbit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro
- ./fluentbit/parsers.conf:/fluent-bit/etc/parsers.conf:ro
Comment on lines +6 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Fluent-Bit 설정 파일의 호스트 경로가 프로젝트 구조와 일치하지 않습니다. 현재 프로젝트 구조상 설정 파일은 ./docker/fluent-bit/ 디렉토리에 위치하므로 이를 반영해야 합니다.

      - ./docker/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro
      - ./docker/fluent-bit/parsers.conf:/fluent-bit/etc/parsers.conf:ro

ports:
- "24224:24224"
environment:
- ELASTIC_USERNAME=${ELASTIC_USERNAME}
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
networks:
- interview-network
- logging-network
restart: unless-stopped

postgres:
image: postgres:17
container_name: interview-postgres
ports:
- "${POSTGRES_PORT}:5432"
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- interview-network
restart: unless-stopped

app:
image: ghcr.io/wisoft-prepair/backend-java:latest
container_name: interview-api
ports:
- "7300:7300"
env_file:
- .env
environment:
- SPRING_PROFILES_ACTIVE=prod
- TZ=Asia/Seoul
depends_on:
postgres:
condition: service_healthy
fluent-bit:
condition: service_started
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: interview-service
fluentd-async: "true"
networks:
- interview-network
restart: unless-stopped

volumes:
postgres-data:

networks:
interview-network:
driver: bridge
logging-network:
external: true
9 changes: 8 additions & 1 deletion docker/fluent-bit/fluent-bit.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[SERVICE]
Flush 1
Daemon Off
Log_Level info
Parsers_File parsers.conf

Expand All @@ -15,6 +16,12 @@
Parser json
Reserve_Data On

[FILTER]
Name modify
Match *
Add source fluent-bit
Add host ${HOSTNAME}

[OUTPUT]
Name es
Match *
Expand All @@ -24,4 +31,4 @@
HTTP_Passwd ${ELASTIC_PASSWORD}
Index interview-service
Retry_Limit 5
Suppress_Type_Name On
Suppress_Type_Name On
Loading