Spring Cloud Gateway 완벽 가이드

요약

[백엔드] Spring Cloud Gateway 2026: 마이크로서비스 API 게이트웨이 뚝딱!

마이크로서비스 아키텍처의 핵심, Spring Cloud Gateway로 API 트래픽을 효율적으로 관리하고 안정적인 시스템을 구축하는 가이드입니다.

핵심 키워드: API Gateway, 마이크로서비스, Spring Cloud, 라우팅, 필터링

이 글의 순서

1. 마이크로서비스와 API 게이트웨이의 필요성

2. Spring Cloud Gateway 아키텍처와 주요 기능 분석

3. 실제 운영에서 마주치는 도전과 해결책

4. Spring Cloud Gateway 실전 구축 가이드 2026

5. 자주 묻는 질문 (FAQ)

6. 마무리: Spring Cloud Gateway의 미래와 권퓨터의 생각

도입

마이크로서비스와 API 게이트웨이의 필요성

안녕하세요, 권퓨터입니다! 최근 개발 트렌드를 이끄는 핵심 키워드 중 하나는 바로 마이크로서비스 아키텍처(MSA)입니다. MSA는 거대한 단일 애플리케이션(모놀리식)을 작고 독립적인 서비스들로 분리하여 개발, 배포, 운영하는 방식이죠. 각 서비스는 독립적으로 배포될 수 있고, 특정 서비스에 문제가 발생하더라도 전체 시스템에 미치는 영향을 최소화할 수 있다는 장점이 있습니다.

하지만 MSA가 만능은 아닙니다. 서비스가 많아질수록 관리해야 할 복잡도가 기하급수적으로 증가합니다. 특히 클라이언트(웹 브라우저, 모바일 앱 등)가 여러 마이크로서비스와 직접 통신해야 한다면, 다음과 같은 문제에 직면하게 됩니다.

  • 라우팅 복잡성: 클라이언트가 어떤 서비스에 요청을 보내야 할지, 각 서비스의 주소는 무엇인지 알아야 합니다. 서비스가 추가되거나 변경되면 클라이언트 코드도 함께 수정해야 하죠.
  • 인증/인가 문제: 각 서비스마다 인증 및 인가 로직을 구현해야 한다면 중복 코드가 발생하고 관리 포인트가 늘어납니다.
  • 횡단 관심사 처리: 로깅, 모니터링, 속도 제한(Rate Limiting) 등 여러 서비스에 공통적으로 적용되어야 하는 기능들을 개별 서비스에 구현하기 어렵습니다.
  • 네트워크 오버헤드: 클라이언트가 여러 서비스에 개별적으로 요청을 보내야 하므로 네트워크 지연이 발생할 수 있습니다.

이러한 문제들을 우아하게 해결해주는 것이 바로 API 게이트웨이(API Gateway)입니다. API 게이트웨이는 클라이언트와 마이크로서비스 사이에 위치하여 모든 클라이언트 요청을 단일 진입점으로 받아들이고, 이를 적절한 마이크로서비스로 라우팅하는 역할을 수행합니다. 즉, 마이크로서비스의 ‘대문’ 역할을 하는 셈이죠.

그리고 오늘 우리가 다룰 Spring Cloud Gateway (SCG)는 Spring 생태계에서 이러한 API 게이트웨이 역할을 훌륭하게 수행하는 솔루션입니다. 비동기(Reactive) 기반으로 높은 성능을 자랑하며, 다양한 라우팅 규칙과 필터 기능을 제공하여 마이크로서비스 아키텍처의 복잡성을 효과적으로 관리할 수 있도록 돕습니다. 2026년 현재, 여전히 가장 강력하고 유연한 선택지 중 하나로 평가받고 있습니다.

핵심 포인트

API 게이트웨이는 마이크로서비스 아키텍처의 복잡성(라우팅, 인증, 횡단 관심사)을 해소하고 클라이언트와 서비스 간의 단일 진입점을 제공하여 시스템을 단순화하고 안정성을 높이는 핵심 컴포넌트입니다.

심층 분석

