[내일배움캠프_사전캠프_8기] TIL Days-31 AI 대규모 시스템 물류 관리 및 배송 시스템을 위한 MSA 기반 플랫폼 팀 프로젝트 Day4 프로젝트 초기 환경 구축

2026. 8. 4. 16:03카테고리 없음

[Delivery Project] 인프라 기동부터 Eureka 연동, CI 오류 해결까지

어제는 Delivery Project의 본격적인 개발에 들어가기 전에 프로젝트의 전체적인 틀을 잡았다.

API 설계, 테이블 설계, ERD 설계, 인프라 설계를 진행했고, 팀원들이 같은 기준으로 개발할 수 있도록 팀 컨벤션도 정리했다.

오늘은 어제 설계한 내용을 실제 개발 환경에 적용하면서 Docker 기반 인프라를 실행하고, Eureka를 통한 서비스 디스커버리를 구성한 뒤, GitHub Actions CI에서 발생한 오류까지 해결하는 과정을 진행했다.


1. 전날 진행한 설계 및 팀 컨벤션

개발을 시작하기 전에 프로젝트의 기본적인 구조부터 맞췄다.

API 설계

각 서비스에서 어떤 API를 제공할지 정의하고 요청과 응답 구조를 정리했다.

단순히 API URL만 정하는 것이 아니라 HTTP Method, 요청 데이터, 응답 데이터, 상태 코드 등을 고려해 서비스 간 통신의 기준을 만들었다.

테이블 설계와 ERD

서비스에서 필요한 데이터를 기준으로 테이블을 설계하고, 각 테이블 사이의 관계를 ERD로 정리했다.

특히 실제 구현 단계에서 JPA Entity를 만들 때 데이터베이스 구조와 객체 구조가 자연스럽게 연결될 수 있도록 설계했다.

인프라 설계

프로젝트에서 사용할 PostgreSQL, Redis, Eureka 등의 구성 요소와 각각의 역할을 정리했다.

전체적인 구조는 다음과 같은 형태로 잡았다.

                    ┌───────────────┐
                    │ Eureka Server │
                    │    :8761      │
                    └───────┬───────┘
                            │
                    Service Discovery
                            │
              ┌─────────────┴─────────────┐
              │                           │
       ┌──────▼──────┐             ┌──────▼──────┐
       │ Order Service│             │ Other Service│
       │    :900x     │             │    :900x     │
       └──────┬──────┘             └─────────────┘
              │
       ┌──────┴──────┐
       │             │
 ┌─────▼─────┐ ┌────▼────┐
 │PostgreSQL │ │  Redis  │
 └───────────┘ └─────────┘

팀 컨벤션

여러 명이 함께 개발하는 프로젝트이기 때문에 코드 작성 방식도 미리 맞췄다.

Git을 사용할 때는 커밋 메시지 규칙을 정하고, 브랜치와 PR을 어떤 방식으로 관리할지도 정리했다.

또한 패키지 구조와 네이밍 규칙도 통일했다.

예를 들어 Java에서는 클래스와 메서드, 변수 등의 명명 규칙을 정하고, 데이터베이스나 설정 파일에서 사용하는 네이밍 방식도 구분했다.

대표적으로 데이터베이스에서는 snake_case를 사용하고 Java 코드에서는 camelCase를 사용하는 식으로 각각의 환경에 맞는 규칙을 정했다.

이런 컨벤션은 처음에는 단순한 규칙처럼 보이지만, 프로젝트 규모가 커질수록 코드의 일관성을 유지하는 데 중요하다고 생각한다.


2. Docker로 개발 인프라 기동

오늘은 먼저 실제 개발 환경에서 필요한 인프라부터 실행했다.

프로젝트에서는 PostgreSQL과 Redis를 Docker Compose로 관리하고 있기 때문에 Docker Desktop을 실행한 뒤 다음 명령어를 사용했다.

docker compose --env-file .env -f docker/docker-compose-infra.yaml up -d postgres redis

실행 후 컨테이너 상태를 확인했다.

docker compose -f docker/docker-compose-infra.yaml ps

PostgreSQL과 Redis가 healthy 상태로 올라오는 것을 확인했다.

postgres   healthy
redis      healthy

이 과정을 통해 애플리케이션을 실행하기 전에 필요한 데이터베이스와 Redis 환경이 정상적으로 준비되었는지 확인할 수 있었다.


