[MSA] 마이크로서비스 코딩 공작소 - 8장

서정범·2024년 2월 14일
0

MSA

목록 보기
10/10

8. 스프링 클라우드 게이트웨이를 이용한 서비스 라우팅

마이크로서비스와 같은 분산형 아키텍처에서는 보안과 로깅, 여러 서비스 호출에 걸친 사용자 추적처럼 중요한 작업들을 해야 할 시점이 옵니다.

이러한 기능들을 각 서비스에 직접 구축하게 될 경우 다음과 같은 결과를 초래할 수 있습니다.

  • 이러한 기능을 각 서비스에 일관되게 구현하기 어렵습니다.
  • 보안과 로깅 같은 횡단 관심사(cross-cutting concerns)의 구현 책임을 개별 개발 팀에 전가하면 잘못 구현하거나 아예 누락할 수 있습니다.
  • 모든 서비스에 걸쳐 강한 의존성을 만들 수 있습니다.

이 문제를 해결하기 위해서 다음과 같은 것을 고려해야 합니다.

  1. 횡단 관심사를 독립적으로 배치
  2. 아키텍처의 모든 마이크로서비스 호출에 대한 필터와 라우터 역할을 할 수 있는 서비스로 추상화

이러한 서비스를 게이트웨이(gateway)라고 합니다.

서비스 클라이언트(웹 서버와 같은)는 더 이상 마이크로서비스를 직접 호출하지 않으며, 모든 호출은 단일 정책 시행 지점(PEP, Policy Enforcement Point) 역할을 하는 서비스 게이트웨이를 경유한 다음 최종 목적지로 라우팅됩니다.

여기서 설명할 내용은 다음과 같습니다.

  • 하나의 URL 뒤에 모든 서비스를 배치하고 서비스 디스커버리(service discovery)를 사용하여 해당 호출을 실제 서비스 인스턴스에 매핑하는 방법
  • 서비스 게이트웨이를 경유하는 모든 서비스 호출에 상관관계 ID(Tracking ID)를 삽입하는 방법
  • HTTP 응답에 전달받은 상관관계 ID를 삽입하여 클라이언트로 재전송하는 방법

8.0 들어가기 앞서...

먼저, 게이트웨이에 대해서 간단하게 정리를 하고 들어갈까 합니다.

마이크로서비스 아키텍처에서 게이트웨이 서비스는 클라이언트와 백엔드 서비스 간의 중간 계층으로 작동하여, 여러 마이크로서비스로의 라우팅, 크로스-커팅(cross-cutting) 관심사의 처리 등을 담당합니다.

마이크로서비스 아키텍처의 복잡성을 관리하고, 클라이언트와 서비스 간의 상호작용을 단순화하는 데 중요한 역할을 합니다.

특징

  1. 라우팅: 클라이언트는 여러 마이크로서비스의 존재를 몰라도 되며, 단일 진입점을 통해 시스템과 상호작용할 수 있습니다. [투명성]
  2. 요청 변환: 게이트웨이는 필요에 따라 요청을 변환할 수 있습니다. [유연성]
  3. 인증 및 권한 부여: 게이트웨이는 인증 및 권한 부여와 같은 보안 관련 작업을 처리할 수 있습니다. 이를 통해 각 마이크로서비스가 별도로 인증 로직을 구현할 필요 없이, 중앙에서 보안 관련 작업을 처리할 수 있습니다. [중앙 집중화] [보안 강화] [로직 단순화]
  4. 레이팅 리밋팅 및 회로 차단: 게이트웨이는 시스템에 대한 과도한 요청을 제한하거나, 문제가 발생한 서비스에 대한 요청을 차단하는 등의 기능을 제공할 수 있습니다. 이는 서비스의 안정성을 유지하는 데 도움을 줍니다. [안전성]

장점

  1. 복잡성 감소: 클라이언트는 단일 진입점을 통해 모든 서비스와 상호 작용할 수 있으므로, 시스템의 복잡성이 감소합니다.
  2. 보안 강화: 중앙에서 인증 및 권한 부여를 처리함으로써 보안을 강화할 수 있습니다.
  3. 유연성: 게이트웨이를 통해 요청을 라우팅하고 변환함으로써, 백엔드 서비스의 변경이 클라이언트에 미치는 영향을 최소화할 수 있습니다.
  4. 재사용성: 인증, 로깅, 모니터링과 같은 횡단 관심사를 게이트웨이에서 처리함으로써 코드의 재사용성을 높일 수 있습니다.

