From b5c5c02f24f5f0e26ba91174ed6be871a84089ea Mon Sep 17 00:00:00 2001 From: Jinseong Date: Mon, 19 May 2025 01:30:27 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[#96]=20Refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 24 +++++++++++++----- Dockerfile | 49 +++++++++++++++++++++++++++++------- build.gradle | 6 +++++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f93c8b9..051e4f9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,34 +9,46 @@ on: jobs: # ------------------------------------------------------------------- - # 1) CI: 컴파일·테스트·패키징만 + # 1) CI # ------------------------------------------------------------------- build: runs-on: ubuntu-latest steps: - # 코드 저장소에서 최신 커밋 가져오기 + # 1) 저장소 코드 가져오기 - name: Checkout code uses: actions/checkout@v3 + # 2) Java 21 설치 - name: Set up Java 21 uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' - # Gradle을 통해 클린 빌드, 테스트, 패키징을수행 + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + # 4) Gradle로 빌드 - name: Run Gradle Build - run: ./gradlew clean build -x test --no-daemon + run: ./gradlew build -x test --no-daemon --build-cache # ------------------------------------------------------------------- - # 2) CD: EC2에 배포 (ci 성공 후 항상 실행) + # 2) CD: EC2에 배포 # ------------------------------------------------------------------- deploy: needs: build runs-on: ubuntu-latest steps: - # 1) 코드 체크아웃 + # 1) 저장소 코드 가져오기 - name: Checkout code uses: actions/checkout@v3 with: diff --git a/Dockerfile b/Dockerfile index 0ca3536..729d24f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,50 @@ +# --- 1) DEPENDENCY-CACHE --- +FROM gradle:8.4-jdk21 AS cache +WORKDIR /home/gradle/app + +# ① Gradle 캐시를 볼륨 아닌 일반 디렉토리에 저장 +ENV GRADLE_USER_HOME=/home/gradle/cache_home + +# ② Gradle 빌드 스크립트와 설정 파일 복사 +COPY gradlew build.gradle settings.gradle ./ +COPY gradle gradle/ + +# ③ 라이브러리 다운로드 +RUN chmod +x gradlew \ + && ./gradlew dependencies --no-daemon --build-cache + + +# --- 2) BUILD --- FROM gradle:8.4-jdk21 AS builder -WORKDIR /app +WORKDIR /home/gradle/app + +# ④ 캐시된 의존성 복사 (cache 스테이지 → builder) +COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle +# ⑤ Gradle 빌드 스크립트와 설정 파일 복사 COPY gradlew build.gradle settings.gradle ./ -COPY gradle gradle -RUN chmod +x gradlew +COPY gradle gradle/ + +# ⑥ 실제 소스 코드 복사 +COPY src src/ -COPY src src -RUN ./gradlew clean build -x test --no-daemon +# ⑦ 실제 빌드 +RUN chmod +x gradlew \ + && ./gradlew clean build -x test --no-daemon --build-cache + +# --- 3) RUNTIME --- FROM openjdk:21-jdk-slim WORKDIR /app -# ✅ curl 설치 -RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* +# ⑧ 런타임에 필요한 curl 설치 +RUN apt-get update \ + && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* + +# ⑨ 빌드 결과물 복사 +COPY --from=builder /home/gradle/app/build/libs/app.jar app.jar -COPY --from=builder /app/build/libs/Tokkit-Server-0.0.1-SNAPSHOT.jar app.jar +# ⑩ 실행 설정 EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "/app/app.jar"] +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/build.gradle b/build.gradle index 1862362..e7531d0 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,12 @@ java { } } +jar { + archiveBaseName.set("app") + archiveVersion.set("") + archiveClassifier.set("") +} + configurations { compileOnly { extendsFrom annotationProcessor From f027ffb9c8326116ddaaf8ecf98dff2af7c7bdc0 Mon Sep 17 00:00:00 2001 From: Jinseong Date: Mon, 19 May 2025 16:18:13 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[#96]=20Refactor:=20Dockerfile=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 83 +++++++++++++++++++++++++--------- Dockerfile.cicd | 12 +++++ Dockerfile => Dockerfile.local | 18 ++++---- build.gradle | 8 +++- docker-compose.prod.yml | 12 ++--- 5 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 Dockerfile.cicd rename Dockerfile => Dockerfile.local (75%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 051e4f9..50d1006 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,9 +36,24 @@ jobs: restore-keys: | gradle-${{ runner.os }}- - # 4) Gradle로 빌드 + # 4) application.yml 생성 + - name: Generate application.yml from Secrets + run: | + mkdir -p src/main/resources + cat << 'EOF' > src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + + # 5) Gradle로 빌드 - name: Run Gradle Build - run: ./gradlew build -x test --no-daemon --build-cache + run: ./gradlew clean build -x test --no-daemon --build-cache + + # 6) JAR 업로드 + - name: Upload JAR artifact + uses: actions/upload-artifact@v4 + with: + name: springboot-jar + path: build/libs/tokkit.jar # ------------------------------------------------------------------- # 2) CD: EC2에 배포 @@ -54,8 +69,14 @@ jobs: with: fetch-depth: 0 + # 2) JAR 다운로드 + - name: Download JAR artifact + uses: actions/download-artifact@v4 + with: + name: springboot-jar + path: build/libs - # 2) 로컬 설정 파일 생성 및 확인 + # 3) .env 생성 및 확인 - name: Create and verify env files run: | mkdir -p ./src/main/resources @@ -73,7 +94,7 @@ jobs: shell: bash - # 3) SSH 키 설정 (.ssh/config 한 줄씩 작성) + # 4) SSH 키 설정 (.ssh/config) - name: Setup SSH key env: EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} @@ -93,36 +114,54 @@ jobs: shell: bash - # 4) EC2에 최신 코드 Pull 및 설정 파일 배포 + # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 + # 수정하면서 application.yml 관련 코드는 필요없어짐 - name: Update code and config on EC2 run: | - # 코드 Pull - ssh ec2 "cd ${{ secrets.APP_DIR }} && git pull origin ${{ github.ref_name }}" - - # 설정 파일 전송 - scp -F ~/.ssh/config ./src/main/resources/application.yml ec2:${{ secrets.APP_DIR }}/src/main/resources/application.yml - scp -F ~/.ssh/config ./.env ec2:${{ secrets.APP_DIR }}/.env - - # EC2 전송 확인 - echo "✔️ Remote env files on EC2:" - ssh ec2 "ls -l ${{ secrets.APP_DIR }}/src/main/resources/application.yml ${{ secrets.APP_DIR }}/.env" + # 1. EC2에서 git pull 먼저 + ssh -T -F ~/.ssh/config ec2 < Date: Mon, 19 May 2025 17:14:34 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[#96]=20Refactor:=20Dockerhub=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 245 ++++++++-------------------- .github/workflows/tmp.yml | 307 +++++++++++++++++++++++++++++++++++ docker-compose.prod.yml | 9 +- 3 files changed, 383 insertions(+), 178 deletions(-) create mode 100644 .github/workflows/tmp.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 50d1006..faa0017 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,24 +36,42 @@ jobs: restore-keys: | gradle-${{ runner.os }}- - # 4) application.yml 생성 - - name: Generate application.yml from Secrets + # 4) 설정 파일 생성 (CI 단계에서 필요) + - name: Generate Configuration Files run: | + # application.yml 생성 mkdir -p src/main/resources - cat << 'EOF' > src/main/resources/application.yml + cat < src/main/resources/application.yml ${{ secrets.APP_YML }} - EOF + EOT + + # .env 파일 생성 (Docker 이미지에 환경변수로 포함되게 할 경우) + cat < .env + ${{ secrets.ENV_FILE }} + EOT # 5) Gradle로 빌드 - name: Run Gradle Build run: ./gradlew clean build -x test --no-daemon --build-cache - # 6) JAR 업로드 - - name: Upload JAR artifact - uses: actions/upload-artifact@v4 + # → Docker Hub 로그인 + - name: Log in to Docker Hub + uses: docker/login-action@v2 with: - name: springboot-jar - path: build/libs/tokkit.jar + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # → Docker 이미지 빌드 & 푸시 + - name: Build and push Docker image + id: build_push + run: | + IMAGE_TAG=${{ secrets.DOCKER_USERNAME }}/tokkit-springboot:latest + docker build \ + --file Dockerfile.cicd \ + --tag $IMAGE_TAG \ + . + docker push $IMAGE_TAG + echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT # ------------------------------------------------------------------- # 2) CD: EC2에 배포 @@ -63,178 +81,57 @@ jobs: runs-on: ubuntu-latest steps: - # 1) 저장소 코드 가져오기 - - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - # 2) JAR 다운로드 - - name: Download JAR artifact - uses: actions/download-artifact@v4 - with: - name: springboot-jar - path: build/libs - - # 3) .env 생성 및 확인 - - name: Create and verify env files - run: | - mkdir -p ./src/main/resources - - # 파일 생성 - 플레이스홀더 누락 문제 해결 - # application.yml 생성 - cat <<'EOF' > ./src/main/resources/application.yml - ${{ secrets.APP_YML }} - EOF - - # .env 파일 생성 - cat <<'EOF' > ./.env - ${{ secrets.ENV_FILE }} - EOF - shell: bash - - - # 4) SSH 키 설정 (.ssh/config) - name: Setup SSH key - env: - EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} - EC2_HOST: ${{ secrets.EC2_HOST }} - EC2_USER: ${{ secrets.EC2_USER }} - run: | - mkdir -p ~/.ssh - echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - - # SSH config 작성 - echo "Host ec2" > ~/.ssh/config - echo " HostName $EC2_HOST" >> ~/.ssh/config - echo " User $EC2_USER" >> ~/.ssh/config - echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config - echo " StrictHostKeyChecking no" >> ~/.ssh/config - shell: bash - - - # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 - # 수정하면서 application.yml 관련 코드는 필요없어짐 - - name: Update code and config on EC2 - run: | - # 1. EC2에서 git pull 먼저 - ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") - - if [ "$HEALTH_STATUS" = "healthy" ]; then - echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" - break - else - echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" - sleep $RETRY_INTERVAL - fi - - if [ "$i" -eq "$MAX_RETRIES" ]; then - echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." - exit 1 - fi - done - - - # 모든 컨테이너 상태 점검 - COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" - CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") - UNHEALTHY_COUNT=0 - - echo "📊 컨테이너 상태 확인 중..." - for CONTAINER_ID in $CONTAINERS; do - # 각 컨테이너의 상태 확인 - CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") - CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') - - if [ "$CONTAINER_STATUS" != "running" ]; then - echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" - UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) - else - echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" - fi - done - - - # 비정상 컨테이너 처리 - if [ "$UNHEALTHY_COUNT" -gt 0 ]; then - echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" - echo "=== 비정상 컨테이너 상세 상태 ===" - for CID in $CONTAINERS; do - STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") - if [ "$STATUS" != "running" ]; then - NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') - echo "🪫 $NAME 상태: $STATUS" - echo "--- $NAME 로그 (tail 50) ---" - ssh ec2 "docker logs $NAME --tail=50" - fi + ssh -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} <<'EOF' + set -e + cd ${{ secrets.APP_DIR }} + + echo "📥 Git pull" + git fetch origin + git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + + echo "📝 Generate .env" + cat <<'EOT' > ./.env + ${{ secrets.ENV_FILE }} + EOT + + echo "📝 Generate application.yml" + mkdir -p src/main/resources + cat <<'EOT' > src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOT + + echo "🔒 Docker login" + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \ + --username "${{ secrets.DOCKER_USERNAME }}" \ + --password-stdin + + # Docker Hub 사용자명 환경 변수 설정 + export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" + + echo "🚀 Docker Compose up (dev + prod)" + docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up -d + + echo "🔍 Health check" + for i in \$(seq 1 5); do + STATUS=\$(docker inspect --format='{{.State.Health.Status}}' tokkit-springboot 2>/dev/null || echo none) + echo " → Attempt \$i: \$STATUS" + [ "\$STATUS" = "healthy" ] && { echo "✅ Healthy"; exit 0; } + sleep 10 done - - # 상태 테이블 출력 - ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + echo "❌ Health check failed" exit 1 - - # 성공 처리 - else - TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) - echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" - ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" - fi + EOF # 8) SSH 설정 정리 - name: Cleanup SSH if: always() run: rm -rf ~/.ssh - shell: bash + shell: bash \ No newline at end of file diff --git a/.github/workflows/tmp.yml b/.github/workflows/tmp.yml new file mode 100644 index 0000000..05824bb --- /dev/null +++ b/.github/workflows/tmp.yml @@ -0,0 +1,307 @@ +name: Tokkit CI/CD Pipeline + +on: + push: + branches: + - develop + - main + workflow_dispatch: + +jobs: + # ------------------------------------------------------------------- + # 1) CI + # ------------------------------------------------------------------- + build: + runs-on: ubuntu-latest + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + + # 2) Java 21 설치 + - name: Set up Java 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + +# # 4) application.yml 생성 +# - name: Generate application.yml from Secrets +# run: | +# mkdir -p src/main/resources +# cat << 'EOF' > src/main/resources/application.yml +# ${{ secrets.APP_YML }} +# EOF + + # 5) Gradle로 빌드 + - name: Run Gradle Build + run: ./gradlew clean build -x test --no-daemon --build-cache + + + # → Docker Hub 로그인 + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # → Docker 이미지 빌드 & 푸시 + - name: Build and push Docker image + id: build_push + run: | + IMAGE_TAG=${{ secrets.DOCKER_USERNAME }}/tokkit-springboot:latest + docker build \ + --file Dockerfile.cicd \ + --tag $IMAGE_TAG \ + . + docker push $IMAGE_TAG + echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT + +# +# # 6) JAR 업로드 +# - name: Upload JAR artifact +# uses: actions/upload-artifact@v4 +# with: +# name: springboot-jar +# path: build/libs/tokkit.jar + + # ------------------------------------------------------------------- + # 2) CD: EC2에 배포 + # ------------------------------------------------------------------- + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Setup SSH key + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.EC2_SSH_KEY }} + + - name: Deploy to EC2 + run: | + ssh -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} <<'EOF' + set -e + cd ${{ secrets.APP_DIR }} + + echo "📥 Git pull" + git fetch origin + git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + + echo "📝 Generate .env" + cat <<'EOT' > ./.env + ${{ secrets.ENV_FILE }} + EOT + + echo "📝 Generate application.yml" + mkdir -p src/main/resources + cat <<'EOT' > src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOT + + echo "🔒 Docker login" + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \ + --username "${{ secrets.DOCKER_USERNAME }}" \ + --password-stdin + + # Docker Hub 사용자명 환경 변수 설정 + export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" + + echo "🚀 Docker Compose up (dev + prod, with build)" + docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up --build -d + + echo "🔍 Health check" + for i in \$(seq 1 5); do + STATUS=\$(docker inspect --format='{{.State.Health.Status}}' tokkit-springboot 2>/dev/null || echo none) + echo " → Attempt \$i: \$STATUS" + [ "\$STATUS" = "healthy" ] && { echo "✅ Healthy"; exit 0; } + sleep 10 + done + echo "❌ Health check failed" + exit 1 + EOF + +# # 1) 저장소 코드 가져오기 +# - name: Checkout code +# uses: actions/checkout@v3 +# with: +# fetch-depth: 0 +# +# # 2) JAR 다운로드 +# - name: Download JAR artifact +# uses: actions/download-artifact@v4 +# with: +# name: springboot-jar +# path: build/libs +# +# # 3) .env 생성 및 확인 +# - name: Create and verify env files +# run: | +# mkdir -p ./src/main/resources +# +# # 파일 생성 - 플레이스홀더 누락 문제 해결 +# # application.yml 생성 +# cat <<'EOF' > ./src/main/resources/application.yml +# ${{ secrets.APP_YML }} +# EOF +# +# # .env 파일 생성 +# cat <<'EOF' > ./.env +# ${{ secrets.ENV_FILE }} +# EOF +# shell: bash +# +# +# # 4) SSH 키 설정 (.ssh/config) +# - name: Setup SSH key +# env: +# EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} +# EC2_HOST: ${{ secrets.EC2_HOST }} +# EC2_USER: ${{ secrets.EC2_USER }} +# run: | +# mkdir -p ~/.ssh +# echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key +# chmod 600 ~/.ssh/deploy_key +# +# # SSH config 작성 +# echo "Host ec2" > ~/.ssh/config +# echo " HostName $EC2_HOST" >> ~/.ssh/config +# echo " User $EC2_USER" >> ~/.ssh/config +# echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config +# echo " StrictHostKeyChecking no" >> ~/.ssh/config +# shell: bash +# +# +# # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 +# # 수정하면서 application.yml 관련 코드는 필요없어짐 +# - name: Update code and config on EC2 +# run: | +# # 1. EC2에서 git pull 먼저 +# ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") +# +# if [ "$HEALTH_STATUS" = "healthy" ]; then +# echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" +# break +# else +# echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" +# sleep $RETRY_INTERVAL +# fi +# +# if [ "$i" -eq "$MAX_RETRIES" ]; then +# echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." +# exit 1 +# fi +# done +# +# +# # 모든 컨테이너 상태 점검 +# COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" +# CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") +# UNHEALTHY_COUNT=0 +# +# echo "📊 컨테이너 상태 확인 중..." +# for CONTAINER_ID in $CONTAINERS; do +# # 각 컨테이너의 상태 확인 +# CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") +# CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') +# +# if [ "$CONTAINER_STATUS" != "running" ]; then +# echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" +# UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) +# else +# echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" +# fi +# done +# +# +# # 비정상 컨테이너 처리 +# if [ "$UNHEALTHY_COUNT" -gt 0 ]; then +# echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" +# echo "=== 비정상 컨테이너 상세 상태 ===" +# for CID in $CONTAINERS; do +# STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") +# if [ "$STATUS" != "running" ]; then +# NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') +# echo "🪫 $NAME 상태: $STATUS" +# echo "--- $NAME 로그 (tail 50) ---" +# ssh ec2 "docker logs $NAME --tail=50" +# fi +# done +# +# # 상태 테이블 출력 +# ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" +# exit 1 +# +# # 성공 처리 +# else +# TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) +# echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" +# ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" +# fi + + # 8) SSH 설정 정리 + - name: Cleanup SSH + if: always() + run: rm -rf ~/.ssh + shell: bash diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4ec8c19..6cbe421 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,11 +2,12 @@ services: # springboot springboot: container_name: tokkit-springboot - build: - context: . # 현재 디렉토리 기준 Dockerfile 위치 - dockerfile: Dockerfile.cicd + image: ${DOCKER_USERNAME}/tokkit-springboot:latest +# dockerfile: Dockerfile.cicd +# build: +# context: . # 현재 디렉토리 기준 Dockerfile 위치 # dockerfile: Dockerfile.local # 수동 배포 시 사용하는 Dockerfile - image: tokkit/springboot:latest +# image: tokkit/springboot:latest ports: - "8080:8080" env_file: From 9da4d372a5005ae94af2414e0f992d18b5023f19 Mon Sep 17 00:00:00 2001 From: Jinseong Date: Tue, 20 May 2025 00:29:36 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[#96]=20Refactor:=20CICD=20Jar=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.old.yml | 201 +++++++++++++++++++++++++ .github/workflows/deploy.yml | 246 ++++++++++++++++++++++--------- .github/workflows/tmp.yml | 206 +++----------------------- docker-compose.prod.yml | 10 +- 4 files changed, 397 insertions(+), 266 deletions(-) create mode 100644 .github/workflows/deploy.old.yml diff --git a/.github/workflows/deploy.old.yml b/.github/workflows/deploy.old.yml new file mode 100644 index 0000000..4652f3a --- /dev/null +++ b/.github/workflows/deploy.old.yml @@ -0,0 +1,201 @@ +name: Tokkit CI/CD Pipeline + +on: + push: + branches: + - develop + - main + workflow_dispatch: + +jobs: + # ------------------------------------------------------------------- + # 1) CI + # ------------------------------------------------------------------- + build: + runs-on: ubuntu-latest + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + + # 2) Java 21 설치 + - name: Set up Java 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + # 4) Gradle로 빌드 + - name: Run Gradle Build + run: ./gradlew build -x test --no-daemon --build-cache + + # ------------------------------------------------------------------- + # 2) CD: EC2에 배포 + # ------------------------------------------------------------------- + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + + # 2) 로컬 설정 파일 생성 및 확인 + - name: Create and verify env files + run: | + mkdir -p ./src/main/resources + + # 파일 생성 - 플레이스홀더 누락 문제 해결 + # application.yml 생성 + cat <<'EOF' > ./src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + + # .env 파일 생성 + cat <<'EOF' > ./.env + ${{ secrets.ENV_FILE }} + EOF + shell: bash + + + # 3) SSH 키 설정 (.ssh/config 한 줄씩 작성) + - name: Setup SSH key + env: + EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + run: | + mkdir -p ~/.ssh + echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + # SSH config 작성 + echo "Host ec2" > ~/.ssh/config + echo " HostName $EC2_HOST" >> ~/.ssh/config + echo " User $EC2_USER" >> ~/.ssh/config + echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config + echo " StrictHostKeyChecking no" >> ~/.ssh/config + shell: bash + + + # 4) EC2에 최신 코드 Pull 및 설정 파일 배포 + - name: Update code and config on EC2 + run: | + # 코드 Pull + ssh ec2 "cd ${{ secrets.APP_DIR }} && git pull origin ${{ github.ref_name }}" + + # 설정 파일 전송 + scp -F ~/.ssh/config ./src/main/resources/application.yml ec2:${{ secrets.APP_DIR }}/src/main/resources/application.yml + scp -F ~/.ssh/config ./.env ec2:${{ secrets.APP_DIR }}/.env + + # EC2 전송 확인 + echo "✔️ Remote env files on EC2:" + ssh ec2 "ls -l ${{ secrets.APP_DIR }}/src/main/resources/application.yml ${{ secrets.APP_DIR }}/.env" + + + # EC2의 application.yml 내용 확인 (플레이스홀더 누락 여부) + echo "✅ EC2의 application.yml 플레이스홀더 확인:" + ssh ec2 "grep -o '\\\${[^}]*}' ${{ secrets.APP_DIR }}/src/main/resources/application.yml || echo '플레이스홀더 없음'" + shell: bash + + + # 5) Docker Compose 빌드 및 실행 + - name: Build & Deploy with Docker Compose + run: | + ssh ec2 "cd ${{ secrets.APP_DIR }} && \ + docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up --build -d" + shell: bash + + + # 6) 배포 상태 확인 및 상세 로그 + - name: Check deployment status + run: | + # 헬스체크 루프 + # 헬스 체크 최대 10회(30초 간격) + MAX_RETRIES=10 + RETRY_INTERVAL=30 + + # depend_on에 의해 springboot가 가장 마지막 실행 + for i in $(seq 1 $MAX_RETRIES); do + HEALTH_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Health.Status}}' tokkit-springboot" 2>/dev/null || echo "none") + + if [ "$HEALTH_STATUS" = "healthy" ]; then + echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" + break + else + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" + sleep $RETRY_INTERVAL + fi + + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." + exit 1 + fi + done + + + # 모든 컨테이너 상태 점검 + COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" + CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") + UNHEALTHY_COUNT=0 + + echo "📊 컨테이너 상태 확인 중..." + for CONTAINER_ID in $CONTAINERS; do + # 각 컨테이너의 상태 확인 + CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") + CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') + + if [ "$CONTAINER_STATUS" != "running" ]; then + echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" + UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) + else + echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" + fi + done + + + # 비정상 컨테이너 처리 + if [ "$UNHEALTHY_COUNT" -gt 0 ]; then + echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" + echo "=== 비정상 컨테이너 상세 상태 ===" + for CID in $CONTAINERS; do + STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") + if [ "$STATUS" != "running" ]; then + NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') + echo "🪫 $NAME 상태: $STATUS" + echo "--- $NAME 로그 (tail 50) ---" + ssh ec2 "docker logs $NAME --tail=50" + fi + done + + # 상태 테이블 출력 + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + exit 1 + + # 성공 처리 + else + TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) + echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + fi + + # 7) SSH 설정 정리 + - name: Cleanup SSH + if: always() + run: rm -rf ~/.ssh + shell: bash \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index faa0017..6cbb714 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,43 +35,23 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | gradle-${{ runner.os }}- - - # 4) 설정 파일 생성 (CI 단계에서 필요) - - name: Generate Configuration Files + # 4) application.yml 생성 + - name: Generate application.yml from Secrets run: | - # application.yml 생성 mkdir -p src/main/resources - cat < src/main/resources/application.yml + cat << 'EOF' > src/main/resources/application.yml ${{ secrets.APP_YML }} - EOT - - # .env 파일 생성 (Docker 이미지에 환경변수로 포함되게 할 경우) - cat < .env - ${{ secrets.ENV_FILE }} - EOT - + EOF # 5) Gradle로 빌드 - name: Run Gradle Build run: ./gradlew clean build -x test --no-daemon --build-cache - # → Docker Hub 로그인 - - name: Log in to Docker Hub - uses: docker/login-action@v2 + # 6) JAR 업로드 + - name: Upload JAR artifact + uses: actions/upload-artifact@v4 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - # → Docker 이미지 빌드 & 푸시 - - name: Build and push Docker image - id: build_push - run: | - IMAGE_TAG=${{ secrets.DOCKER_USERNAME }}/tokkit-springboot:latest - docker build \ - --file Dockerfile.cicd \ - --tag $IMAGE_TAG \ - . - docker push $IMAGE_TAG - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT + name: springboot-jar + path: build/libs/tokkit.jar # ------------------------------------------------------------------- # 2) CD: EC2에 배포 @@ -81,55 +61,175 @@ jobs: runs-on: ubuntu-latest steps: - - name: Setup SSH key - uses: webfactory/ssh-agent@v0.9.0 + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 with: - ssh-private-key: ${{ secrets.EC2_SSH_KEY }} + fetch-depth: 0 - - name: Deploy to EC2 + # 2) JAR 다운로드 + - name: Download JAR artifact + uses: actions/download-artifact@v4 + with: + name: springboot-jar + path: build/libs + + # 3) .env 생성 및 확인 + - name: Create and verify env files run: | - ssh -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} <<'EOF' - set -e - cd ${{ secrets.APP_DIR }} - - echo "📥 Git pull" - git fetch origin - git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }} - git pull origin ${{ github.ref_name }} - - echo "📝 Generate .env" - cat <<'EOT' > ./.env - ${{ secrets.ENV_FILE }} - EOT - - echo "📝 Generate application.yml" - mkdir -p src/main/resources - cat <<'EOT' > src/main/resources/application.yml - ${{ secrets.APP_YML }} - EOT - - echo "🔒 Docker login" - echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \ - --username "${{ secrets.DOCKER_USERNAME }}" \ - --password-stdin - - # Docker Hub 사용자명 환경 변수 설정 - export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" - - echo "🚀 Docker Compose up (dev + prod)" - docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up -d - - echo "🔍 Health check" - for i in \$(seq 1 5); do - STATUS=\$(docker inspect --format='{{.State.Health.Status}}' tokkit-springboot 2>/dev/null || echo none) - echo " → Attempt \$i: \$STATUS" - [ "\$STATUS" = "healthy" ] && { echo "✅ Healthy"; exit 0; } - sleep 10 - done - echo "❌ Health check failed" - exit 1 + mkdir -p ./src/main/resources + + # 파일 생성 - 플레이스홀더 누락 문제 해결 + # application.yml 생성 + cat <<'EOF' > ./src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + + # .env 파일 생성 + cat <<'EOF' > ./.env + ${{ secrets.ENV_FILE }} EOF + shell: bash + + # 4) SSH 키 설정 (.ssh/config) + - name: Setup SSH key + env: + EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + run: | + mkdir -p ~/.ssh + echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + # SSH config 작성 + echo "Host ec2" > ~/.ssh/config + echo " HostName $EC2_HOST" >> ~/.ssh/config + echo " User $EC2_USER" >> ~/.ssh/config + echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config + echo " StrictHostKeyChecking no" >> ~/.ssh/config + shell: bash + + + # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 + # 수정하면서 application.yml 관련 코드는 필요없어짐 + - name: Update code and config on EC2 + run: | + # 1. EC2에서 git pull 먼저 + ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") + + if [ "$HEALTH_STATUS" = "healthy" ]; then + echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" + break + else + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" + sleep $RETRY_INTERVAL + fi + + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." + exit 1 + fi + done + + + # 모든 컨테이너 상태 점검 + COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" + CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") + UNHEALTHY_COUNT=0 + + echo "📊 컨테이너 상태 확인 중..." + for CONTAINER_ID in $CONTAINERS; do + # 각 컨테이너의 상태 확인 + CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") + CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') + + if [ "$CONTAINER_STATUS" != "running" ]; then + echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" + UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) + else + echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" + fi + done + + + # 비정상 컨테이너 처리 + if [ "$UNHEALTHY_COUNT" -gt 0 ]; then + echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" + echo "=== 비정상 컨테이너 상세 상태 ===" + for CID in $CONTAINERS; do + STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") + if [ "$STATUS" != "running" ]; then + NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') + echo "🪫 $NAME 상태: $STATUS" + echo "--- $NAME 로그 (tail 50) ---" + ssh ec2 "docker logs $NAME --tail=50" + fi + done + + # 상태 테이블 출력 + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + exit 1 + + # 성공 처리 + else + TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) + echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + fi # 8) SSH 설정 정리 - name: Cleanup SSH if: always() diff --git a/.github/workflows/tmp.yml b/.github/workflows/tmp.yml index 05824bb..f80e0e2 100644 --- a/.github/workflows/tmp.yml +++ b/.github/workflows/tmp.yml @@ -36,19 +36,24 @@ jobs: restore-keys: | gradle-${{ runner.os }}- -# # 4) application.yml 생성 -# - name: Generate application.yml from Secrets -# run: | -# mkdir -p src/main/resources -# cat << 'EOF' > src/main/resources/application.yml -# ${{ secrets.APP_YML }} -# EOF + # 4) 설정 파일 생성 (CI 단계에서 필요) + - name: Generate Configuration Files + run: | + # application.yml 생성 + mkdir -p src/main/resources + cat < src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOT + + # .env 파일 생성 (Docker 이미지에 환경변수로 포함되게 할 경우) + cat < .env + ${{ secrets.ENV_FILE }} + EOT # 5) Gradle로 빌드 - name: Run Gradle Build run: ./gradlew clean build -x test --no-daemon --build-cache - # → Docker Hub 로그인 - name: Log in to Docker Hub uses: docker/login-action@v2 @@ -68,14 +73,6 @@ jobs: docker push $IMAGE_TAG echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT -# -# # 6) JAR 업로드 -# - name: Upload JAR artifact -# uses: actions/upload-artifact@v4 -# with: -# name: springboot-jar -# path: build/libs/tokkit.jar - # ------------------------------------------------------------------- # 2) CD: EC2에 배포 # ------------------------------------------------------------------- @@ -101,13 +98,13 @@ jobs: git pull origin ${{ github.ref_name }} echo "📝 Generate .env" - cat <<'EOT' > ./.env + cat <<-'EOT' > ./.env ${{ secrets.ENV_FILE }} EOT echo "📝 Generate application.yml" mkdir -p src/main/resources - cat <<'EOT' > src/main/resources/application.yml + cat <<-'EOT' > src/main/resources/application.yml ${{ secrets.APP_YML }} EOT @@ -119,8 +116,8 @@ jobs: # Docker Hub 사용자명 환경 변수 설정 export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" - echo "🚀 Docker Compose up (dev + prod, with build)" - docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up --build -d + echo "🚀 Docker Compose up (dev + prod)" + docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up -d echo "🔍 Health check" for i in \$(seq 1 5); do @@ -133,175 +130,8 @@ jobs: exit 1 EOF -# # 1) 저장소 코드 가져오기 -# - name: Checkout code -# uses: actions/checkout@v3 -# with: -# fetch-depth: 0 -# -# # 2) JAR 다운로드 -# - name: Download JAR artifact -# uses: actions/download-artifact@v4 -# with: -# name: springboot-jar -# path: build/libs -# -# # 3) .env 생성 및 확인 -# - name: Create and verify env files -# run: | -# mkdir -p ./src/main/resources -# -# # 파일 생성 - 플레이스홀더 누락 문제 해결 -# # application.yml 생성 -# cat <<'EOF' > ./src/main/resources/application.yml -# ${{ secrets.APP_YML }} -# EOF -# -# # .env 파일 생성 -# cat <<'EOF' > ./.env -# ${{ secrets.ENV_FILE }} -# EOF -# shell: bash -# -# -# # 4) SSH 키 설정 (.ssh/config) -# - name: Setup SSH key -# env: -# EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} -# EC2_HOST: ${{ secrets.EC2_HOST }} -# EC2_USER: ${{ secrets.EC2_USER }} -# run: | -# mkdir -p ~/.ssh -# echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key -# chmod 600 ~/.ssh/deploy_key -# -# # SSH config 작성 -# echo "Host ec2" > ~/.ssh/config -# echo " HostName $EC2_HOST" >> ~/.ssh/config -# echo " User $EC2_USER" >> ~/.ssh/config -# echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config -# echo " StrictHostKeyChecking no" >> ~/.ssh/config -# shell: bash -# -# -# # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 -# # 수정하면서 application.yml 관련 코드는 필요없어짐 -# - name: Update code and config on EC2 -# run: | -# # 1. EC2에서 git pull 먼저 -# ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") -# -# if [ "$HEALTH_STATUS" = "healthy" ]; then -# echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" -# break -# else -# echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" -# sleep $RETRY_INTERVAL -# fi -# -# if [ "$i" -eq "$MAX_RETRIES" ]; then -# echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." -# exit 1 -# fi -# done -# -# -# # 모든 컨테이너 상태 점검 -# COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" -# CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") -# UNHEALTHY_COUNT=0 -# -# echo "📊 컨테이너 상태 확인 중..." -# for CONTAINER_ID in $CONTAINERS; do -# # 각 컨테이너의 상태 확인 -# CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") -# CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') -# -# if [ "$CONTAINER_STATUS" != "running" ]; then -# echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" -# UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) -# else -# echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" -# fi -# done -# -# -# # 비정상 컨테이너 처리 -# if [ "$UNHEALTHY_COUNT" -gt 0 ]; then -# echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" -# echo "=== 비정상 컨테이너 상세 상태 ===" -# for CID in $CONTAINERS; do -# STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") -# if [ "$STATUS" != "running" ]; then -# NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') -# echo "🪫 $NAME 상태: $STATUS" -# echo "--- $NAME 로그 (tail 50) ---" -# ssh ec2 "docker logs $NAME --tail=50" -# fi -# done -# -# # 상태 테이블 출력 -# ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" -# exit 1 -# -# # 성공 처리 -# else -# TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) -# echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" -# ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" -# fi - # 8) SSH 설정 정리 - name: Cleanup SSH if: always() run: rm -rf ~/.ssh - shell: bash + shell: bash \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 6cbe421..2d9fe14 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,12 +2,12 @@ services: # springboot springboot: container_name: tokkit-springboot - image: ${DOCKER_USERNAME}/tokkit-springboot:latest -# dockerfile: Dockerfile.cicd -# build: -# context: . # 현재 디렉토리 기준 Dockerfile 위치 +# image: ${DOCKER_USERNAME}/tokkit-springboot:latest + build: + context: . # 현재 디렉토리 기준 Dockerfile 위치 # dockerfile: Dockerfile.local # 수동 배포 시 사용하는 Dockerfile -# image: tokkit/springboot:latest + dockerfile: Dockerfile.cicd + image: tokkit/springboot:latest ports: - "8080:8080" env_file: From f00011b019ad2111f74c935089070a605044497d Mon Sep 17 00:00:00 2001 From: Jinseong Date: Tue, 20 May 2025 11:15:31 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[#96]=20Refactor:=20CICD=20Docker=20Hub=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ploy.old.yml => deploy.basic.yml.disabled} | 0 .github/workflows/deploy.jar.yml.disabled | 237 ++++++++++++++++++ .github/workflows/deploy.yml | 216 ++++++++-------- .github/workflows/tmp.yml | 137 ---------- Dockerfile.cicd | 3 + docker-compose.prod.yml | 12 +- 6 files changed, 349 insertions(+), 256 deletions(-) rename .github/workflows/{deploy.old.yml => deploy.basic.yml.disabled} (100%) create mode 100644 .github/workflows/deploy.jar.yml.disabled delete mode 100644 .github/workflows/tmp.yml diff --git a/.github/workflows/deploy.old.yml b/.github/workflows/deploy.basic.yml.disabled similarity index 100% rename from .github/workflows/deploy.old.yml rename to .github/workflows/deploy.basic.yml.disabled diff --git a/.github/workflows/deploy.jar.yml.disabled b/.github/workflows/deploy.jar.yml.disabled new file mode 100644 index 0000000..6cbb714 --- /dev/null +++ b/.github/workflows/deploy.jar.yml.disabled @@ -0,0 +1,237 @@ +name: Tokkit CI/CD Pipeline + +on: + push: + branches: + - develop + - main + workflow_dispatch: + +jobs: + # ------------------------------------------------------------------- + # 1) CI + # ------------------------------------------------------------------- + build: + runs-on: ubuntu-latest + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + + # 2) Java 21 설치 + - name: Set up Java 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + # 3) Gradle 관련 파일들 캐싱 + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + # 4) application.yml 생성 + - name: Generate application.yml from Secrets + run: | + mkdir -p src/main/resources + cat << 'EOF' > src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + # 5) Gradle로 빌드 + - name: Run Gradle Build + run: ./gradlew clean build -x test --no-daemon --build-cache + + # 6) JAR 업로드 + - name: Upload JAR artifact + uses: actions/upload-artifact@v4 + with: + name: springboot-jar + path: build/libs/tokkit.jar + + # ------------------------------------------------------------------- + # 2) CD: EC2에 배포 + # ------------------------------------------------------------------- + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + # 1) 저장소 코드 가져오기 + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # 2) JAR 다운로드 + - name: Download JAR artifact + uses: actions/download-artifact@v4 + with: + name: springboot-jar + path: build/libs + + # 3) .env 생성 및 확인 + - name: Create and verify env files + run: | + mkdir -p ./src/main/resources + + # 파일 생성 - 플레이스홀더 누락 문제 해결 + # application.yml 생성 + cat <<'EOF' > ./src/main/resources/application.yml + ${{ secrets.APP_YML }} + EOF + + # .env 파일 생성 + cat <<'EOF' > ./.env + ${{ secrets.ENV_FILE }} + EOF + shell: bash + + + # 4) SSH 키 설정 (.ssh/config) + - name: Setup SSH key + env: + EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + run: | + mkdir -p ~/.ssh + echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + # SSH config 작성 + echo "Host ec2" > ~/.ssh/config + echo " HostName $EC2_HOST" >> ~/.ssh/config + echo " User $EC2_USER" >> ~/.ssh/config + echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config + echo " StrictHostKeyChecking no" >> ~/.ssh/config + shell: bash + + + # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 + # 수정하면서 application.yml 관련 코드는 필요없어짐 + - name: Update code and config on EC2 + run: | + # 1. EC2에서 git pull 먼저 + ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") + + if [ "$HEALTH_STATUS" = "healthy" ]; then + echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" + break + else + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" + sleep $RETRY_INTERVAL + fi + + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." + exit 1 + fi + done + + + # 모든 컨테이너 상태 점검 + COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" + CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") + UNHEALTHY_COUNT=0 + + echo "📊 컨테이너 상태 확인 중..." + for CONTAINER_ID in $CONTAINERS; do + # 각 컨테이너의 상태 확인 + CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") + CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') + + if [ "$CONTAINER_STATUS" != "running" ]; then + echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" + UNHEALTHY_COUNT=$((UNHEALTHY_COUNT + 1)) + else + echo "✅ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (정상)" + fi + done + + + # 비정상 컨테이너 처리 + if [ "$UNHEALTHY_COUNT" -gt 0 ]; then + echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" + echo "=== 비정상 컨테이너 상세 상태 ===" + for CID in $CONTAINERS; do + STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") + if [ "$STATUS" != "running" ]; then + NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') + echo "🪫 $NAME 상태: $STATUS" + echo "--- $NAME 로그 (tail 50) ---" + ssh ec2 "docker logs $NAME --tail=50" + fi + done + + # 상태 테이블 출력 + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + exit 1 + + # 성공 처리 + else + TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) + echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" + ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + fi + # 8) SSH 설정 정리 + - name: Cleanup SSH + if: always() + run: rm -rf ~/.ssh + shell: bash \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6cbb714..9161b3a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,7 +9,7 @@ on: jobs: # ------------------------------------------------------------------- - # 1) CI + # 1) CI - 프로젝트 빌드 및 Docker Hub로 이미지 푸시 # ------------------------------------------------------------------- build: runs-on: ubuntu-latest @@ -35,133 +35,101 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | gradle-${{ runner.os }}- - # 4) application.yml 생성 - - name: Generate application.yml from Secrets + + # 4) application.yml 생성 (secrets에서 불러오기) + - name: Generate Configuration Files run: | + # application.yml 생성 mkdir -p src/main/resources - cat << 'EOF' > src/main/resources/application.yml + cat > src/main/resources/application.yml << 'EOF' ${{ secrets.APP_YML }} EOF + # 5) Gradle로 빌드 - name: Run Gradle Build run: ./gradlew clean build -x test --no-daemon --build-cache - # 6) JAR 업로드 - - name: Upload JAR artifact - uses: actions/upload-artifact@v4 + # 6) Docker Hub 로그인 + - name: Log in to Docker Hub + uses: docker/login-action@v2 with: - name: springboot-jar - path: build/libs/tokkit.jar + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # 7) Docker 이미지 빌드 및 푸시 + - name: Build and push Docker image + id: build_push + run: | + IMAGE_TAG=${{ secrets.DOCKER_USERNAME }}/tokkit-springboot:latest + docker build \ + --file Dockerfile.cicd \ + --tag $IMAGE_TAG \ + . + docker push $IMAGE_TAG + echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT # ------------------------------------------------------------------- - # 2) CD: EC2에 배포 + # 2) CD: EC2 서버에 배포 # ------------------------------------------------------------------- deploy: needs: build runs-on: ubuntu-latest steps: - # 1) 저장소 코드 가져오기 - - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - # 2) JAR 다운로드 - - name: Download JAR artifact - uses: actions/download-artifact@v4 + # 1) SSH 키 설정 + - name: Setup SSH key + uses: webfactory/ssh-agent@v0.9.0 with: - name: springboot-jar - path: build/libs + ssh-private-key: ${{ secrets.EC2_SSH_KEY }} - # 3) .env 생성 및 확인 - - name: Create and verify env files + # 2) EC2에 접속해 코드 및 설정 파일 반영, Docker Compose로 배포 + - name: Deploy to EC2 run: | - mkdir -p ./src/main/resources + # 명확한 SSH 연결 정보 설정 + SSH_HOST="${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" - # 파일 생성 - 플레이스홀더 누락 문제 해결 - # application.yml 생성 - cat <<'EOF' > ./src/main/resources/application.yml - ${{ secrets.APP_YML }} - EOF + # 원격 서버에 명령 실행 + ssh -o StrictHostKeyChecking=no $SSH_HOST <<'EOT' + set -e + cd ${{ secrets.APP_DIR }} - # .env 파일 생성 - cat <<'EOF' > ./.env + echo "📥 Git pull" + git fetch origin + git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + + echo "📝 .env 생성" + cat > ./.env << 'EOF' ${{ secrets.ENV_FILE }} EOF - shell: bash - - - # 4) SSH 키 설정 (.ssh/config) - - name: Setup SSH key - env: - EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} - EC2_HOST: ${{ secrets.EC2_HOST }} - EC2_USER: ${{ secrets.EC2_USER }} - run: | - mkdir -p ~/.ssh - echo "$EC2_SSH_KEY" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - - # SSH config 작성 - echo "Host ec2" > ~/.ssh/config - echo " HostName $EC2_HOST" >> ~/.ssh/config - echo " User $EC2_USER" >> ~/.ssh/config - echo " IdentityFile ~/.ssh/deploy_key" >> ~/.ssh/config - echo " StrictHostKeyChecking no" >> ~/.ssh/config - shell: bash - - - # 5) EC2에 최신 코드 Pull 및 설정 파일 배포 - # 수정하면서 application.yml 관련 코드는 필요없어짐 - - name: Update code and config on EC2 - run: | - # 1. EC2에서 git pull 먼저 - ssh -T -F ~/.ssh/config ec2 < ./src/main/resources/application.yml << 'EOF' + ${{ secrets.APP_YML }} EOF - # 2. 파일 전송 - scp -F ~/.ssh/config build/libs/tokkit.jar ec2:${{ secrets.APP_DIR }}/build/libs/tokkit.jar - scp -F ~/.ssh/config src/main/resources/application.yml ec2:${{ secrets.APP_DIR }}/src/main/resources/application.yml - scp -F ~/.ssh/config .env ec2:${{ secrets.APP_DIR }}/.env - - # 3. 전송 결과 확인 - ssh -T -F ~/.ssh/config ec2 </dev/null || echo "none") + HEALTH_STATUS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.State.Health.Status}}' tokkit-springboot 2>/dev/null || echo 'none'") if [ "$HEALTH_STATUS" = "healthy" ]; then echo "✅ tokkit-springboot 컨테이너 헬스체크 성공" break + elif [ "$HEALTH_STATUS" = "none" ]; then + echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: 컨테이너가 없거나 상태 확인 불가)" + + # 컨테이너 로그 확인 (컨테이너가 존재하는 경우) + CONTAINER_EXISTS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker ps -a --format '{{.Names}}' | grep tokkit-springboot || echo ''") + if [ ! -z "$CONTAINER_EXISTS" ]; then + echo "📋 tokkit-springboot 컨테이너 로그 (최근 30줄):" + ssh -o StrictHostKeyChecking=no $SSH_HOST "docker logs tokkit-springboot --tail 30" + fi else echo "⏳ 헬스체크 대기 중... 시도 $i/$MAX_RETRIES (현재 상태: $HEALTH_STATUS)" - sleep $RETRY_INTERVAL fi + sleep $RETRY_INTERVAL + if [ "$i" -eq "$MAX_RETRIES" ]; then echo "❌ tokkit-springboot 컨테이너가 헬스체크를 통과하지 못했습니다." + + # 배포 상태 확인 + echo "📊 docker-compose 상태:" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml ps" + + # 스프링부트 컨테이너 로그 확인 + echo "📋 tokkit-springboot 컨테이너 로그 (최근 100줄):" + ssh -o StrictHostKeyChecking=no $SSH_HOST "docker logs tokkit-springboot --tail 100 || echo '로그를 가져올 수 없습니다.'" + + # 환경 변수 확인 + echo "🔍 환경 변수 확인:" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && grep DOCKER_USERNAME .env || echo 'DOCKER_USERNAME not found in .env'" + exit 1 fi done - - # 모든 컨테이너 상태 점검 + # ✅ 모든 컨테이너 실행 상태 점검 COMPOSE_FILES="-f docker-compose.dev.yml -f docker-compose.prod.yml" - CONTAINERS=$(ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") + CONTAINERS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps -q") UNHEALTHY_COUNT=0 echo "📊 컨테이너 상태 확인 중..." for CONTAINER_ID in $CONTAINERS; do # 각 컨테이너의 상태 확인 - CONTAINER_STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") - CONTAINER_NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') + CONTAINER_STATUS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.State.Status}}' $CONTAINER_ID") + CONTAINER_NAME=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.Name}}' $CONTAINER_ID" | sed 's/^\///') if [ "$CONTAINER_STATUS" != "running" ]; then echo "⚠️ 컨테이너 $CONTAINER_NAME 상태: $CONTAINER_STATUS (실행 중 아님)" @@ -205,32 +195,32 @@ jobs: fi done - # 비정상 컨테이너 처리 if [ "$UNHEALTHY_COUNT" -gt 0 ]; then echo "❌ 배포 실패: $UNHEALTHY_COUNT 개 컨테이너 비정상" echo "=== 비정상 컨테이너 상세 상태 ===" for CID in $CONTAINERS; do - STATUS=$(ssh ec2 "docker inspect --format='{{.State.Status}}' $CID") + STATUS=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.State.Status}}' $CID") if [ "$STATUS" != "running" ]; then - NAME=$(ssh ec2 "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') + NAME=$(ssh -o StrictHostKeyChecking=no $SSH_HOST "docker inspect --format='{{.Name}}' $CID" | sed 's/^\///') echo "🪫 $NAME 상태: $STATUS" echo "--- $NAME 로그 (tail 50) ---" - ssh ec2 "docker logs $NAME --tail=50" + ssh -o StrictHostKeyChecking=no $SSH_HOST "docker logs $NAME --tail=50" fi done # 상태 테이블 출력 - ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" exit 1 # 성공 처리 else TOTAL_COUNT=$(echo "$CONTAINERS" | wc -w) echo "✅ 배포 성공: 모든 컨테이너($TOTAL_COUNT 개) 정상 실행" - ssh ec2 "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" + ssh -o StrictHostKeyChecking=no $SSH_HOST "cd ${{ secrets.APP_DIR }} && docker-compose $COMPOSE_FILES ps" fi - # 8) SSH 설정 정리 + + # 4) SSH 키 제거 (보안 정리) - name: Cleanup SSH if: always() run: rm -rf ~/.ssh diff --git a/.github/workflows/tmp.yml b/.github/workflows/tmp.yml deleted file mode 100644 index f80e0e2..0000000 --- a/.github/workflows/tmp.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Tokkit CI/CD Pipeline - -on: - push: - branches: - - develop - - main - workflow_dispatch: - -jobs: - # ------------------------------------------------------------------- - # 1) CI - # ------------------------------------------------------------------- - build: - runs-on: ubuntu-latest - steps: - # 1) 저장소 코드 가져오기 - - name: Checkout code - uses: actions/checkout@v3 - - # 2) Java 21 설치 - - name: Set up Java 21 - uses: actions/setup-java@v3 - with: - java-version: '21' - distribution: 'temurin' - - # 3) Gradle 관련 파일들 캐싱 - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - gradle-${{ runner.os }}- - - # 4) 설정 파일 생성 (CI 단계에서 필요) - - name: Generate Configuration Files - run: | - # application.yml 생성 - mkdir -p src/main/resources - cat < src/main/resources/application.yml - ${{ secrets.APP_YML }} - EOT - - # .env 파일 생성 (Docker 이미지에 환경변수로 포함되게 할 경우) - cat < .env - ${{ secrets.ENV_FILE }} - EOT - - # 5) Gradle로 빌드 - - name: Run Gradle Build - run: ./gradlew clean build -x test --no-daemon --build-cache - - # → Docker Hub 로그인 - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - # → Docker 이미지 빌드 & 푸시 - - name: Build and push Docker image - id: build_push - run: | - IMAGE_TAG=${{ secrets.DOCKER_USERNAME }}/tokkit-springboot:latest - docker build \ - --file Dockerfile.cicd \ - --tag $IMAGE_TAG \ - . - docker push $IMAGE_TAG - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT - - # ------------------------------------------------------------------- - # 2) CD: EC2에 배포 - # ------------------------------------------------------------------- - deploy: - needs: build - runs-on: ubuntu-latest - - steps: - - name: Setup SSH key - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.EC2_SSH_KEY }} - - - name: Deploy to EC2 - run: | - ssh -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} <<'EOF' - set -e - cd ${{ secrets.APP_DIR }} - - echo "📥 Git pull" - git fetch origin - git checkout -B ${{ github.ref_name }} origin/${{ github.ref_name }} - git pull origin ${{ github.ref_name }} - - echo "📝 Generate .env" - cat <<-'EOT' > ./.env - ${{ secrets.ENV_FILE }} - EOT - - echo "📝 Generate application.yml" - mkdir -p src/main/resources - cat <<-'EOT' > src/main/resources/application.yml - ${{ secrets.APP_YML }} - EOT - - echo "🔒 Docker login" - echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \ - --username "${{ secrets.DOCKER_USERNAME }}" \ - --password-stdin - - # Docker Hub 사용자명 환경 변수 설정 - export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" - - echo "🚀 Docker Compose up (dev + prod)" - docker-compose -f docker-compose.dev.yml -f docker-compose.prod.yml up -d - - echo "🔍 Health check" - for i in \$(seq 1 5); do - STATUS=\$(docker inspect --format='{{.State.Health.Status}}' tokkit-springboot 2>/dev/null || echo none) - echo " → Attempt \$i: \$STATUS" - [ "\$STATUS" = "healthy" ] && { echo "✅ Healthy"; exit 0; } - sleep 10 - done - echo "❌ Health check failed" - exit 1 - EOF - - # 8) SSH 설정 정리 - - name: Cleanup SSH - if: always() - run: rm -rf ~/.ssh - shell: bash \ No newline at end of file diff --git a/Dockerfile.cicd b/Dockerfile.cicd index 772a5b9..f133708 100644 --- a/Dockerfile.cicd +++ b/Dockerfile.cicd @@ -8,5 +8,8 @@ RUN apt-get update \ # CI/CD에서 전송된 tokkit.jar만 복사 COPY build/libs/tokkit.jar tokkit.jar +# application.yml 복사 +#COPY src/main/resources/application.yml src/main/resources/application.yml + EXPOSE 8080 ENTRYPOINT ["java", "-jar", "tokkit.jar"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 2d9fe14..b618a4e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,12 +2,12 @@ services: # springboot springboot: container_name: tokkit-springboot -# image: ${DOCKER_USERNAME}/tokkit-springboot:latest - build: - context: . # 현재 디렉토리 기준 Dockerfile 위치 -# dockerfile: Dockerfile.local # 수동 배포 시 사용하는 Dockerfile - dockerfile: Dockerfile.cicd - image: tokkit/springboot:latest + image: ${DOCKER_USERNAME}/tokkit-springboot:latest +# build: +# context: . # 현재 디렉토리 기준 Dockerfile 위치 +## dockerfile: Dockerfile.local # 수동 배포 시 사용하는 Dockerfile +# dockerfile: Dockerfile.cicd +# image: tokkit/springboot:latest ports: - "8080:8080" env_file: From 1a642342e295fc6b0ab9c4e48fede9ffc1ea9e9a Mon Sep 17 00:00:00 2001 From: Jinseong Date: Tue, 20 May 2025 15:30:50 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[#96]=20Refactor:=20CICD=20=EC=B5=9C?= =?UTF-8?q?=EC=A2=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{deploy.basic.yml.disabled => basic_deploy.yml.disabled} | 0 .github/workflows/{deploy.yml => docker_hub_deploy.yml} | 0 .../{deploy.jar.yml.disabled => jar_deploy.yml.disabled} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{deploy.basic.yml.disabled => basic_deploy.yml.disabled} (100%) rename .github/workflows/{deploy.yml => docker_hub_deploy.yml} (100%) rename .github/workflows/{deploy.jar.yml.disabled => jar_deploy.yml.disabled} (100%) diff --git a/.github/workflows/deploy.basic.yml.disabled b/.github/workflows/basic_deploy.yml.disabled similarity index 100% rename from .github/workflows/deploy.basic.yml.disabled rename to .github/workflows/basic_deploy.yml.disabled diff --git a/.github/workflows/deploy.yml b/.github/workflows/docker_hub_deploy.yml similarity index 100% rename from .github/workflows/deploy.yml rename to .github/workflows/docker_hub_deploy.yml diff --git a/.github/workflows/deploy.jar.yml.disabled b/.github/workflows/jar_deploy.yml.disabled similarity index 100% rename from .github/workflows/deploy.jar.yml.disabled rename to .github/workflows/jar_deploy.yml.disabled