3. Eureka Server 실행

다음으로 Eureka Server를 실행했다.

Eureka는 여러 서비스가 존재하는 환경에서 서비스의 위치를 관리하고 서로를 찾을 수 있도록 도와주는 서비스 디스커버리 서버다.

eureka-server 디렉터리에서 Gradle을 이용해 서버를 실행했다.

.\gradlew.bat bootRun

처음 실행했을 때는 8761 포트가 이미 사용 중이라는 오류가 발생했다.

Web server failed to start.
Port 8761 was already in use.

처음에는 Eureka 자체의 문제라고 생각할 수도 있지만, 실제 원인은 단순히 8761 포트를 다른 프로세스가 사용하고 있었던 것이었다.

Windows에서 다음 명령어로 어떤 프로세스가 해당 포트를 사용하고 있는지 확인할 수 있었다.

netstat -ano | findstr :8761

PID를 확인한 뒤 해당 프로세스를 정리하고 다시 Eureka Server를 실행했다.

결과적으로 다음 로그를 확인할 수 있었다.

Started Eureka Server
Tomcat started on port 8761
Started EurekaServerApplication

Eureka Server가 정상적으로 실행된 것이다.


4. Order Service를 Eureka에 등록

Eureka Server가 정상적으로 실행된 후 Order Service를 실행했다.

Order Service는 실행되면서 자신의 정보를 Eureka Server에 등록한다.

실제로 Eureka Server 로그에서 다음 내용을 확인할 수 있었다.

Registered instance ORDER-SERVICE/...:order-service:9004
with status UP

이 로그를 통해 Order Service가 Eureka에 정상적으로 등록되었음을 확인했다.

최종적으로 Eureka Dashboard에서 ORDER-SERVICE가 UP 상태로 등록되는 것까지 확인했다.

ORDER-SERVICE    UP (1)

이번 과정을 통해 단순히 애플리케이션을 실행하는 것에서 끝나는 것이 아니라 서비스가 Eureka에 자신을 등록하고 다른 서비스가 해당 서비스를 발견할 수 있는 구조가 실제로 동작하는 것을 확인할 수 있었다.


5. Actuator와 Zipkin 설정 확인

Order Service의 설정 파일도 함께 정리했다.

기존에는 Actuator와 함께 tracing, Zipkin 관련 설정이 포함되어 있었다.

하지만 현재 개발 단계에서는 Zipkin을 필수적으로 사용하지 않기 때문에 Zipkin 관련 설정은 우선 제거하고 Actuator 설정은 유지했다.

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

Actuator를 사용하면 서비스의 상태를 다음과 같은 엔드포인트를 통해 확인할 수 있다.

/actuator/health
/actuator/info
/actuator/metrics

특히 /actuator/health는 CI나 로컬 환경에서 애플리케이션이 정상적으로 실행되고 있는지 확인할 때 유용하다.


6. PR을 올린 뒤 CI 오류 발생

개발한 내용을 PR로 올린 뒤 GitHub Actions CI를 확인했다.

Gradle 자체는 정상적으로 실행되었다.

compileJava     SUCCESS
processResources SUCCESS
classes          SUCCESS
bootJar          SUCCESS
assemble         SUCCESS
compileTestJava  SUCCESS

하지만 테스트 단계에서 실패했다.

OrderServiceApplicationTests > contextLoads() FAILED

처음에는 단순한 테스트 코드 문제라고 생각할 수 있었지만, 내부 로그를 확인해보니 Spring Context를 생성하는 과정에서 Hibernate가 실패하고 있었다.

BeanCreationException
    ↓
ServiceException
    ↓
HibernateException
    ↓
DialectFactoryImpl

7. CI 환경의 PostgreSQL 설정 문제 확인

CI에서는 PostgreSQL을 GitHub Actions의 service container로 실행하고 있었다.

postgres:
  image: postgres:16
  env:
    POSTGRES_DB: delivery
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: postgres

그런데 Spring Boot의 application.yml에서는 PostgreSQL 접속 정보를 환경변수로 받고 있었다.

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/${POSTGRES_DB}
    username: ${POSTGRES_USERNAME}
    password: ${POSTGRES_PASSWORD}

여기서 중요한 점은 PostgreSQL 컨테이너에 설정한 환경변수와 Spring Boot 애플리케이션이 사용하는 환경변수는 별개라는 것이다.