단점

  1. 성능 저하: 모든 요청이 게이트웨이를 거쳐야 하므로, 추가적인 네트워크 홉과 처리 시간이 발생할 수 있습니다.
  2. 단일 실패 지점(SPOF): 게이트웨이가 다운되면 시스템 전체의 접근성이 영향을 받을 수 있으므로, 고가용성을 위한 추가적인 구성이 필요합니다.
  3. 관리 복잡성: 게이트웨이 자체의 구성 및 관리에 추가적인 노력이 필요할 수 있습니다.

8.1 서비스 게이트웨이란?

서비스 게이트웨이는 애플리케이션 내 마이크로서비스를 호출하기 위해 유입되는 모든 트래픽의 게이트키퍼(gatekeeper) 역할을 합니다.

서비스 게이트웨이가 있으면 서비스 클라이언트는 각 서비스 URL을 직접 호출하지 않고 서비스 게이트웨이에 호출을 보냅니다. 또한 서비스 게이트웨이는 클라이언트와 개별 서비스 호출 사이에 있기 때문에 서비스를 호출하는 중앙 정책 시행 지점(PEP) 역할도 합니다.

서비스 게이트웨이에서 구현할 수 있는 횡단 관심사 예는 다음과 같습니다.

  • 정적 라우팅(static routing)
  • 동적 라우팅(dynamic routing)
  • 인증(authentication)과 인가(authorization)
  • 지표 수집(metric collection)과 로깅(logging)

중요한 것은 서비스 게이트웨이가 단일 장애 지점이나 잠재적 병목 지점이 될 수 있기 때문에 다음 사항을 염두에 둬야 합니다.

  • 로드 밸런서는 서비스 앞에 있을 때 유용: 이 경우 로드 밸런서를 여러 서비스 게이트웨이 앞에 두는 것은 적절한 설계이며, 필요할 때 서비스 게이트웨이 구현체를 확장할 수 있습니다. 하지만 모든 서비스 인스턴스 앞에 로드 밸런서를 두는 것은 병목점이 될 수 있어 좋은 생각은 아니다.
  • 서비스 게이트웨이를 무상태(stateless)로 작성: 어떤 정보도 서비스 게이트웨이의 메모리에 저장하면 안됩니다. 주의하지 않으면 게이트웨이의 확장성을 제한할 수 있습니다. 따라서 데이터는 모든 서비스 게이트웨이 인스턴스에 복제되어야 합니다.
  • 서비스 게이트웨이를 가볍게 유지: 서비스 게이트웨이는 서비스를 호출할 때 '병목점'이 될 수 있습니다. 서비스 게이트웨이에서 여러 데이터베이스를 호출하는 복잡한 코드가 있다면 추적하기 어려운 성능 문제의 원인이 될 수 있습니다.

8.2 스프링 클라우드 게이트웨이 소개

스프링 클라우드 게이트웨이는 스프링 프레임워크 5, 프로젝트 리액터(Projext Reactor), 스프링 부트 2.0을 기반으로 한 API 게이트웨이 구현체입니다.

해당 게이트웨이는 논블로킹 방식인데, 주요 스레드를 차단하지 않는 방식으로 동작한다는 의미입니다.

따라서 이러한 스레드는 언제나 요청을 받아 백그라운드에서 비동기식으로 처리하고 처리가 완료되면 응답을 반환합니다.

다음과 같은 기능들을 제공합니다.

  • 애플리케이션의 모든 서비스 경로를 단일 URL에 매핑: 스프링 클라우드 게이트웨이는 하나의 URL에 제한되지 않고 실제로 여러 경로의 진입점을 정의하고 경로 매핑을 세분화할 수 있습니다(각 서비스 엔드포인트가 고유한 경로로 매핑됩니다). 하지만 가장 일반적진 사용 사례라면 모든 서비스 클라이언트 호출이 통과하는 단일 진입점을 제공하는 것입니다.
  • 게이트웨이로 유입되는 요청과 응답을 검사하고 조치를 취할 수 있는 필터(filters)를 작성: 필터를 이용하여 유입되고 유출되는 HTTP 요청 및 응답을 수정할 수 있습니다.
  • 요청을 실행하거나 처리하기 전에 해당 요청이 주어진 조건을 충족하는지 확인할 수 있는 서술자(predicates)를 만든다: 스프링 클라우드 게이트웨이에는 자체 Route Predicate Factories 세트가 포함되어 있습니다.

여기서 비동기 프로그래밍 모델에 대해서 설명하기 위해서 전통적인 멀티 스레딩, 단일 스레드 비동기 모델, 스레드 풀, 리액티브 프로그래밍 등과 같은 개념들이 설명되어야 하는데 필자가 아직 정확히 이해하지 못한 내용들이라 일단 넘어가도록 하겠습니다.

