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
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ jobs:
- name: Build
run: ./gradlew assemble --no-daemon

# 배포(deploy.yml)가 이 JAR 을 그대로 받아쓰도록 업로드해, deploy 단계의 재빌드(중복)를 없앤다.
# deploy 는 push CI 의 workflow_run 으로만 트리거되므로(PR 은 배포 안 됨) push 일 때만 올린다.
# 보존 3일이 안전한 이유: 아티팩트는 이 CI 직후(수분 내) 그 workflow_run 배포가 소비하므로 3일은 큰 여유다.
# 롤백·재배포도 옛 아티팩트에 기대지 않는다 - 자동배포는 새 push 라 늘 새 아티팩트, 수동 재배포는
# 러너 빌드 fallback(deploy.yml), blue-green 롤백은 돌던 컨테이너 유지. 누적 방지 겸 짧게 둔다.
# JAR 이 없으면 즉시 실패시킨다.
- name: Upload JAR artifact
if: github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: app-jar
path: build/libs/*.jar
retention-days: 3
if-no-files-found: error
# JAR 은 이미 압축된 zip 이라 기본 level 6 재압축은 크기 이득 ~0 에 CPU 만 쓴다.
# store(0)로 헛수고를 없애 업로드를 줄인다. CI 는 deploy 가 workflow_run 으로 기다리는 크리티컬 패스다.
compression-level: 0

test:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
Expand Down
54 changes: 38 additions & 16 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ jobs:
environment: ${{ needs.resolve.outputs.environment }}
permissions:
contents: read
# 자동 배포 시 트리거한 CI 실행의 JAR 아티팩트를 크로스런 다운로드하려면 actions:read 가 필요하다.
actions: read
# dev/prod 가 서로 배포를 막지 않도록 환경별 동시성 그룹. 같은 환경 내 재배포는 직렬화(취소 안 함).
# 그룹은 브랜치명이 아니라 "환경"으로 묶어야 한다 — feature 브랜치 수동배포(dispatch)도 dev EC2 로
# 가므로 dev 자동배포와 같은 그룹이어야 같은 박스에서 동시 blue-green 조작 레이스를 막는다.
Expand Down Expand Up @@ -141,13 +143,30 @@ jobs:
blocks
sparse-checkout-cone-mode: true

- name: Set up JDK 25
# 자동 배포(workflow_run): 이 배포를 트리거한 CI 실행이 이미 빌드·업로드한 JAR 을 그대로 받아
# 러너에서의 재빌드(약 47초)를 없앤다. run-id 로 그 CI 실행에 못박아, 배포 커밋과 다른 커밋의 JAR 을
# 집는 레이스를 차단한다(그냥 '최신' 아티팩트를 집으면 배포 도중 다른 push 로 커밋 불일치가 난다).
# 자동 경로는 러너에 JDK·Gradle 이 필요 없다 — 도커 빌드의 layertools extract 는 이미지 안에서 돈다.
- name: Download JAR from triggering CI run
id: fetch_jar
if: github.event_name == 'workflow_run'
uses: actions/download-artifact@v4
with:
name: app-jar
path: build/libs
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

# 수동 배포(workflow_dispatch): 트리거한 CI 실행이 없어 아티팩트가 없다. 이때만 러너에서 직접 빌드한다.
- name: Set up JDK 25 (manual dispatch fallback)
if: github.event_name == 'workflow_dispatch'
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'

- name: Cache Gradle
- name: Cache Gradle (manual dispatch fallback)
if: github.event_name == 'workflow_dispatch'
uses: actions/cache@v4
with:
path: |
Expand All @@ -156,8 +175,9 @@ jobs:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', '**/gradle-wrapper.properties') }}
restore-keys: gradle-${{ runner.os }}-

- name: Build JAR
- name: Build JAR (manual dispatch fallback)
id: build
if: github.event_name == 'workflow_dispatch'
run: ./gradlew bootJar --no-daemon

