Spring Cloud - Zuul API gateway & Proxy !(Netflix Zuul)

2019. 2. 25. 00:22Web/Spring Cloud

Spring Cloud - Zuul API gateway & Proxy !(Netflix Zuul)


Netflix Zuul 이란 무엇인가?

마이크로서비스 아키텍쳐(MSA)에서 Netflix Zuul은 간단히 API gateway 또는 API Service,Edge Service로 정의된다.

그래서 하는 일이 무엇이냐? 마이크로서비스 아키텍쳐에서 여러 클라이언트 요청을 적절한 서비스로 프록시하거나 라우팅하기 위한 서비스이다.




위의 이미지에서 보이듯, 모든 마이크로서비스의 종단점은 숨기고 모든 요청을 최앞단에서 Zuul이 받아 

적절한 서비스로 분기를 시키게된다. 모든 마이크로서비스의 종단점을 숨겨야하는 이유가 무엇인가?


1) 클라이언트는 일부 마이크로서비스만 필요로한다.

2) 클라이언트별로 적용돼야 할 정책이 있다면 그 정책을 여러 곳에서 분산해 두는 것보단 한곳에 두고 적용하는 것이

더욱안전하다.(크로스오리진 접근정책이 바로 이런 방식의 대표적인 예임) 또한 서비스 단에서 사용자별 분기처리 로직은

구현하기 까다롭다.

3)대역폭이 제한돼 있는 환경에서 데이터 집계가 필요하다면 다수의 클라이언트의 요청이 집중되지 않게 중간에 게이트웨이를

두는것이 좋다.



Netflix Zuul 설계목적?



우선 Zuul은 JVM-based router and Server-side load Balancer이다. Zuul을 사용함으로써 서버사이드에서
동적 라우팅, 모니터링, 회복 탄력성, 보안 기능을 지원한다(Filter를 통한 구현)
또한 Zuul은 다른 기업용 API 게이트웨이 제품과는 달리 개발자가 특정한 요구 사항에 알맞게 설정하고 프로그래밍할 수 있게
개발자에게 완전한 통제권을 준다.


Zuul 프록시는 내부적으로 서비스 탐색을 위해 Eureka(유레카) 서버를 사용하고, 서비스 인스턴스 사이의 부하 분산을 위해 Ribbon(리본)을 사용한다.
위에서도 이야기 했던 것처럼 Zuul은 API계층에서 서비스의 기능을 재정의해서 뒤에 있는 서비스의 동작을 바꿀수 있다.




만약 이전 포스팅에서 Eureka에 대해 읽어 보았다면, 위의 그림만 보아도 Zuul이 어떤식으로

동작하는지 이해가 될것이다.


▶︎▶︎▶︎Spring Cloud - Eureka를 이용한 마이크로서비스 동적등록&탐색&부하분산처리



그렇다면 Zuul은 어떠한 요구사항일때 쓸모가 있을까?


많은 요구사항이 있지만, 아래와 같은 요구사항일때 특히 더 쓸모가 있다.


1) 인증이나 보안을 모든 마이크로서비스 종단점에 각각 적용하는 대신 게이트웨이 한곳에 적용한다. 게이트웨이는

요청을 적절한 서비스에 전달하기 전에 보안 정책 적용, 토큰 처리 등을 수행할 수 있다. 또한 특정 블랙리스트(IP차단) 사용자를

거부할 수 있는 비즈니스 정책 적용이 가능하다.

2) 모니터링, 데이터 집계 등을 마이크로서비스 단에서 처리하는 것이아니라, Zuul에서 처리해 외부로 데이터를

내보낼때 사용할 수 있다.

3)부하 슈레딩(shredding),부하 스로틀링(throttling)이 필요한 상황에서도 유용하다.

4)세밀한 제어를 필요로 하는 부하 분산 처리에 유용하다(Zuul+Eureka+Ribbon)



예제 프로젝트의 구성은 Spring Cloud Config, Eureka, Zuul로 구성되어 있습니다.

▶︎▶︎▶︎Spring Cloud Config

▶︎▶︎▶︎Spring Cloud Eureka




예제프로젝트는 위의 이미지의 구성입니다. 하지만 편의상 Zuul은 하나의 인스턴스만 그리고 2개의 마이크로서비스 인스턴스만

띄울 예정입니다. 그리고 마이크로서비스 인스턴스들은 또한 편의상 Spring Cloud Config를 이용하지 않았습니다.

만약 모든 구성을 스프링클라우드 컨피그로 가신다면 다른 유레카나 주울과 같은 컨피그 구성으로 가시면 됩니다.

그리고 이전 포스팅에서 유레카 서버를 독립설치형이 아닌 클러스터링된 구성으로 진행 할 것입니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#유레카 서버 - 1
spring.application.name=eureka-server1
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8899/eureka/
#eureka.instance.hostname=localhost
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true
 
