sales-7

이지예·2026년 9월 17일

개발

목록 보기
29/58

series: 멀티클라우드 견적 자동화 시스템

배포 가능한 상태 ≠ 배포가 끝난 상태

이 시리즈의 마지막 편은 성공담이 아니라 정직한 중간 점검입니다. 기능은 다 만들었고 테스트도 24건이 통과하지만, 그게 "배포해도 안전하다"는 뜻은 아니라는 걸 스스로 짚고 넘어가려고 합니다.

만든 것: Docker Compose 전체 스택

로컬 개발은 H2(인메모리)로 했지만, 배포 환경은 MySQL + nginx + Spring Boot 세 컨테이너로 구성했습니다.

services:
  mysql:
    image: mysql:8.0
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]

  backend:
    build: ./backend
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: mysql
      # DB 접속 정보·AWS 자격증명은 환경변수로 주입 (실제 값은 .env 등 별도 관리)

  frontend:
    build: ./frontend      # vite 빌드 → nginx 정적 서빙
    depends_on:
      - backend

nginx가 정적 번들을 서빙하면서 /api, /swagger-ui 요청을 백엔드로 프록시하도록 구성했습니다. 이렇게 하면 프론트가 동일 출처 상대경로(/api/v1)를 쓰게 되어, 배포 환경에서 CORS 설정 자체가 필요 없어집니다. 개발 중엔 Vite dev server가 이 프록시 역할을 대신합니다.

검증된 것과 안 된 것을 구분해서 적어둔다

여기서 중요하게 생각한 건, "다 만들었다"와 "다 확인했다"를 섞지 않는 것이었습니다.

항목상태
견적 산출 로직 (경계값·필터)✅ 단위 테스트
REST API 계약·에러 포맷✅ 통합 테스트
PDF 내용 검증✅ 텍스트 재추출 테스트
화면 E2E (샘플 데이터 기준)✅ Playwright
Docker 이미지 빌드❌ 아직 한 번도 빌드해본 적 없음
MySQL 실제 동작❌ 로컬은 계속 H2로만 돌렸음
벤더 API 실제 응답 파싱❌ 로직만 구현, 실 계정 검증 전

이 표를 만들면서 스스로도 좀 찔렸습니다. docker-compose.yml을 다 써놓고 "이대로 배포하면 되겠다"고 생각하기 쉬운데, 실제로는 한 번도 실행해보지 않은 설정 파일이었습니다. 코드 리뷰만으로는 못 잡는 문제들이 있다는 걸 알기 때문에, 배포 전에 반드시 로컬에서 직접 띄워봐야 한다고 결론 내렸습니다.

코드만 읽고 예상해본 위험 요소

실행은 못 해봤지만, 코드를 다시 읽으면서 "이거 터질 것 같다"고 예상한 지점들은 미리 적어뒀습니다.

1. 타임존 불일치 가능성

견적 생성 시각은 OffsetDateTime으로 저장하는데, MySQL에는 타임존을 인식하는 컬럼 타입이 없습니다. 이력 조회의 기간 필터가 시스템 기본 타임존 기준으로 시작/종료 시각을 계산하는데:

public OffsetDateTime fromDateTime() {
    return from.atStartOfDay(ZoneId.systemDefault()).toOffsetDateTime();
}

컨테이너의 기본 타임존과 DB 접속 URL의 타임존 설정이 어긋나면, "오늘 만든 견적이 오늘 날짜 필터에서 안 나온다"는 식의 버그가 생길 수 있습니다. 이건 H2로 돌리는 테스트로는 절대 드러나지 않는 종류의 문제라, MySQL 프로파일로 직접 확인이 필요한 1순위 항목입니다.

2. 컨테이너 시작 순서

depends_on은 컨테이너가 "시작됐는지"만 확인하지 "요청을 받을 준비가 됐는지"는 보장하지 않습니다. mysql에는 헬스체크를 걸어서 backend가 실제로 DB가 준비될 때까지 기다리게 했지만, nginx → backend 사이에는 아직 같은 안전장치가 없습니다. nginx가 시작 시점에 백엔드 호스트명을 한 번만 확인하는 구조라면, 백엔드가 아직 안 떴을 때 nginx 자체가 죽는 상황이 생길 수 있습니다.

순서를 지키기로 했다

이 목록을 정리하면서 내린 결론은 하나입니다. "배포 스크립트가 있다"와 "배포할 준비가 됐다"는 다르다. 아래 순서를 지키기로 했습니다.

  1. 로컬에서 docker compose up -d --build로 이미지가 실제로 빌드되고 컨테이너 3개가 뜨는지 확인
  2. MySQL 프로파일에서 스키마가 의도대로 생성되는지, 특히 유니크 제약과 타임존 필터를 직접 확인
  3. 실제 벤더 계정으로 가격 동기화 1회 실행, Azure SKU 추정치를 공식 문서와 대조
  4. 여기까지 통과한 뒤에 배포 환경(어디에 올릴지)을 결정

배포처를 정하는 논의는 이 검증이 끝난 다음 순서라고 판단했습니다. 검증되지 않은 설정 파일 위에 도메인과 서버를 얹는 건, 문제를 확인할 기회만 뒤로 미루는 일이라고 생각했기 때문입니다.

시리즈를 마치며

이 프로젝트에서 반복해서 배운 것은 크게 세 가지였습니다.

  1. 외부 시스템은 반드시 실패한다는 전제로 설계할 것 — 캐싱과 벤더 단위 실패 격리는 이 전제에서 나온 구조였고, 실제로 개발 과정에서 도움이 됐습니다.
  2. 틀린 값을 내느니 "모른다"고 답하는 게 낫다 — Azure SKU 파싱에서 불규칙한 시리즈를 제외한 것도, 매칭 실패 시 금액 대신 사유를 반환하도록 한 것도 이 기준에서 나왔습니다.
  3. "구현했다"와 "검증했다"는 다른 말이다 — 특히 코드를 눈으로만 읽어서는 잡히지 않는 문제(타임존, 컨테이너 기동 순서, API 계약 불일치)가 생각보다 많았습니다.

여기까지 읽어주셔서 감사합니다. 저장소는 공개되어 있으니, 코드 레벨의 디테일이 궁금하시면 직접 살펴보셔도 좋습니다.

profile
github: @dlwldP

0개의 댓글