Springboot를 이용해 MSA구조로 개발을 해보고 싶어서 프로젝트를 진행하던 도중 각 마이크로서비스 개발 및 스웨거로 API 테스트를 마치고 클라이언트 요청을 수신해 각 마이크로서비스로 라우팅해주는 게이트웨이를 구성하니 게이트웨이 스웨거에서 모든 마이크로서비스의 API 기능을 통합해서 보고 싶었다.
먼저 이 프로젝트는 기존 모놀리식으로 구현한 프로젝트를 MSA로 분리하면서 Springboot 버전을 2.x.x에서 3.x.x로 옮겼다.
버전이 올라감에 따라 Swagger를 지원하는 의존성도 달라졌는데, 기존 SpringFox에서 제공한 Swagger는 이제 사용하지 못하고 Springdoc-openapi에서 지원한다.
따라서 게이트웨이를 도입하기 전에
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.0.4'
Springdoc에서 제공하는 MVC기반 Swagger를 사용해 API 테스트를 진행했었다.
그래서 gateway에서 깡통 swagger를 띄우기 위해 동일한 의존성을 사용했는데 에러가 터진다.
찾아보니 Spring Cloud Gateway는 비동기식 프로그래밍을 목표로 기존 DispatcherServlet 기반 요청처리 구조인 MVC 패턴에서 벗어나, 비동기식, 논블로킹 프로그래밍에 최적화된 WebFlux를 기반으로 만들어졌다고 한다.
따라서 위 의존성을 지우고
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webflux-ui', version: '2.0.2' // WebFlux용 Swagger
WebFlux용 Swagger를 지원해주는 의존성으로 수정하니 일단 깡통 스웨거를 켜는데에는 성공했다.
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.components(new Components())
.info(apiInfo());
}
private Info apiInfo() {
return new Info()
.title("--")
.description("-- API")
.version("1.0.0");
}
}
Swagger 설정은 이정도로 했다.
이렇게 게이트웨이에 WebFlux 기반 Swagger를 설정하고 연결된 마이크로서비스의 Swagger도 Webflux기반으로 설정을 해보려다가 시간을 엄청 날렸다. Springdoc 설정을 아무리 바꿔봐도 api 문서가 로드되지 않아서 기존에 사용하던 mvc 기반 Swagger로 롤백 후에 성공했다. 잘 쓰던 건 최대한 건들지 말아야겠다.
이제 만들어진 gateway 깡통 스웨거에 마이크로 서비스의 api문서를 통합할 차례이다.
springdoc:
swagger-ui:
use-root-path: false
enabled: true
urls:
- name: s1
url: /api/s1/v3/api-docs
- name: s2
url: /api/s2/v3/api-docs
- name: s3
url: /api/s3/v3/api-docs
- name: s4
url: /api/s4/v3/api-docs
- name: s5
url: /api/s5/v3/api-docs
gateway의 application.yml Springdoc 설정이다.
urls에 각 마이크로서비스의 명칭과 API 문서 엔드포인트를 적어서 등록한다.
use-root-path는 켜놨더니 localhost:8080/에 접속해도 swagger가 켜지길래 false로 설정했다.

이런식으로 연결된 마이크로 서비스의 API 문서에 접근이 가능해진다.
그 이후에 이미 테스트가 완료된 각 마이크로서비스의 API를 테스트 해봤는데..
CORS 에러가 발생했다.
CORS(Cross-Origin Resource Sharing)은 교차 출처 자원 공유라고도 하지만, 보통 CORS를 소리나는 대로 읽는다.
여기서 Origin이란 http://www.naver.com 이라는 URL이 존재할 때, 그냥 네이버 주소라고 생각할 수도 있지만 이는 사실 http,https(프로토콜), www.naver.com(호스트), :80.:443(포트) 이렇게 3가지 요소로 구성되어있는데, 이를 합쳐 Origin이라고 한다.
따라서 프로토콜, 호스트, 포트가 다른 Origin으로부터 자원을 공유하려고 하거나 공유받으려고 시도할 때 CORS 정책 위반으로 해당 요청, 응답이 차단된다.
게이트웨이 스웨거(8080포트)에서 각 마이크로서비스(808X)로 요청을 보낼 때 흐름은
1. localhost:8080/api/s1/... 로 요청을 보낸다. 이때 게이트웨이에서 8080포트에 요청이 들어오면 gateway 설정에 의해 포트 뒤 엔드포인트에 매칭되는 URL로 해당 요청을 라우팅하게 된다.
2. 따라서 8080포트에서 808X로 요청을 보낸다.
3. 수신받은 마이크로 서비스에서 해당 요청에 대한 응답을 생성한다.
4. 808X 포트에서 8080 포트로 응답을 보낸다.
이 과정에서 CORS가 발생했으니 당연히 2번 과정에서 오류가 발생했다고 생각했는데, 808X에 올라간 마이크로서비스에서 게이트웨이로 보낼 응답을 생성한 로그를 확인하고 4번 과정에서 오류가 발생한다고 확신하게 되었다.
8080 -> 808X는 라우팅 설정으로 CORS가 발생하지 않은 것 같다.
모든 마이크로서비스의 Swagger Configuration은 별 내용도 없고 gateway와 동일하니 넘겼고,
application yml에서는 springdoc 설정도 추가하지 않았다.
따라서 설정이나, properties는 건들지 않고 해당 엔드포인트에 매핑된 컨트롤러에 CORS 매핑을 해주기로 했다.
application yml이나 WebMVCConfig bean으로 할수도 있지만 annotation 선에서 처리할 수 있어서
@RestController
@RequestMapping("/api/comm")
@AllArgsConstructor
@CrossOrigin(origins = "http://localhost:8080")
이렇게 @CrossOrigin annotation을 각 마이크로서비스 컨트롤러에 추가해줬다.
그 후 테스트 해보니 정상적으로 응답을 수신할 수 있었다.
이제 Spring Cloud Gateway를 이용한 API Gateway의 swagger에 모든 마이크로서비스의 API 문서를 통합했고,
이제 게이트웨이 윗단에 NGINX를 추가하는 과정에서 또 문제가 발생했다.
NGINX는 비동기 입출력 처리 방식을 가진 웹 서버로, 많은 클라이언트들의 요청을 빠르게 처리할 수 있는 고성능 웹서버이다. 또한 클라이언트 요청을 받아 다른 서버(여기서는 내가 만든 gateway를 칭한다)로 보낸 후 응답을 받아 클라이언트에게 반환해주는 리버스 프록시 기능도 있다. 여기서 중요한 점은 리버스 프록시를 통해서 내 프로젝트 실 주소를 클라이언트가 알 수 없다는 점이다. 1차적으로 게이트웨이 라우팅을 통해 내 마이크로서비스의 주소를 외부로 노출하지 않았고, NGINX를 통해서 게이트웨이의 주소조차 클라이언트에게 노출하지 않게 된다.
로드밸런싱 기능도 있다고 하는데 토이프로젝트에 무슨 이중화 삼중화를.. 나중에 기회되면 Docker에 2개 올려봐야겠다.
일단 NGINX를 깔고 exe 파일을 실행하거나 cmd를 통해 접근해 실행하고, 포트없이 Localhost에 접속 시 NGINX 초기화면이 떴다면 성공이다.
그 후에 NGINX설치경로/conf/nginx.conf 파일을 열어 프록시 정보를 입력해야한다.
server {
listen 80;
server_name localhost;
location /gateway/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server 하위에 location /XX/{ } 블럭이 작성해야하는 부분인데 이는 /gateway/로 시작하는 80포트로 들어오는 엔드포인트를 8080포트로 연결해준다는 것이다. Http 기본 포트로 80포트가 생략되니 localhost/gateway/.... 이런 URL에 접속하게 되면 NGINX가 이를 localhost:8080/...로 전달해준다.
이를 통해서 스웨거에 접속하려고 하니 스웨거는 접속되었지만..

이런 메세지가 뜨면서 아무것도 로딩되지 않았다.
NGINX를 켜놓으면 접속기록(Rest 요청 기록)과 에러로그를 확인할 수 있고 이는 NGINX설치경로/logs에 있다.
접속기록로그 (access.log)를 확인해보니

swagger 자체 접속은 200코드를 받아 정상적으로 되었지만 그 후에 swagger-config를 요청하는 엔드포인트를 확인해보면
/v3/로 시작하는 것을 알 수 있다. nginx.conf에 /v3/에 대한 프록시를 설정해주지 않았으니 404가 뜨는 것이다.
/gateway/를 붙여주는 설정을 찾아 적용해봤지만 되질 않아서 /v3/에 대한 리버스 프록시를 추가로 설정해줬다.
location /v3/ {
proxy_pass http://localhost:8080/v3/; # Swagger 프록시
}
/gateway/를 붙여주는 것과 동일하게 /v3/ 엔드포인트를 만나면 localhost:8080/v3/로 프록시되도록 설정했다.
이렇게 하니 해당 오류가 사라지고..

API 문서를 찾을 수 없다는 에러가 뜨기 시작했다ㅡㅡ
긍정적으로 생각하면 Swagger를 로딩하는 과정을 잘 알 수 있었다.
1. Swagger 깡통 로딩
2. Swagger 설정 요청
3. Swagger API 문서 요청
아까와 동일한 방법으로 NGINX 로그를 확인해보니

/v3/에 이어 내가 매핑했던 /api/ 엔드포인트를 못 찾아서 404를 리턴받고 있었다.
다행히 같은 패턴의 에러가 발생해서
location /api/ {
proxy_pass http://localhost:8080/api/; # Swagger API 문서로의 프록시
}
/api/도 동일하게 게이트웨이 주소로 프록시를 걸었다.
그 후에 NGINX를 reload하고(nginx -s reload)
http://localhost/gateway/webjars/swagger-ui/index.html 이 주소로 접속해서

NGINX + Spring Cloud Gateway를 이용한 Swagger 통합에 성공했다!!
이렇게 통합된 Swagger에서 Rest를 쏘면 또 CORS가 발생한다.
해당 문제 해결하고 다시 써야겠다..
'공부 > Spring' 카테고리의 다른 글
[SpringBoot 개인정리] MSA 구조로 생성한 프로젝트 실행 시 Unable to determine Dialect without JDBC metadata 오류 해결방법 (0) | 2024.10.15 |
---|---|
[SpringBoot] Swagger UI 접속 시 java.lang.NoSuchMethodError 발생할 경우 대처 방법 (1) | 2024.10.10 |
[Spring] Maven? Gradle? (0) | 2022.02.23 |
[Spring] Spring vs SpringBoot (0) | 2022.02.22 |
[Spring] @Configuration과 @Bean Annotation을 이용한 Bean 등록 (1) | 2022.02.21 |