PostgreSQL 컨테이너에는 POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD가 설정되어 있었지만 Spring Boot가 실행되는 CI Job 환경에는 해당 값이 전달되지 않고 있었다.

그래서 CI의 테스트 단계에 다음 환경변수를 추가했다.

env:
  POSTGRES_DB: delivery
  POSTGRES_USERNAME: postgres
  POSTGRES_PASSWORD: postgres
  TRACING_SAMPLING_PROBABILITY: '0.0'

이를 통해 Spring Boot가 다음과 같은 데이터베이스 접속 정보를 사용할 수 있도록 수정했다.

Database : delivery
Username : postgres
Password : postgres
Host     : localhost
Port     : 5432

8. 아직 남아 있는 Hibernate 오류

환경변수를 수정한 뒤에도 CI에서 contextLoads()가 실패하는 문제가 남아 있었다.

현재 로그의 핵심은 다음과 같다.

org.hibernate.service.spi.ServiceException
    Caused by:
org.hibernate.HibernateException
    at DialectFactoryImpl.java:190

따라서 단순한 환경변수 문제만으로 끝나지 않고, Hibernate가 PostgreSQL의 정보를 가져오는 과정에서 정확히 어떤 이유로 실패했는지 추가적으로 확인할 필요가 있다.

이를 위해 Gradle 테스트 실행 시 --stacktrace 옵션을 추가해 실제 Caused by의 원인을 더 자세하게 확인하도록 했다.

./gradlew build --no-daemon --stacktrace

또한 Spring Boot와 Spring Cloud 버전 조합도 함께 확인했다.

현재 프로젝트는 다음 버전을 사용하고 있다.

Spring Boot 4.1.0
Spring Cloud 2025.1.2
Java 21

Spring Boot와 Spring Cloud는 서로 호환되는 버전을 사용하는 것이 중요하기 때문에 이후 CI 문제를 해결하면서 버전 호환성도 함께 확인할 예정이다.


9. 오늘 배운 점

오늘 작업을 하면서 단순히 코드를 작성하는 것과 실제 프로젝트를 실행 가능한 상태로 만드는 것은 꽤 다르다는 것을 느꼈다.

특히 다음 부분을 직접 경험할 수 있었다.

1. 인프라는 애플리케이션 코드와 별개의 문제가 아니다

PostgreSQL이나 Redis가 정상적으로 실행되지 않으면 애플리케이션도 정상적으로 동작할 수 없다.

Docker Compose를 이용해 필요한 인프라를 먼저 준비하고 애플리케이션을 실행하는 흐름을 경험했다.

2. 서비스 디스커버리의 동작 과정을 이해할 수 있었다

Order Service가 단순히 900 포트에서 실행되는 것이 아니라 Eureka Server에 자신의 정보를 등록한다는 것을 실제 로그를 통해 확인했다.

Order Service
     ↓
Eureka Server에 등록
     ↓
ORDER-SERVICE
     ↓
UP

3. 로컬 환경과 CI 환경은 다르다

로컬에서는 .env나 Docker 환경이 이미 구성되어 있기 때문에 정상적으로 동작하더라도 GitHub Actions에서는 동일하게 동작하지 않을 수 있다.

특히 환경변수는 어디에 설정되어 있는지가 중요하다는 것을 알게 되었다.

4. 로그를 위에서부터 읽는 것이 아니라 핵심 원인을 찾아야 한다

처음에는 여러 에러가 한꺼번에 출력되기 때문에 무엇이 문제인지 찾기 어려웠다.

하지만 로그의 마지막 결과와 Caused by를 따라가면서 실제 실패 지점을 좁혀나갈 수 있었다.


마무리

어제는 API, DB, ERD, 인프라와 팀 컨벤션 등 프로젝트를 만들기 위한 설계 단계를 진행했다면, 오늘은 그 설계를 실제 개발 환경에 연결하는 작업을 진행했다.

어제
설계
 ├─ API 설계
 ├─ 테이블 설계
 ├─ ERD
 ├─ 인프라 설계
 └─ 팀 컨벤션

오늘
구현 및 검증
 ├─ Docker 인프라 실행
 ├─ PostgreSQL / Redis 확인
 ├─ Eureka Server 실행
 ├─ Order Service 등록
 ├─ Actuator 설정
 ├─ PR 생성
 ├─ GitHub Actions CI 확인
 └─ CI 환경의 PostgreSQL/Hibernate 오류 분석