DevOps

Swagger UI를 활용한 공용 API 문서 서버 구성

분산된 Spring RestDocs 문서를 Swagger UI로 통합하고 epages-restdocs 플러그인과 Jenkins CI/CD를 활용해 자동화된 API 문서 서버를 구축한 과정을 정리한다.

swagger openapi docker api-docs spring-restdocs

문제 상황

사내 Java 백엔드 서비스들은 Spring RestDocs로 API 문서를 생성하고 있었다. 서비스 수가 늘어나면서 두 가지 문제가 생겼다.

  • 문서 분산: 특정 API를 찾으려면 어떤 서비스에 있는지부터 파악해야 했다. 공통 API 정보도 여러 문서에 흩어져 있어서 탐색에 시간이 걸렸다.
  • 테스트 불편: RestDocs는 API 테스트 인터페이스를 제공하지 않는다. Postman을 별도로 띄워야 했다.

하나의 서버에서 전체 API 문서를 확인하고 바로 테스트할 수 있는 환경이 필요했다.


Swagger UI 서버 구성

Swagger UI를 Docker로 띄우고 각 서비스의 OpenAPI Spec 파일을 마운트하는 구조다.

Docker Compose

version: '3'
services:
  swagger:
    image: swaggerapi/swagger-ui
    container_name: swagger-ui
    env_file: .env
    ports:
      - 8888:8080
    volumes:
      - ./data/dist:/usr/share/nginx/html/apis
    restart: unless-stopped

./data/dist 디렉토리에 각 서비스의 OAS JSON 파일을 넣으면 Swagger UI에서 바로 접근할 수 있다.

환경 변수 (.env)

BASE_URL=/swagger
QUERY_CONFIG_ENABLED=true
URLS=[{ url: './apis/service1.json', name: 'service1'}, { url: './apis/service2.json', name: 'service2'}]

URLS에 등록한 서비스가 Swagger UI 상단 드롭다운에 표시된다.

swagger-initializer.js 커스터마이징

기본 설정 외에 세밀한 제어가 필요하면 swagger-initializer.js를 직접 마운트한다.

window.onload = function() {
  window.ui = SwaggerUIBundle({
    urls: [
      { url: "./apis/service1.json", name: "Service 1" },
      { url: "./apis/service2.json", name: "Service 2" },
    ],
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout",
    defaultModelsExpandDepth: -1,     // 하단 Model 섹션 숨김
    docExpansion: "list",             // API 목록만 펼치고 상세는 접힌 상태
    filter: true,                     // 검색 필터 활성화
    tryItOutEnabled: true,            // Try it out 기본 활성화
    requestInterceptor: (req) => {
      // 공통 인증 헤더 자동 추가
      req.headers['X-API-KEY'] = 'your-api-key';
      return req;
    }
  });
};

Docker Compose에서 마운트한다.

volumes:
  - ./data/dist:/usr/share/nginx/html/apis
  - ./swagger-initializer.js:/usr/share/nginx/html/swagger-initializer.js

주요 설정 옵션은 다음과 같다.

옵션설명권장값
defaultModelsExpandDepthModel 섹션 표시 깊이 (-1: 숨김)-1
docExpansionAPI 목록 펼침 수준 (none/list/full)"list"
filter상단 검색 필터 활성화true
tryItOutEnabledTry it out 버튼 기본 활성화true
persistAuthorization인증 정보 브라우저 저장true

OpenAPI Spec 생성 (epages-restdocs)

기존 Spring RestDocs 테스트 코드에서 OpenAPI Spec JSON을 자동 생성하는 플러그인이다. RestDocs 기반 테스트를 유지하면서 Swagger UI용 OAS 파일을 함께 만들 수 있다.

1. Gradle 플러그인 설정

plugins {
    id 'com.epages.restdocs-api-spec' version '0.18.2'
}

openapi3 {
    setServer("https://api.example.com")
    title = "My Service API"
    description = "API documentation for My Service"
    version = "1.0.0"
    format = "json"               // json 또는 yaml
    outputDirectory = "build/api-spec"
    outputFileNamePrefix = "my-service-api"
}

2. OpenAPI Spec 생성 코드 예시

기존 RestDocs 테스트 코드에 ResourceSnippetParameters만 추가하면 된다.

resultActions
  .andDo(
    MockMvcRestDocumentationWrapper.document(operationName,
      ResourceDocumentation.resource(
        ResourceSnippetParameters.builder()
          .tag("User API")                    // Swagger UI 태그 그룹핑
          .summary("사용자 정보 조회")           // API 요약
          .description("사용자 ID로 상세 정보를 조회한다.")
          .build()
      ),
      requestFields(new FieldDescriptors().getFieldDescriptors()),
      responseFields(
        fieldWithPath("comment").description("the comment"),
        fieldWithPath("flag").description("the flag"),
        fieldWithPath("count").description("the count"),
        fieldWithPath("id").description("id"),
        fieldWithPath("_links").ignored()
      ),
      links(linkWithRel("self").description("some"))
  )
);