#유레카 서버 - 2
spring.application.name=eureka-server2
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8899/eureka/
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true
 
#주울 
server.port=8060
zuul.routes.search-apigateway.serviceId=eurekaclient
zuul.routes.search-apigateway.path=/api/**
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8899/eureka/
cs


위는 깃저장소에 있는 spring cloud config 설정파일입니다.(편의상 하나의 파일로 작성함. 실제로는 따로 파일을 나눠야함)

조금 설명할 점이 있다면, 유레카 독립모드와 클러스터 모드의 차이점입니다. 독립모드는 자신을 유레카서버에 등록하지 않고, 

캐시한 서비스 목록을 패치하지 않습니다. 하지만 클러스터모드에서는 유레카서버들이 서로 통신해야하기 때문에 자신을 서비스로

등록하고, 자신들의 서버목록들을 빠르게 통신하기 위해 캐시합니다. 그리고 defaultZone에 모든 유레카서버의 경로를 ","구분으로

나열합니다.(사실 서로 크로스해서 상대방의 주소만 써도됨. 하지만 나중에 유레카 서버가 많고 서로 하나씩 크로스됬다는 구성에서

만약 하나의 유레카서버가 죽어서 유레카서버끼리의 통신이 단절될 가능성도 있음. 그래서 모든 유레카서버 목록을 나열해서

통신하도록 하는 것이 좋음.)

그리고 주울도 하나의 유레카클라이언트이며 주울 프록시이다. 그래서 defaultZone에 유레카서버들을 나열한다. 그리고 프록시 설정이

한가지 방법만 있는 것은 아닌데 이 설정파일에는 /api/**로 들어오는 요청을 모두 eurekaclient로 보내라는 설정이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#서비스 - 1, application.properties
server.port=8090
spring.application.name=eurekaclient
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8899/eureka/
eureka.client.healthcheck.enabled=true
 
#서비스 - 2, application.properties
server.port=8080
spring.application.name=eurekaclient
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8899/eureka/
eureka.client.healthcheck.enabled=true
 
#서비스 1,2 호출하는 클라이언트, application.properties
server.port=8070
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8899/eureka/
spring.application.name=eureka-call-client
 
 
#config server, bootstrap.properties
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/yeoseong/spring-cloud-configserver.git
management.security.enabled=false
management.endpoint.env.enabled=true
 
#eureka server - 1, bootstrap.properties
spring.application.name=eureka
spring.profiles.active=server1
server.port=8889
spring.cloud.config.uri=http://localhost:8888
management.security.enabled=false
 
#eureka server - 2, bootstrap.properties
spring.application.name=eureka
spring.profiles.active=server2
server.port=8899
spring.cloud.config.uri=http://localhost:8888
management.security.enabled=false
 
#zuul , bootstrap.properties
spring.application.name=zuulapi
spring.cloud.config.uri=http://localhost:8888
spring.profiles.active=dev
management.security.enabled=false
 
zuul.routes.eurekaclient=/api3/**
 
 
 애플리케이션들의 application.properties,bootstrap.properties 입니다.(스프링클라우드컨피그 사용여부에 따라 다름)
 
cs


나머지 설정들은 이전 포스팅에서 보고 왔다면 모두 이해할수 있다. 마지막 하나만 설명하자면, zuul의 설정이다. 이미 깃에 있는 설정파일에서 하나의

프록시 룰을 정해줬다. 하지만 그 방법말고도 다른방법이 있다.

zuul.routes.serviceId(eureka)=/path/**로도 라우팅 규칙을 정해줄 수 있다.


이제는 소스 설명이다. 유레카 및 컨피그, 마이크로서비스 클라이언트 소스는 이전과 동일하기 때문에 따로 작성하지 않는다.

▶︎▶︎▶︎Spring Cloud Config

▶︎▶︎▶︎Spring Cloud Eureka




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApiApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ZuulApiApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public ZuulFilter zuulFilter() {
        return new ZuulCustomFilter();
    }
    
    @RestController
    class ZuulController{
        
        @Autowired
        RestTemplate restTemplate;
        
        @GetMapping("/api2")
        public String zuulProxy() {
            System.out.println("ZuulController.zuulProxy() :::: /api2");
            return restTemplate.getForObject("http://eurekaclient/eureka/client"String.class);
        }
        
    }
}
cs


위의 소스를 설명하면, @EnableZuulProxy로 이 애플리케이션이 주울 프록시임을 명시한다. 그리고 주울도 하나의 유레카 클라이언트임으로

@EnableDiscoveryClient로 명시해준다. 그리고 주울의 특징중 하나는 스프링 기반으로 만들어진 API임으로 개발자가 자신이 커스터마이징해서

사용할 수 있다는 점이다. @RestController로 직접 주울의 엔드포인트를 정의해서 원하는 서비스로 보낼수 있다. 더 세밀한 무엇인가가

필요하다면 이렇게 컨트롤러를 만들어서 커스터마이징해도 좋을 듯싶다. 그리고 주울도 위에서 말했듯이 하나의 유레카 클라이언트고

내부적으로 리본을 사용해 로드벨런싱 한다고 했으니, 컨트롤러에서 라우팅할때 @LoadBalanced된 RestTemplate을 이용해야

로드밸런싱이 된다.(지금까지 총 3가지 라우팅 룰을 다뤘다.)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ZuulCustomFilter extends ZuulFilter{
    
    private static Logger logger = LoggerFactory.getLogger(ZuulCustomFilter.class);
    
    /**
     * Criteria - 필터 실행 여부를 결정
     */
    @Override
    public boolean shouldFilter() {
        // TODO Auto-generated method stub
        return true;
    }
    
    /**
     * Action - Criteria 만족 시에 실행할 비즈니스 로직
     */
    @Override
    public Object run() throws ZuulException {
        // TODO Auto-generated method stub
        
        logger.info("ZuulCustomFilter :::: {}","pre filter");
        
        return null;
    }
    
    /**
     * Type - pre,route,post
     */
    @Override
    public String filterType() {
        // TODO Auto-generated method stub
        return "pre";
    }
    
    /**
     * Order - 필터 실행 순서를 결정, 숫자가 낮을 수록 우선순위가 높아짐.
     */
    @Override
    public int filterOrder() {
        // TODO Auto-generated method stub
        return 0;
    }
    
}
cs