- name: Login to Docker Hub
Expand All @@ -167,20 +187,18 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# 러너가 arm64 네이티브(EC2 Graviton 과 동일 arch)라 크로스빌드가 불필요하다. buildx 없이 기본 docker
# 데몬으로 arm64 이미지를 네이티브 빌드해 setup-buildx(약 10초)를 없앤다. 태그·image ref 는 셸 보간
# injection 을 피해 env 로 받아 "$VAR" 로 쓴다(repo 컨벤션).
- name: Build and push Docker image
id: docker_push
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.ci
push: true
platforms: linux/arm64
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/piki-core:latest
${{ secrets.DOCKERHUB_USERNAME }}/piki-core:${{ needs.resolve.outputs.ref }}
env:
IMAGE_LATEST: ${{ secrets.DOCKERHUB_USERNAME }}/piki-core:latest
IMAGE_SHA: ${{ secrets.DOCKERHUB_USERNAME }}/piki-core:${{ needs.resolve.outputs.ref }}
run: |
docker build -f Dockerfile.ci -t "$IMAGE_LATEST" -t "$IMAGE_SHA" .
docker push "$IMAGE_LATEST"
docker push "$IMAGE_SHA"

- name: Upload nginx config
id: upload_nginx
Expand Down Expand Up @@ -647,6 +665,8 @@ jobs:
STATUS="❌ 실패 (런타임 프로비저닝)"
elif [ "${{ steps.build.outcome }}" = "failure" ]; then
STATUS="❌ 실패 (Gradle 빌드)"
elif [ "${{ steps.fetch_jar.outcome }}" = "failure" ]; then
STATUS="❌ 실패 (CI 아티팩트 다운로드)"
else
STATUS="⚠️ 알 수 없음"
fi
Expand All @@ -660,7 +680,7 @@ jobs:
echo "| 환경 | ${{ needs.resolve.outputs.environment }} |"
echo "| 커밋 | [\`$SHORT_SHA\`]($COMMIT_URL) |"
echo "| 현재 활성 슬롯 | $NOW |"
echo "| Gradle 빌드 | ${{ steps.build.outcome }} |"
echo "| JAR 준비 | ${{ github.event_name == 'workflow_run' && steps.fetch_jar.outcome || steps.build.outcome }} |"
echo "| Docker 푸시 | ${{ steps.docker_push.outcome }} |"
echo "| EC2 배포 | ${{ steps.deploy.outcome }} |"
echo "| 헬스체크 & 전환 | ${{ steps.health.outcome }} |"
Expand Down Expand Up @@ -743,6 +763,8 @@ jobs:
CAUSE="${CAUSE:-환경 도메인↔EC2_HOST 불일치 (secret 오설정 — dev/prod 교차배선)}"
elif [ "${{ steps.build.outcome }}" = "failure" ]; then
CAUSE="Gradle 빌드 실패 (Build JAR)"
elif [ "${{ steps.fetch_jar.outcome }}" = "failure" ]; then
CAUSE="CI 아티팩트(JAR) 다운로드 실패 (트리거 CI 의 app-jar 누락/만료 · actions:read 권한)"
elif [ "${{ steps.docker_login.outcome }}" = "failure" ]; then
CAUSE="Docker Hub 로그인 실패"
elif [ "${{ steps.docker_push.outcome }}" = "failure" ]; then
Expand Down
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,10 @@ tasks.withType<Test> {
// JDK 21+부터 동적 에이전트 로딩이 제한됨. Mockito(ByteBuddy)가 런타임에 에이전트를 붙이므로 명시적 허용 필요.
jvmArgs("-XX:+EnableDynamicAgentLoading")
}

// Spring Boot 플러그인은 실행 가능한 boot jar 와 라이브러리용 plain jar 를 함께 만든다. 이 앱은 라이브러리로
// 쓰이지 않아 plain jar 가 불필요하고, build/libs 에 jar 가 2개면 배포·CI 의 `COPY build/libs/*.jar` 와
// 아티팩트 전달이 어느 jar 인지 모호해져 깨진다. plain jar 를 꺼 산출물을 boot jar 하나로 고정한다.
tasks.named("jar") {
enabled = false
}
Loading