Circuit Break – Istio Service Mesh로 구현하기

해당 포스트에서는 Istio Service Mesh를 사용하여 Circuit Break 를 구현하고 예시와 함께 테스트하는 방법에 대한 가이드입니다.

(※ 이 포스트에서는 Istio를 설치하는 과정은 다루지 않습니다.)

목차

1. Istio Circuit Break(회로 차단)
2. 시작하기 전
3. Istio Circuit Break 구성
4. 클라이언트 추가
5. Istio Circuit Break 확인

1. Istio Circuit Break(회로 차단)

회로 차단(Circuit Break)은 탄력적인 마이크로서비스 애플리케이션을 만드는 데 중요한 패턴입니다. 회로 차단을 사용하면 오류, 대기 시간 급증 및 기타 네트워크 특성으로 인한 바람직하지 않은 영향의 영향을 제한하는 애플리케이션을 작성할 수 있습니다.

이 포스트에서는 회로 차단 규칙을 구성한 다음 의도적으로 회로 차단기를 “tripping”하여 구성을 테스트합니다.

2. 시작하기 전

Circuit Break 을 테스트하기 위한 서비스를 배포합니다. 배포 할 httpbin 서비스에 Istio Sidecar를 주입합니다:

$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml)
$ kubectl get pods -A
NAMESPACE       NAME                                  READY   STATUS    RESTARTS         AGE
default         httpbin-6bb666bdb5-8n7n8              2/2     Running   0                11m
...

현재 배포한 httpbin 서비스는 테스트에서 백엔드 역할을 합니다.

3. Istio Circuit Break 구성

Istio에서 Circuit Break을 위해서는 Istio의 DestinationRule 리소스를 정의해야 합니다. DestinationRule은 특정 대상으로의 트래픽에 대한 정책을 정의합니다. (이 구성은 위에서 배포한 httpbin 서비스에 대한 트래픽 정책을 설정합니다.)

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  trafficPolicy:                    # 트래픽 정책 정의
    connectionPool:                 # 연결 풀 정책 정의
      tcp:                          # TCP 연결에 대한 설정
        maxConnections: 1           # 최대 연결 수
      http:                         # HTTP 연결에 대한 설정
        http1MaxPendingRequests: 1  # 처리되지 않은 최대 요청 수
        maxRequestsPerConnection: 1 # 연결 당 최대 요청 수
    outlierDetection:               # 이상 감지
      consecutive5xxErrors: 1       # 연속적인 5xx 에러 감지 횟수
      interval: 1s                  # 이상 감지 주기
      baseEjectionTime: 3m          # 이상이 감지된 인스턴스 제외 시간

이 설정은 어떤 이상 상황이나 과부하가 발생하면 즉시 해당 인스턴스를 트래픽에서 제외하도록 설계되어 있습니다. 이 구성은 매우 제한적인 환경 또는 테스트 환경에서 사용하기 적합합니다.

4. 클라이언트 추가

테스트에서 httpbin 서비스로 트래픽을 보내는 클라이언트를 만듭니다. 여기서 클라이언트는 fortio라는 간단한 부하 테스트 클라이언트입니다. Fortio를 사용하면 나가는 HTTP 호출에 대한 연결 수, 동시성 및 지연을 제어할 수 있습니다. 이 클라이언트를 사용하여 DestinationRule에서 설정한 회로 차단기 정책을 “trip”합니다.

Fortio 서비스에 Istio Sidecar를 주입하여 배포합니다:

$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/sample-client/fortio-deploy.yaml)

클라이언트 (fortio Pod)에 연결하고 fortio 도구를 사용하여 httpbin을 호출합니다:

$ export FORTIO_POD=$(kubectl get pods -l app=fortio -o 'jsonpath={.items[0].metadata.name}')
$ kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio curl -quiet http://httpbin:8000/get

HTTP/1.1 200 OK
server: envoy
date: Thu, 16 May 2024 04:09:50 GMT
content-type: application/json
content-length: 594
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 230

{
  "args": {}, 
  "headers": {
    "Host": "httpbin:8000", 
    "User-Agent": "fortio.org/fortio-1.60.3", 
    "X-B3-Parentspanid": "ec2674b760564bdf", 
    "X-B3-Sampled": "1", 
    "X-B3-Spanid": "28a32220dc3c518b", 
    "X-B3-Traceid": "d9fa9ac429cb958fec2674b760564bdf", 
    "X-Envoy-Attempt-Count": "1", 
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=21ec91fc2302081abf259325c2b71d432bd3d322329fd031a8bf72acb9127600;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
  }, 
  "origin": "127.0.0.6", 
  "url": "http://httpbin:8000/get"
}