또한 주울은 필터를 정의해서 필요한 요청,응답에 대한 전/후처리가 가능합니다.

Pre Filter

주로 backend에 보내줄 정보를 RequestContext에 담는 역할

Payco의 AccessToken으로 email을 넘겨주는 경우





























public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
}

@Override
public String filterType() {
return PRE_TYPE;
}

@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
return "member-api".equals(context.get(SERVICE_ID_KEY));
}

@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String email = paycoTokenToEmail(request);
context.addZuulRequestHeader("X-PAYCO-EMAIL", email);
return null;
}
}

email은 소중한 개인 정보입니다. 다루실 때 주의하시기 바랍니다.

Route Filter

pre filter 이후에 실행되며, 다른 서비스로 보낼 요청을 작성한다

이 필터는 주로 request, response를 client가 요구하는 모델로 변환하는 작업을 수행한다

아래의 예제는 Servlet Request를 OkHttp3 Request로 변환하고, 요청을 실행하고,

OkHttp3 Response를 Servlet Response로 변환하는 작업을 수행한다












































































public class OkHttpRoutingFilter extends ZuulFilter {
@Autowired
private ProxyRequestHelper helper;

@Override
public String filterType() {
return ROUTE_TYPE;
}

@Override
public int filterOrder() {
return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
}

@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}

@Override
public Object run() {
OkHttpClient httpClient = new OkHttpClient.Builder()
// customize
.build();

RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();

String method = request.getMethod();

String uri = this.helper.buildZuulRequestURI(request);

Headers.Builder headers = new Headers.Builder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);

while (values.hasMoreElements()) {
String value = values.nextElement();
headers.add(name, value);
}
}

InputStream inputStream = request.getInputStream();

RequestBody requestBody = null;
if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
MediaType mediaType = null;
if (headers.get("Content-Type") != null) {
mediaType = MediaType.parse(headers.get("Content-Type"));
}
requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
}

Request.Builder builder = new Request.Builder()
.headers(headers.build())
.url(uri)
.method(method, requestBody);

Response response = httpClient.newCall(builder.build()).execute();

LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();

for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
responseHeaders.put(entry.getKey(), entry.getValue());
}

this.helper.setResponse(response.code(), response.body().byteStream(),
responseHeaders);
context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
return null;
}
}

Post Filter

Response를 생성하는 작업을 처리한다

아래 예제는 X-Sample 헤더에 임의의 UUID를 넣는 소스이다

























public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}

@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());
return null;
}
}

▶︎▶︎▶︎참고


 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@EnableDiscoveryClient
