[API Gateway Service란?]
API Gateway
- API Gateway 서비스는 사용자가 설정한 라우팅 설정에 따라 각각의 엔드포인트로 클라이언트를 대신하여 요청하고, 응답을 받으면 클라이언트한테 다시 전달해주는 Proxy 역할 수행
- 시스템의 내부 구조는 숨기고, 외부에 요청에 대해 적절한 형태로 가공해서 응답할 수 있다는 장점
[그림 설명]
- 마이크로서비스가 3개 있다 가정시, 클라이언트 사이드에서 마이크로서비스를 직접 호출하는 그림 즉, 클라이언트 측에서 마이크로서비스의 주소를 직접 이용하여 요청
- 새로운 마이크로서비스가 추가되거나 기존에 있던 마이크로서비스의 주소가 변경 되었다고 가정해도 마이크로서비스 자체가 독립적으로 빌드, 배포되는 장점
- But, 클라이언트 사이드에서 마이크로서비스의 엔드포이트(주소)를 직접적으로 호출했을 경우, 클라이언트 사이드에 있는 애플리케이션도 같이 수정, 배포 필요 → 따라서, 단일 진입점을 가지고 있는 형태로서 개발 필요
[그림 설명]
- 따라서, 서버(백엔드) 사이드에 중간에 게이트웨이 역할을 해줄 수 있는 일종의 진입로를 두고, 각각의 마이크로서비스로 요청되는 모든 정보에 대해 일괄적으로 처리 가능
- 클라이언트는 직접적으로 마이크로서비스 호출X, 게이트웨이만 상대
- 게이트웨이에서 정보 변경, 갱신 작업이 훨씬 더 수월해질 것
- 게이트웨이 사용시 다음과 같은 작업 수행 가능
- 인증 및 권한 부여
- (마이크로)서비스 검색 통합
- 응답 캐싱
- 응답할 수 있는 캐싱 정보 저장 가능
- 정책, 회로 차단기 및 QoS 다시 시도
- 클라이언트로부터 요청이 들어온 마이크로서비스에 문제가 발생 시 더이상 요청을 넘겨주면 안됨 → 회로차단기 기능도 단일 진입점에서 처리 가능
- 속도 제한
- 부하 분산
- 마이크로서비스가 가지는 하나의 서비스 내용을 3개의 인스턴스가 나눠서 작업했다고 가정 시, 클라이언트의 요청을 어디서 처리할지 판단해주는 API Gateway 역할도 수행 가능
- 로깅, 추적, 상관 관계
- 로그만 전문적으로 처리해줄 수 있는 시스템을 ELK와 같은 제품을 이용하여 처리할 수도 있지만, API Gateway를 사용하여 로그 기록 시 어떤 클라이언트가 접속을 했는지, 어디로 이동 했는지, 어떤 마이크로서비스가 사용되었는지에 대한 로그 파일도 단일화해서 처리 가능
- 헤더, 쿼리 문자열 및 청구 변환
- IP 허용 목록에 추가
- 허용할 수 있는 IP와 차단할 IP 판단
- 방화벽이나 프록시 역할을 해주는 게이트웨이를 통해 들어온 정보만 처리하는 식으로 등록도 가능
Netflix Ribbon
- Spring Cloud에서의 MSA 간 통신
: 스프링 클라우드에서 마이크로서비스들 간의 통신을 위해 사용되는 가장 대표적인 방법
1. RestTemplate
RestTemplate restTemplate = new RestTemplate(); restTemplate.getForObject(“http://localhost:8080", User.class, 200);
2. Feign Client
@FeignClient(“stores”) @RequestMapping(method = RequestMethod.GET, value = “/stores”) List getStores(); List getStores();
- 스프링 클라우드에서는 스프링 클라우드 Feign Cleint라는 API를 이용해서 호출 가능
- 인터페이스 하나 생성
- 해당 인터페이스에서 웹으로 호출하고 싶은 추가적인 마이크로서비스의 이름 등록 (Feign Client)
- Feign Client를 등록하게 되면 서버의 주소나 포트 번호 없이 마이크로서비스의 이름을 이용하여 호출 가능
- Ribbon : Cleint side Load Balancer
: 클라이언트 사이드에서 사용할 수 있는 로드 밸런서라는 뜻으로, 클라이언트 프로그램 안에서 이동하고자 하는 마이크로서비스의 주소값을 직접 관리- 초창기 때 스프링 클라우드에서는 로드 밸런서 역할을 해주는 별도의 서비스 프로젝트를 위해 리본이라는 서비스 제공
- 넷플릭스가 가지고 있는 서비스 기술이 Ribbon이라는 서비스로 스프링 클라우드 재단에 기부돼서 사용
- 서버의 주소나 포트 번호 없이 마이크로서비스의 이름을 이용하여 호출 가능
- 비동기 처리가 잘 되지 않는 방식 → 최근에 잘 사용 X
- 서비스 이름으로 호출
- Health Check
*Spring Cloud Ribbon은 Spring Boot 2.4에서 Maintenance 상태
[그림 설명]
- 별도의 서비스나 시스템을 이용하여 API Gateway를 중간에 설치하지 않고, 클라이언트 측 내부에 리본이라는 서비스를 구축해서 사용
Netflix Zuul 구현
- Netflix Zuul이란 Gateway 역할을 해주는 서비스
- API Gateway와 동일한 역할
*Spring Cloud Zuul은 Spring Boot 2.4에서 Maintenance 상태
[그림 설명]
- 클라이언트에서는 Netflix Zuul에 데이터 요청을 하게 되면 Netflix Zuul이 그 요청을 First Service 혹은 Second Service로 보내주는 역할
Netflix Zuul을 활용한 Zuul Service
- Netflix Zuul 서비스를 이용하기 위해 @EnableZuulProxy 어노테이션 추가
- application.yml에 다음과 같은 정보 추가
- api가 8000번 포트로 /first-service라는 요청이 들어오면 8081번 포트로 forwarding
- api가 8000번 포트로 /second-service라는 요청이 들어오면 8082번 포트로 forwarding
Netflix Zuul을 활용한 ZuulFilter
- Zuul Filter를 통해 마이크로서비스가 요청될 때 사전 작업, 사후 작업을 처리할 예정
- 사전 작업 예: 인증 서비스
- 사후 작업 예: 로깅
- run()이라는 메서드에 필요한 내용 작성
- filterType()에 “pre” → 사전 필터 / filterType()에 “post” → 사후 필터
Spring Cloud Gateway
- application.yml에 다음과 같은 정보 추가
- Eureka 서버에 연동
- cloud.gateway.routes
- id : 고유한 라우터의 이름
- uri : 해당하는 정보를 어디로 포워딩 시켜줄 것인지 위치 정보 기재
- predicates : 요청 조건
→ Spring Cloud Gateway를 사용하는 가장 큰 목적은 비동기 처리가 가능해진다는 점
- 기존에 Tomcat 서버가 구동 됐던 것과 달리 Spring Cloud API Gateway를 사용하게 되면 비동기 방식으로 Netty 서비스가 작동됨.
- 위와 같이 입력시, localhost:8000/first-service라는 요청이 들어오면 localhost:8081/first-service/welcome으로 redirect
- Netflix Zuul과 달리 predicates에 작성한 /first-service/가 localhost:8081/ 뒤에 붙어 redirect됨.
[Spring Cloud Gateway - Filter 적용]
- 클라이언트가 Spring Cloud Gateway에 어떤 요청을 전달하게 되면 해당 Gateway에서 First service로 갈지 Second service로 갈지 판단한 다음 서비스의 요청을 분기해줌
- Gateway Handler Mapping: 클라이언트로부터 어떤 요청이 들어왔는지 요청 정보를 받음
- Predicdate : 어떤 이름으로 요청되었는지 조건을 분기
- Pre filter / Post filter : 작업이 일어나기 전의 사전 필터와 작업이 끝난 뒤에 호출되는 사후 필터를 작성하여 요청 정보 구성 가능
-
- config 파일로 구성 가능
- application.yml 파일로 작성 가능
-
[Spring Cloud Gateway - Custom Filter]
Custom Filter - CustomFilter.java
- AbstractGatewayFilterFactory를 상속 받아 사용자 정의에 의해 CustomFilter 생성
- 자기 자신의 내부의 config를 매개변수로 등록
- apply라는 메소드 하나를 정의해서 구현 → GatewayFilter를 반환시켜 줌으로써 어떠한 작업을 수행할지 정의
- 예) Pre Filter에 어떠한 사용자가 로그인을 했을 때 서버로부터 받은 JWT 토큰이 정상적으로 동작하는지 체크 가능
- 람다식으로 작성된 → 뒤의 {}가 우리가 반환할 객체
- 비동기 방식으로 데이터를 사용할 땐 ServerHttpRequest, ServerHttpResponse 객체 이용
- ServerRequest, ServerResponse X
- exchange 객체가 비동기 방식의 ServerHttpRequest, ServerHttpResponse를 사용하게 도와줌.
- return chain.filter(exchange).then … → 처리가 끝난 후의 post filter 등록
- 반환시켜주는 chain에 연결 시켜 post filter 추가
- WebFlux라는 기능을 이용하여 서버 구축 시 반환값으로서 Mono 데이터 타입 사용 가능 (하나의 데이터 타입 하나를 주겠다는 의미)
- 기존의 동기 방식의 서버가 아닌 비동기 방식의 서버를 지원할 때 단일값 전달시 mono 타입으로 전달
[Spring Cloud Gateway - Global Filter]
Global Filter - GlobalFilter.java
- 라우터가 실행되더라도 공통적으로 다 시행될 수 있는 공통 필터
- 커스텀 필터는 원하는 라우터 정보에 개별적으로 등록이 필요했음.
- Global Filter는 모든 필터 중 가장 먼저 실행되고, 가장 마지막에 종료됨.
- Config 파일에 설정돼있는 변수의 설정값(preLogger, postLogger)은 application.yml 파일에서 필요 내용 등록
application.yml
- routes 위에 default-filters 등록 후, 이름과 전달하고자 하는 파라미터 지정
- args에 지정한 정보가 Config 클래스의 설정이 될 것
- 현재는 환경설정 정보인 application.yml이 마이크로서비스에 내장돼있음 (변경을 위해 재빌드, 재패키지, 재배포 필요) → 외부에 환경설정 정보를 삽입하도록 변경할 예정 (마이크로서비스 자체는 갱신되지 않도록 작업할 수 O)
Test
- Global Filter는 모든 필터 중 가장 먼저 실행되고, 가장 마지막에 종료되는 것을 볼 수 있음.
[Spring Cloud Gateway - Logging Filter]
Logging Filter - LoggingFilter.java
- 람다식으로 작성된 → 뒤의 {}가 우리가 반환할 객체
이는, 다음의 코드를 합쳐서 쓴 것
GatewayFilter filter = new OrderedGatewayFilter();
return filter;
OrderedGatewayFilter.java (Spring Cloud Gateway 라이브러리의 일부)
package org.springframework.cloud.gateway.filter;
public class OrderedGatewayFilter implements org.springframework.cloud.gateway.filter.GatewayFilter, org.springframework.core.Ordered {
private final org.springframework.cloud.gateway.filter.GatewayFilter delegate;
private final int order;
public OrderedGatewayFilter(org.springframework.cloud.gateway.filter.GatewayFilter delegate, int order) { /* compiled code */ }
public org.springframework.cloud.gateway.filter.GatewayFilter getDelegate() { /* compiled code */ }
public reactor.core.publisher.Mono<java.lang.Void> filter(org.springframework.web.server.ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { /* compiled code */ }
public int getOrder() { /* compiled code */ }
public java.lang.String toString() { /* compiled code */ }
}
→ filter라는 메소드가 재정의됨으로써 필터가 해야 할 역할을 지정 가능 GatewayFilterChain은 다양한 필터(pre filter, post filter, pre chain, post chain) 등을 연결시켜 작업할 수 있게 도와줌
LoggingFilter.java
@Override
public GatewayFilter apply(Config config) {
GatewayFilter filter = new OrderedGatewayFilter((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Logging filter baseMessage: {}", config.getBaseMessage());
if (config.isPreLogger()) {
log.info("Logging PRE Filter: request id -> {}", request.getId());
}
// Custom Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (config.isPostLogger()) {
log.info("Logging POST Filter: response code -> {}", response.getStatusCode());
}
}));
}, Ordered.HIGHEST_PRECEDENCE);
return filter;
}
- OrderedGatewayFilter의 1번째 매개변수: OrderedGatewayFilter의 exchange, chain을 받아 처리
2번째 매개변수 : Ordered.HIGHEST_PRECEDENCE → 필터 우선순위 지정
application.yml
Test
- Gateway Handler에서 어떠한 정보가 들어오는지 판단한 다음 Global Filter, Custom Filter, Logging Filter가 적용
- Proxied Service는 First service, Second service와 같은 추가로 구현한 서비스를 의미
- Filter를 거쳐 Service 실행 후, 처리가 끝나면 역순으로 반환됨.
[Spring Cloud Gateway - Load Balancer]
1) Spring Cloud Gateway - Eureka 연동
- Eureka라는 Naming 서비스에 Spring Cloud Gateway를 등록하고 first service, second service를 등록
- Eureka Server가 하는 역할 : Service Discovery, 등록
- 1) 클라이언트에서 API Gateway를 통과해서 어떠한 요청 정보를 보내게 되면
2) 먼저 Eureka에 전달되고, Eureka가 마이크로서비스의 위치 정보를 API Gateway에 전달
3) API Gateway가 해당하는 마이크로서비스로 직접 포워딩
2) Eureka Client 추가 - pom.xml, application.yml
pom.xml
- Eureka Client Dependency 추가
application.yml
- Eureka 서비스 등록을 위한 설정 정보 추가
- register-with-eureka : true / fetch-registry: true (default)
- service-url.defaultZone : Eureka 서버의 위치 정보 등록
- Eureka(Discovery) 서버에 등록된 서비스로 포워딩
- 클라이언트로부터 요청 정보가 전달되면 Discovery 서비스에 등록된 마이크로 서비스로 포워딩
- 기존에 HTTP 프로토콜을 이용하여 localhost:8081로 가는 것이 아닌, Eureka 서버에 클라이언트 요청 정보를 전달해주겠다는 의미
uri: <http://localhost:8081/> # 기존 uri: lb://MY-FIRST-SERVICE # 변경 후
- lb는 load balancer 의미
→ lb는 디스커버리 서비스 안에 포함되어 있는 인스턴스 이름을 찾겠다는 기호로 생각
→ 예를 들어, my-first-service라는 마이크로서비스를 각각 8081, 9091의 포트번호를 갖는 2개의 서버로 기동했다고 생각해보자. 클라이언트의 요청이 들어왔을 때, lb는 8081 혹은 9091 중 하나로 포워딩을 해주는 역할을 한다.
3) Eureka Server – Service 등록 확인
→ 마이크로 서비스를 하나 이상 기동시켜 로드밸런서의 기능 구현 가능
4) First Service, Second Service를 각각 2개씩 기동
Intellij에서 서버를 2개 기동하는 방법
- VM Options → -Dserver.port=[다른포트]
- $ mvn spring-boot:run -Dspring-boot.run.jvmArguments=’-Dserver.port=9003’
- $ mvn clean compile package $ java -jar -Dserver.port=9004 ./target/user-service-0.0.1-SNAPSHOT.jar
5) Eureka Server – Service 등록확인
6) Random port 사용
first-service와 second-service의 application.yml
first-service와 second-service의 Controller (random port 확인)
localhost:8000/first-service/check로 클라이언트 요청 결과
→ random port로 기동된 52888, 52887 포트의 서버가 round robin 방식으로 번갈아가면서 호출
→ Spring Gateway를 사용하면 간단한 라우팅 기능, 로드밸런서 기능까지 포함해서 사용 가능
[참고 문헌]
'Backend > Spring Cloud' 카테고리의 다른 글
[MSA] E-commerce 애플리케이션 프로젝트 (0) | 2024.07.28 |
---|---|
[MSA] 마이크로서비스 Service Discovery (0) | 2024.07.06 |
[MSA] Microservice와 Spring Cloud 소개 (0) | 2024.07.05 |