tag로 Swagger UI에서 API를 그룹핑하고 summary/description으로 각 API의 설명을 표시할 수 있다.

3. OAS 문서 생성 실행

# OpenAPI 3.0 스펙 생성
./gradlew openapi3

# 생성 결과 확인
ls build/api-spec/
# my-service-api.json

생성된 JSON 파일을 Swagger UI 서버의 data/dist 디렉토리에 업로드하면 된다.


Jenkins CI/CD 연동

빌드 시 OAS 문서를 생성하고 배포 단계에서 Swagger UI 서버로 자동 업로드하도록 구성했다. API가 변경될 때마다 문서가 자동으로 갱신된다.

pipeline {
    agent any

    stages {
        stage('Build & Generate API Spec') {
            steps {
                sh './gradlew clean build openapi3'
            }
        }

        stage('Upload API Docs') {
            steps {
                sh """
                    scp -o StrictHostKeyChecking=no \
                        my-service/build/api-spec/my-service-api.json \
                        ubuntu@xxx.xxx.xxx.xxx:/home/ubuntu/swagger-ui/data/dist/
                """
            }
        }
    }

    post {
        success {
            echo 'API 문서가 성공적으로 업데이트되었다.'
        }
    }
}

신규 서비스 온보딩

새로운 서비스를 Swagger UI에 추가할 때는 다음 절차를 따른다.

1. 서비스 프로젝트에 epages-restdocs 설정 추가

plugins {
    id 'com.epages.restdocs-api-spec' version '0.18.2'
}

openapi3 {
    setServer("https://api.example.com")
    title = "New Service API"
    version = "1.0.0"
    format = "json"
    outputDirectory = "build/api-spec"
    outputFileNamePrefix = "new-service-api"
}

2. Swagger UI .env 파일에 URL 추가

URLS=[{ url: './apis/service1.json', name: 'service1'}, { url: './apis/service2.json', name: 'service2'}, { url: './apis/new-service-api.json', name: 'new-service'}]

3. Swagger UI 컨테이너 재시작

docker compose restart swagger

.env 자동 생성 스크립트

서비스가 많아지면 수동으로 .env를 관리하기 어렵다. data/dist 디렉토리의 JSON 파일을 자동 탐색해서 .env를 생성하는 스크립트를 만들었다.

#!/bin/bash
# generate-env.sh — data/dist 내 JSON 파일로 .env 자동 생성

API_DIR="./data/dist"
ENV_FILE=".env"

URLS="URLS=["
first=true

for file in "$API_DIR"/*.json; do
    filename=$(basename "$file")
    name="${filename%.json}"

    if [ "$first" = true ]; then
        first=false
    else
        URLS+=", "
    fi
    URLS+="{ url: './apis/$filename', name: '$name'}"
done
URLS+="]"

cat > "$ENV_FILE" <<EOF
BASE_URL=/swagger
QUERY_CONFIG_ENABLED=true
$URLS
EOF

echo ".env 파일이 생성되었다."
docker compose restart swagger

Jenkins 파이프라인의 Upload 단계 이후에 이 스크립트를 실행하면 신규 서비스 추가 시 .env 수정 없이 자동으로 반영된다.


트러블슈팅

CORS 에러로 Try it out이 동작하지 않는 경우

Swagger UI에서 API를 테스트할 때 CORS 에러가 발생할 수 있다. 백엔드 서비스에 Swagger UI 도메인을 허용하는 CORS 설정을 추가한다.

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("https://swagger.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
            .allowedHeaders("*");
    }
}

Mixed Content 에러 (HTTPS → HTTP)

Swagger UI를 HTTPS로 서빙하는데 API 서버가 HTTP인 경우 브라우저가 요청을 차단한다.

  • 해결 1: OAS 생성 시 setServer를 HTTPS URL로 설정
  • 해결 2: 리버스 프록시(Nginx)를 사용하여 HTTPS 종단 처리
server {
    listen 443 ssl;
    server_name swagger.example.com;

    ssl_certificate     /etc/ssl/certs/cert.pem;
    ssl_certificate_key /etc/ssl/private/key.pem;

    location / {
        proxy_pass http://localhost:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

OAS 파일이 갱신되었는데 Swagger UI에 반영되지 않는 경우

브라우저 캐시 문제일 수 있다. swagger-initializer.js에서 캐시 버스팅을 추가한다.

urls: [
  { url: `./apis/service1.json?v=${Date.now()}`, name: "Service 1" },
],


참고