배포 서버에서는 왜 REST Docs가 적용되지 않았을까 🤔

상호·2024년 11월 1일
8

🌱 Spring Boot

목록 보기
4/7
post-thumbnail

한창 큐시즘 30기 밋업 프로젝트를 달리고 있는 요즘이다.
자세한 회고록은 끝나고 쓰겠지만, 중간중간 트러블 슈팅한 내용을 기록해두려고 글을 적는다!


📄 REST Docs + Swagger

우리 팀은 HitZone 서비스를 만들면서,
API 명세서를 REST Docs + Swagger로 관리하기로 결정했다.

이렇게 하면,
1) 테스트 코드를 자연스럽게 작성하게 되고
2) 깔끔하면서도 가독성 좋은 문서를 만들 수가 있게 된다.

기본적인 구축은 같은 백엔드 팀원 준형이형이 해주었기에,
나는 명확한 동작 원리는 잘 모르는 상태였음을 미리 알린다!

적용 방법에 대해서는 후에 글로 한 번 작성해보려고 한다.


🥲 배포 서버에서는 왜..?

API를 만든 후 테스트 코드까지 작성하고,
빌드 후 실행하게 되면 아래와 같이 REST Docs가 Swagger에 잘 적용되어서 보였다.

하지만 이상하게도 배포 서버에서는 보이지 않았다.


🏃🏻 초기 해결 시도

이를 해결하기 위해 초기에 여러 방법을 시도해보았다.

1. 배포 서버를 기본 서버 URL로 설정

build.gradle


def serverUrl = "https://git.hitzone.store"

openapi3 {
    server = serverUrl
    title = "히트존 API 문서"
    description = "Spring REST Docs with Swagger UI."
    version = "0.0.1"
    outputFileNamePrefix = 'open-api-3.0.1'
    format = 'json'
    outputDirectory = 'build/resources/main/static/docs'
}

이런식으로 설정을 하였지만 해결되지 않았다.
후에 테스트를 해본 결과 기본 서버 URL은 큰 역할을 하는 것 같지는 않았다.
위처럼 배포 도메인으로 설정해두었지만, 로컬에서도 잘 실행되었기 때문이다.

2. CORS 허용, Swagger 서버 설정 추가

SecurityConfig (기존)

...

	@Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173", "http://211.188.55.153:8080"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setMaxAge(3600L);
        configuration.setExposedHeaders(Arrays.asList("Set-Cookie", "Authorization"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    
...

SecurityConfig (배포 도메인 추가)

...

	@Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173", "https://git.hitzone.store"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setMaxAge(3600L);
        configuration.setExposedHeaders(Arrays.asList("Set-Cookie", "Authorization"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    
...

배포 도메인을 추가하여 CORS를 허용하였다.

SwaggerConfig

...

@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        OpenAPI openAPI = new OpenAPI()
                .info(new Info().title("히트존 API 문서").version("0.0.1").description("Spring REST Docs with Swagger UI."))
                .servers(List.of(
                        new Server().url("http://localhost:8080").description("로컬 서버"),
                        new Server().url("https://git.hitzone.store").description("배포 서버")
                ));
                
...

또한 위와 같이 로컬과 배포 서버를 분리하였다.

당연하게도 이를 통해 해결되지는 않았다.
하지만 어차피 이 부분을 해 두지 않으면, 배포 도메인 Swagger에서 테스트를 할 때 CORS 에러가 발생하게 되니 해두어야 한다.


🕵🏻‍♂️ 문제를 찾은 것 같다

그렇게 갈피를 못 잡고 있던 도중,
Swagger는 되는데 REST Docs가 적용되지 않는 문제라면 해당 문서가 제대로 생성되지 않아서가 아닐까? 하는 생각이 들었다.

빌드를 하게 되면 /resources/static/docs 디렉토리에 open-api-3.0.1.json 파일이 생기게 되고 이를 Swagger에서 적용하는 구조이다.

그렇기에 open-api-3.0.1.json 파일 자체에 문제가 있다면 당연하게도 REST Docs가 제대로 적용되지 않을 것이다.

도커 컨테이너 내부 확인

우리는 도커를 사용해서 인스턴스 내에 컨테이너로 서버를 띄우는 구조였기에,
도커 컨테이너 내부를 한 번 확인해보았다.

아니나 다를까 위와 같이 open-api-3.0.1.json이 제대로 생성되지 않았음을 확인할 수 있었다.
이제 문제를 파악했으니, 해당 파일이 잘 생성될 수 있는 방법만 찾으면 되었다.


🧑🏻‍💻 해결 과정

첫 번째 시도 : 도커파일 수정

처음에는 도커파일에 문제가 있다고 생각했다.

Dockerfile(기존)

# Alpine & Slim 이미지 사용 (용량 및 보안 개선)
# 버전 명시 (latest 지양)
# 최종 이미지의 경량화를 위해 Multi-Stage 빌드 사용
FROM openjdk:17-jdk-slim as build

WORKDIR /app

COPY ./build/libs/backend-0.0.1-SNAPSHOT.jar app.jar

# 최종 이미지: 경량화된 Alpine 이미지를 사용하여 빌드된 파일을 실행
FROM openjdk:17-jdk-alpine as final

WORKDIR /app

COPY --from=build /app/app.jar app.jar

# HEALTHCHECK 추가
# 컨테이너가 시작된 후 5초마다, 최대 3초 동안 http://localhost:8080으로 헬스 체크
HEALTHCHECK --interval=5s --timeout=3s --start-period=30s --retries=3 \
  CMD curl --fail http://localhost:8080 || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

EXPOSE 8080

Dockerfile(변경)

# 빌드 단계
FROM openjdk:17-jdk-slim as build

WORKDIR /app

# Gradle 빌드 실행
COPY . .
RUN ./gradlew build openapi3 asciidoctor

# 최종 이미지 단계
FROM openjdk:17-jdk-alpine as final

WORKDIR /app

# 빌드된 JAR 파일과 정적 문서 파일을 복사
COPY --from=build /app/build/libs/backend-0.0.1-SNAPSHOT.jar app.jar
COPY --from=build /app/build/resources/main/static/docs /app/static/docs

# HEALTHCHECK 추가
HEALTHCHECK --interval=5s --timeout=3s --start-period=30s --retries=3 \
  CMD curl --fail http://localhost:8080 || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

EXPOSE 8080

그래서 위와 같이 기존과 다르게 빌드 단계도 추가를 하고,
openapi3 asciidoctor 와 같은 옵션들도 추가하였다.

하지만 여전히 되지 않았다.

두 번째 시도 : Github Actions 스크립트 수정

그렇게 뭐가 문제지... 하며 Github Actions가 돌아가는 모습을 바라보다가 한 부분이 눈에 띄었다.

빌드를 하는 부분이었는데, -x test라는 옵션이 붙어있었다.
이는 테스트를 하지 않고 빌드를 한다는 옵션이기에,
Github Actions를 통해 빌드를 진행할 때 테스트가 실행되지 않아 REST Docs가 빈 채로 생성되었던 것이다.

...
      - name: ⏱️gradle build를 위한 권한을 부여합니다.
        run: chmod +x gradlew

      - name: ⏱️gradle build 중입니다.
        run: ./gradlew clean build openapi3 asciidoctor
...

그렇기에 위와 같이 스크립트를 수정했고,

# 최종 이미지 단계
FROM openjdk:17-jdk-alpine as final

WORKDIR /app

# 빌드된 JAR 파일과 정적 문서 파일을 복사
COPY ./build/libs/backend-0.0.1-SNAPSHOT.jar app.jar
COPY ./build/resources/main/static/docs /app/static/docs

# HEALTHCHECK 추가
HEALTHCHECK --interval=5s --timeout=3s --start-period=30s --retries=3 \
  CMD curl --fail http://localhost:8080 || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

EXPOSE 8080

도커파일에서도 불필요한 빌드 단계를 다시 제거했다.


✨ 결과

그 결과...

컨테이너 내부에서 open-api-3.0.1.json 이 잘 생성된 것을 확인했고,

위와 같이 배포 서버에서도 REST Docs가 잘 적용됨을 확인할 수 있었다!

테스트 또한 문제 없이 해 볼 수 있었다.


💡 느낀 점

  1. -x test는 빌드 시에 테스트 과정을 생략하여 시간 단축을 하는 데 활용한다. 때문에 테스트가 필요 없는 환경에서 사용하는데, 이를 배포시에 사용했다는 것은 테스트 코드를 짜지 않았다고 고백하는 거나 다름이 없다.
  2. 지금까지 프로젝트를 하면서 테스트 코드를 제대로 짜본 적이 없었기 때문에 지금과 같은 일이 생겼다고 생각한다. 이에 부끄러움을 느끼면서 앞으로는 테스트 코드를 무조건 작성하는 개발자가 되어야겠다.
  3. 실행만 잘 되고나면 빌드 단계에는 신경을 쓰지 않는 경우가 많다. 하지만 빌드 단계부터 하나하나 해당 코드가 어떤 기능인지를 알아야겠다고 생각이 들었다.
profile
상호작용하는 백엔드 개발자

4개의 댓글

comment-user-thumbnail
2024년 11월 1일

진짜 존경합니다.

1개의 답글
comment-user-thumbnail
2024년 11월 2일

멋져요!!!!

1개의 답글