Skip to content
Open
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
164 changes: 74 additions & 90 deletions .github/workflows/backend-cd.yml
Original file line number Diff line number Diff line change
@@ -1,90 +1,74 @@
name: ✨ Linkiving backend CD ✨

on:
workflow_dispatch:
pull_request:
types: [ closed ]
branches:
- main

jobs:
backend-docker-build-and-push:
if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' }}
runs-on: ubuntu-latest

steps:
- name: ✨ Checkout repository
uses: actions/checkout@v3

- name: ✨ JDK 17 설정
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

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

- name: 🗂️ Make config
run: |
# src/main/resources 경로 이동
mkdir -p ./src/main/resources
cd ./src/main/resources

# yml 파일 생성

touch ./application.yml
echo "$APPLICATION" > ./application.yml

env:
APPLICATION: ${{ secrets.APPLICATION }}
shell: bash

- name: ✨ Gradlew 권한 설정
run: chmod +x ./gradlew

- name: ✨ Jar 파일 빌드
run: |
./gradlew bootJar

- name: ✨ DockerHub에 로그인
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}

- name: ✨ Docker Image 빌드 후 DockerHub에 Push
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/Dockerfile
push: true
platforms: linux/amd64
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:latest

backend-docker-pull-and-run:
runs-on: [ self-hosted, prod ]
needs: [ backend-docker-build-and-push ]
if: ${{ needs.backend-docker-build-and-push.result == 'success' && (github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true) }}

steps:
- name: ✨ Checkout repository
uses: actions/checkout@v5

- name: 🗂️ Grafana 환경변수 설정
run: |
echo "GRAFANA_ADMIN_USER=admin" > ./docker/.env
echo "GRAFANA_ADMIN_PASSWORD=${{ secrets.GRAFANA_ADMIN_PASSWORD }}" >> ./docker/.env
shell: bash

- name: ✨ 배포 스크립트 실행
run: |
chmod +x deploy.sh
./deploy.sh
name: ✨ Linkiving backend local bundle CD ✨

on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main

permissions:
contents: read

concurrency:
group: backend-local-bundle-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build-local-bundle:
runs-on: ubuntu-latest

steps:
- name: ✨ Checkout repository
uses: actions/checkout@v5

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

- name: ✨ Gradle caching
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: 🗂️ Create local application profile
shell: bash
env:
APPLICATION_LOCAL: ${{ secrets.APPLICATION_LOCAL }}
run: |
mkdir -p ./src/main/resources
printf '%s\n' "$APPLICATION_LOCAL" > ./src/main/resources/application-local.yml

grep -q '^spring:' ./src/main/resources/application-local.yml
grep -q '^security:' ./src/main/resources/application-local.yml
grep -q '^app:' ./src/main/resources/application-local.yml

- name: ✨ Executable permissions
run: chmod +x ./gradlew ./scripts/prepare-local-bundle.sh

- name: ✨ Jar 파일 빌드
run: ./gradlew bootJar

- name: 🐳 Build local Docker bundle
env:
BUNDLE_VERSION: ${{ github.event_name == 'pull_request' && format('pr-{0}-{1}', github.event.pull_request.number, github.sha) || format('main-{0}', github.sha) }}
IMAGE_TAG: linkiving-local:${{ github.sha }}
run: ./scripts/prepare-local-bundle.sh

