Skip to content

Feat/#141 GitHub Actions 기반 백엔드 CI/CD 구축 - #142

Open
klaqwe1 wants to merge 4 commits into
devfrom
feat/#141-backend-ci-cd
Open

Feat/#141 GitHub Actions 기반 백엔드 CI/CD 구축#142
klaqwe1 wants to merge 4 commits into
devfrom
feat/#141-backend-ci-cd

Conversation

@klaqwe1

@klaqwe1 klaqwe1 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

PR 타입

  • Bugfix
  • Feature
  • Code style update
  • Refactoring
  • Other... Please describe:

작업 내용

  • dev, master PR/push에서 백엔드 CI verify 실행
  • Gradle dependency 및 build cache 적용
  • GitHub runner에서 테스트와 bootJar를 한 번만 수행
  • AMD64 runner에서 기존 Android APK 통합 테스트 유지
  • master push 시 ARM64 backend image를 private GHCR에 게시
  • immutable image digest를 사용한 개발 서버 자동 배포 구성
  • 이전 backend가 immutable GHCR digest이고 healthy인 경우에만 자동 rollback
  • legacy, mutable, no-health, unhealthy image의 자동 rollback 차단
  • MySQL 환경변수를 필수 3개로 격리하고 외부 host port 제거
  • Actuator health 기반 배포 검증과 실패 backend 중지 처리
  • actionlint, shellcheck, Compose secret 격리 검증 추가

관련 이슈: #141

테스트 결과

image

배포 관련 주의사항

  • 이 PR은 dev 대상이므로 merge해도 개발 서버 자동 배포는 실행되지 않습니다.
  • 자동 배포는 이후 dev -> master 승격 PR이 merge될 때 실행됩니다.
  • 첫 master 배포 전에 development Environment 승인 gate와 서버 설정을 완료해야 합니다.

머지 후 작업

이 PR은 dev 대상입니다. 이 PR이 merge되어도 GHCR image 게시와 개발 서버 자동 배포는 실행되지 않습니다.

1. dev merge 직후

  • dev push의 CI verify 성공 확인
  • 동일 workflow 재실행 후 Gradle dependency/build cache hit 확인
  • Android builder Gradle cache hit 확인
  • dev, master branch protection에서 PR 필수 설정
  • 필수 status check로 CI verify 지정
  • masterdev -> master 승격 PR만 허용

CI verify가 required check 목록에 보이지 않으면, 이 PR을 dev에 merge해 workflow가 한 번 실행된 뒤 다시 설정합니다.

2. GitHub Development Environment 설정

Settings -> Environments -> development에서 다음을 설정합니다.

  • Required reviewers 지정
  • self-review 방지 설정 확인
  • deployment branch를 master로 제한
  • 첫 배포 전까지 승인 gate 유지