요청이 성공했으면, 테스트 준비는 완료됐습니다.

5. Istio Circuit Break 확인

DestinationRule 설정에서 maxConnections: 1 및 http1maxPendingRequests: 1을 지정했습니다. 이러한 규칙은 둘 이상의 연결 및 요청을 동시에 초과하는 경우 istio-proxy가 추가 요청 및 연결을 위해 회로를 열 때 일부 오류가 표시되어야 함을 나타냅니다.

1. 두 개의 동시 연결 (-c 2)로 서비스를 호출하고 20개의 요청(-n 20)을 보냅니다:

$ kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get

{"ts":1715832845.395420,"level":"info","r":1,"file":"logger.go","line":254,"msg":"Log level is now 3 Warning (was 2 Info)"}
Fortio 1.60.3 running at 0 queries per second, 2->2 procs, for 20 calls: http://httpbin:8000/get
Starting at max qps with 2 thread(s) [gomax 2] for exactly 20 calls (10 per thread + 0)
{"ts":1715832845.408731,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715832845.413531,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715832845.640006,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
Ended after 2.092591853s : 20 calls. qps=9.5575
Aggregated Function Time : count 20 avg 0.2080457 +/- 0.0995 min 0.001602731 max 0.459732905 sum 4.16091407
# range, mid point, percentile, count
>= 0.00160273 <= 0.002 , 0.00180137 , 5.00, 1
> 0.003 <= 0.004 , 0.0035 , 10.00, 1
> 0.004 <= 0.005 , 0.0045 , 15.00, 1
> 0.2 <= 0.25 , 0.225 , 90.00, 15
> 0.25 <= 0.3 , 0.275 , 95.00, 1
> 0.45 <= 0.459733 , 0.454866 , 100.00, 1
# target 50% 0.223333
# target 75% 0.24
# target 90% 0.25
# target 99% 0.457786
# target 99.9% 0.459538
Error cases : count 3 avg 0.0032372763 +/- 0.001321 min 0.001602731 max 0.004837482 sum 0.009711829
# range, mid point, percentile, count
>= 0.00160273 <= 0.002 , 0.00180137 , 33.33, 1
> 0.003 <= 0.004 , 0.0035 , 66.67, 1
> 0.004 <= 0.00483748 , 0.00441874 , 100.00, 1
# target 50% 0.0035
# target 75% 0.00420937
# target 90% 0.00458624
# target 99% 0.00481236
# target 99.9% 0.00483497
# Socket and IP used for each connection:
[0]   3 socket used, resolved to 10.107.165.49:8000, connection timing : count 3 avg 0.00048484433 +/- 4.599e-05 min 0.000432365 max 0.000544363 sum 0.001454533
[1]   2 socket used, resolved to 10.107.165.49:8000, connection timing : count 2 avg 0.0009095145 +/- 0.0001995 min 0.000710006 max 0.001109023 sum 0.001819029
Connection time (s) : count 5 avg 0.0006547124 +/- 0.0002459 min 0.000432365 max 0.001109023 sum 0.003273562
Sockets used: 5 (for perfect keepalive, would be 2)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.107.165.49:8000: 5
Code 200 : 17 (85.0 %)
Code 503 : 3 (15.0 %)
Response Header Sizes : count 20 avg 197.2 +/- 82.84 min 0 max 232 sum 3944
Response Body/Total Sizes : count 20 avg 738.25 +/- 208.9 min 241 max 826 sum 14765
All done 20 calls (plus 0 warmup) 208.046 ms avg, 9.6 qps

Istio-proxy의 성능 덕분에 더의 모든 요청이 처리되었다는 것을 확인할 수 있습니다:

Code 200 : 17 (85.0 %)
Code 503 : 3 (15.0 %)

2. 이번에는 동시 연결 수를 최대 3개로 늘립니다:

$ kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 30 -loglevel Warning http://httpbin:8000/get

{"ts":1715833057.131564,"level":"info","r":1,"file":"logger.go","line":254,"msg":"Log level is now 3 Warning (was 2 Info)"}
Fortio 1.60.3 running at 0 queries per second, 2->2 procs, for 30 calls: http://httpbin:8000/get
Starting at max qps with 3 thread(s) [gomax 2] for exactly 30 calls (10 per thread + 0)
{"ts":1715833057.146253,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.148821,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715833057.152320,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.154565,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715833057.159006,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715833057.164942,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715833057.169078,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715833057.177364,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
{"ts":1715833057.385741,"level":"warn","r":11,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":2,"run":0}
{"ts":1715833057.407692,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.412648,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.418503,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.424979,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.430693,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.434757,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.439285,"level":"warn","r":10,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":1,"run":0}
{"ts":1715833057.616992,"level":"warn","r":9,"file":"http_client.go","line":1104,"msg":"Non ok http code","code":503,"status":"HTTP/1.1 503","thread":0,"run":0}
Ended after 2.563116236s : 30 calls. qps=11.705
Aggregated Function Time : count 30 avg 0.12760624 +/- 0.1533 min 0.001714153 max 0.470499837 sum 3.82818728
# range, mid point, percentile, count
>= 0.00171415 <= 0.002 , 0.00185708 , 6.67, 2
> 0.003 <= 0.004 , 0.0035 , 13.33, 2
> 0.004 <= 0.005 , 0.0045 , 30.00, 5
> 0.005 <= 0.006 , 0.0055 , 46.67, 5
> 0.006 <= 0.007 , 0.0065 , 50.00, 1
> 0.007 <= 0.008 , 0.0075 , 53.33, 1
> 0.008 <= 0.009 , 0.0085 , 56.67, 1
> 0.2 <= 0.25 , 0.225 , 83.33, 8
> 0.25 <= 0.3 , 0.275 , 90.00, 2
> 0.4 <= 0.45 , 0.425 , 93.33, 1
> 0.45 <= 0.4705 , 0.46025 , 100.00, 2
# target 50% 0.007
# target 75% 0.234375
# target 90% 0.3
# target 99% 0.467425
# target 99.9% 0.470192
Error cases : count 17 avg 0.0048072972 +/- 0.001684 min 0.001714153 max 0.008241827 sum 0.081724053
# range, mid point, percentile, count
>= 0.00171415 <= 0.002 , 0.00185708 , 11.76, 2
> 0.003 <= 0.004 , 0.0035 , 23.53, 2
> 0.004 <= 0.005 , 0.0045 , 52.94, 5
> 0.005 <= 0.006 , 0.0055 , 82.35, 5
> 0.006 <= 0.007 , 0.0065 , 88.24, 1
> 0.007 <= 0.008 , 0.0075 , 94.12, 1
> 0.008 <= 0.00824183 , 0.00812091 , 100.00, 1
# target 50% 0.0049
# target 75% 0.00575
# target 90% 0.0073
# target 99% 0.00820072
# target 99.9% 0.00823772
# Socket and IP used for each connection:
[0]   8 socket used, resolved to 10.107.165.49:8000, connection timing : count 8 avg 0.00083728063 +/- 0.0005726 min 0.000248855 max 0.001819578 sum 0.006698245
[1]   9 socket used, resolved to 10.107.165.49:8000, connection timing : count 9 avg 0.000497115 +/- 0.0001185 min 0.000304218 max 0.000700618 sum 0.004474035
[2]   2 socket used, resolved to 10.107.165.49:8000, connection timing : count 2 avg 0.0007440905 +/- 0.0003643 min 0.000379751 max 0.00110843 sum 0.001488181
Connection time (s) : count 19 avg 0.00066634005 +/- 0.0004303 min 0.000248855 max 0.001819578 sum 0.012660461
Sockets used: 19 (for perfect keepalive, would be 3)
Uniform: false, Jitter: false, Catchup allowed: true
IP addresses distribution:
10.107.165.49:8000: 19
Code 200 : 13 (43.3 %)
Code 503 : 17 (56.7 %)
Response Header Sizes : count 30 avg 100.53333 +/- 115 min 0 max 232 sum 3016
Response Body/Total Sizes : count 30 avg 494.5 +/- 289.9 min 241 max 826 sum 14835
All done 30 calls (plus 0 warmup) 127.606 ms avg, 11.7 qps

이제 예상되는 회로 차단 동작이 표시되기 시작합니다. 요청 중 43.3% 만이 성공했고, 나머지는 회로 차단으로 인해 트랩되었습니다.

또한 istio-proxy 통계에서 자세한 내용을 확인할 수 있습니다:

$ kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending

cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.remaining_pending: 1
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_active: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_failure_eject: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_overflow: 101
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_total: 160

Istio에 대한 컨설팅이나 기술지원이 필요하신 경우 아래 폼을 통해 신청하거나 NGINX STORE에 연락하여 논의하십시오.