Spring Cloud Gateway 아키텍처와 주요 기능 분석

2.1. Spring Cloud Gateway란?

Spring Cloud Gateway는 Spring Framework 5 기반의 리액티브(Reactive) API 게이트웨이입니다. 전통적인 서블릿(Servlet) 기반의 API 게이트웨이와 달리, Netty 서버 위에서 Spring WebFlux를 사용하여 비동기, 논블로킹(Non-blocking) 방식으로 동작합니다. 이는 대량의 요청을 효율적으로 처리할 수 있게 하여 높은 처리량과 낮은 지연 시간을 보장합니다.

과거 Spring Cloud 생태계에서는 Netflix Zuul이 널리 사용되었지만, Zuul 1.x는 서블릿 블로킹 I/O 모델 기반이었습니다. Zuul 2.x는 리액티브를 지원했지만, Spring Cloud 프로젝트는 Zuul 대신 Spring Cloud Gateway를 적극적으로 밀고 있습니다. 그 이유는 바로 성능과 유연성 때문입니다.

<Spring Cloud Gateway와 Netflix Zuul 비교 (2026년 기준)>

특징Spring Cloud GatewayNetflix Zuul (1.x)
기반 기술Spring WebFlux, Project Reactor, NettyServlet API, Blocking I/O
I/O 모델비동기, 논블로킹 (Reactive)동기, 블로킹
성능높은 처리량, 낮은 지연 시간 (동일 리소스 대비)스레드당 처리량 제한적, 높은 부하 시 성능 저하
개발 편의성Spring Framework와의 높은 통합성, Java/YAML 설정Groovy 기반의 필터 작성, Spring과의 통합은 가능
로드밸런싱Spring Cloud LoadBalancer 통합 (기본), Eureka/Consul 연동Netflix Ribbon 통합, Eureka 연동
유지보수활발한 개발 및 커뮤니티 지원, Spring 6+ 호환성개발 중단 (Zuul 1.x), 제한적인 지원

이러한 비교를 통해 알 수 있듯이, 2026년 현재 새로운 프로젝트를 시작한다면 Spring Cloud Gateway가 훨씬 더 강력하고 미래 지향적인 선택임을 알 수 있습니다.

2.2. 라우팅 (Routing)의 이해와 설정