@SpringBootApplication
public class Eurekaclient3Application {
 
    
    public static void main(String[] args) {
        SpringApplication.run(Eurekaclient3Application.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @RestController
    class EurekaClientController{
        
        @Autowired
        RestTemplate restTemplate;
        
        @GetMapping("/eureka/client")
        public String eurekaClient() {
            
//            String result = restTemplate.getForObject("http://eurekaclient/eureka/client", String.class);
            System.out.println("EurekaClientController :::: /eureka/client");
            String result = restTemplate.getForObject("http://zuulapi/api/eureka/client"String.class);
            
            return result;
        }
        @GetMapping("/eureka/client2")
        public String eurekaClient2() {
            
            System.out.println("EurekaClientController :::: /eureka/client2");
            String result = restTemplate.getForObject("http://zuulapi/api2"String.class);
            
            return result;
        }
        
        @GetMapping("/eureka/client3")
        public String eurekaClient3() {
            
            System.out.println("EurekaClientController :::: /eureka/client3");
            String result = restTemplate.getForObject("http://zuulapi/api3/eureka/client"String.class);
            
            return result;
        }
    }
}
cs


이제 마이크로서비스를 호출하는 클라이언트 소스입니다..(주울호출) 모두 /apin/~으로 주울에게 요청이 갑니다.

그리고 주울에서는 각각의 마이크로서비스로 /apin/을 제외한 나머지 Uri를 해당 마이크로서비스들에게 요청보냅니다.

(물론 설정으로 앞의 프리픽스까지 붙여서 요청보내게 할 수 있음)

postman 이나 curl로 호출이 잘되는지 확인 해보시면 될듯합니다..



<유레카 및 주울 설정 메모>


구글링을 막하다가 유레카 및 주울 설정들을 막 메모한 것들입니다.

정리하기 힘들어서.... 그냥 메모 그대로 올립니다....


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
================================================================================Zuul=========================================================================================================
#Netflix Zuul 1.x은 외부 API 호출시 클라이언트 사이드 로드 밸런서로 Netflix Ribbon를 사용한다. 
#또한, Netflix Ribbon는 외부 API 서비스의 물리적인 노드 정보를 발견하는 역할로 Netflix Eureka에 의존한다. 
#만약 Netflix Eureka(별도 독립 서비스 구축 필요)를 사용하지 않는다면 ribbon.eureka.enabled 옵션을 false로 설정하면 된다.
#zuul.sensitive-headers에 특정 헤더 이름을 설정하면 라우팅 전에 해당 헤더를 제거할 수 있다. 보안 문제로 라우팅되지 말아야할 헤더가 있을 경우 활용할 수 있다.
#zuul.host.connect-timeout-millis으로 API 요청 후 연결까지의 타임아웃을 설정할 수 있다. 설정된 타임아웃이 초과했을 경우 ZuulException(내부적으로는 java.net.ConnectException) 예외가 발생한다.
#zuul.host.socket-timeout-millis으로 API 요청 후 응답까지의 타임아웃을 설정할 수 있다. 설정된 타임아웃이 초과했을 경우 ZuulException(내부적으로는 java.net.SocketTimeoutException) 예외가 발생한다.
#zuul.routes.url을 직접적으로 명시하면 Netflix Ribbon을 사용하지 않는다.
#zuul.routes.stripPrefix를 false로 설정하면 라우팅시 url에 path가 그대로 보존되어 결합된다. 인지적으로 가장 자연스러운 설정이다. true(기본값)로 설정시에는 url에서 path 부분은 제거되고 나머지 부분이 추가되어 라우팅된다.
 
#구성 등록 정보 zuul.max.host.connections는 
#두 개의 새 등록 정보 zuul.host.maxTotalConnections 및 zuul.host.maxPerRouteConnections로 대체되었습니다. 
#기본값은 각각 200 및 20입니다.
 
#모든 경로의 기본 Hystrix 격리 패턴 (ExecutionIsolationStrategy)은 SEMAPHORE입니다. 
#zuul.ribbonIsolationStrategy는 격리 패턴이 선호되는 경우 THREAD로 변경할 수 있습니다.
#THREAD일때, WAS의 스레드로 API요청을 받는 것이 아니라, Hystrix의 별도의 스레드를 이용하여 
#WAS의 스레드와 격리한다.
 
#프록시는 리본을 사용하여 검색을 통해 전달할 인스턴스를 찾습니다. 
#모든 요청은 hystrix 명령으로 실행되므로 실패는 Hystrix 메트릭에 나타납니다. 
#회선이 열리면 프록시는 서비스에 접속하려고 시도하지 않습니다.
 
#서비스가 자동으로 추가되는 것을 건너 뛰려면 zuul.ignored-services를 서비스 ID 패턴 목록으로 설정하십시오. 
#zuul.ignored-services='*'
 
#zuul.routes.eurekaclient=/api3/**
 
#서비스아이디가 아니라 직접 URL을 등록할 수 있지만, 이것은 클라우드의 로드벨런싱 효과를 얻을 수 없다.
 
#zuul.strip-prefix=true 으로 설정된 prefix를 붙여서 요청을 보낸다.
 
 
#zuul:
#  routes:
#    users:
#      path: /myusers/**
#      serviceId: users
#
#ribbon:
#  eureka:
#    enabled: false
#
#users:
#  ribbon:
#    listOfServers: example.com,google.com
#위와 같이 직접 리본으로 라우팅할 리스트를 작성할 수있다. 이것을 사용하려면 eureka 사용을 비활성화해야한다.
 
#만약 X-Forwarded-Host 헤더같은 것이 요청에 들어갔을 경우,
#헤더값을 추가못하게 설정할수 있다.
#zuul.add-proxy-headers=false
 
 
#기본 경로 (/)를 설정하면 @EnableZuulProxy가있는 응용 프로그램이 독립 실행 형 서버로 작동 할 수 있습니다. 
#예를 들어, zuul.route.home : /는 모든 트래픽 ( "/ **")을 "home"서비스로 라우팅합니다.
 
#zuul:
#  routes:
#    users:
#      path: /myusers/**
#      sensitiveHeaders: Cookie,Set-Cookie,Authorization
#      url: https://downstream
# 위처럼 bypass시킬 헤더값 목록을 지정
 
# zuul:
#  routes:
#    users:
#      path: /myusers/**
#      sensitiveHeaders:
#      url: https://downstream
# 모든 헤더값을 bypass
 
#zuul.ignored-headers=Cookie,Set-Cookie
#Cookie,Set-Cookie 헤더는 버린다.
 
 
#spring security를 쓰고 시큐리티헤더를 통과시키려면
#zuul.ignore-security-headers=false
 
#zuul:
#  forceOriginalQueryStringEncoding: true
# 원래 인코딩값으로 강제로 바꾼다.
 
#Zuul이 서비스 검색을 사용하는 경우 ribbon.ReadTimeout 및 ribbon.SocketTimeout 리본 등록 정보로 이러한 시간 초과를 구성해야합니다.
#URL을 지정하여 Zuul 경로를 구성한 경우 zuul.host.connect-timeout-millis 및 zuul.host.socket-timeout-millis를 사용해야합니다.
 
#기본적으로 Zuul은 모든 Cross Origin Request (CORS)를 서비스로 라우팅합니다. 
#대신 Zuul이 이러한 요청을 처리하기 원하는 경우 사용자 지정 WebMvcConfigurer bean을 제공하여 수행 할 수 있습니다.
#@Bean
#public WebMvcConfigurer corsConfigurer() {
#    return new WebMvcConfigurer() {
#        public void addCorsMappings(CorsRegistry registry) {
#            registry.addMapping("/path-1/**")
#                    .allowedOrigins("http://allowed-origin.com")
#                    .allowedMethods("GET", "POST");
#        }
#    };
#}
================================================================================Zuul=========================================================================================================
================================================================================Eureka=========================================================================================================
#<Eureka 등장 용어 정리>
#    <Eureka 행동 관련>
#        Service Registration: 서비스가 자기 자신의 정보를 Eureka에 등록하는 행동
#        Service Registry: 서비스가 스스로 등록한 정보들의 목록, 가용한 서비스들의 위치 정보로 갱신됨
#        Service Discovery: 서비스 클라이언트가 요청을 보내고자 하는 대상의 정보를 Service Registry를 통해 발견하는 과정
#    <Eureka 구성 요소 관련>
#        Eureka Client: 서비스들의 위치 정보를 알아내기 위해 Eureka에 질의하는 서비스를 가리킴 (like Service consumer)
#        Eureka Service: Eureka Client에 의해 발견의 대상이 되도록 Eureka에 등록을 요청한 서비스를 가리킴 (like Service provider)
#        Eureka Server: Eureka Service가 자기 자신을 등록(Service Registration)하는 서버이자 Eureka Client가 가용한 서비스 목록(Service Registry)을 요청하는 서버
#        Eureka Instance: Eureka에 등록되어 목록에서 조회 가능한 Eureka Service를 의미
#
#<Eureka Client 동작과 Server간 Communication>
#    <Self-Identification & Registration>
#        Eureka Client는 어떻게 Eureka Server로부터 서비스 목록을 받아올까?
#        REST endpoint /eureka/apps를 통해 등록된 인스턴스 정보를 확인할 수 있다.
#        
#        Traffic을 받을 준비가 되면 Eureka Instance의 status가 STARTING → UP으로 바뀐다
#        status:STARTING은 Eureka Instance가 초기화 작업을 진행 중인 상태로 Traffic을 받을 준비가 안되었다는 의미이다
#        eureka.instance.instance-enabled-onit 설정값을 통해 Startup 후 Traffic 받을 준비가 되었을 때 status:UP이 되도록 할 수 있다 (default: false)
#        
#        등록 이후 heartbeat은 eureka.instance.lease-renewal-interval-in-seconds에 설정된 주기마다 스케쥴러가 실행된다 (default: 30)
#        
#        Eureka Server는 interval에 따라 Eureka Service의 status(UP/DOWN/..)를 판단하고 
#        가장 최근 heartbeat 시점 + interval 이후에 heartbeat을 받지 못하면 
#        eureka.instance.lease-expiration-duration-in-seconds에 설정된 시간만큼 기다렸다가 
#        해당 Eureka Instance를 Registry에서 제거한다 (default: 90, 단, Eureka Instance가 정상적으로 종료된 경우 Registry에서 바로 제거된다)
#        위의 값은 lease-renewal-interval-in-seconds보다는 커야한다.
#        
#        등록 이후 Instance 정보가 변경 되었을 때 Registry 정보를 갱신하기 위한 REST를 
#        eureka.client.instance-info-replication-interval-seconds에 설정된 주기마다 호출한다 (default: 30)
#        eureka.client.initial-instance-info-replication-interval-seconds (default: 40)
#        
#        Eureka Server 추가, 변경, 삭제가 일어날 때 Eureka Client가 얼마나 자주 service urls를 갱신할 것인지 
#        eureka.client.eureka-service-url-poll-interval-seconds 값으로 조정할 수 있다 
#        #default: 0, 단 DNS를 통해 service urls를 가져오는 경우)
#    
#    <Service Discovery>
#        -Instance Startup 시점
#        Eureka로부터 Registry 정보를 fetch한다
#        Instance Startup 이후 Fetch Registry
#        등록 이후 Eureka Client는 eureka.client.registry-fetch-interval-seconds에 설정된 주기마다 Local Cache Registry 정보를 갱신한다 (default: 30)
    
    
#<Eureka Server 동작과 Peer Server간 Communication>    
#    <Self-Identification & Registration>
#        -Instance Startup 시점
#            Peer nodes를 찾아서 Registry 정보 등 Sync 맞추는 작업을 한다
#            eureka.server.registry-sync-retrires 값을 통해 Peer nodes로부터 Registry 정보를 얻기 위한 재시도 횟수를 조정할 수 있다 (default: 5)
#            Eureka Server가 시작되고 Peer nodes로부터 Instance들을 가져올 수 없을 때 얼마나 
#            기다릴 것인지 eureka.server.wait-time-in-ms-when-sync-empty 시간(milliseconds)을 조정할 수 있다 (default: 3000)
#
#            나머지 과정은 Server도 Eureka Client이기 때문에 'Eureka Client > Self-Identification & Registration > Instance Startup 시점'에 설명한 바와 같이 동일하게 동작한다
#            Standalone으로 구성하는 경우 Peer nodes가 없기 때문에 eureka.client.register-with-eureka: false 설정을 통해 등록 과정을 생략할 수 있다
 
 
#<Eureka Server Response Cache 설정>  
#    Eureka server에서 eureka client에게 자신의 registry 정보를 제공 시 사용하는 cache.  
#    client에게 더 빠른 registry 정보 제공을 위해 실제 registry 값이 아닌 cache의 값을 제공 함.  
#    eureka.server.response-cache-update-interval-ms: 3000 # 기본 30초
 
#<Eureka Client Cache 설정> 
#    Eureka client에 존재하는 cache로 eureka server에 서비스 정보 요청 시 이 cache의 값을 이용 한다.   
#    eureka.client.fetchRegistry 값이 false이면 client cache는 적용되지 않는다.   
#    eureka.client.registryFetchIntervalSeconds: 3 # 기본 30초
 
#어떤 경우에는 유레카가 호스트 이름보다는 서비스의 IP 주소를 광고하는 것이 바람직합니다.
#eureka.instance.preferIpAddress를 true로 설정하고 응용 프로그램이 eureka에 등록하면 호스트 이름 대신 IP 주소를 사용합니다.
 
#spring-boot-starter-security를 ​​통해 서버의 classpath에 Spring Security를 ​​추가하기 만하면 유레카 서버를 보호 할 수 있습니다. 
#기본적으로 Spring Security가 classpath에있을 때, 모든 요청에 ​​대해 유효한 CSRF 토큰을 앱에 보내야합니다. 
#유레카 고객은 일반적으로 유효한 CSRF (cross site request forgery) 토큰을 보유하지 않으므로 / eureka / ** 엔드 포인트에 대해이 요구 사항을 비활성화해야합니다.
#@EnableWebSecurity
#class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#
#    @Override
#    protected void configure(HttpSecurity http) throws Exception {
#        http.csrf().ignoringAntMatchers("/eureka/**");
#        super.configure(http);
#    }
#}
 
#Eureka Discovery Client를 사용하지 않으려면 eureka.client.enabled를 false로 설정할 수 있습니다. 
#Eureka Discovery Client는 spring.cloud.discovery.enabled가 false로 설정된 경우에도 비활성화됩니다.
 
#eureka.client.serviceUrl.defaultZone URL 중 하나에 자격 증명이 포함되어 있으면 HTTP 기본 인증이 자동으로 유레카 클라이언트에 추가됩니다 
#(컬 스타일, http : // user : password @ localhost : 8761 / eureka). 
#보다 복잡한 요구를 위해, DiscoveryClientOptionalArgs 타입의 @Bean을 생성하고 ClientFilter 인스턴스를 클라이언트에 삽입 할 수 있습니다.
#이 인스턴스는 모두 클라이언트에서 서버로의 호출에 적용됩니다.
 
#Eureka 인스턴스의 상태 페이지 및 상태 표시기는 각각 Spring Boot Actuator 응용 프로그램의 유용한 끝점의 기본 위치 인 / info 및 / health로 기본 설정됩니다.
#eureka:
#  instance:
#    statusPageUrlPath: ${server.servletPath}/info
#    healthCheckUrlPath: ${server.servletPath}/health
 
#HTTPS를 통해 앱과 연락하려는 경우 EurekaInstanceConfig에서 다음과 같은 두 가지 플래그를 설정할 수 있습니다.
#eureka.instance.[nonSecurePortEnabled]=[false]
#eureka.instance.[securePortEnabled]=[true]
 
#eureka.hostname == eureka.instance.hostname
 
#기본적으로 Eureka는 클라이언트 하트 비트를 사용하여 클라이언트가 작동 중인지 확인합니다.
#별도로 지정하지 않는 한, Discovery Client는 Spring Boot Actuator에 따라 응용 프로그램의 현재 상태 검사 상태를 전파하지 않습니다. 
#따라서 성공적으로 등록한 후 Eureka는 응용 프로그램이 항상 UP 상태임을 발표합니다. 
#이 동작은 Eureka 상태 점검을 활성화하여 응용 프로그램 상태를 Eureka에 전파함으로써 변경 될 수 있습니다. 
#결과적으로 다른 모든 응용 프로그램은 'UP'이외의 상태로 응용 프로그램에 트래픽을 보내지 않습니다.
#반드시 actuator 의존이 필요
 
#상태 검사를 더 많이 제어해야하는 경우에는 com.netflix.appinfo.HealthCheckHandler를 직접 구현하는 것이 좋습니다.
 
#라우터를 사용하는 경우 (권장 또는 필수, 플랫폼 설정 방식에 따라 다름) 라우터를 사용하도록 명시 적으로 호스트 이름과 포트 번호 (보안 또는 비보안)를 설정해야합니다.
#eureka:
#  client:
#    healthcheck:
#      enabled: true
#만약 bootstrap.properties에 등록하면 UNKNOWN등의 비정상적인 상태값이 나올수 있다.(반드시 application.xxx에 설정하자)
 
#Cloud Foundry에는 글로벌 라우터가있어 동일한 앱의 모든 인스턴스가 동일한 호스트 이름을 갖는다면,
#이것은 반드시 유레카 사용의 문제가 있지는 않다.
#그러나 라우터를 사용하는 경우 (권장 또는 필수, 플랫폼 설정 방식에 따라 다름) 라우터를 사용하도록 명시 적으로 호스트 이름과 포트 번호 (보안 또는 비보안)를 설정해야합니다.
#eureka:
#  instance:
#    hostname: ${vcap.application.uris[0]}
#    nonSecurePort: 80
 
 
 
 
 
#########################################################################################################################################
#Register
#    eureka.instance, eureka.client 설정값을 바탕으로 Eureka에 등록하기 위한 Eureka Instance 정보를 만듦
#    Client가 eureka 서버로 첫 hearbeat 전송 시 Eureka Instance 정보를 등록
#    등록된 instance 정보는 eureka dashboard나  http://eurekaserver/eureka/apps를 통해 확인할 수 있음
#Renew
#    Client는 eureka에 등록 이후 설정된 주기마다 heatbeat를 전송하여 자신의 존재를 알림
#    eureka.instance.lease-renewal-interval-in-seconds (default: 30)
#    설정된 시간동안 heartbeat를 받지 못하면 해당 Eureka Instance를 Registry에서 제거
#    eureka.instance.lease-expiration-duration-in-seconds (default: 90)
#    renew 관련 interval은 변경하지 않는것을 권장 함(서버 내부적으로 client를 관리하는 로직 때문)
#Fetch Registry
#    Client는 Server로부터 Registry(서버에 등록된 인스턴스 목록) 정보를 가져와서 로컬에 캐시
#    캐시 된 정보는 설정된 주기마다 업데이트 됨
#    eureka.client.registryFetchIntervalSeconds (default: 30)
#Cancel
#    Client가 shutdown될 때 cancel 요청을 eureka 서버로 보내서 registry에서 제거 하게 됨
#Time Lag
#    Eureka server와 client의 registry 관련 캐시 사용으로 인해 client가 호출 하려는 다른 instance 정보가 최신으로 갱신되는데 약간의 시간 차가 있음
 
 
#Peering
#    여러대의 eureka server를 사용하여 서로 peering 구성이 가능하다.
#    Eureka server는 설정에 정의된 peer nodes를 찾아서 Registry 정보 등 Sync 맞추는 작업을 한다 .
#    
#    관련 설정
#        Standalone으로 구성하려면 아래 처럼 설정
#            eureka.client.register-with-eureka: false
#        Peer nodes 로부터 registry를 갱신할 수 없을 때 재시도 횟수
#            eureka.server.registry-sync-retrires (default: 5)
#        Peer nodes 로부터 registry를 갱신할 수 없을때 재시도를 기다리는 시간
#            eureka.server.wait-time-in-ms-when-sync-empty (default: 3000) milliseconds
 
#Self-Preservation Mode(자가보존모드)
#    Eureka 서버는 등록된 instance로부터 heartbeat를 주기적으로 받는다.
#    하지만 네트워크 단절 등의 상황으로 hearbeat를 받을 수 없는 경우 보통 registry에서 해당 instance를 제거 한다.
#    Eureka로의 네트워크는 단절되었지만, 해당 서비스 API를 호출하는데 문제가 없는 경우가 있을수 있어서,
#    self-preservation 을 사용하여 registry에서 문제된 instance를 정해진 기간 동안 제거하지 않을 수 있다.
#    EvictionTask가 매분 마다 Expected heartbeats 수와 Actual heartbeats 수를 비교하여 Self-Preservation 모드 여부를 결정한다.
#        eureka.server.eviction-interval-timer-in-ms (default: 60 * 1000)
 
#Expected heartbeats updating scheduler
#    기본 매 15분(renewal-threshold-update-interval-ms) 마다 수행되며 preservation mode로 가기 위한 임계값을 계산한다.
#    예를 들어 인스턴스 개수가 N개이고, renewal-percent-threshold값이 0.85이면 계산식은 아래와 같다.
#    최소 1분이내 받아야 할 heartbeat 총 수 = 2  N  0.85  
#    위 값은 아래 설정으로 변경 가능 
#        eureka.instance.lease-renewal-interval-in-seconds (default: 30)
#        eureka.server.renewal-percent-threshold (default: 0.85)
#        scheduler 수행 주기 설정 eureka.server.renewal-threshold-update-interval-ms (default: 15  60  1000)
 
#Actual heartbeats calculation scheduler
#    기본 매 1분 마다 수행되며 실제 받은 heartbeats 횟수를 계산하다.
 
#eureka
#    instance:
#         preferIpAddress: true # 서비스간 통신 시 hostname 보다 ip 를 우선 사용 함
 
#server:
#  port: 8761
#
#eureak:
#  server:
#    enable-self-preservation: true
#  client:
#    registerWithEureka: true      
#    fetchRegistry: true           
#
#---
#
#spring:
#  profiles: eureka1
#eureka:
#  instance:
#    hostname: eureka1
#  client:
#    serviceUrl:
#      defaultZone: http://eureka2:8761/eureka/
#
#---
#spring:
#  profiles: eureka2
#eureka:
#  instance:
#    hostname: eureka2
#  client:
#    serviceUrl:
#      defaultZone: http://eureka1:8761/eureka/
 
#동일서버에서 실행하는 경우 instance hostname은 unique하게 설정되어야 한다.
#registerWithEureka true로 설정
#    true설정시 서버 자신도 유레카 클라이언트로 등록한다.
#fetchRegistry true로 설정
#    defaultZone의 유레카 서버에서 클라이언트 정보를 가져온다(registerWithEureka가 true로 설정되어야 동작함)
#profile 추가하여 서로 참조하도록 serviceUrl.defaultZone 설정
#self preservation
 
#spring:
#  application:
#    name: customer-service
#
#eureka:
#  client:
#    serviceUrl:
#      defaultZone: http://eureka1:8761/eureka/,http://eureka2:8761/eureka/
#    enabled: true
#eureka.client.serviceUrl.defaultZone에 clustering한 유레카 서버 모두 입력
#    heart-beat는 defaultZone의 very first 항목인 eureka1에 만 전송
#여러개의 Eureka에 등록할 경우 defaultZone에 ,(comma)로 구분하여 입력한다.
 
#유레카 (Eureka)는 CAP 정리의 관점에서 AP 시스템입니다. 그러면 레지스트리의 정보가 네트워크 파티션 동안 서버간에 일치하지 않게됩니다. 자체 보존 기능은 이러한 불일치를 최소화하기위한 노력입니다.
#
#자기 보존 정의
#    자체 보존은 Eureka 서버가 특정 임계 값 이상으로 하트 비트 (피어 및 ​​클라이언트 마이크로 서비스에서)를 수신하지 않을 때 레지스트리에서 만료 인스턴스를 중지하는 기능 입니다.
================================================================================Eureka=========================================================================================================
 
cs