-
Notifications
You must be signed in to change notification settings - Fork 0
[DP-558] 인스턴스 마이그레이션 from EC2 #189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
815d7eb
ci(cicd): Add Lightsail Blue/Green deploy workflow for PROD
ssosee cd60192
ci(cicd): Add Lightsail Blue/Green deploy workflow for PROD
ssosee 5d7fc9c
ci(cicd): Update Lightsail deployment workflow and Dockerfile for imp…
ssosee fd29aeb
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee c7e4ebb
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee 07bcb90
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee 528eb03
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee a0f18c9
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee 848768e
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee 18a1e2d
fix(ci/cd): Update archive file path in Lightsail deployment workflow
ssosee b312b0d
fix(ci/cd): Adjust Slack notification author name and include health …
ssosee 092b445
fix(ci/cd): Adjust Slack notification author name and include health …
ssosee fd9682c
fix(ci/cd): Improve health check logic in Lightsail deployment workflow
ssosee f7eb946
fix(ci/cd): Improve health check logic in Lightsail deployment workflow
ssosee c37aea7
fix(ci/cd): Improve health check logic in Lightsail deployment workflow
ssosee b14762c
fix(ci/cd): Improve health check logic in Lightsail deployment workflow
ssosee 5207d8b
fix(ci/cd): Update branch targets in deployment workflows for Lightsa…
ssosee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| name: Build and Deploy to PROD (Lightsail Blue/Green via SSH + Docker) | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
|
|
||
| env: | ||
| # --- 애플리케이션/컨테이너 공통 --- | ||
| PROJECT_NAME: "devdevdev" | ||
| 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" # 컨테이너 내부 포트(Spring Boot) | ||
|
|
||
| # --- 헬스체크 --- | ||
| HEALTHCHECK_PATH: "/actuator/health" # Actuator 미사용이면 "/" 로 변경하세요 | ||
| HEALTHCHECK_TIMEOUT: "3" # curl 타임아웃(초) | ||
| HEALTHCHECK_RETRY: "20" # 재시도 횟수 (5초 * 20 = 최대 100초) | ||
|
|
||
| # --- SSH/Lightsail --- | ||
| SSH_USER: "ec2-user" # SSH 접속 사용자 | ||
| LIGHTSAIL_HOST: "${{ secrets.LIGHTSAIL_HOST }}" # 퍼블릭 IP 또는 도메인 | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build and Deploy (Blue/Green) | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v3 | ||
|
|
||
| # ====== 리소스/시크릿 주입 (현재 파이프라인과 동일) ====== | ||
| - 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 | ||
|
|
||
| # ====== Gradle 빌드 (Docker가 JAR을 COPY할 수 있도록 선행) ====== | ||
| - name: Grant execute permission for gradlew | ||
| run: chmod +x ./gradlew | ||
|
|
||
| - name: Build with Gradle (bootJar) | ||
| run: ./gradlew bootJar -x test -x asciidoctor | ||
|
|
||
| # ====== Docker 빌드 ====== | ||
| - 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 | ||
| # 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 준비 ====== | ||
| - 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}/image.tar.gz | ||
|
|
||
| - name: Upload blue/green deploy script | ||
| run: | | ||
| cat > 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:-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}" | ||
| GREEN_NAME="${CONTAINER_BASE}${GREEN_SUFFIX}" | ||
|
|
||
| # 아카이브 경로는 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}" | ||
| 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) | ||
| fi | ||
| 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}" | ||
| else | ||
| 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}" | ||
|
|
||
| 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}" \ | ||
| --restart=always \ | ||
| -p 127.0.0.1:${TARGET_PORT}:${APP_PORT} \ | ||
| -e SPRING_PROFILES_ACTIVE=prod \ | ||
| ${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 | ||
| 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 5 | ||
| done | ||
| 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 | ||
| 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 | ||
| fi | ||
|
|
||
| echo "[8/9] Cleanup old archives (keep last 3)" | ||
| cd "$HOME" && ls -t image*.tar.gz | tail -n +4 | xargs -r rm -f | ||
|
|
||
| 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} \ | ||
| "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 | ||
| uses: 8398a7/action-slack@v3 | ||
| with: | ||
| status: ${{ job.status }} | ||
| author_name: "[PROD-TEST] 배포 결과를 알려드려요" | ||
| fields: repo,message,commit,author,eventName,ref,took | ||
| env: | ||
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | ||
| if: always() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ENV TZ=Asia/Seoul | ||
| EXPOSE 8080 | ||
| ENTRYPOINT ["java","-jar","-Dspring.profiles.active=prod","/app/app.jar"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요거 이미지 바뀐 이유가 있을까요?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GPT가 바꾸라고 해서 바꾼거 같은데, 한번 찾아볼께요 !
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Temurin (Eclipse Adoptium)은 OpenJDK 공용 빌드 중 가장 많이 쓰이는 배포판 중 하나이고 Adoptium Temurin 은 LTS 지원이 안정적이고 보안 업데이트가 빠름
이미지 용량이 openjdk 계열보다 작고 최적화되어 있어서 바꿨다고 합니다.