From 815d7eb3291ab2569d64aed66bbc30a84880448a Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Wed, 20 Aug 2025 23:32:30 +0900 Subject: [PATCH 01/17] ci(cicd): Add Lightsail Blue/Green deploy workflow for PROD --- .github/workflows/cicd-light-sail-prod.yml | 195 +++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 .github/workflows/cicd-light-sail-prod.yml diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml new file mode 100644 index 00000000..165a2de8 --- /dev/null +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -0,0 +1,195 @@ +name: Build and Deploy to PROD (Lightsail Blue/Green via SSH + Docker) + +on: + push: + branches: [ "main-test" ] + +env: + PROJECT_NAME: "devdevdev" + IMAGE_NAME: "devdevdev/app" # 로컬 빌드 이미지 이름 + CONTAINER_BASE: "devdevdev-main-server" # 컨테이너 베이스명 + BLUE_SUFFIX: "-blue" + GREEN_SUFFIX: "-green" + BLUE_PORT: "18080" # 호스트 포트(Blue) + GREEN_PORT: "18081" # 호스트 포트(Green) + APP_PORT: "8080" # 컨테이너 내부 포트 + HEALTHCHECK_PATH: "/actuator/health" # 헬스체크 경로 + HEALTHCHECK_TIMEOUT: "3" # curl 타임아웃(초) + HEALTHCHECK_RETRY: "10" # 재시도 횟수 + SSH_USER: "ec2-user" # SSH 접속 사용자 + LIGHTSAIL_HOST: "${{ secrets.LIGHTSAIL_HOST }}" + +jobs: + build: + name: Build and Deploy (Blue/Green) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + # ----- Build prerequisites (리소스/시크릿 주입은 기존 그대로 유지) ----- + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: 21 + distribution: corretto + + - name: make application-prod.yml + run: | + cd ./src/main/resources + echo "${{ secrets.application_prod }}" >> ./application-prod.yml + echo "${{ secrets.application_jwt_prod }}" >> ./application-jwt-prod.yml + echo "${{ secrets.application_oauth2_prod }}" >> ./application-oauth2-prod.yml + echo "${{ secrets.application_storage_s3_prod }}" >> ./application-storage-s3-prod.yml + echo "${{ secrets.application_open_ai }}" >> ./application-open-ai.yml + echo "${{ secrets.application_opensearch_prod }}" >> ./application-opensearch-prod.yml + + - name: make application-test.yml + run: | + cd ./src/test/resources + echo "${{ secrets.application_storage_s3 }}" >> ./application-storage-s3.yml + echo "${{ secrets.application_open_ai }}" >> ./application-open-ai.yml + echo "${{ secrets.application_opensearch_test }}" >> ./application-opensearch-test.yml + + # ----- Docker build ----- + - name: Use Dockerfile-prod if present + run: | + if [ -f Dockerfile-prod ]; then + rm -f Dockerfile + cp Dockerfile-prod Dockerfile + fi + + - name: Build Docker image + run: | + docker build -t ${IMAGE_NAME}:${GITHUB_SHA} -t ${IMAGE_NAME}:latest . + + - name: Save image as archive + run: | + mkdir -p out + docker save ${IMAGE_NAME}:${GITHUB_SHA} | gzip > out/image-${GITHUB_SHA}.tar.gz + echo "ARCHIVE=out/image-${GITHUB_SHA}.tar.gz" >> $GITHUB_ENV + + # ----- SSH 준비 ----- + - name: Prepare SSH key + run: | + echo "${{ secrets.LIGHTSAIL_SSH_KEY }}" > key.pem + chmod 600 key.pem + mkdir -p ~/.ssh + ssh-keyscan -H ${LIGHTSAIL_HOST} >> ~/.ssh/known_hosts + + # ----- 전송 ----- + - name: Upload image archive + run: | + scp -i key.pem -o StrictHostKeyChecking=yes "$ARCHIVE" \ + ${SSH_USER}@${LIGHTSAIL_HOST}:/home/${SSH_USER}/ + + - name: Upload blue/green deploy script + run: | + cat > deploy_blue_green.sh <<'EOS' + #!/usr/bin/env bash + set -euo pipefail + + IMAGE_NAME="${IMAGE_NAME}" + CONTAINER_BASE="${CONTAINER_BASE}" + BLUE_SUFFIX="${BLUE_SUFFIX}" + GREEN_SUFFIX="${GREEN_SUFFIX}" + BLUE_PORT="${BLUE_PORT}" + GREEN_PORT="${GREEN_PORT}" + APP_PORT="${APP_PORT}" + HEALTHCHECK_PATH="${HEALTHCHECK_PATH}" + HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT}" + HEALTHCHECK_RETRY="${HEALTHCHECK_RETRY}" + + UPSTREAM_FILE="/etc/nginx/conf.d/backend-upstream.conf" + + BLUE_NAME="${CONTAINER_BASE}${BLUE_SUFFIX}" + GREEN_NAME="${CONTAINER_BASE}${GREEN_SUFFIX}" + + ARCHIVE_FILE="$(ls -t ~/image-*.tar.gz | head -n1)" + echo "[1/8] Load image: ${ARCHIVE_FILE}" + sudo docker load -i "$ARCHIVE_FILE" + + # 현재 활성 포트를 파악(업스트림 파일에서 파싱) + ACTIVE_PORT="" + if [ -f "${UPSTREAM_FILE}" ]; then + ACTIVE_PORT=$(grep -oE '127\.0\.0\.1:([0-9]+)' "${UPSTREAM_FILE}" | awk -F: '{print $2}' || true) + fi + echo "[2/8] Current active port: ${ACTIVE_PORT:-unknown}" + + # 타깃(유휴) 색/포트 결정 + if [ "${ACTIVE_PORT}" = "${BLUE_PORT}" ]; then + TARGET_NAME="${GREEN_NAME}" + TARGET_PORT="${GREEN_PORT}" + OLD_NAME="${BLUE_NAME}" + OLD_PORT="${BLUE_PORT}" + else + TARGET_NAME="${BLUE_NAME}" + TARGET_PORT="${BLUE_PORT}" + OLD_NAME="${GREEN_NAME}" + OLD_PORT="${GREEN_PORT}" + fi + + echo "[3/8] Target container: ${TARGET_NAME} on ${TARGET_PORT}" + + # 유휴 컨테이너 정리 + if sudo docker ps -a --format '{{.Names}}' | grep -qw "${TARGET_NAME}"; then + sudo docker stop "${TARGET_NAME}" || true + sudo docker rm "${TARGET_NAME}" || true + fi + + # 새 컨테이너 실행 (내부포트 8080 -> 호스트 TARGET_PORT 바인딩) + echo "[4/8] Run new container" + sudo docker run -d \ + --name "${TARGET_NAME}" \ + --restart=always \ + -p 127.0.0.1:${TARGET_PORT}:${APP_PORT} \ + -e SPRING_PROFILES_ACTIVE=prod \ + ${IMAGE_NAME}:latest + + # 헬스체크 (재시도) + echo "[5/8] Health check on http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" + ok=0 + for i in $(seq 1 ${HEALTHCHECK_RETRY}); do + if curl -fsS --max-time ${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" >/dev/null 2>&1; then + ok=1 + break + fi + echo " retry $i/${HEALTHCHECK_RETRY}..." + sleep 3 + done + if [ "$ok" -ne 1 ]; then + echo "[!] Health check failed. Rolling back." + sudo docker logs --tail 200 "${TARGET_NAME}" || true + sudo docker stop "${TARGET_NAME}" || true + sudo docker rm "${TARGET_NAME}" || true + exit 1 + fi + + # 업스트림 스위치 + echo "[6/8] Switch upstream to ${TARGET_PORT}" + echo "server 127.0.0.1:${TARGET_PORT};" | sudo tee "${UPSTREAM_FILE}" >/dev/null + sudo nginx -t + sudo systemctl reload nginx + + # 구 버전 종료 및 정리 + echo "[7/8] Stop old container: ${OLD_NAME} (if any)" + if sudo docker ps -a --format '{{.Names}}' | grep -qw "${OLD_NAME}"; then + sudo docker stop "${OLD_NAME}" || true + sudo docker rm "${OLD_NAME}" || true + fi + + # 아카이브 청소 + echo "[8/8] Cleanup old archives (keep last 3)" + cd ~ && ls -t image-*.tar.gz | tail -n +4 | xargs -r rm -f + + echo "Blue/Green deploy done." + EOS + chmod +x deploy_blue_green.sh + scp -i key.pem -o StrictHostKeyChecking=yes deploy_blue_green.sh ${SSH_USER}@${LIGHTSAIL_HOST}:/home/${SSH_USER}/ + + # ----- 원격 실행 ----- + - name: Remote Blue/Green deploy + run: | + ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} \ + "sudo bash /home/${SSH_USER}/deploy_blue_green.sh" + env: From cd601924024d49a35f729df7942d86dfab273b52 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Wed, 20 Aug 2025 23:39:13 +0900 Subject: [PATCH 02/17] ci(cicd): Add Lightsail Blue/Green deploy workflow for PROD --- .github/workflows/cicd-light-sail-prod.yml | 130 +++++++++++---------- 1 file changed, 70 insertions(+), 60 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 165a2de8..cc0aed4f 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -13,10 +13,10 @@ env: BLUE_PORT: "18080" # 호스트 포트(Blue) GREEN_PORT: "18081" # 호스트 포트(Green) APP_PORT: "8080" # 컨테이너 내부 포트 - HEALTHCHECK_PATH: "/actuator/health" # 헬스체크 경로 + HEALTHCHECK_PATH: "/actuator/health" # 없으면 "/" 로 바꾸세요 HEALTHCHECK_TIMEOUT: "3" # curl 타임아웃(초) - HEALTHCHECK_RETRY: "10" # 재시도 횟수 - SSH_USER: "ec2-user" # SSH 접속 사용자 + HEALTHCHECK_RETRY: "20" # 재시도 횟수 (약 60초) + SSH_USER: "ec2-user" # SSH 접속 사용자 LIGHTSAIL_HOST: "${{ secrets.LIGHTSAIL_HOST }}" jobs: @@ -27,7 +27,7 @@ jobs: steps: - uses: actions/checkout@v3 - # ----- Build prerequisites (리소스/시크릿 주입은 기존 그대로 유지) ----- + # ---- 애플리케이션 설정파일 주입 ---- - name: Set up JDK 21 uses: actions/setup-java@v3 with: @@ -51,7 +51,7 @@ jobs: echo "${{ secrets.application_open_ai }}" >> ./application-open-ai.yml echo "${{ secrets.application_opensearch_test }}" >> ./application-opensearch-test.yml - # ----- Docker build ----- + # ---- Docker 빌드 ---- - name: Use Dockerfile-prod if present run: | if [ -f Dockerfile-prod ]; then @@ -69,7 +69,7 @@ jobs: docker save ${IMAGE_NAME}:${GITHUB_SHA} | gzip > out/image-${GITHUB_SHA}.tar.gz echo "ARCHIVE=out/image-${GITHUB_SHA}.tar.gz" >> $GITHUB_ENV - # ----- SSH 준비 ----- + # ---- SSH 준비 ---- - name: Prepare SSH key run: | echo "${{ secrets.LIGHTSAIL_SSH_KEY }}" > key.pem @@ -77,7 +77,7 @@ jobs: mkdir -p ~/.ssh ssh-keyscan -H ${LIGHTSAIL_HOST} >> ~/.ssh/known_hosts - # ----- 전송 ----- + # ---- 아카이브/스크립트 전송 ---- - name: Upload image archive run: | scp -i key.pem -o StrictHostKeyChecking=yes "$ARCHIVE" \ @@ -85,7 +85,7 @@ jobs: - name: Upload blue/green deploy script run: | - cat > deploy_blue_green.sh <<'EOS' + cat > deploy_blue_green.sh </dev/null + ACTIVE_PORT="\${BLUE_PORT}" fi - echo "[2/8] Current active port: ${ACTIVE_PORT:-unknown}" + echo "[2/9] Current active port: \${ACTIVE_PORT}" # 타깃(유휴) 색/포트 결정 - if [ "${ACTIVE_PORT}" = "${BLUE_PORT}" ]; then - TARGET_NAME="${GREEN_NAME}" - TARGET_PORT="${GREEN_PORT}" - OLD_NAME="${BLUE_NAME}" - OLD_PORT="${BLUE_PORT}" + if [ "\${ACTIVE_PORT}" = "\${BLUE_PORT}" ]; then + TARGET_NAME="\${GREEN_NAME}" + TARGET_PORT="\${GREEN_PORT}" + OLD_NAME="\${BLUE_NAME}" + OLD_PORT="\${BLUE_PORT}" else - TARGET_NAME="${BLUE_NAME}" - TARGET_PORT="${BLUE_PORT}" - OLD_NAME="${GREEN_NAME}" - OLD_PORT="${GREEN_PORT}" + TARGET_NAME="\${BLUE_NAME}" + TARGET_PORT="\${BLUE_PORT}" + OLD_NAME="\${GREEN_NAME}" + OLD_PORT="\${GREEN_PORT}" fi + echo "[3/9] Target container: \${TARGET_NAME} on \${TARGET_PORT}" - echo "[3/8] Target container: ${TARGET_NAME} on ${TARGET_PORT}" - - # 유휴 컨테이너 정리 - if sudo docker ps -a --format '{{.Names}}' | grep -qw "${TARGET_NAME}"; then - sudo docker stop "${TARGET_NAME}" || true - sudo docker rm "${TARGET_NAME}" || true + # 유휴 컨테이너 정리 후 새로 실행 + if sudo docker ps -a --format '{{.Names}}' | grep -qw "\${TARGET_NAME}"; then + sudo docker stop "\${TARGET_NAME}" || true + sudo docker rm "\${TARGET_NAME}" || true fi - # 새 컨테이너 실행 (내부포트 8080 -> 호스트 TARGET_PORT 바인딩) - echo "[4/8] Run new container" + echo "[4/9] Run new container" sudo docker run -d \ - --name "${TARGET_NAME}" \ + --name "\${TARGET_NAME}" \ --restart=always \ - -p 127.0.0.1:${TARGET_PORT}:${APP_PORT} \ + -p 127.0.0.1:\${TARGET_PORT}:\${APP_PORT} \ -e SPRING_PROFILES_ACTIVE=prod \ - ${IMAGE_NAME}:latest + \${IMAGE_NAME}:latest - # 헬스체크 (재시도) - echo "[5/8] Health check on http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" + # 헬스체크: actuator가 없으면 HEALTHCHECK_PATH를 "/" 로 바꾸세요. + echo "[5/9] Health check http://127.0.0.1:\${TARGET_PORT}\${HEALTHCHECK_PATH}" ok=0 - for i in $(seq 1 ${HEALTHCHECK_RETRY}); do - if curl -fsS --max-time ${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" >/dev/null 2>&1; then - ok=1 - break + for i in $(seq 1 \${HEALTHCHECK_RETRY}); do + if curl -fsS --max-time \${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:\${TARGET_PORT}\${HEALTHCHECK_PATH}" >/dev/null 2>&1; then + ok=1; break fi - echo " retry $i/${HEALTHCHECK_RETRY}..." + echo " retry \$i/\${HEALTHCHECK_RETRY}..." sleep 3 done - if [ "$ok" -ne 1 ]; then + if [ "\$ok" -ne 1 ]; then echo "[!] Health check failed. Rolling back." - sudo docker logs --tail 200 "${TARGET_NAME}" || true - sudo docker stop "${TARGET_NAME}" || true - sudo docker rm "${TARGET_NAME}" || true + sudo docker logs --tail 200 "\${TARGET_NAME}" || true + sudo docker stop "\${TARGET_NAME}" || true + sudo docker rm "\${TARGET_NAME}" || true exit 1 fi # 업스트림 스위치 - echo "[6/8] Switch upstream to ${TARGET_PORT}" - echo "server 127.0.0.1:${TARGET_PORT};" | sudo tee "${UPSTREAM_FILE}" >/dev/null + echo "[6/9] Switch upstream to \${TARGET_PORT}" + echo "server 127.0.0.1:\${TARGET_PORT};" | sudo tee "\${UPSTREAM_FILE}" >/dev/null sudo nginx -t sudo systemctl reload nginx - # 구 버전 종료 및 정리 - echo "[7/8] Stop old container: ${OLD_NAME} (if any)" - if sudo docker ps -a --format '{{.Names}}' | grep -qw "${OLD_NAME}"; then - sudo docker stop "${OLD_NAME}" || true - sudo docker rm "${OLD_NAME}" || true + # 구 컨테이너 종료/삭제 + echo "[7/9] Stop old container: \${OLD_NAME} (if any)" + if sudo docker ps -a --format '{{.Names}}' | grep -qw "\${OLD_NAME}"; then + sudo docker stop "\${OLD_NAME}" || true + sudo docker rm "\${OLD_NAME}" || true fi - # 아카이브 청소 - echo "[8/8] Cleanup old archives (keep last 3)" + # 오래된 아카이브 정리 + echo "[8/9] Cleanup old archives (keep last 3)" cd ~ && ls -t image-*.tar.gz | tail -n +4 | xargs -r rm -f - echo "Blue/Green deploy done." + echo "[9/9] Done." EOS chmod +x deploy_blue_green.sh scp -i key.pem -o StrictHostKeyChecking=yes deploy_blue_green.sh ${SSH_USER}@${LIGHTSAIL_HOST}:/home/${SSH_USER}/ - # ----- 원격 실행 ----- + # ---- 원격 실행 ---- - name: Remote Blue/Green deploy run: | ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} \ "sudo bash /home/${SSH_USER}/deploy_blue_green.sh" + + # ---- Slack 알림 ---- + - name: action-slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: "[PROD] 배포 결과를 알려드려요" + fields: repo,message,commit,author,eventName,ref,took env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() From 5d7fc9c7b25c96d1c550cd102deb3c1fb4449fbd Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Wed, 20 Aug 2025 23:58:45 +0900 Subject: [PATCH 03/17] ci(cicd): Update Lightsail deployment workflow and Dockerfile for improved clarity and structure --- .github/workflows/cicd-light-sail-prod.yml | 51 +++++++++++++++------- Dockerfile-prod | 13 +++--- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index cc0aed4f..4d0536c8 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -5,19 +5,26 @@ on: branches: [ "main-test" ] env: + # --- 애플리케이션/컨테이너 공통 --- PROJECT_NAME: "devdevdev" - IMAGE_NAME: "devdevdev/app" # 로컬 빌드 이미지 이름 + IMAGE_NAME: "devdevdev/app" # 로컬 빌드 이미지 이름(태그 latest, SHA) CONTAINER_BASE: "devdevdev-main-server" # 컨테이너 베이스명 BLUE_SUFFIX: "-blue" GREEN_SUFFIX: "-green" + + # --- 포트 구성 --- BLUE_PORT: "18080" # 호스트 포트(Blue) GREEN_PORT: "18081" # 호스트 포트(Green) - APP_PORT: "8080" # 컨테이너 내부 포트 - HEALTHCHECK_PATH: "/actuator/health" # 없으면 "/" 로 바꾸세요 + APP_PORT: "8080" # 컨테이너 내부 포트(Spring Boot) + + # --- 헬스체크 --- + HEALTHCHECK_PATH: "/actuator/health" # Actuator 미사용이면 "/" 로 변경하세요 HEALTHCHECK_TIMEOUT: "3" # curl 타임아웃(초) HEALTHCHECK_RETRY: "20" # 재시도 횟수 (약 60초) - SSH_USER: "ec2-user" # SSH 접속 사용자 - LIGHTSAIL_HOST: "${{ secrets.LIGHTSAIL_HOST }}" + + # --- SSH/Lightsail --- + SSH_USER: "ec2-user" # SSH 접속 사용자 + LIGHTSAIL_HOST: "${{ secrets.LIGHTSAIL_HOST }}" # 퍼블릭 IP 또는 도메인 jobs: build: @@ -27,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v3 - # ---- 애플리케이션 설정파일 주입 ---- + # ====== 리소스/시크릿 주입 (현재 파이프라인과 동일) ====== - name: Set up JDK 21 uses: actions/setup-java@v3 with: @@ -51,7 +58,14 @@ jobs: echo "${{ secrets.application_open_ai }}" >> ./application-open-ai.yml echo "${{ secrets.application_opensearch_test }}" >> ./application-opensearch-test.yml - # ---- Docker 빌드 ---- + # ====== Gradle 빌드 (Docker가 JAR을 COPY할 수 있도록 선행) ====== + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Build with Gradle (bootJar) + run: ./gradlew bootJar + + # ====== Docker 빌드 ====== - name: Use Dockerfile-prod if present run: | if [ -f Dockerfile-prod ]; then @@ -61,7 +75,10 @@ jobs: - name: Build Docker image run: | - docker build -t ${IMAGE_NAME}:${GITHUB_SHA} -t ${IMAGE_NAME}:latest . + docker build \ + -t ${IMAGE_NAME}:${GITHUB_SHA} \ + -t ${IMAGE_NAME}:latest \ + . - name: Save image as archive run: | @@ -69,7 +86,7 @@ jobs: docker save ${IMAGE_NAME}:${GITHUB_SHA} | gzip > out/image-${GITHUB_SHA}.tar.gz echo "ARCHIVE=out/image-${GITHUB_SHA}.tar.gz" >> $GITHUB_ENV - # ---- SSH 준비 ---- + # ====== SSH 준비 ====== - name: Prepare SSH key run: | echo "${{ secrets.LIGHTSAIL_SSH_KEY }}" > key.pem @@ -77,7 +94,7 @@ jobs: mkdir -p ~/.ssh ssh-keyscan -H ${LIGHTSAIL_HOST} >> ~/.ssh/known_hosts - # ---- 아카이브/스크립트 전송 ---- + # ====== 아카이브/스크립트 전송 ====== - name: Upload image archive run: | scp -i key.pem -o StrictHostKeyChecking=yes "$ARCHIVE" \ @@ -100,7 +117,9 @@ jobs: HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT}" HEALTHCHECK_RETRY="${HEALTHCHECK_RETRY}" + # Nginx 업스트림 경로(사전에 구성됨) UPSTREAM_FILE="/etc/nginx/conf.d/backend-upstream.upstream" + BLUE_NAME="\${CONTAINER_BASE}\${BLUE_SUFFIX}" GREEN_NAME="\${CONTAINER_BASE}\${GREEN_SUFFIX}" @@ -108,10 +127,10 @@ jobs: echo "[1/9] Load image: \${ARCHIVE_FILE}" sudo docker load -i "\${ARCHIVE_FILE}" - # 현재 활성 포트 확인(없으면 BLUE로 초기화) + # 현재 활성 포트 확인 (없으면 BLUE로 초기화) ACTIVE_PORT="" if [ -f "\${UPSTREAM_FILE}" ]; then - ACTIVE_PORT=$(grep -oE '127\.0\.0\.1:([0-9]+)' "\${UPSTREAM_FILE}" | awk -F: '{print $2}' || true) + ACTIVE_PORT=\$(grep -oE '127\.0\.0\.1:([0-9]+)' "\${UPSTREAM_FILE}" | awk -F: '{print \$2}' || true) fi if [ -z "\${ACTIVE_PORT}" ]; then echo "server 127.0.0.1:\${BLUE_PORT};" | sudo tee "\${UPSTREAM_FILE}" >/dev/null @@ -147,10 +166,10 @@ jobs: -e SPRING_PROFILES_ACTIVE=prod \ \${IMAGE_NAME}:latest - # 헬스체크: actuator가 없으면 HEALTHCHECK_PATH를 "/" 로 바꾸세요. + # 헬스체크 (Actuator 미사용 시 HEALTHCHECK_PATH="/" 로 설정) echo "[5/9] Health check http://127.0.0.1:\${TARGET_PORT}\${HEALTHCHECK_PATH}" ok=0 - for i in $(seq 1 \${HEALTHCHECK_RETRY}); do + for i in \$(seq 1 \${HEALTHCHECK_RETRY}); do if curl -fsS --max-time \${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:\${TARGET_PORT}\${HEALTHCHECK_PATH}" >/dev/null 2>&1; then ok=1; break fi @@ -187,13 +206,13 @@ jobs: chmod +x deploy_blue_green.sh scp -i key.pem -o StrictHostKeyChecking=yes deploy_blue_green.sh ${SSH_USER}@${LIGHTSAIL_HOST}:/home/${SSH_USER}/ - # ---- 원격 실행 ---- + # ====== 원격 실행 ====== - name: Remote Blue/Green deploy run: | ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} \ "sudo bash /home/${SSH_USER}/deploy_blue_green.sh" - # ---- Slack 알림 ---- + # ====== Slack 알림 ====== - name: action-slack uses: 8398a7/action-slack@v3 with: diff --git a/Dockerfile-prod b/Dockerfile-prod index 7c6e0949..d0416e95 100644 --- a/Dockerfile-prod +++ b/Dockerfile-prod @@ -1,9 +1,6 @@ -FROM openjdk:21-jdk -# JAR 파일 메인 디렉토리에 복사 +FROM eclipse-temurin:21-jre +WORKDIR /app COPY build/libs/*.jar app.jar - -# 타임존 설정 -ENV TZ Asia/Seoul - -# 시스템 진입점 정의 -CMD java -jar -Dspring.profiles.active=prod /app.jar \ No newline at end of file +ENV TZ=Asia/Seoul +EXPOSE 8080 +ENTRYPOINT ["java","-jar","-Dspring.profiles.active=prod","/app/app.jar"] \ No newline at end of file From fd29aeb0c71f0e97e9c81d38d21187e5f60abbce Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 00:30:36 +0900 Subject: [PATCH 04/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 4d0536c8..5bb135f0 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -123,7 +123,7 @@ jobs: BLUE_NAME="\${CONTAINER_BASE}\${BLUE_SUFFIX}" GREEN_NAME="\${CONTAINER_BASE}\${GREEN_SUFFIX}" - ARCHIVE_FILE="$(ls -t ~/image-*.tar.gz | head -n1)" + ARCHIVE_FILE="$(ls -t /home/${SSH_USER}/image-*.tar.gz | head -n1)" echo "[1/9] Load image: \${ARCHIVE_FILE}" sudo docker load -i "\${ARCHIVE_FILE}" From c7e4ebbe7f6cde64c339c34b942c42d2a49cbf3d Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 00:44:38 +0900 Subject: [PATCH 05/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 5bb135f0..0ed3a33e 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -105,6 +105,7 @@ jobs: cat > deploy_blue_green.sh </dev/null 2>&1 || true IMAGE_NAME="${IMAGE_NAME}" CONTAINER_BASE="${CONTAINER_BASE}" @@ -123,7 +124,7 @@ jobs: BLUE_NAME="\${CONTAINER_BASE}\${BLUE_SUFFIX}" GREEN_NAME="\${CONTAINER_BASE}\${GREEN_SUFFIX}" - ARCHIVE_FILE="$(ls -t /home/${SSH_USER}/image-*.tar.gz | head -n1)" + ARCHIVE_FILE="$(ls -t "$HOME"/image-*.tar.gz | head -n1)" echo "[1/9] Load image: \${ARCHIVE_FILE}" sudo docker load -i "\${ARCHIVE_FILE}" @@ -209,8 +210,7 @@ jobs: # ====== 원격 실행 ====== - name: Remote Blue/Green deploy run: | - ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} \ - "sudo bash /home/${SSH_USER}/deploy_blue_green.sh" + ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} "bash /home/${SSH_USER}/deploy_blue_green.sh" # ====== Slack 알림 ====== - name: action-slack From 07bcb900044dfc1bdef11f5a3a2331e6c3130faf Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 00:55:56 +0900 Subject: [PATCH 06/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 107 ++++++++++----------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 0ed3a33e..194ad587 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -83,8 +83,8 @@ jobs: - name: Save image as archive run: | mkdir -p out - docker save ${IMAGE_NAME}:${GITHUB_SHA} | gzip > out/image-${GITHUB_SHA}.tar.gz - echo "ARCHIVE=out/image-${GITHUB_SHA}.tar.gz" >> $GITHUB_ENV + docker save ${IMAGE_NAME}:${GITHUB_SHA} | gzip > out/image.tar.gz + echo "ARCHIVE=out/image.tar.gz" >> $GITHUB_ENV # ====== SSH 준비 ====== - name: Prepare SSH key @@ -98,13 +98,14 @@ jobs: - name: Upload image archive run: | scp -i key.pem -o StrictHostKeyChecking=yes "$ARCHIVE" \ - ${SSH_USER}@${LIGHTSAIL_HOST}:/home/${SSH_USER}/ + ${SSH_USER}@${LIGHTSAIL_HOST}:/home/${SSH_USER}/image.tar.gz - name: Upload blue/green deploy script run: | - cat > deploy_blue_green.sh < deploy_blue_green.sh <<'EOS' #!/usr/bin/env bash set -euo pipefail + sudo systemctl enable --now docker >/dev/null 2>&1 || true IMAGE_NAME="${IMAGE_NAME}" @@ -118,89 +119,80 @@ jobs: HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT}" HEALTHCHECK_RETRY="${HEALTHCHECK_RETRY}" - # Nginx 업스트림 경로(사전에 구성됨) UPSTREAM_FILE="/etc/nginx/conf.d/backend-upstream.upstream" + BLUE_NAME="${CONTAINER_BASE}${BLUE_SUFFIX}" + GREEN_NAME="${CONTAINER_BASE}${GREEN_SUFFIX}" - BLUE_NAME="\${CONTAINER_BASE}\${BLUE_SUFFIX}" - GREEN_NAME="\${CONTAINER_BASE}\${GREEN_SUFFIX}" + ARCHIVE_FILE="/home/${SSH_USER}/image.tar.gz" - ARCHIVE_FILE="$(ls -t "$HOME"/image-*.tar.gz | head -n1)" - echo "[1/9] Load image: \${ARCHIVE_FILE}" - sudo docker load -i "\${ARCHIVE_FILE}" + echo "[debug] whoami=$(whoami) HOME=$HOME" + echo "[1/9] Load image: ${ARCHIVE_FILE}" + ls -lh "${ARCHIVE_FILE}" || { echo "[!] archive missing"; exit 1; } + gzip -t "${ARCHIVE_FILE}" + gunzip -c "${ARCHIVE_FILE}" | sudo docker load - # 현재 활성 포트 확인 (없으면 BLUE로 초기화) ACTIVE_PORT="" - if [ -f "\${UPSTREAM_FILE}" ]; then - ACTIVE_PORT=\$(grep -oE '127\.0\.0\.1:([0-9]+)' "\${UPSTREAM_FILE}" | awk -F: '{print \$2}' || true) + if [ -f "${UPSTREAM_FILE}" ]; then + ACTIVE_PORT=$(grep -oE '127\.0\.0\.1:([0-9]+)' "${UPSTREAM_FILE}" | awk -F: '{print $2}' || true) fi - if [ -z "\${ACTIVE_PORT}" ]; then - echo "server 127.0.0.1:\${BLUE_PORT};" | sudo tee "\${UPSTREAM_FILE}" >/dev/null - ACTIVE_PORT="\${BLUE_PORT}" + if [ -z "${ACTIVE_PORT}" ]; then + echo "server 127.0.0.1:${BLUE_PORT};" | sudo tee "${UPSTREAM_FILE}" >/dev/null + ACTIVE_PORT="${BLUE_PORT}" fi - echo "[2/9] Current active port: \${ACTIVE_PORT}" - - # 타깃(유휴) 색/포트 결정 - if [ "\${ACTIVE_PORT}" = "\${BLUE_PORT}" ]; then - TARGET_NAME="\${GREEN_NAME}" - TARGET_PORT="\${GREEN_PORT}" - OLD_NAME="\${BLUE_NAME}" - OLD_PORT="\${BLUE_PORT}" + echo "[2/9] Current active port: ${ACTIVE_PORT}" + + if [ "${ACTIVE_PORT}" = "${BLUE_PORT}" ]; then + TARGET_NAME="${GREEN_NAME}"; TARGET_PORT="${GREEN_PORT}" + OLD_NAME="${BLUE_NAME}"; OLD_PORT="${BLUE_PORT}" else - TARGET_NAME="\${BLUE_NAME}" - TARGET_PORT="\${BLUE_PORT}" - OLD_NAME="\${GREEN_NAME}" - OLD_PORT="\${GREEN_PORT}" + TARGET_NAME="${BLUE_NAME}"; TARGET_PORT="${BLUE_PORT}" + OLD_NAME="${GREEN_NAME}"; OLD_PORT="${GREEN_PORT}" fi - echo "[3/9] Target container: \${TARGET_NAME} on \${TARGET_PORT}" + echo "[3/9] Target container: ${TARGET_NAME} on ${TARGET_PORT}" - # 유휴 컨테이너 정리 후 새로 실행 - if sudo docker ps -a --format '{{.Names}}' | grep -qw "\${TARGET_NAME}"; then - sudo docker stop "\${TARGET_NAME}" || true - sudo docker rm "\${TARGET_NAME}" || true + if sudo docker ps -a --format '{{.Names}}' | grep -qw "${TARGET_NAME}"; then + sudo docker stop "${TARGET_NAME}" || true + sudo docker rm "${TARGET_NAME}" || true fi echo "[4/9] Run new container" sudo docker run -d \ - --name "\${TARGET_NAME}" \ + --name "${TARGET_NAME}" \ --restart=always \ - -p 127.0.0.1:\${TARGET_PORT}:\${APP_PORT} \ + -p 127.0.0.1:${TARGET_PORT}:${APP_PORT} \ -e SPRING_PROFILES_ACTIVE=prod \ - \${IMAGE_NAME}:latest + ${IMAGE_NAME}:latest - # 헬스체크 (Actuator 미사용 시 HEALTHCHECK_PATH="/" 로 설정) - echo "[5/9] Health check http://127.0.0.1:\${TARGET_PORT}\${HEALTHCHECK_PATH}" + echo "[5/9] Health check http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" ok=0 - for i in \$(seq 1 \${HEALTHCHECK_RETRY}); do - if curl -fsS --max-time \${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:\${TARGET_PORT}\${HEALTHCHECK_PATH}" >/dev/null 2>&1; then + for i in $(seq 1 ${HEALTHCHECK_RETRY}); do + if curl -fsS --max-time ${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" >/dev/null 2>&1; then ok=1; break fi - echo " retry \$i/\${HEALTHCHECK_RETRY}..." + echo " retry $i/${HEALTHCHECK_RETRY}..." sleep 3 done - if [ "\$ok" -ne 1 ]; then - echo "[!] Health check failed. Rolling back." - sudo docker logs --tail 200 "\${TARGET_NAME}" || true - sudo docker stop "\${TARGET_NAME}" || true - sudo docker rm "\${TARGET_NAME}" || true + if [ "$ok" -ne 1 ]; then + echo "[!] Health check failed. Rollback." + sudo docker logs --tail 200 "${TARGET_NAME}" || true + sudo docker stop "${TARGET_NAME}" || true + sudo docker rm "${TARGET_NAME}" || true exit 1 fi - # 업스트림 스위치 - echo "[6/9] Switch upstream to \${TARGET_PORT}" - echo "server 127.0.0.1:\${TARGET_PORT};" | sudo tee "\${UPSTREAM_FILE}" >/dev/null + echo "[6/9] Switch upstream to ${TARGET_PORT}" + echo "server 127.0.0.1:${TARGET_PORT};" | sudo tee "${UPSTREAM_FILE}" >/dev/null sudo nginx -t sudo systemctl reload nginx - # 구 컨테이너 종료/삭제 - echo "[7/9] Stop old container: \${OLD_NAME} (if any)" - if sudo docker ps -a --format '{{.Names}}' | grep -qw "\${OLD_NAME}"; then - sudo docker stop "\${OLD_NAME}" || true - sudo docker rm "\${OLD_NAME}" || true + echo "[7/9] Stop old container: ${OLD_NAME} (if any)" + if sudo docker ps -a --format '{{.Names}}' | grep -qw "${OLD_NAME}"; then + sudo docker stop "${OLD_NAME}" || true + sudo docker rm "${OLD_NAME}" || true fi - # 오래된 아카이브 정리 echo "[8/9] Cleanup old archives (keep last 3)" - cd ~ && ls -t image-*.tar.gz | tail -n +4 | xargs -r rm -f + cd "/home/${SSH_USER}" && ls -t image*.tar.gz | tail -n +4 | xargs -r rm -f echo "[9/9] Done." EOS @@ -210,7 +202,8 @@ jobs: # ====== 원격 실행 ====== - name: Remote Blue/Green deploy run: | - ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} "bash /home/${SSH_USER}/deploy_blue_green.sh" + ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} \ + "bash /home/${SSH_USER}/deploy_blue_green.sh" # ====== Slack 알림 ====== - name: action-slack From 528eb035d1fd780c6c9fc15e907fcdaefa846800 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 01:05:57 +0900 Subject: [PATCH 07/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 194ad587..386dd2c2 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -108,16 +108,16 @@ jobs: sudo systemctl enable --now docker >/dev/null 2>&1 || true - IMAGE_NAME="${IMAGE_NAME}" - CONTAINER_BASE="${CONTAINER_BASE}" - BLUE_SUFFIX="${BLUE_SUFFIX}" - GREEN_SUFFIX="${GREEN_SUFFIX}" - BLUE_PORT="${BLUE_PORT}" - GREEN_PORT="${GREEN_PORT}" - APP_PORT="${APP_PORT}" - HEALTHCHECK_PATH="${HEALTHCHECK_PATH}" - HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT}" - HEALTHCHECK_RETRY="${HEALTHCHECK_RETRY}" + IMAGE_NAME=${IMAGE_NAME:-devdevdev/app} + CONTAINER_BASE=${CONTAINER_BASE:-devdevdev-main-server} + BLUE_SUFFIX=${BLUE_SUFFIX:--blue} + GREEN_SUFFIX=${GREEN_SUFFIX:--green} + BLUE_PORT=${BLUE_PORT:-18080} + GREEN_PORT=${GREEN_PORT:-18081} + APP_PORT=${APP_PORT:-8080} + HEALTHCHECK_PATH=${HEALTHCHECK_PATH:-/} # actuator 없으면 / + HEALTHCHECK_TIMEOUT=${HEALTHCHECK_TIMEOUT:-3} + HEALTHCHECK_RETRY=${HEALTHCHECK_RETRY:-20} UPSTREAM_FILE="/etc/nginx/conf.d/backend-upstream.upstream" BLUE_NAME="${CONTAINER_BASE}${BLUE_SUFFIX}" From a0f18c9ebf1126e021cc9c8a5c7108bd473aaa61 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 01:16:02 +0900 Subject: [PATCH 08/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 386dd2c2..96ce4ed4 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -63,7 +63,7 @@ jobs: run: chmod +x ./gradlew - name: Build with Gradle (bootJar) - run: ./gradlew bootJar + run: ./gradlew bootJar -x test # ====== Docker 빌드 ====== - name: Use Dockerfile-prod if present @@ -123,9 +123,9 @@ jobs: BLUE_NAME="${CONTAINER_BASE}${BLUE_SUFFIX}" GREEN_NAME="${CONTAINER_BASE}${GREEN_SUFFIX}" - ARCHIVE_FILE="/home/${SSH_USER}/image.tar.gz" - - echo "[debug] whoami=$(whoami) HOME=$HOME" + # 아카이브 경로는 HOME 기준으로 + ARCHIVE_FILE="$HOME/image.tar.gz" + echo "[1/9] Load image: ${ARCHIVE_FILE}" ls -lh "${ARCHIVE_FILE}" || { echo "[!] archive missing"; exit 1; } gzip -t "${ARCHIVE_FILE}" From 848768e95c52ee6ccf939fb830d4f188d0aa1b8f Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 01:20:44 +0900 Subject: [PATCH 09/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 96ce4ed4..985928fa 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -63,7 +63,7 @@ jobs: run: chmod +x ./gradlew - name: Build with Gradle (bootJar) - run: ./gradlew bootJar -x test + run: ./gradlew bootJar -x test -x asciidoctor # ====== Docker 빌드 ====== - name: Use Dockerfile-prod if present From 18a1e2d3944447bccc37d2f0098cdc71c594acab Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Thu, 21 Aug 2025 01:28:08 +0900 Subject: [PATCH 10/17] fix(ci/cd): Update archive file path in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 985928fa..1051b10d 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -83,7 +83,8 @@ jobs: - name: Save image as archive run: | mkdir -p out - docker save ${IMAGE_NAME}:${GITHUB_SHA} | gzip > out/image.tar.gz + # SHA와 latest 두 태그 모두 아카이브에 포함 + docker save ${IMAGE_NAME}:${GITHUB_SHA} ${IMAGE_NAME}:latest | gzip > out/image.tar.gz echo "ARCHIVE=out/image.tar.gz" >> $GITHUB_ENV # ====== SSH 준비 ====== @@ -130,7 +131,20 @@ jobs: ls -lh "${ARCHIVE_FILE}" || { echo "[!] archive missing"; exit 1; } gzip -t "${ARCHIVE_FILE}" gunzip -c "${ARCHIVE_FILE}" | sudo docker load - + + # 보강: :latest 태그가 없으면 가장 최근 태그를 latest로 재태깅 + if ! sudo docker image inspect "${IMAGE_NAME}:latest" >/dev/null 2>&1; then + echo "[info] ${IMAGE_NAME}:latest not found. Retagging…" + # 해당 리포의 임의의 태그 하나를 찾아 latest로 붙임 + NEW_TAG=$(sudo docker images --format '{{.Repository}}:{{.Tag}}' \ + | awk -v repo="${IMAGE_NAME}" -F: '$1==repo && $2!="latest"{print $2; exit}') + if [ -n "${NEW_TAG:-}" ]; then + sudo docker tag "${IMAGE_NAME}:${NEW_TAG}" "${IMAGE_NAME}:latest" + else + echo "[!] no tag to retag as latest"; exit 1 + fi + fi + ACTIVE_PORT="" if [ -f "${UPSTREAM_FILE}" ]; then ACTIVE_PORT=$(grep -oE '127\.0\.0\.1:([0-9]+)' "${UPSTREAM_FILE}" | awk -F: '{print $2}' || true) From b312b0daa6827e16d60833cd21a924fcb0f1623e Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 16:56:35 +0900 Subject: [PATCH 11/17] fix(ci/cd): Adjust Slack notification author name and include health endpoint in security constants --- .github/workflows/cicd-light-sail-prod.yml | 2 +- .../devdevdev/global/constant/SecurityConstant.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 1051b10d..beaae177 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -224,7 +224,7 @@ jobs: uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} - author_name: "[PROD] 배포 결과를 알려드려요" + author_name: "[PROD-TEST] 배포 결과를 알려드려요" fields: repo,message,commit,author,eventName,ref,took env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/global/constant/SecurityConstant.java b/src/main/java/com/dreamypatisiel/devdevdev/global/constant/SecurityConstant.java index 2e249471..798895ae 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/global/constant/SecurityConstant.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/global/constant/SecurityConstant.java @@ -22,6 +22,7 @@ public class SecurityConstant { "/**.html", "/**.css", "/**.js", + "/actuator/health", "/devdevdev/api/v1/oauth2/authorization/**", "/devdevdev/api/v1/oauth2/authorization/kakao", "/devdevdev/api/v1/login/oauth2/code/**", @@ -62,6 +63,7 @@ public class SecurityConstant { "/**.html", "/**.css", "/**.js", + "/actuator/health", "/devdevdev/api/v1/oauth2/authorization/**", "/devdevdev/api/v1/oauth2/authorization/kakao", "/devdevdev/api/v1/login/oauth2/code/**", From 092b4450b95aaf03b085f4386e250121f24a8084 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 17:40:56 +0900 Subject: [PATCH 12/17] fix(ci/cd): Adjust Slack notification author name and include health endpoint in security constants --- .github/workflows/cicd-light-sail-prod.yml | 12 +++++++----- src/main/resources/application-local.yml | 11 +++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index beaae177..3bf27681 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -180,10 +180,12 @@ jobs: echo "[5/9] Health check http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" ok=0 for i in $(seq 1 ${HEALTHCHECK_RETRY}); do - if curl -fsS --max-time ${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" >/dev/null 2>&1; then - ok=1; break - fi - echo " retry $i/${HEALTHCHECK_RETRY}..." + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time ${HEALTHCHECK_TIMEOUT} \ + "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}") + if [ "$code" -ge 200 ] && [ "$code" -lt 400 ]; then + ok=1; break + fi + echo " retry $i/${HEALTHCHECK_RETRY} (code=$code)" sleep 3 done if [ "$ok" -ne 1 ]; then @@ -191,7 +193,7 @@ jobs: sudo docker logs --tail 200 "${TARGET_NAME}" || true sudo docker stop "${TARGET_NAME}" || true sudo docker rm "${TARGET_NAME}" || true - exit 1 + exit 1 fi echo "[6/9] Switch upstream to ${TARGET_PORT}" diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 1f004d1b..8c612903 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -61,6 +61,17 @@ spring: max-idle: 8 min-idle: 2 +management: + endpoints: + web: + exposure: + include: health,info + endpoint: + health: + show-details: never + probes: + enabled: true + #MyBatis mybatis: type-aliases-package: com.dreamypatisiel.devdevdev.domain.repository From fd9682cb29076d8ef8ad5d44cf302396137338d6 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 18:08:29 +0900 Subject: [PATCH 13/17] fix(ci/cd): Improve health check logic in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 3bf27681..f141f726 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -178,14 +178,15 @@ jobs: ${IMAGE_NAME}:latest echo "[5/9] Health check http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" + code=$(curl -sS -o /dev/null -w "%{http_code}" \ + --max-time ${HEALTHCHECK_TIMEOUT} --noproxy '*' \ + "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" || echo "000") ok=0 for i in $(seq 1 ${HEALTHCHECK_RETRY}); do - code=$(curl -s -o /dev/null -w "%{http_code}" --max-time ${HEALTHCHECK_TIMEOUT} \ - "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}") - if [ "$code" -ge 200 ] && [ "$code" -lt 400 ]; then - ok=1; break - fi - echo " retry $i/${HEALTHCHECK_RETRY} (code=$code)" + if curl -fsS --max-time ${HEALTHCHECK_TIMEOUT} "http://127.0.0.1:${TARGET_PORT}${HEALTHCHECK_PATH}" >/dev/null 2>&1; then + ok=1; break + fi + echo " retry $i/${HEALTHCHECK_RETRY}..." sleep 3 done if [ "$ok" -ne 1 ]; then @@ -193,7 +194,7 @@ jobs: sudo docker logs --tail 200 "${TARGET_NAME}" || true sudo docker stop "${TARGET_NAME}" || true sudo docker rm "${TARGET_NAME}" || true - exit 1 + exit 1 fi echo "[6/9] Switch upstream to ${TARGET_PORT}" From f7eb94618101efd65cc9d6e90b5990b8def4f0c5 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 18:22:11 +0900 Subject: [PATCH 14/17] fix(ci/cd): Improve health check logic in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index f141f726..356e8e0d 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -20,7 +20,7 @@ env: # --- 헬스체크 --- HEALTHCHECK_PATH: "/actuator/health" # Actuator 미사용이면 "/" 로 변경하세요 HEALTHCHECK_TIMEOUT: "3" # curl 타임아웃(초) - HEALTHCHECK_RETRY: "20" # 재시도 횟수 (약 60초) + HEALTHCHECK_RETRY: "40" # 재시도 횟수 (5초 * 40 = 최대 200초) # --- SSH/Lightsail --- SSH_USER: "ec2-user" # SSH 접속 사용자 @@ -187,7 +187,7 @@ jobs: ok=1; break fi echo " retry $i/${HEALTHCHECK_RETRY}..." - sleep 3 + sleep 5 done if [ "$ok" -ne 1 ]; then echo "[!] Health check failed. Rollback." @@ -220,7 +220,17 @@ jobs: - name: Remote Blue/Green deploy run: | ssh -i key.pem -o StrictHostKeyChecking=yes ${SSH_USER}@${LIGHTSAIL_HOST} \ - "bash /home/${SSH_USER}/deploy_blue_green.sh" + "env IMAGE_NAME='${IMAGE_NAME}' \ + CONTAINER_BASE='${CONTAINER_BASE}' \ + BLUE_SUFFIX='${BLUE_SUFFIX}' \ + GREEN_SUFFIX='${GREEN_SUFFIX}' \ + BLUE_PORT='${BLUE_PORT}' \ + GREEN_PORT='${GREEN_PORT}' \ + APP_PORT='${APP_PORT}' \ + HEALTHCHECK_PATH='${HEALTHCHECK_PATH}' \ + HEALTHCHECK_TIMEOUT='${HEALTHCHECK_TIMEOUT}' \ + HEALTHCHECK_RETRY='${HEALTHCHECK_RETRY}' \ + bash /home/${SSH_USER}/deploy_blue_green.sh" # ====== Slack 알림 ====== - name: action-slack From c37aea7073ff9d971b747b1c829499430193c68a Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 18:28:39 +0900 Subject: [PATCH 15/17] fix(ci/cd): Improve health check logic in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 356e8e0d..7c66c9b8 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -209,7 +209,7 @@ jobs: fi echo "[8/9] Cleanup old archives (keep last 3)" - cd "/home/${SSH_USER}" && ls -t image*.tar.gz | tail -n +4 | xargs -r rm -f + cd "$HOME" && ls -t image*.tar.gz | tail -n +4 | xargs -r rm -f echo "[9/9] Done." EOS From b14762c08ad93425a91eba1ac83cc8f6d515acb4 Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 19:25:49 +0900 Subject: [PATCH 16/17] fix(ci/cd): Improve health check logic in Lightsail deployment workflow --- .github/workflows/cicd-light-sail-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 7c66c9b8..35338269 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -20,7 +20,7 @@ env: # --- 헬스체크 --- HEALTHCHECK_PATH: "/actuator/health" # Actuator 미사용이면 "/" 로 변경하세요 HEALTHCHECK_TIMEOUT: "3" # curl 타임아웃(초) - HEALTHCHECK_RETRY: "40" # 재시도 횟수 (5초 * 40 = 최대 200초) + HEALTHCHECK_RETRY: "20" # 재시도 횟수 (5초 * 20 = 최대 100초) # --- SSH/Lightsail --- SSH_USER: "ec2-user" # SSH 접속 사용자 From 5207d8b0ea7bdf042c618c504e7c1f3dca71361d Mon Sep 17 00:00:00 2001 From: howisitgoing Date: Sun, 24 Aug 2025 23:46:37 +0900 Subject: [PATCH 17/17] fix(ci/cd): Update branch targets in deployment workflows for Lightsail and EC2 --- .github/workflows/cicd-ec2-prod.yml | 2 +- .github/workflows/cicd-light-sail-prod.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd-ec2-prod.yml b/.github/workflows/cicd-ec2-prod.yml index 18583aa0..dcf3ce14 100644 --- a/.github/workflows/cicd-ec2-prod.yml +++ b/.github/workflows/cicd-ec2-prod.yml @@ -3,7 +3,7 @@ name: Build and Deploy to PROD on: push: - branches: [ "main" ] + branches: [ "main-ec2" ] # 환경 변수 $변수명으로 사용 env: diff --git a/.github/workflows/cicd-light-sail-prod.yml b/.github/workflows/cicd-light-sail-prod.yml index 35338269..3ae85a22 100644 --- a/.github/workflows/cicd-light-sail-prod.yml +++ b/.github/workflows/cicd-light-sail-prod.yml @@ -2,7 +2,7 @@ name: Build and Deploy to PROD (Lightsail Blue/Green via SSH + Docker) on: push: - branches: [ "main-test" ] + branches: [ "main" ] env: # --- 애플리케이션/컨테이너 공통 ---