8.3 스프링 클라우드 게이트웨이에서 라우팅 구성

스프링 클라우드 게이트웨이는 본래 리버스 프록시입니다.

리버스 프록시(reverse proxy)는 자원에 도달하려는 클라이언트와 자원 사이에 위치한 중개 서버입니다.

클라이언트는 어떤 서버와 통신하고 있는지 알지 못합니다. 리버스 프록시는 클라이언트 요청을 캡처한 후 클라이언트를 대신하여 원격 자원을 호출합니다.

스프링 클라우드 게이트웨이에서는 상위 경로에 매핑하는 방법이 다음과 같이 있습니다.

  • 서비스 디스커버리를 이용한 자동 경로 매핑
  • 서비스 디스커버리를 이용한 수동 경로 매핑

8.3.1 서비스 디스커버리를 이용한 자동 경로 매핑

게이트웨이에 대한 모든 경로 매핑은 yml 파일에서 경로를 정의해서 수행합니다.

하지만 스프링 클라우드 게이트웨이는 다음 코드처럼 gateway-server 구성 파일에 구성 정보를 추가해서 서비스 ID를 기반으로 요청을 자동으로 라우팅할 수 있습니다.

spring:
	cloud:
    	gateway:
        	discovery.locator:		-------- 서비스 디스커버리에 등록된 서비스를 기반으로 게이트웨이가 경로를 생성하도록 설정한다.
            	enabled: true
                lowerCaseServiceId: true

해당 코드처럼 스프링 클라우드 게이트웨이는 호출되는 서비스의 유레카 서비스 ID를 자동으로 사용하여 하위 서비스 인스턴스와 매핑합니다.

예를 들어서 다음과 같은 URL이 있다고 해봅시다.

http://localhost:8072/organization-service/v1/organization/blabla

여기서 lcoalhost:8072는 게이트웨이의 서버를 의미할 것입니다.

게이트웨이 서버는 바로 뒤에 있는 organization-service를 확인하고 이것을 이용하여 호출해야 되는 서버를 확인할 것입니다.

8.3.2 서비스 디스커버리를 이용한 수동 경로 매핑

스프링 클라우드 게이트웨이는 유레카 서비스 ID로 생성된 자동화된 경로에만 의존하지 않고 명시적으로 경로 매핑을 정의할 수 있어 코드를 더욱 세분화할 수 있습니다.

다음은 수동으로 정의한 코드입니다.