- name: 📦 Upload local bundle artifact
uses: actions/upload-artifact@v4
with:
name: linkiving-core-local-bundle-${{ github.run_number }}
path: |
dist/*.zip
dist/*.sha256
retention-days: 14
95 changes: 95 additions & 0 deletions .github/workflows/release.yml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

release.yml이 이미지를 :${{ github.ref_name }}과 :latest 두 태그로 푸시하는 점은 좋은 것 같습니다.
다만 실제 배포 경로를 보면, deploy.shdocker compose pull을 사용하고 docker/docker-compose.yml의 blue/green이 image: linkivingsofa/core:latest로 고정되어 있어서, 어떤 버전 태그로 릴리즈하든 운영에는 항상 latest가 배포됩니다.

이로 인해:

  • 푸시하는 버전 태그(:v1.2.0 등)가 배포에 사용되지 않습니다.
  • 특정 버전으로의 롤백이 불가능합니다.

버전 태그 릴리즈의 의미를 살리려면, 배포 시 이미지 태그를 주입하는 방식이 더 좋을 것 같습니다.
예를 들어 compose의 image를 linkivingsofa/core:${IMAGE_TAG:-latest}로 두고, release.yml에서 IMAGE_TAG=${{ github.ref_name }}를 deploy 단계에 전달하면, pull이 해당 버전을 받고 롤백도 태그 변경으로 가능해질 것 같습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: 🚀 Linkiving backend release deploy 🚀

on:
push:
tags:
- 'v*'

permissions:
contents: write

concurrency:
group: backend-release-${{ github.ref }}
cancel-in-progress: false

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

steps:
- name: ✨ Checkout repository
uses: actions/checkout@v5

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

- name: ✨ Gradle caching
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: 🗂️ Make production config
shell: bash
env:
APPLICATION: ${{ secrets.APPLICATION }}
run: |
mkdir -p ./src/main/resources
printf '%s' "$APPLICATION" > ./src/main/resources/application.yml

- name: ✨ Gradlew 권한 설정
run: chmod +x ./gradlew

- name: ✨ Jar 파일 빌드
run: ./gradlew bootJar

- name: ✨ DockerHub에 로그인
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}

- name: ✨ Docker image 빌드 후 DockerHub에 push
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
push: true
platforms: linux/amd64
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:${{ github.ref_name }}
${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:latest

backend-docker-pull-and-run:
runs-on: [ self-hosted, prod ]
needs: [ backend-docker-build-and-push ]
if: ${{ needs.backend-docker-build-and-push.result == 'success' }}

steps:
- name: ✨ Checkout repository
uses: actions/checkout@v5

- name: ✨ 배포 스크립트 실행
env:
IMAGE_TAG: ${{ github.ref_name }}
run: |
chmod +x deploy.sh
./deploy.sh

create-release:
runs-on: ubuntu-latest
needs: [ backend-docker-pull-and-run ]
if: ${{ needs.backend-docker-pull-and-run.result == 'success' }}

steps:
- name: 📝 Create GitHub release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ out/
*.yml
!docker/prometheus.yml
!docker/grafana/**/*.yml
!docker/local-bundle/*.yml
.editorconfig
25 changes: 15 additions & 10 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ REPO_ROOT="${SCRIPT_DIR}"
COMPOSE_FILE="${REPO_ROOT}/docker/docker-compose.yml"

PROJECT="linkiving-core"
COMPOSE="sudo docker compose -p ${PROJECT} -f ${COMPOSE_FILE}"
DEPLOY_IMAGE_TAG="${IMAGE_TAG:-latest}"

compose() {
sudo IMAGE_TAG="${DEPLOY_IMAGE_TAG}" docker compose -p "${PROJECT}" -f "${COMPOSE_FILE}" "$@"
}

# compose 파일 존재 확인
if [ ! -f "${COMPOSE_FILE}" ]; then
Expand All @@ -23,9 +27,10 @@ fi

echo "✅ Using compose file: ${COMPOSE_FILE}"
echo "✅ Using project name: ${PROJECT}"
echo "✅ Using image tag: ${DEPLOY_IMAGE_TAG}"

echo "이미지 업데이트 중..."
if ! ${COMPOSE} pull; then
if ! compose pull; then
echo "❌ Docker 이미지 pull 실패! GitHub Actions 빌드를 확인해주세요."
echo "❌ 배포를 중단합니다."
exit 1
Expand All @@ -34,7 +39,7 @@ echo "✅ 새로운 이미지가 성공적으로 pull되었습니다."

# Prometheus & Grafana 실행 (설정 변경 시 자동 반영)
echo "모니터링 서비스 시작 중..."
${COMPOSE} up -d prometheus grafana
compose up -d prometheus grafana
echo "✅ Prometheus & Grafana가 시작되었습니다."

echo "사용하지 않는 이미지 정리 중..."
Expand All @@ -45,14 +50,14 @@ EXIST_BLUE=$(sudo docker ps --filter "name=blue" --filter "status=running" -q)

if [ -z "$EXIST_BLUE" ]; then
echo "BLUE 컨테이너 실행"
${COMPOSE} up -d blue
compose up -d blue
BEFORE_COLOR="green"
AFTER_COLOR="blue"
BEFORE_PORT=8081
AFTER_PORT=8080
else
echo "GREEN 컨테이너 실행"
${COMPOSE} up -d green
compose up -d green
BEFORE_COLOR="blue"
AFTER_COLOR="green"
BEFORE_PORT=8080
Expand Down Expand Up @@ -87,13 +92,13 @@ done
# 헬스체크 실패 시 롤백
if [ $HEALTH_CHECK_COUNT -eq $MAX_RETRY ]; then
echo "❌ 서버가 정상적으로 구동되지 않았습니다. 롤백을 시작합니다."
${COMPOSE} stop ${AFTER_COLOR}
${COMPOSE} rm -f ${AFTER_COLOR}
compose stop "${AFTER_COLOR}"
compose rm -f "${AFTER_COLOR}"

# 이전 컨테이너가 있다면 다시 시작
if [ "${BEFORE_COLOR}" != "" ]; then
echo "이전 ${BEFORE_COLOR} 컨테이너를 다시 시작합니다."
${COMPOSE} up -d ${BEFORE_COLOR}
compose up -d "${BEFORE_COLOR}"
fi

echo "❌ 배포 실패 - 롤백 완료"
Expand Down Expand Up @@ -142,8 +147,8 @@ if [ "${BEFORE_COLOR}" != "" ]; then
echo "이전 컨테이너 종료 전 30초 대기..."
sleep 30

${COMPOSE} stop ${BEFORE_COLOR} 2>/dev/null || true
${COMPOSE} rm -f ${BEFORE_COLOR} 2>/dev/null || true
compose stop "${BEFORE_COLOR}" 2>/dev/null || true
compose rm -f "${BEFORE_COLOR}" 2>/dev/null || true
echo "✅ 이전 ${BEFORE_COLOR} 컨테이너가 종료되었습니다."
fi

Expand Down
Loading
Loading