Variables:

  • DEV_HOST=158.180.72.167
  • DEV_SSH_PORT=22
  • DEV_SSH_USER=ubuntu
  • `DEV_DEPLOY_PATH

Secrets:

  • DEV_SSH_PRIVATE_KEY
  • DEV_SSH_KNOWN_HOSTS

3. 서버 사전 준비

  • GitHub Actions 전용 또는 기존 SSH key 접속 확인
  • 배포 계정 ubuntu가 Docker 명령을 실행할 수 있는지 확인
  • private GHCR read:packages token으로 로그인
  • ~/.docker/config.json 권한을 600으로 설정
  • 배포 경로에 backend.env 생성
  • backend.env 권한을 600으로 설정
  • deploy-backend.sh 권한을 700으로 설정
  • 기존 Compose project 이름 확인
  • backend.envCOMPOSE_PROJECT_NAME 설정
  • SPRING_JPA_HIBERNATE_DDL_AUTO=update 설정
  • persistent DB에서 dev profile 비활성 확인
  • 기존 서버 Swagger 설정 유지 및 접근 경로 확인
  • Docker disk 여유 확인

서버 backend.env에만 저장할 값:

  • MySQL 비밀번호
  • JWT secret
  • Kakao client secret
  • AWS key
  • 개발 test-user 비밀번호
  • 도메인 및 OAuth redirect URL

기존 노출 가능성이 있는 값은 최초 배포 전에 회전하고 새 backend.env에 반영합니다.

4. dev -> master PR merge 전

  • docker/FIRST_DEPLOYMENT.md 전체 절차 검토
  • maintenance 시간 확정
  • 서버 외부 암호화 DB backup 위치 준비
  • 기존 backend/database/Redis inventory 준비
  • 기존 Compose와 env 백업 경로 준비
  • 기존 MySQL volume 또는 bind mount 확인
  • 복구 담당자와 수동 rollback 절차 확인
  • development Environment 승인자가 대기 가능한지 확인

위 준비가 끝나기 전에는 dev -> master PR을 merge하지 않습니다.

5. master merge 후, 배포 승인 전

master merge 후에는 다음 순서로 진행합니다.

  1. CI verify 성공
  2. master JAR artifact 생성
  3. ARM64 GHCR image 게시
  4. Deploy development job이 Environment 승인 대기
  5. 최초 DB 이전 수행
  6. 검증 완료 후 Environment 승인

승인 전에 확인할 항목:

  • 게시된 image가 해당 master commit과 일치
  • exact @sha256: digest 확보
  • ARM64 digest 사전 pull
  • image architecture가 arm64인지 확인
  • 기존 backend를 maintenance 상태에서 중지
  • 최종 MySQL dump 생성
  • dump 크기와 checksum 확인
  • 주요 table row count 기록
  • 서버 외부 암호화 backup 완료
  • 기존 DB storage 보존
  • mysql_data volume이 기존에 존재하지 않는지 확인
  • 새 database에 dump restore
  • migration 전후 row count 비교
  • 기존 Redis container가 유지되는지 확인
  • docker compose --env-file backend.env config --quiet 통과

모든 검증이 끝날 때까지 Deploy development를 승인하지 않습니다.

6. 최초 배포 승인 후

  • /actuator/health{"status":"UP"}만 반환
  • backend container health가 healthy
  • 실행 image가 승인한 exact digest와 일치
  • image architecture가 arm64
  • database container 확인
  • Redis container가 교체되지 않았는지 확인
  • 주요 DB row count 확인
  • 로그인 및 주요 API smoke test
  • Kakao OAuth redirect 확인
  • Redis 사용 기능 확인
  • 임시 upload/download 확인
  • 개발 서버 Swagger 접근 확인
  • 검증 완료 후 maintenance 해제

PR 체크리스트

  • 커밋 메시지가 가이드라인을 따르는가
  • 테스트 코드를 모두 통과하였는가
  • 관련 이슈를 연결하였는가

@klaqwe1
klaqwe1 requested a review from kym8821 July 20, 2026 20:32
@klaqwe1 klaqwe1 self-assigned this Jul 20, 2026
@klaqwe1 klaqwe1 added the enhancement New feature or request label Jul 20, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new deployment pipeline and environment configuration, including a simplified single-stage Dockerfile, a Docker Compose setup with health checks, a robust backend deployment script with automatic rollback capabilities, and comprehensive deployment documentation. Additionally, Spring Boot Actuator is integrated to expose a health endpoint. The review feedback highlights two critical issues: first, calling setExecutable(true) on the Gradle wrapper will fail on non-POSIX filesystems like Windows, breaking local development; second, the base JRE image lacks wget, which will cause the Docker Compose health check to fail and trigger an unintended rollback.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +116 to +119
Path gradleWrapper = destination.resolve(GRADLE_WRAPPER_FILE_NAME);
if (!gradleWrapper.toFile().setExecutable(true)) {
throw new IOException("Failed to make Gradle wrapper executable: " + gradleWrapper);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Calling setExecutable(true) on a File object can fail and return false on non-POSIX filesystems (such as Windows). This will throw an IOException and break local development or testing for developers on Windows. To ensure cross-platform compatibility, we should check if the filesystem supports POSIX attributes before throwing an exception, or log a warning on non-POSIX systems.

      Path gradleWrapper = destination.resolve(GRADLE_WRAPPER_FILE_NAME);
      java.io.File file = gradleWrapper.toFile();
      if (file.exists() && !file.setExecutable(true)) {
        if (java.nio.file.FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
          throw new IOException("Failed to make Gradle wrapper executable: " + gradleWrapper);
        } else {
          log.warn("[AndroidThemeGenerator] Failed to set executable permission on non-POSIX filesystem: {}", gradleWrapper);
        }
      }

Comment thread Dockerfile
Comment on lines 5 to +7
WORKDIR /app
# install docker
RUN apk add --no-cache docker-cli
# run backend container
COPY --from=builder ./app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"] No newline at end of file

COPY build/libs/kakao-theme-maker.jar app.jar

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The base image eclipse-temurin:17-jre-jammy is based on a minimal Ubuntu image which does not pre-install wget. Since the healthcheck in docker-compose.yml relies on wget, the healthcheck will always fail, causing the deployment to fail and trigger an automatic rollback. Please install wget in the Dockerfile.

RUN apt-get update && apt-get install -y --no-install-recommends wget && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY build/libs/kakao-theme-maker.jar app.jar

@kym8821 kym8821 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

내부적으로 검증 로직이 상세하게 작성되어 있고 안정성을 고려한 부분은 좋다고 생각합니다.

다만 일부 검증 항목의 경우 현재 저희 서비스 운영 환경에서는 불필요하게 복잡도를 높이거나, 개발/배포 과정에서 관리 포인트를 증가시킬 수 있는 부분이 있어 보입니다.

해당 검증들이 필요한 이유와 실제 운영 환경에서 기대하는 효과를 함께 확인 부탁드립니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

현재 파일 기준으로는 gradle wrapper에 실행 권한을 부여한 것으로 이해했습니다. 기존에 말씀드렸던 ./gradlew 실행 권한 문제는 해당 방식으로 해결 가능한 것으로 보입니다.

이번 변경에서는 ./gradlew 파일 자체에 실행 권한을 부여하는 방향으로 개선할 예정이니, 해당 방식으로 진행해도 문제가 없는지 확인 부탁드립니다. 문제가 없다면 현재 변경 사항은 롤백해주시면 좋을 것 같습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

현재 테스트는 Spring Boot Actuator가 제공하는 health check 엔드포인트의 정상 응답을 검증하는 것으로 보입니다.

개인적으로는 해당 엔드포인트는 Spring Boot에서 제공하는 기능이고, 별도의 커스텀 로직이 포함되어 있지 않다면 테스트의 실효성이 크지 않을 수 있다고 생각합니다. 오히려 프레임워크 구현에 대한 테스트가 되어 유지보수 대상만 늘어날 가능성도 있다고 봅니다.

혹시 이 테스트를 추가하신 특별한 의도가 있다면 공유해주시면 감사하겠습니다

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

테스트 환경에서만 사용하는 설정이지만, 현재 내용만 봤을 때는 공개 저장소에 위치해야 하는 이유를 잘 이해하지 못했습니다.

혹시 application-test.yml을 private 서브모듈이 아닌 공개 저장소로 옮긴 이유가 있을까요? 의도가 있다면 설명 부탁드립니다.

Comment thread docker/docker-compose.yml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

기존에 개발 환경에서 사용하던 docker-compose를 제거하고 있는데 차라리 CICD용 docker-compose를 별도로 관리하는 것이 좋을 것 같습니다. 현재 변경 사항은 docker-compose를 개발 환경에서 실행하고 싶은 상황에 문제가 발생할 수 있습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

현재 CI/CD 설정을 확인해보니 application.yml에서 관리 가능한 설정값들도 대부분 서버 환경변수로 다시 주입하는 구조로 되어 있는 것 같습니다.

개인적으로는 설정 책임을 조금 더 명확하게 분리하는 방향이 좋을 것 같습니다. application.yml은 애플리케이션 설정 및 환경별 차이를 관리하고, DB password, API key 등 민감한 정보만 GitHub Secrets 또는 서버 환경변수로 관리하는 방식이 적절해 보입니다.

현재처럼 application.yml의 대부분 값을 환경변수로 관리하게 되면 실제 설정 정보가 application.yml, CI/CD 설정, 서버 환경변수로 분산되어 관리 포인트가 증가하고, 어떤 설정이 실제 운영에 적용되는지 추적하기 어려워질 수 있을 것 같습니다.

Spring Profile(dev/prod 등)과 별도 설정 파일 관리를 활용하면 환경별 설정도 충분히 분리 가능해 보이며, 현재 구조에서는 모든 설정을 외부 환경변수화하는 것은 관리 복잡도 대비 과한 설계일 수 있다고 생각합니다.

민감 정보와 배포 환경에서만 필요한 값 위주로 환경변수 관리를 적용하는 방향을 검토해보면 좋을 것 같습니다.

Comment on lines +27 to +35
redis:
image: redis:6.2.6-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

현재 저희 서비스에서는 테스트 환경에서 embedded-redis를 사용하고 있습니다. embedded-redis는 테스트 실행 시 Redis 서버 바이너리를 이용하여 테스트용 Redis 프로세스를 별도로 실행하는 방식으로 동작하는 것으로 알고 있습니다.

다만 현재 GitHub Actions 스크립트에서는 Redis 컨테이너를 별도로 띄우고 있는 것으로 보이는데, embedded-redis와 별개로 Redis 컨테이너가 필요한 이유가 궁금합니다.

Comment on lines +93 to +96
(.services.database.environment |
has("CI_BACKEND_ENV_SENTINEL") | not) and
(.services.backend.environment.CI_BACKEND_ENV_SENTINEL ==
"loaded-from-backend-env") and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

jq를 이용해 config 파일 내 필수 요소들의 존재 여부를 검증하는 부분으로 이해했습니다.

다만 CI_BACKEND_ENV_SENTINEL에 대한 검증 로직이 어떤 목적으로 추가되었는지는 명확하게 이해되지 않습니다.

해당 값이 단순히 환경 파일이 정상적으로 생성되었는지 확인하기 위한 sentinel 값인지, 혹은 특정 환경 설정 누락을 방지하기 위한 검증인지 설명 부탁드립니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants