목표
스프링 게이트웨이를 활용한 MSA 환경을 구축해본다.
과정
nginx 서버를 유지한 상태로 spring 서버 두기
- 스프링 서버 도커 이미지 준비
도커파일
FROM amazoncorretto:17-alpine AS builder
WORKDIR /app
COPY gradlew ./
COPY gradle ./gradle
COPY build.gradle settings.gradle ./
RUN ./gradlew dependencies --no-daemon || true
COPY src ./src
RUN ./gradlew clean bootJar -x test --no-daemon
FROM amazoncorretto:17-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8765
ENTRYPOINT ["java", "-jar", "app.jar"]
이미지 빌드
docker build -t lgcns-spring-rest-api:1.1.0 .
- nginx 설정 파일 수정
// default.conf
upstream blog_servs{
server awsgoo-blog-1:8080;
}
server{
listen 80;
location / {
proxy_pass http://blog_servs;
proxy_set_header Host $host;
}
}
Q. nginx 설정 파일에 Host 를 변경한 이유?스프링 서버에서 받은 Host 의 값은 nginx 의 서버으로 바뀌고 여기서는 blog_servs 값을 가진다. 이 때, _ 는 스프링 서버에서 파싱을 못하는 이유가 있어 400 에러를 발생시킨다. 따라서, client 의 host 값으로 바꿔준다.
보통은 클라이언트의 host 값으로 바꿔주는데 가상 호스트, CORS, 리다이렉션 관련 문제가 발생할 수 있다.
가상 호스트: 서비스에서 가상 호스트와 관련된 로직이 있다면 수행하지 못할 수도 있다.
CORS: 클라이언트의 Origin 을 허용했지만 nginx 서버의 Origin 을 허용하지 않아 CORS 관련 에러가 발생할 수 있다.
리다이렉션: 엉뚱한 곳으로 리다이렉션 될 수 있다.
테스트 결과
$ curl http://localhost:9889/hello
{"koreaTime":"2025-10-16T02:13:51.346790465Z[GMT]","message":"Hello World!","timestamp":1760580831}
spring gateway 구성
- spring gateway project 준비
- 프로퍼티 파일 작성
spring:
application:
name: sc-gateway
cloud:
gateway:
server:
webflux:
routes:
- id: hello_route
uri: http://localhost:8765
predicates: # 조건
- Path=/hello # 경로 조건
- id: abc2hello_route
uri: http://localhost:8765
predicates:
- Path=/abc
- id: def2hello_route
uri: http://localhost:8765
predicates:
- Path=/def
filters:
- RewritePath=/def, /hello # /def 경로에 대한 요청에 대하여 path 를 변경하여 /hello로 보내기
테스트 결과
$ curl http://localhost:8080/hello
{"koreaTime":"2025-10-16T02:18:12.152314835Z[GMT]","message":"Hello World!","timestamp":1760581092}%
$ curl http://localhost:8080/abc
{"timestamp":"2025-10-16T02:18:16.764+00:00","status":404,"error":"Not Found","path":"/abc"}%
$ curl http://localhost:8080/def
{"koreaTime":"2025-10-16T02:18:19.835062131Z[GMT]","message":"Hello World!","timestamp":1760581099}%
spring-gateway 를 도커 컴포즈 파일에 포함시키기
- spring cloud gateay 를 도커 이미지로 빌드
$ docker build -t lgcns-sc-gateway .
- docker compose 파일 수정
scg-proxy:
image: lgcns-sc-gateway
ports:
- "9000:9000"
depends_on:
- blog
1차 테스트 결과
$ curl http://localhost:9000/hello
{"timestamp":"2025-10-16T05:47:09.956+00:00","path":"/hello","status":500,"error":"Internal Server Error","requestId":"60a54f5c-2"}%
500 에러가 발생했다.
1차 테스트 결과 => 트러블 슈팅
먼저, 500(Internal Server Error)이므로 서버 로그를 확인한다.
2025-10-16T05:47:09.957Z ERROR 1 --- [sc-gateway] [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [60a54f5c-2] 500 Server Error for HTTP GET "/hello"
2025-10-16 14:47:09 io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8765
2025-10-16 14:47:09 Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
2025-10-16 14:47:09 Error has been observed at the following site(s):
2025-10-16 14:47:09 *__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
2025-10-16 14:47:09 *__checkpoint ⇢ HTTP GET "/hello" [ExceptionHandlingWebHandler]
2025-10-16 14:47:09 Original Stack Trace:
2025-10-16 14:47:09 Caused by: java.net.ConnectException: Connection refused
로그를 확인하니 뒷 단의 서버 연결을 맺고 있지 못한다. 뒷 단의 서버는 정상적으로 동작 중인데 왜 이런 에러가 발생했는지 확인한다.
설정 파일(프로퍼티 파일, 리소스 파일)을 확인해보면 다음과 같은 설정이 되어 있는 것을 확인할 수 있다.
routes:
- id: hello_route
uri: http://localhost:8765 # 뒷 단 서비스 uri
predicates:
- Path=/hello
원래는 호스트에서 앱을 실행해서 테스트를 했기 때문에 성공했지만 지금은 scg 를 컨테이너로 구동 중이다. 따라서, localhost 도메인은 컨테이너 자기 자신이 되므로 타 컨테이너로 요청을 보낼 수가 없는 것이다. 따라서, uri 를 다음과 같이 수정한다.
routes:
- id: hello_route
uri: http://awsgoo-blog-1:8080
predicates:
- Path=/hello
2차 테스트 결과
수정 후 다시 테스트를 해본다.
curl http://localhost:9000/hello
{"koreaTime":"2025-10-16T06:02:15.348718138Z[GMT]","message":"Hello World!","timestamp":1760594535}%
요청을 성공한 것을 확인할 수 있다.
Q. docker compose 작성 시 depends_on 주의 사항docker compose 에서 컨테이너 순서를 control 하기 위해서 depends_on 이라는 명령을 사용한다. 하지만, depends_on 사용 시 주의할 점이 있다. 위처럼 단순히
depends_on: - blog라고 한다면, 해당 서비스가 준비된 시점이 아니라 단순히 실행될 때에 해당한다.
완전히 준비된 상태에서 다른 컨테이너 구동될 필요가 있는 경우라면 이런 단순 depends_on 은 문제를 일으킬 수 있다. 그럴 때 사용할 수 있는 방법은 health_check 검사를 통해 해당 서비스가 준비되었는 지 확인한 후 구동하면 된다.
참고자료: Control startup and shutdown order in Compose
결론
nginx 를 Spring Cloud Gateway 로 대체하는 과정에 대해 학습하였다. Gateway를 통해 뒷 단의 서버로 라우팅할 수 있다. 보통은 eureka 서버와 함께 아키텍처를 구성하는 이유에 대해 생각해본다.
'devops' 카테고리의 다른 글
| Spring Cloud Gateway 와 Spring Eureka 를 통해 MSA 환경 구축 (0) | 2025.10.20 |
|---|---|
| GW LB 를 이용한 무중단 배포(업데이트) (0) | 2025.10.20 |
| nginx-proxy 이미지 없이 docker compose 를 활용하여 직접 MSA 구성 (0) | 2025.10.20 |
| docker 를 활용하여 직접 msa 구성 (0) | 2025.10.20 |
| docker compose 를 이용한 서비스 디스커버리 및 scale out - in (0) | 2025.10.15 |