spring:
	cloud:
    	gateway:
        	discovery.locator:
            	enabled: true
                lowerCaseServiceId: true
			routes:
            	- id: organization-service	-------- 이 선택적(optional) ID는 임의의 경로에 대한 ID입니다.
                - uri: lb://organization-service	-------- 이 경로의 대상 URI를 설정합니다.
                
                predicates:	-------- 경로(path)는 load() 메서드로 설정되지만, 여러 옵션 중 하나입니다.
                - Path=/organization/**
                
                filters:	-------- 응답을 보내기 전이나 후에 요청 또는 응답을 수정하고자 스프링 web.filters 들을 필터링합니다.
                - RewritePath=/organization/(?<path>.*), /$\{path}	------- 매개 변수 및 교체 순서로 경로 정규식을 받아 요청 경로를 /organization/**에서 /**으로 변경한다.
  • predicates: 라우팅 설정에서 특정 조건을 기반으로 요청을 필터링 하기 위한 구성 요소.

수동과 자동을 사용할 때 알아두어야 되는 것이 있습니다.

게이트웨이가 유레카 서비스 ID를 기반으로 자동화된 경로 매핑을 사용하여 서비스를 노출할 때, 실행 중인 서비스 인스턴스가 없다면 게이트웨이는 서비스 경로를 아예 노출하지 않습니다.

그러나 수동으로 경로를 서비스 디스커버리 ID에 매핑하면 유레카에 등록된 인스턴스가 없더라도 게이트웨이는 여전히 경로를 표시합니다.

이때 서비스에 대한 경로를 호출하면 HTTP 500 에러가 반환됩니다.

이러한 동작들은 취약점이 될 수 있으니 알아둡시다.

이후에 동적으로 라우팅 구성을 재로딩하기 위한 방법으로 스프링 액추에이터(Spring Actuator)를 사용하는 방식이 설명되고 있는데 어렵지 않으므로 넘어가겠습니다.

8.4 스프링 클라우드 게이트웨이의 진정한 능력: predicate과 Filter Factories

게이트웨이로 모든 요청을 프록시(proxy)할 수 있기 때문에 서비스 호출을 단순화할 수 있습니다.

이것은 사용자 정의 로직을 작성하여 동작시킬 수도 있는데, 보안, 로깅, 추적 등과 같은 횡단 관심사 처리를 하기에 적합합니다.

이러한 방식으로 스프링 클라우드 게이트웨이 Predicate과 Filter Factories는 스프링 관점(aspect) 클래스와 유사하게 사용할 수 있습니다.

이들을 사용하면 다양한 동작을 일치시키거나 가로챌 수 있어 원래 코드 작성자 모르게 호출 동작을 변경하거나 꾸밀 수 있습니다.

이것은 각 개별 서비스 서버에서 동작하는 것이 아닌 모든 서비스에 대한 공통 관심사를 구현할 수 있게 해줍니다.

서술자(predicate)를 사용하면 요청을 처리하기 전에 요청이 조건 집합을 충족하는지 확인할 수 있습니다.

동작을 살펴봅시다.

  1. 게이트웨이에서 요청을 수신하면 게이트웨이 핸들러(gateway handler)로 이동하며, 이 핸들러요청된 경로가 액세스하려는 특정 경로의 구성과 일치 여부를 확인하는 역할을 합니다.
  2. 정보가 모두 일치하면 필터를 읽고 해당 필터에 요청을 보내는 역할을 하는 게이트웨이로 진입합니다.
  3. 요청이 모든 필터를 통과하면 해당 요청은 이제 설정된 경로의 마이크로서비스로 전달됩니다.

8.4.1 게이트웨이 Predicate Factories

게이트웨이 서술자는 요청을 실행하거나 처리하기 전에 요청이 조건 집합을 충족하는지 확인하는 객체입니다.

경로마다 논리 AND로 결합할 수 있는 여러 Predicate Factories를 설정할 수 있습니다.

책에 설명되어 있는 서술자는 다음과 같습니다.

8.4.2 게이트웨이 Filter Factories

게이트웨이 Filter Factoires를 사용하면 코드에 정책 시행 지점(PEP)을 삽입하여 모든 서비스 호출에 대해 일관된 방식으로 작업을 수행할 수 있습니다.

즉, 이러한 필터로 수신 및 발신하는 HTTP 요청과 응답을 수정할 수 있습니다.

8.4.3 사용자 정의 필터

스프링 클라우드 게이트웨이 내에서 필터를 사용하여 사용자 정의 로직을 만들 수 있습니다.

필터를 사용하여 각 서비스 요청이 통과하는 비지니스 로직 체인을 구현할 수 있습니다. 스프링 클라우드 게이트웨이는 다음 두 가지 종류의 필터를 지원합니다.

  • 사전 필터(pre-filters): 실제 요청이 목적지로 전송되기 전에 사전 필터가 호출됩니다.
    • 메시지 형식 확인 (예: 주요 HTTP 헤더 존재 여부 확인)
    • 사용자 인증을 하는 게이트키퍼 역할
    • 등등
  • 사후 필터(post-filters): 사후 필터는 대상 서비스 이후 호출되고 응답은 클라이언트로 다시 전송됩니다.
    • 로깅
    • 오류 처리
    • 민감한 정보에 대한 응답 검사
    • 등등

단계를 살펴봅시다.

  1. 게이트웨이에서 정의된 모든 사전 필터는 요청이 게이트웨이에 유입될 때 호출됩니다. 사용자를 다른 엔트포인트나 서비스로 리다이렉션할 수는 없습니다.
  2. 게이트웨이에 들어오는 요청에 대해 사전 필터가 실행된 후 게이트웨이는 목적지(서비스가 향하는 곳)을 결정합니다.
  3. 대상 서비스가 호출된 후 게이트웨이 사후 필터가 호출됩니다. 사후 필터는 호출된 서비스의 응답을 검사하고 수정합니다.

이후 내용들

이후에는 스프링 클라우드 게이트웨이를 이용하여 필터를 구현하는 코드를 설명하고 있습니다.

특히, TreadLocal을 사용하여 Tracking ID를 부여하는 부분을 설명하는데 TreadLocal을 이용하여 상관관계 ID를 요청에 부여해 놓고 그것을 헤더로 전달한다면 요청을 Tracking 할 수 있게 됩니다.

코드에 대한 내용은 생략하도록 하겠습니다.

참고한 자료

  • 스프링 마이크로서비스 코딩 공작소[개정 2판]
profile
개발정리블로그

0개의 댓글