Zuul Proxy

Erea
10 min readJul 3, 2018

--

한마디로 정리하면 Api gateway인 동시에 load balancer(ribbon) 역활을 한다고 보면된다.

장점

인증 및 보안 — 각 리소스에 대한 인증 요구 사항을 식별하고이를 만족시키지 않는 요청을 거부합니다.

통찰력 및 모니터링 — 의미있는 데이터 및 통계를 가장자리에서 추적하여 정확한 생산보기를 제공합니다.

동적 라우팅 — 요청을 다른 백엔드 클러스터로 동적으로 라우팅합니다.

스트레스 테스팅 (Stress Testing) — 성능 측정을 위해 점차적으로 클러스터 트래픽을 증가시킵니다.

Load Shedding — 각 유형의 요청에 대해 용량을 할당하고 제한을 초과하는 요청을 삭제합니다.

정적 응답 처리 — 내부 클러스터로 전달하는 대신 가장자리에서 일부 응답을 직접 작성합니다.

다중 지역 복원력 — ELB 사용을 다양 화하고 회원에게 더 가까이 나아갈 수 있도록 AWS 지역에서 요청을 라우팅합니다.

build.gradle

buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.zuul'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
ext {
springCloudVersion = 'Finchley.RELEASE'
}
dependencies {
compile('org.springframework.cloud:spring-cloud-starter-config')
compile group: 'org.springframework.cloud', name:'spring-cloud-starter-netflix-zuul'
compile group: 'org.springframework.cloud', name:'spring-cloud-starter-netflix-eureka-client'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-hystrix'
testCompile('org.springframework.boot:spring-boot-starter-test')
compile('org.springframework.boot:spring-boot-starter-actuator')

}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

bootstrap.yml

zuul:
threadPool:
useSeparateThreadPools: true
routes:
prefix: /api
ribbon:
eager-load:
enabled: true
sensitiveHeaders: Cookie,Set-Cookie
ignored-services: "*"
gateway:
path: /test/**
serviceId: gateway
stripPrefix: false
serviceA:
url: http://localhost:8888
serviceId: serviceA
ribbonIsolationStrategy: THREAD

zuul properties

serviceId(eureka 연동) 및 물리적인 url로 매칭시킬수 있다.

url: http://example.com 로 매칭시

http://localhost:8899/api/ 로 접속시 http://example.com/users_service이 호출되는구조

http://localhost:8899/api/1 => http://example.com/1

stripPrefix : false

prefix를 그대로 전달할지 여부

true

http://127.0.0.1:8899/api/test/

=>

http://example.com/

false

http://127.0.0.1:8899/api/test/

=>

http://example.com/test

zuul.retryable

리본 클라이언트가 실패한 요청을 자동으로 다시 시도합니다

zuul.addProxyHeaders = true

X-Forwarded-Host 자동으로 추가

sensitiveHeaders : Cookie,Set-Cookie,Authorization // 기본값

해당값이 기본값이며 즉 블랙리스트 값이다

sensitiveHeaders : 로 비워두면 쿠키등이 그대로 다운스트림에 전달된다.

forceOriginalQueryStringEncoding: true

쿼리 문자열 원본 그대로 가져오기

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000

ribbon:

ConnectTimeout: 3000

ReadTimeout: 60000

해당 설정 2개가 세트다

hystrix를 쓰는경우 리본의 타임아웃설정은 63000 가 될것이고 hystrix는 60000 초이다

리본에서는 retry 횟수가 1이면

총 2회 (63000 x2) 126000 초 이상의 hystrix 타임아웃 시간이 필요하다.

application.java

package com.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableHystrix
public class EurekaApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}

Hystrix fallback provider 구성

import com.netflix.hystrix.exception.HystrixTimeoutException;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyFallBackProvider implements FallbackProvider {

@Override
public String getRoute() {
return "*";
}

@Override
public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
System.out.println(cause);
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}

private ClientHttpResponse response(final HttpStatus status) {
System.out.println("status::"+status);
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}

@Override
public int getRawStatusCode() throws IOException {
return status.value();
}

@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}

@Override
public void close() {
}

@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}

@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}

@Bean
public MyFallBackProvider getMyFallbackProvider() {
return new MyFallBackProvider();
}

참조

https://github.com/Netflix/zuul

Originally published at https://erea.tistory.com on July 3, 2018.

--

--