Spring Cloud Gateway의 핵심 기능 중 하나는 바로 라우팅입니다. 클라이언트로부터 요청을 받으면, 미리 정의된 규칙(Predicate)에 따라 해당 요청을 어떤 목적지(URI)로 전달할지 결정합니다. 이 라우팅 규칙은 크게 경로(Route)조건(Predicate), 그리고 필터(Filter)로 구성됩니다.

  • Route: 특정 요청을 매핑할 수 있는 ID, 목적지 URI, 그리고 Predicate와 Filter의 집합입니다.
  • Predicate: HTTP 요청의 특정 속성(경로, 헤더, 메서드, 쿼리 파라미터 등)을 기반으로 라우팅 규칙을 정의하는 조건입니다. 예를 들어, /api/user/** 경로로 오는 요청만 특정 서비스로 라우팅하도록 설정할 수 있습니다.
  • Filter: 요청이 라우팅되기 전이나 후에 특정 로직을 적용하는 데 사용됩니다. 자세한 내용은 다음 섹션에서 다루겠습니다.

라우팅 설정은 주로 application.yml 파일에서 선언적으로 정의하거나, Java 코드로 RouteLocator 빈을 등록하여 프로그래밍 방식으로 정의할 수 있습니다.

코드 설명

아래 YAML 설정은 Spring Cloud Gateway의 가장 기본적인 라우팅 정의를 보여줍니다. /api/user/** 경로로 들어오는 모든 요청을 http://localhost:8081/user 경로로 리다이렉트합니다. 또한, StripPrefix=1 필터를 사용하여 /api 부분을 제거합니다.

spring:
  cloud:
    gateway:
      routes:
        - id: user_service_route
          uri: http://localhost:8081 # 목적지 서비스 URI
          predicates:
            - Path=/api/user/** # 클라이언트 요청 경로 조건
          filters:
            - StripPrefix=1 # 요청 경로에서 첫 번째 경로 세그먼트 제거 (/api)

        - id: product_service_route
          uri: lb://PRODUCT-SERVICE # 서비스 디스커버리 (Eureka)를 통한 라우팅
          predicates:
            - Path=/api/product/**
          filters:
            - StripPrefix=1

2.3. 필터링 (Filtering)으로 요청/응답 제어

필터는 Spring Cloud Gateway의 강력한 기능으로, 요청(Request)이 라우팅되기 전(pre-filter)이나 응답(Response)이 클라이언트에 전달되기 전(post-filter)에 특정 로직을 적용할 수 있게 합니다. 이를 통해 인증/인가, 로깅, 속도 제한, 헤더 추가/삭제 등 다양한 횡단 관심사(Cross-cutting Concerns)를 중앙에서 처리할 수 있습니다.

Spring Cloud Gateway는 다양한 내장 필터(Built-in Filters)를 제공합니다. 예를 들어:

  • AddRequestHeader: 요청 헤더에 특정 값을 추가합니다.
  • RateLimiter: 특정 시간 동안의 요청 수를 제한하여 서비스 과부하를 방지합니다.
  • Retry: 백엔드 서비스 오류 시 요청을 자동으로 재시도합니다.
  • RewritePath: 요청 경로를 변경하여 백엔드 서비스에 전달합니다.

또한, GlobalFilter 인터페이스를 구현하여 모든 라우팅에 적용되는 글로벌 필터(Global Filter)를 만들거나, 특정 라우팅에만 적용되는 사용자 정의 필터(Custom Filter)를 만들 수도 있습니다.

코드 설명

아래 코드는 모든 요청에 X-Request-Id 헤더를 추가하는 간단한 글로벌 필터 예시입니다. Mono<Void>를 반환하며, chain.filter(exchange) 호출 전후로 pre/post 로직을 추가할 수 있습니다.

package com.kwonputer.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.UUID;

@Component
public class CustomGlobalHeaderFilter implements GlobalFilter, Ordered {

    private static final String REQUEST_ID_HEADER = "X-Request-Id";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // Pre-filter logic: 요청이 라우팅되기 전에 실행됩니다.
        ServerHttpRequest request = exchange.getRequest();
        String requestId = request.getHeaders().getFirst(REQUEST_ID_HEADER);

        if (requestId == null || requestId.isEmpty()) {
            // 요청 ID가 없으면 새로 생성하여 헤더에 추가
            requestId = UUID.randomUUID().toString();
            ServerHttpRequest modifiedRequest = request.mutate()
                    .header(REQUEST_ID_HEADER, requestId)
                    .build();
            exchange = exchange.mutate().request(modifiedRequest).build();
            System.out.println("[Pre-Filter] Added X-Request-Id: " + requestId + " for path: " + request.getPath());
        } else {
            System.out.println("[Pre-Filter] Existing X-Request-Id: " + requestId + " for path: " + request.getPath());
        }

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // Post-filter logic: 응답이 클라이언트에 전달되기 전에 실행됩니다.
            // 여기서는 요청 ID를 응답 헤더에 추가하여 추적을 용이하게 할 수 있습니다.
            // ServerHttpResponse response = exchange.getResponse();
            // response.getHeaders().add(REQUEST_ID_HEADER, requestId);
            System.out.println("[Post-Filter] Finished processing request with X-Request-Id: " + requestId);
        }));
    }

    @Override
    public int getOrder() {
        // 필터의 실행 순서를 정의합니다. 숫자가 낮을수록 먼저 실행됩니다.
        return -1; // 가장 먼저 실행되도록 설정
    }
}

2.4. 로드 밸런싱 (Load Balancing) 통합

마이크로서비스 환경에서 각 서비스는 여러 인스턴스로 복제되어 실행되는 경우가 많습니다. 이때 API 게이트웨이는 클라이언트 요청을 이 여러 인스턴스 중 하나로 균등하게 분배하는 로드 밸런싱(Load Balancing) 기능을 수행해야 합니다. Spring Cloud Gateway는 Spring Cloud LoadBalancer와 긴밀하게 통합되어 이 기능을 손쉽게 구현할 수 있습니다.

Spring Cloud LoadBalancer는 클라이언트 측 로드 밸런서로, 서비스 디스커버리(예: Eureka, Consul)와 연동하여 등록된 서비스 인스턴스 목록을 가져온 후, 적절한 로드 밸런싱 알고리즘(라운드 로빈, 가중치 기반 등)에 따라 요청을 분배합니다. 게이트웨이 설정에서 uri: lb://SERVICE-NAME 형태로 목적지 URI를 지정하면, Spring Cloud Gateway가 자동으로 해당 서비스의 인스턴스를 찾아 로드 밸런싱을 수행합니다.

예를 들어, PRODUCT-SERVICE라는 이름으로 Eureka에 3개의 인스턴스가 등록되어 있다면, 게이트웨이는 uri: lb://PRODUCT-SERVICE 설정만으로 이 3개의 인스턴스에 요청을 골고루 분산시켜줍니다. 이는 서비스의 가용성을 높이고 특정 인스턴스에 부하가 집중되는 것을 방지하여 시스템 전체의 안정성을 확보하는 데 매우 중요합니다.

핵심 포인트

Spring Cloud Gateway는 Predicate 기반의 유연한 라우팅, 요청/응답을 제어하는 강력한 필터 체인, 그리고 서비스 디스커버리와 연동된 로드 밸런싱 기능을 통해 마이크로서비스 아키텍처의 트래픽을 효과적으로 관리합니다.

Spring Cloud Gateway 아키텍처 다이어그램
Spring Cloud Gateway 아키텍처 다이어그램

문제 해결

실제 운영에서 마주치는 도전과 해결책

3.1. 성능 문제: 리액티브 스택의 최적화

Spring Cloud Gateway는 기본적으로 비동기, 논블로킹 방식으로 동작하므로 높은 성능을 제공합니다. 하지만 잘못된 필터 구현이나 과도한 필터 체인, 혹은 백엔드 서비스의 블로킹 작업으로 인해 성능 병목이 발생할 수 있습니다. 예를 들어, 커스텀 필터 내에서 외부 시스템과의 동기 통신을 시도하거나, 데이터베이스 I/O를 블로킹 방식으로 처리하면 리액티브의 장점을 잃게 됩니다.

해결책:

  • 논블로킹 유지: 커스텀 필터나 서비스 로직을 구현할 때 Mono, Flux와 같은 리액티브 타입을 사용하여 모든 작업이 비동기적으로 처리되도록 해야 합니다. 외부 API 호출 시 WebClient를 사용하는 것이 좋습니다.
  • 필터 최적화: 불필요한 필터는 제거하고, 복잡한 로직은 백엔드 서비스로 옮겨 게이트웨이의 부담을 줄여야 합니다. 필터의 순서(Order)를 신중하게 결정하여 효율성을 높이는 것도 중요합니다.
  • 모니터링 강화: Prometheus, Grafana, Micrometer 등을 활용하여 게이트웨이의 CPU, 메모리, 요청 처리 시간 등을 실시간으로 모니터링하고 병목 지점을 빠르게 식별해야 합니다.
문제 01

과도한 필터 체인으로 인한 성능 저하

게이트웨이에 너무 많은 커스텀 필터를 체인으로 연결하거나, 각 필터가 복잡한 동기 작업을 수행할 경우, 요청 처리 시간이 길어져 전체 시스템의 지연 시간이 증가하고 처리량이 감소할 수 있습니다.

해결 — 필터 간소화 및 비동기 처리

필요 없는 필터는 제거하고, 필터 내에서 수행되는 모든 I/O 작업은 WebClient와 같은 비동기 클라이언트를 사용하도록 변경합니다. 복잡한 비즈니스 로직은 백엔드 서비스로 위임하여 게이트웨이의 역할을 최소화합니다.

3.2. 보안 강화: 인증/인가 구현

API 게이트웨이는 모든 요청의 단일 진입점이므로, 보안을 중앙에서 처리하기에 가장 적합한 위치입니다. 각 마이크로서비스마다 인증/인가 로직을 중복해서 구현하는 대신, 게이트웨이에서 일괄적으로 처리함으로써 보안 정책을 일관되게 적용하고 관리할 수 있습니다.

해결책:

  • JWT (JSON Web Token) 검증 필터: 가장 일반적인 방법은 게이트웨이에서 JWT 토큰을 검증하는 커스텀 필터를 구현하는 것입니다. 클라이언트로부터 받은 JWT 토큰의 유효성을 검사하고, 유효한 경우 토큰에 포함된 사용자 정보를 추출하여 백엔드 서비스로 전달합니다.
  • OAuth2 Resource Server 연동: 보다 강력한 인증/인가가 필요하다면 Spring Security 5와 OAuth2 Resource Server를 Spring Cloud Gateway에 통합할 수 있습니다. 이를 통해 OIDC(OpenID Connect) 기반의 인증 및 OAuth2 기반의 권한 부여를 구현할 수 있습니다.
  • 권한(Role) 기반 라우팅: 특정 역할(예: ADMIN)을 가진 사용자에게만 특정 서비스로의 접근을 허용하도록 라우팅 Predicate를 확장할 수도 있습니다.

코드 설명

아래는 JWT 토큰을 추출하고 간단히 검증하는 필터의 스켈레톤 코드입니다. 실제 구현에서는 JWT 라이브러리(예: jjwt)를 사용하여 서명 검증, 만료 시간 확인 등 복잡한 로직이 추가됩니다. 검증에 실패하면 401 Unauthorized 응답을 반환합니다.

package com.kwonputer.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class JwtAuthFilter extends AbstractGatewayFilterFactory<JwtAuthFilter.Config> {

    public JwtAuthFilter() {
        super(Config.class);
    }

    public static class Config {
        // 필터에 필요한 설정 (예: JWT secret key 등)
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // Authorization 헤더에서 JWT 토큰 추출
            if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                return onError(exchange, "No Authorization header", HttpStatus.UNAUTHORIZED);
            }

            String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
            String jwt = authorizationHeader.replace("Bearer ", "");

            // TODO: 실제 JWT 토큰 검증 로직 구현 (서명, 만료, 유효성 등)
            // if (!isJwtValid(jwt)) {
            //     return onError(exchange, "JWT Token is not valid", HttpStatus.UNAUTHORIZED);
            // }

            System.out.println("[JWT Filter] Validating JWT: " + jwt.substring(0, 10) + "..."); // 토큰 앞부분만 출력
            // 검증 성공 시, 요청 헤더에 사용자 정보 추가 (선택 사항)
            // ServerHttpRequest modifiedRequest = request.mutate()
            //         .header("X-User-Id", "user123")
            //         .build();
            // return chain.filter(exchange.mutate().request(modifiedRequest).build());

            return chain.filter(exchange); // 다음 필터 또는 라우팅으로 진행
        };
    }

    private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        System.err.println("[JWT Filter Error] " + err);
        return response.setComplete();
    }

    // TODO: 실제 JWT 유효성 검사 메서드
    // private boolean isJwtValid(String jwt) {
    //     // 실제 JWT 라이브러리 (jjwt 등)를 사용하여 검증
    //     return true;
    // }
}

3.3. Circuit Breaker와 Fallback 처리

마이크로서비스 환경에서는 하나의 서비스 장애가 전체 시스템의 연쇄적인 장애로 이어질 수 있습니다. 이를 방지하기 위해 서킷 브레이커(Circuit Breaker) 패턴을 적용하는 것이 필수적입니다. Spring Cloud Gateway는 Spring Cloud Circuit Breaker (기본적으로 Resilience4j 기반)와 연동하여 이 기능을 제공합니다.

서킷 브레이커는 특정 서비스의 호출 실패율이 임계값을 초과하면 해당 서비스로의 요청을 일시적으로 차단(회로 개방)하고, 대신 미리 정의된 폴백(Fallback) 로직을 실행합니다. 이는 장애가 발생한 서비스가 복구될 시간을 벌어주고, 클라이언트에게는 사용자 친화적인 대체 응답을 제공하여 사용자 경험을 유지하는 데 도움을 줍니다.

해결책:

  • Resilience4j 통합: Spring Cloud Circuit Breaker 라이브러리를 의존성에 추가하고, application.yml 또는 Java 코드로 서킷 브레이커 설정을 구성합니다.
  • Fallback URI 설정: 라우팅 설정 시 FallbackHeaders 필터와 함께 특정 폴백 URI를 지정하여, 서비스 장애 시 해당 URI로 요청을 리다이렉트할 수 있습니다. 예를 들어, /fallback/users와 같은 별도의 폴백 컨트롤러를 만들어 ‘서비스 이용에 불편을 드려 죄송합니다’와 같은 메시지를 반환하도록 할 수 있습니다.

핵심 포인트

Spring Cloud Gateway는 논블로킹 최적화, 중앙 집중식 보안(JWT, OAuth2), 그리고 Resilience4j 기반의 서킷 브레이커와 폴백 처리를 통해 안정적이고 고성능의 마이크로서비스 운영 환경을 구축할 수 있게 합니다.

Spring Cloud Gateway 서킷 브레이커 패턴 다이어그램
Spring Cloud Gateway 서킷 브레이커 패턴 다이어그램

실전 적용

Spring Cloud Gateway 구축 가이드 2026

4.1. 프로젝트 초기 설정

Spring Cloud Gateway 프로젝트를 시작하는 가장 쉬운 방법은 Spring Initializr를 이용하는 것입니다. 다음 의존성들을 추가하여 프로젝트를 생성합니다.

  • Spring Cloud Gateway: 게이트웨이 핵심 기능
  • Eureka Discovery Client: 서비스 디스커버리 연동 (선택 사항, 다른 디스커버리 툴 사용 가능)
  • Spring Boot Actuator: 모니터링 및 운영 관리 (선택 사항)
  • Spring Cloud Circuit Breaker (Resilience4j): 서킷 브레이커 (선택 사항)

Maven pom.xml 파일에 다음과 같은 의존성이 추가될 것입니다 (버전은 Spring Boot 및 Spring Cloud 버전에 따라 다를 수 있습니다).

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
    </dependency>
    <!-- 기타 필요한 의존성 -->
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.1</version> <!-- 최신 버전 확인 및 사용 -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

4.2. 기본 라우팅 설정

가장 간단한 라우팅 설정은 application.yml 파일에 직접 정의하는 것입니다. 예를 들어, /api/legacy/** 경로로 들어오는 요청을 기존의 모놀리식 서비스로 라우팅하는 설정을 해보겠습니다.

코드 설명

이 설정은 게이트웨이가 8080 포트에서 실행되며, /api/legacy로 시작하는 모든 요청을 http://localhost:8090/legacy 경로로 라우팅합니다. StripPrefix=1 필터 덕분에 /api 부분이 제거됩니다.

server:
  port: 8080

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: legacy_service_route
          uri: http://localhost:8090 # 기존 레거시 서비스
          predicates:
            - Path=/api/legacy/**
          filters:
            - StripPrefix=1 # /api 제거

4.3. 사용자 정의 필터 추가

앞서 CustomGlobalHeaderFilter 예시를 보았지만, 이번에는 특정 라우팅에만 적용되는 로깅 필터를 만들어 보겠습니다. 이 필터는 요청이 들어올 때마다 간단한 로그를 남기는 역할을 합니다.

코드 설명

LoggingGatewayFilterFactoryapplication.yml에서 - Logging=true와 같이 사용할 수 있습니다. 필터 내부에서는 요청 경로를 출력하는 간단한 로깅을 수행합니다. chain.filter(exchange) 호출 전후로 pre/post 로직을 추가할 수 있습니다.

package com.kwonputer.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {

    public LoggingGatewayFilterFactory() {
        super(Config.class);
    }

    public static class Config {
        private boolean enabled; // 필터 활성화 여부
        // 추가적인 설정 가능 (예: logLevel)
        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            // Pre-filter logic
            String path = exchange.getRequest().getURI().getPath();
            System.out.println("[Logging Filter] Incoming request for path: " + path);

            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                // Post-filter logic
                System.out.println("[Logging Filter] Outgoing response for path: " + path + ", Status: " + exchange.getResponse().getStatusCode());
            }));
        };
    }
}

이제 application.yml에 이 필터를 적용해봅시다.

spring:
  cloud:
    gateway:
      routes:
        - id: user_service_route
          uri: lb://USER-SERVICE
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - Logging=true # 우리가 만든 필터 적용

4.4. 서비스 디스커버리 연동 (Eureka)

실제 마이크로서비스 환경에서는 각 서비스의 인스턴스가 동적으로 생성되고 삭제됩니다. 이때 게이트웨이가 직접 서비스의 IP 주소와 포트를 아는 것은 비효율적입니다. 서비스 디스커버리(Service Discovery)를 사용하면 각 서비스가 자신의 정보를 중앙 서버(예: Eureka Server)에 등록하고, 게이트웨이는 이 중앙 서버로부터 서비스 인스턴스 정보를 조회하여 라우팅할 수 있습니다.

Eureka Server를 먼저 실행하고, 각 마이크로서비스가 Eureka Client로 등록되도록 설정합니다. 그리고 Gateway 프로젝트의 application.yml에 다음 설정을 추가합니다.

코드 설명

이 설정은 게이트웨이가 Eureka Server에 클라이언트로 등록하고, Eureka에서 서비스 인스턴스 정보를 가져오도록 합니다. uri: lb://USER-SERVICE와 같이 lb:// 프리픽스를 사용하면, 게이트웨이가 자동으로 Eureka에서 USER-SERVICE의 인스턴스를 찾아 로드 밸런싱을 수행합니다.

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka # Eureka Server 주소
  instance:
    preferIpAddress: true # IP 주소로 등록되도록 설정

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # Eureka를 통해 서비스 인스턴스 찾기 활성화
          lowerCaseServiceId: true # 서비스 ID를 소문자로 변환
      routes:
        - id: user_service_dynamic_route
          uri: lb://USER-SERVICE # Eureka에 등록된 서비스 이름으로 라우팅
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - Logging=true

Eureka 서비스 디스커버리 연동 흐름도

활용 사례: API 버전 관리

API 게이트웨이를 사용하여 클라이언트 요청의 URL에 따라 다른 버전의 백엔드 서비스로 라우팅할 수 있습니다. 예를 들어, /api/v1/usersuser-service-v1으로, /api/v2/usersuser-service-v2로 라우팅하여 API의 하위 호환성을 유지하면서 점진적으로 서비스를 업데이트할 수 있습니다.

활용 사례: A/B 테스트 라우팅

특정 사용자 그룹(예: 특정 헤더를 가진 사용자)에게만 새로운 기능이 적용된 서비스 인스턴스로 요청을 보내는 A/B 테스트를 구현할 수 있습니다. Header Predicate를 사용하여 특정 헤더가 있는 경우에만 다른 서비스로 라우팅하도록 설정할 수 있습니다. 이는 신규 기능의 시장 반응을 측정하고 위험을 최소화하는 데 유용합니다.

자주 묻는 질문 (FAQ)

Q. Spring Cloud Gateway와 Netflix Zuul 중 어떤 것을 사용해야 하나요?

2026년 현재 새로운 프로젝트에서는 Spring Cloud Gateway를 사용하는 것을 강력히 권장합니다. Zuul 1.x는 블로킹 I/O 모델 기반으로 성능 한계가 명확하며, Zuul 2.x는 개발이 중단되었습니다. Spring Cloud Gateway는 리액티브 스택 기반으로 높은 성능과 확장성을 제공하며, Spring 생태계에서 적극적으로 지원하고 있습니다.

Q. Gateway에 너무 많은 필터를 추가하면 성능에 문제가 생길까요?

네, 가능성이 있습니다. 각 필터는 요청 처리 파이프라인에 추가적인 작업을 수행하므로, 필터의 수가 많아지거나 각