Istio Hands-on Study [1기]

[3주차] 복원력(애플리케이션 네트워킹 문제 해결) : Transparent 시간 초과 및 재시도

구구달스 2025. 4. 20. 21:13
CloudNet@ 가시다님이 진행하는 Istio Hands-on Study [1기]

⏱️ Istio로 Timeout 관리하기:
분산 환경의 지연 제어

분산 시스템에서 **지연(Latency)**은 서비스 성능을 저하시키고 연쇄 장애를 유발할 수 있는 주요 요인입니다.

이를 방지하기 위해 Timeout 설정은 필수적입니다. 

Istio를 사용해 서비스 호출에 Timeout을 설정하는 방법을 설명하고, 실제로 테스트해보겠습니다.


🌐 Timeout의 중요성과 동작 원리

  • 분산 환경에서는 서비스 호출이 느려지면 자원이 오래 점유되고, 서비스가 밀리며, 최악의 경우 연쇄 장애(Cascading Failures)가 발생할 수 있습니다. Timeout연결 또는 요청에 최대 허용 시간을 설정해 이러한 문제를 방지합니다.
  • Timeout 설정 시 주의할 점은 서비스 호출 간 상호작용입니다. 예를 들어:
    • 서비스 A가 서비스 B를 1초 Timeout으로 호출하고,
    • 서비스 B가 서비스 C를 2초 Timeout으로 호출한다면,
  • 가장 제한적인 Timeout(1초)이 먼저 적용됩니다. 따라서 서비스 B→C의 2초 Timeout은 발동되지 않을 수 있습니다.
    일반적으로 시스템의 Edge(외부 요청이 들어오는 지점)에서는 긴 Timeout을, 내부 호출에서는 짧은 Timeout을 설정하는 것이 좋습니다.
Timeout은 지연으로 인한 연쇄 장애를 방지하며,
Edge에서 긴 Timeout,내부에서 짧은 Timeout을 설정하는 것이 효과적입니다.

🛠️ 테스트 환경 초기화

  • Timeout 설정을 테스트하기 위해 환경을 초기화합니다.
    simple-web와 simple-backend 서비스를 배포하고, 이전에 설정한 DestinationRule을 삭제합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-web.yaml -n istioinaction
kubectl apply -f ch6/simple-backend.yaml -n istioinaction
kubectl delete destinationrule simple-backend-dr -n istioinaction
serviceaccount/simple-web unchanged
service/simple-web unchanged
deployment.apps/simple-web configured
serviceaccount/simple-backend unchanged
service/simple-backend unchanged
deployment.apps/simple-backend-1 configured
deployment.apps/simple-backend-2 configured
destinationrule.networking.istio.io "simple-backend-dr" deleted
  • 서비스를 호출해 정상 응답(HTTP 200)응답 시간을 확인합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.171s
user    0m0.009s
sys     0m0.000s
200

real    0m0.205s
user    0m0.003s
sys     0m0.012s
200

real    0m0.180s
user    0m0.007s
sys     0m0.000s
200
...
  • 응답은 HTTP 200이며, 응답 시간은 약 10~20ms로 안정적입니다.
환경을 초기화하고 서비스 호출을 테스트해 정상 응답과 응답 시간을 확인합니다.

🐢 지연 서비스 배포

  • 실제 환경에서 지연을 시뮬레이션하기 위해 simple-backend-1 서비스를 수정해 50% 확률로 1초 지연을 추가합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-delayed.yaml
...
        - name: "TIMING_VARIANCE"
          value: "10ms" #각 요청의 응답 시간이 기준 시간에서 최대 10ms까지 무작위로 변할 수 있음
        - name: "TIMING_50_PERCENTILE"
          value: "1000ms" # 요청의 50%가 약 1초 안에 응답
...

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction
deployment.apps/simple-backend-1 configured

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
TIMING_VARIANCE=10ms
TIMING_50_PERCENTILE=150ms
  • 다시 서비스를 호출합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m1.021s
user    0m0.009s
sys     0m0.000s
200

real    0m0.180s
user    0m0.012s
sys     0m0.000s
200
...

 

  • 일부 호출은 1초 이상 걸리며, 여전히 HTTP 200을 반환합니다. 하지만 지연이 5초, 100초로 늘어난다면 서비스에 큰 부담이 될 수 있습니다.
지연을 추가한 서비스를 배포해 일부 호출이 1초 이상 걸리는 상황을 시뮬레이션합니다.

⏰ Istio로 Timeout 설정하기

  • Istio의 VirtualService 리소스를 사용해 simple-backend 호출에 0.5초 Timeout을 설정합니다.
    이를 통해 요청이 0.5초를 초과하면 실패하도록 만듭니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-vs-timeout.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend #이 라우팅 규칙이 적용될 대상 서비스를 지정
  http:
  - route: #트래픽을 어디로 보낼지 지정
    - destination: #트래픽의 목적지
        host: simple-backend #목적지 서비스의 이름이 'simple-backend'
    timeout: 0.5s #timeout은 요청에 대한 최대 대기 시간
  • 설정을 적용합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-vs-timeout.yaml -n istioinaction -n istioinaction
virtualservice.networking.istio.io/simple-backend-vs created
  • 다시 서비스를 호출합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.174s
user    0m0.011s
sys     0m0.000s
500

real    0m0.547s
user    0m0.016s
sys     0m0.000s
200

real    0m0.176s
user    0m0.011s
sys     0m0.000s
200
...

 

  • 응답 시간이 0.5초로 제한되었지만, Timeout을 초과한 호출은 HTTP 500 에러를 반환합니다.

VirtualService로 0.5초 Timeout을 설정하면 응답 시간이 제한되지만,
초과 시 HTTP 500 에러가 발생합니다.

📌핵심 요약

  • 분산 시스템에서 지연(Latency) 은 연쇄 장애를 유발할 수 있으므로 Timeout 설정이 필수적입니다.
  • Timeout은 Edge에서 길게, 내부 호출에서 짧게 설정하는 것이 효과적입니다.
  • 실습에서는 simple-web와 simple-backend 서비스를 배포하고, simple-backend-1에 1초 지연을 추가해 지연 상황을 시뮬레이션했습니다.
  • Istio의 VirtualService를 사용해 simple-backend 호출에 0.5초 Timeout을 설정했으며, 결과적으로 응답 시간이 0.5초로 제한되었지만 Timeout 초과 시 HTTP 500 에러가 발생했습니다.
  • Istio의 Timeout 설정은 서비스 안정성을 높이는 강력한 도구입니다.

🔄 Istio로 Retry 정책 설정하기:
서비스 안정성 강화

분산 시스템에서는 네트워크 문제나 일시적인 장애로 요청이 실패할 수 있습니다.

Retry 정책은 이러한 실패를 극복해 사용자 경험을 개선하지만, 잘못된 설정은 시스템 부하를 가중시킬 수 있습니다.


🌐 Retry의 역할과 주의점

  • 서비스 호출 중 일시적인 네트워크 장애가 발생하면, 요청을 재시도(Retry)해 성공 가능성을 높일 수 있습니다. 이는 사용자 경험을 개선하는 데 유용하지만, 무제한 Retry는 시스템에 부담을 주고 연쇄 장애(Cascading Failures)를 유발할 수 있습니다. 예를 들어, 과부하 상태의 서비스에 Retry를 반복하면 상황이 더 악화될 수 있습니다.
  • Istio는 Retry 정책을 통해 이 균형을 조정할 수 있도록 다양한 설정을 제공합니다.
    Retry를 설정할 때는 재시도 횟수, 재시도 조건, 타임아웃 등을 신중히 고려해야 합니다.
Retry는 일시적 장애를 극복해 사용자 경험을 개선하지만,
과도한 Retry는 시스템 부하를 증가시키므로 적절히 설정해야 합니다.

그림 : simple-web이 simple-backend를 호출할 때, simple-backend-1이 75% 확률로 HTTP 503 에러를 반환하는 상황을 보여줍니다.


🛠️ 테스트 환경 초기화

  • Retry 테스트를 위해 환경을 초기화합니다. simple-web와 simple-backend 서비스를 기본 상태로 배포합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-web.yaml -n istioinaction
kubectl apply -f ch6/simple-backend.yaml -n istioinaction
serviceaccount/simple-web unchanged
service/simple-web unchanged
deployment.apps/simple-web unchanged
serviceaccount/simple-backend unchanged
service/simple-backend unchanged
deployment.apps/simple-backend-1 configured
deployment.apps/simple-backend-2 unchanged
  • Istio는 기본적으로 최대 2회의 Retry를 수행합니다. 이 기본 동작을 이해하기 위해, 먼저 Retry를 비활성화합니다.
    Istio 1.12.0 이상에서는 istioctl로 기본 Retry 설정을 변경할 수 있습니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ docker exec -it myk8s-control-plane bash
root@myk8s-control-plane:/# istioctl install --set profile=default --set meshConfig.defaultHttpRetryPolicy.attempts=0
This will install the Istio 1.17.8 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete                                                                             Making this installation the default for injection and validation.

Thank you for installing Istio 1.17.  Please take a few minutes to tell us about your install/upgrade experience!  https://forms.gle/hMHGiwZHPU7UQRWe9
root@myk8s-control-plane:/# exit
exit
환경을 초기화하고 기본 Retry를 비활성화해 Retry 동작을 테스트할 준비를 합니다.

🐢 주기적 장애 서비스 배포

  • 테스트를 위해 simple-backend-1 서비스를 수정해 75% 확률로 HTTP 503 에러를 반환하도록 설정합니다:
    • HTTP 요청을 받음
    • 75% 확률로 HTTP 503 (Service Unavailable) 에러를 발생시킴
    • 나머지 25%의 경우는 "Hello from simple-backend-1" 메시지를 정상적으로 응답함
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-periodic-failure-503.yaml
...
    spec:
      serviceAccountName: simple-backend
      containers:
      - env:
        - name: "LISTEN_ADDR"
          value: "0.0.0.0:8080"
        - name: "SERVER_TYPE"
          value: "http"
        - name: "NAME"
          value: "simple-backend"
        - name: "MESSAGE"
          value: "Hello from simple-backend-1"
        - name: "ERROR_TYPE"
          value: "http_error" 
        - name: "ERROR_RATE"
          value: "0.75" #75%의 확률로 에러 발생
        - name: "ERROR_CODE"
          value: "503" #발생시킬 HTTP 에러 코드 (503: Service Unavailable)
...

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-periodic-failure-503.yaml -n istioinaction
deployment.apps/simple-backend-1 configured
  • 서비스를 호출해 결과를 확인합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.169s
user    0m0.003s
sys     0m0.006s
500

real    0m0.017s
user    0m0.004s
sys     0m0.007s
200

real    0m0.169s
user    0m0.007s
sys     0m0.000s
200

real    0m0.177s
user    0m0.013s
sys     0m0.000s
500
...

 

  • 일부 호출이 HTTP 500 에러를 반환합니다. 이는 simple-backend-1의 주기적 장애(HTTP 503) 때문입니다.

simple-backend-1에 주기적 장애(HTTP 503)를 추가해
Retry 정책의 효과를 테스트할 준비를 합니다.

🔄 Istio의 기본 Retry 동작

  • Istio의 기본 Retry 정책은 특정 상황에서 최대 2회 재시도를 수행합니다. Retry가 적용되는 기본 상황은 다음과 같습니다:
    • connect-failure: 연결 실패
    • refused-stream: 스트림 거부
    • unavailable: gRPC 상태 코드 14
    • cancelled: gRPC 상태 코드 1
    • retriable-status-codes: 기본적으로 HTTP 503
  • 이전 단계에서 Retry를 비활성화했으므로, 이제 명시적으로 Retry를 활성화합니다.
    VirtualService를 사용해 simple-backend 호출에 최대 2회 Retry를 설정합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-enable-retry.yamlapiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries: #retries는 요청이 실패했을 때 재시도하는 방법을 설정
      attempts: 2 #초기 요청이 실패하면 최대 2번까지 재시도
  • 설정을 적용합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-enable-retry.yaml -n istioinaction
virtualservice.networking.istio.io/simple-backend-vs configured
  • 다시 서비스를 호출합니다:
for i in {1..10}; do curl -s \
  -H "Host: simple-web.istioinaction.io" localhost \
  | jq .code; printf "\n"; done


(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.167s
user    0m0.010s
sys     0m0.000s
200

real    0m0.174s
user    0m0.012s
sys     0m0.000s
200

real    0m0.193s
user    0m0.013s
sys     0m0.000s
200
...
  • HTTP 503 에러가 Retry 덕분에 모두 HTTP 200으로 해결되었습니다.

Retry 정책을 활성화하면 HTTP 503과 같은 retriable 에러가 발생해도
재시도를 통해 성공 응답을 받을 수 있습니다.

🛠️ Retry 정책 세부 설정

  • Istio는 Retry 동작을 세밀히 제어할 수 있는 다양한 설정을 제공합니다.
    다음은 Retry 정책의 주요 파라미터를 포함VirtualService 예시입니다:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: gateway-error,connect-failure,retriable-4xx #어떤 종류의 오류가 발생했을 때 재시도할지 지정
      #gateway-error: 게이트웨이 오류(일반적으로 502, 503, 504 응답 코드)가 발생하면 재시도
      #connect-failure: 연결 실패가 발생하면 재시도
      #retriable-4xx: 재시도 가능한 4xx 오류(일반적으로 409 응답 코드)가 발생하면 재시도
      perTryTimeout: 300ms #각 시도는 0.3초 안에 완료되어야 하며, 그렇지 않으면 해당 시도는 타임아웃 처리
      retryRemoteLocalities: true #첫 번째 시도가 실패한 경우 다른 지역에 위치한 서비스 인스턴스에도 재시도
  • attempts: 최대 재시도 횟수
  • retryOn: Retry를触发하는 조건(예: HTTP 상태 코드, gRPC 코드)
  • perTryTimeout: 각 Retry 시도별 타임아웃
  • retryRemoteLocalities: 다른 지역의 엔드포인트로 Retry 허용 여부
  • 이 설정은 Retry 동작을 더 정교하게 제어합니다. Envoy 문서에서 retryOn 설정에 대한 자세한 내용을 확인할 수 있습니다.
Retry 정책은 재시도 횟수, 조건, 타임아웃, 지역 간 재시도 여부 등을 설정해
세밀한 제어가 가능합니다.

⚠️ HTTP 500 에러에 대한 Retry 설정

  • 기본 Retry 정책은 HTTP 500 에러를 재시도하지 않습니다.
    이를 테스트하기 위해 simple-backend-1HTTP 500 에러를 75% 확률로 반환하도록 수정합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-periodic-failure-500.yaml
...
    spec:
      serviceAccountName: simple-backend
      containers:
      - env:
        - name: "LISTEN_ADDR"
          value: "0.0.0.0:8080"
        - name: "SERVER_TYPE"
          value: "http"
        - name: "NAME"
          value: "simple-backend"
        - name: "MESSAGE"
          value: "Hello from simple-backend-1"
        - name: "ERROR_TYPE"
          value: "http_error"
        - name: "ERROR_RATE"
          value: "0.75"
        - name: "ERROR_CODE"
          value: "500"
...

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-periodic-failure-500.yaml -n istioinaction
deployment.apps/simple-backend-1 configured
  • 서비스를 호출합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.172s
user    0m0.011s
sys     0m0.001s
200

real    0m0.175s
user    0m0.004s
sys     0m0.011s
200

real    0m0.175s
user    0m0.012s
sys     0m0.001s
500
...

  • HTTP 500 에러가 그대로 반환됩니다. 이를 해결하기 위해 HTTP 500 에러에도 Retry를 적용하도록 VirtualService를 수정합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-vs-retry-500.yaml
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2 #실패 시 최대 2번까지 재시도
      retryOn: 5xx #HTTP 5xx 에러(서버 내부 오류 등)가 발생할 경우에만 재시도
  • 설정을 적용합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction
virtualservice.networking.istio.io/simple-backend-vs configured
  • 다시 호출합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.191s
user    0m0.003s
sys     0m0.008s
200

real    0m0.177s
user    0m0.012s
sys     0m0.001s
200

real    0m0.169s
user    0m0.000s
sys     0m0.012s
200
...
  • HTTP 500 에러가 Retry 덕분에 모두 HTTP 200으로 해결되었습니다.

HTTP 500 에러에 Retry를 적용하려면
retryOn: 5xx를 설정해 기본 동작을 확장해야 합니다.

⏰ Retry와 Timeout의 상호작용

  • 각 Retry 시도는 perTryTimeout을 가지며, 전체 요청의 Timeout과 조화를 이루어야 합니다. 예를 들어:
    • 전체 Timeout이 1초이고,
    • Retry 횟수가 3회, 각 perTryTimeout이 500ms라면,
  • 전체 Timeout(1초)이 먼저 적용되어 모든 Retry가 완료되지 않을 수 있습니다.
    또한, Retry 간에는 Backoff Delay(기본 25ms x 시도 번호)가 적용되어 전체 Timeout에 영향을 줍니다.

 

그림 : 요청 실패 시 Retry와 Backoff Delay가 적용되는 흐름을 보여줍니다.

 

Retry의 perTryTimeout과 Backoff Delay는 전체 Timeout보다 짧아야 하며,
이를 고려해 설정해야 합니다.

⚡ Thundering Herd 문제와 Retry 전략

  • Istio의 기본 Retry 설정(최대 2회)Thundering Herd 문제를 일으킬 수 있습니다.
    예를 들어, 5단계 호출 체인에서 각 단계가 2회 Retry를 하면, 단일 요청이 최대 32개 요청으로 증폭될 수 있습니다.

그림 : Retry가 호출 체인에서 증폭되어 과부하를 유발하는 Thundering Herd 문제를 보여줍니다.

 

  • 이를 방지하기 위한 전략:
    • Edge에서 Retry 제한: 외부 요청에서 Retry를 1회 또는 비활성화.
    • 내부에서만 Retry: 호출 체인 깊은 곳에서만 Retry 허용.
    • Retry Budget: 전체 Retry 횟수를 제한(현재 Istio API에서 미지원, 별도 해결책 필요).
  • 또한, retryRemoteLocalities: true를 설정하면 Retry가 다른 지역의 엔드포인트로 확장되어, 로컬 엔드포인트가 비정상일 때 유용합니다.
Thundering Herd 문제를 피하려면 Edge에서 Retry를 제한하고,
retryRemoteLocalities를 활용해 지역 간 Retry를 허용할 수 있습니다.

📌핵심 요약

  • Istio의 Retry 정책은 일시적 장애를 극복해 서비스 안정성을 높이지만, 과도한 Retry는 Thundering Herd 문제를 일으킬 수 있습니다.
  • 실습에서는 simple-backend-1에 주기적 장애(HTTP 503, HTTP 500)를 추가하고, VirtualService로 Retry 정책을 설정했습니다.
  • 기본 Retry는 HTTP 503에 적용되지만, HTTP 500에는 retryOn: 5xx를 추가해야 작동합니다.
  • Retry는 attempts, retryOn, perTryTimeout, retryRemoteLocalities로 세밀히 제어 가능하며, 전체 Timeout과 Backoff Delay를 고려해야 합니다.
  • Thundering Herd 문제를 피하려면 Edge에서 Retry를 제한하거나 지역 간 Retry를 활용하는 전략이 필요합니다.
  • Istio의 Retry 설정은 서비스 신뢰성을 높이는 강력한 도구입니다.

🚀 Istio로 고급 Retry 설정하기:
EnvoyFilter와 Request Hedging

Istio의 Retry 정책은 네트워크 장애를 극복하는 데 유용하지만, 기본 설정으로는 조정할 수 없는 세부 옵션도 있습니다.

이번 글에서는 EnvoyFilter를 사용해 Retry의 Backoff 시간Retriable Status Code를 조정하고, 고급 기능인 Request Hedging을 설정하는 방법을 설명합니다


🛠️ EnvoyFilter로 Retry 설정 확장

  • Istio의 기본 Retry 정책은 Backoff 시간(기본 25ms)과 Retriable Status Code(기본 HTTP 503)를 고정적으로 사용합니다.
    하지만 Istio API에서 직접 지원하지 않는 이러한 설정을 EnvoyFilter를 통해 Envoy 구성에 직접 적용할 수 있습니다.
  • 다음은 simple-backend 서비스에 HTTP 400과 408을 Retriable Status Code로 추가하고, Backoff 시간을 50ms로 설정하는 EnvoyFilter 예시입니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-ef-retry-status-codes.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: simple-backend-retry-status-codes
  namespace: istioinaction
spec:
  workloadSelector: #필터가 적용될 워크로드(Pod)를 선택
    labels:
      app: simple-web
  configPatches: #Envoy 설정에 적용할 패치(변경사항)들을 정의
  - applyTo: HTTP_ROUTE #Envoy의 HTTP 라우트 구성을 수정
    match: #패치를 적용할 정확한 위치를 지정
      context: SIDECAR_OUTBOUND #이 패치가 사이드카 프록시의 아웃바운드(외부로 나가는) 트래픽 경로에 적용됨
      routeConfiguration:
        vhost:
          name: "simple-backend.istioinaction.svc.cluster.local:80"
          #'simple-backend.istioinaction.svc.cluster.local:80'이라는 가상 호스트(vhost)에 대한 라우트 설정만 패치
          # 'istioinaction' 네임스페이스의 'simple-backend' 서비스로 가는 80번 포트의 트래픽을 의미
    patch: #patch는 실제로 어떤 변경을 적용할지 정의
      operation: MERGE #기존 설정에 새로운 값을 병합(추가 또는 덮어쓰기)한다는 의미
      value:
        route:
          retry_policy: #라우트의 재시도 정책을 설정
            retry_back_off: #재시도 간격을 설정
              base_interval: 50ms #첫 번째 재시도까지의 기본 대기 시간이 50밀리초(0.05초)임
            retriable_status_codes: #어떤 HTTP 상태 코드에 대해 재시도할지 정의
            - 408 #"Request Timeout" 오류가 발생하면 재시도
            - 400 #"Bad Request" 오류가 발생하면 재시도

주의: EnvoyFilter는 Istio의 추상화된 API를 우회하는 "Break Glass" 솔루션이며, Envoy API는 Istio 릴리스 간 호환성이 보장되지 않을 수 있습니다. 프로덕션 환경에서는 철저한 검증이 필요합니다. 

  • 408 에러코드를 발생합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-periodic-failure-408.yaml
...
    spec:
      serviceAccountName: simple-backend
      containers:
      - env:
        - name: "LISTEN_ADDR"
          value: "0.0.0.0:8080"
        - name: "SERVER_TYPE"
          value: "http"
        - name: "NAME"
          value: "simple-backend"
        - name: "MESSAGE"
          value: "Hello from simple-backend-1"
        - name: "ERROR_TYPE"
          value: "http_error"
        - name: "ERROR_RATE"
          value: "0.75"
        - name: "ERROR_CODE"
          value: "408"
...

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-periodic-failure-408.yaml -n istioinaction
deployment.apps/simple-backend-1 configured
  • 다시 호출합니다:
  • 408 에러는 retryOn: 5xx 에 포함되지 않으므로 에러를 리턴합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.169s
user    0m0.009s
sys     0m0.001s
200

real    0m0.176s
user    0m0.000s
sys     0m0.014s
500

real    0m0.023s
user    0m0.013s
sys     0m0.001s
200
...

EnvoyFilter를 사용하면
Istio API에서 지원하지 않는 Backoff 시간과 Retriable Status Code를 조정할 수 있지만,
프로덕션에서는 주의가 필요합니다.

🔄 VirtualService에서 Retriable Status Code 반영

  • EnvoyFilter 리소스를 적용합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-ef-retry-status-codes.yaml -n istioinaction
envoyfilter.networking.istio.io/simple-backend-retry-status-codes created

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl get envoyfilter -n istioinaction
NAME                                AGE
simple-backend-retry-status-codes   6s
  • EnvoyFilterHTTP 400과 408을 Retriable Status Code로 추가했으므로, VirtualService에서 이를 반영해야 합니다. retryOn에 retriable-status-codes를 추가합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-vs-retry-on.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2 #요청이 실패했을 때 최대 2번까지 다시 시도함 
      retryOn: 5xx,retriable-status-codes
      #HTTP 5xx 에러(서버 오류)가 발생하면 재시도함
      #사용자가 지정한 상태 코드에 대해서도 재시도하도록 허용함
  • 설정을 적용합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction
virtualservice.networking.istio.io/simple-backend-vs configured
  • 다시 호출합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.167s
user    0m0.000s
sys     0m0.009s
500

real    0m0.013s
user    0m0.007s
sys     0m0.000s
200
...
VirtualService의 retryOn에 retriable-status-codes를 추가해
EnvoyFilter에서 설정한 Retriable Status Code를 활성화합니다.

🐢 HTTP 408 에러 테스트

  • 이제 simple-backend-1 서비스를 수정해 75% 확률로 HTTP 408(Timeout) 에러를 반환하도록 설정하고, Retry 정책이 이를 처리하는지 확인합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ cat ch6/simple-backend-periodic-failure-408.yaml
...
    spec:
      serviceAccountName: simple-backend
      containers:
      - env:
        - name: "LISTEN_ADDR"
          value: "0.0.0.0:8080"
        - name: "SERVER_TYPE"
          value: "http"
        - name: "NAME"
          value: "simple-backend"
        - name: "MESSAGE"
          value: "Hello from simple-backend-1"
        - name: "ERROR_TYPE"
          value: "http_error"
        - name: "ERROR_RATE"
          value: "0.75"
        - name: "ERROR_CODE"
          value: "408"
...

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ kubectl apply -f ch6/simple-backend-periodic-failure-408.yaml
deployment.apps/simple-backend-1 created
  • 서비스를 호출합니다:
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~/aews-labs/istio-in-action/book-source-code-master$ for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
200

real    0m0.169s
user    0m0.009s
sys     0m0.001s
200

real    0m0.172s
user    0m0.011s
sys     0m0.001s
200

real    0m0.174s
user    0m0.008s
sys     0m0.005s
200
  • HTTP 408 에러가 Retry 덕분에 모두 HTTP 200으로 해결되었습니다.
    이는 EnvoyFilter와 VirtualService 설정이 제대로 작동했음을 보여줍니다.

EnvoyFilter로 HTTP 408을 Retriable Status Code로 설정하고,
VirtualService에서 이를 반영해 HTTP 408 에러를 성공적으로 처리했습니다.

🏁 Request Hedging: 고급 Retry 전략

  • Request Hedging은 요청이 타임아웃될 때, 다른 엔드포인트로 추가 요청을 보내 "경쟁"시키는 고급 Retry 전략입니다.
    예를 들어:
    • 원래 요청이 타임아웃되면, Envoy가 다른 엔드포인트로 새 요청을 보냅니다.
    • 새 요청이 성공하면 그 응답을 반환하고, 원래 요청이 먼저 성공하면 원래 응답을 반환합니다.
  • Istio API는 Request Hedging을 직접 지원하지 않지만, EnvoyFilter로 설정할 수 있습니다.
    다음은 simple-backend에 Request Hedging을 활성화하는 예시입니다:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: simple-backend-retry-hedge
  namespace: istioinaction
spec:
  workloadSelector: #이 필터가 적용될 워크로드(pod)를 선택
    labels:
      app: simple-web #
  configPatches: #Envoy 프록시의 설정에 적용할 변경사항(패치)들의 목록이 시작
  - applyTo: VIRTUAL_HOST #첫 번째 패치는 'VIRTUAL_HOST'에 적용
    match: #이 패치가 적용될 조건을 지정
      context: SIDECAR_OUTBOUND #사이드카 프록시의 아웃바운드 트래픽 흐름에 적용
      routeConfiguration: #라우팅 설정을 지정
        vhost: #가상 호스트 설정을 지정
          name: "simple-backend.istioinaction.svc.cluster.local:80"
          # 'simple-backend' 서비스의 80포트에 대한 가상 호스트 설정을 수정
    patch: #실제 변경사항을 정의
      operation: MERGE #기존 설정에 새 설정을 병합(merge)합니다
      value: #적용할 새 설정 값을 지정
        hedge_policy: #헤지 정책(hedge policy)을 설정
                      #헤지 정책은 요청에 대한 응답이 늦어지면 같은 요청을 다시 보내는 정책
          hedge_on_per_try_timeout: true #청이 타임아웃에 도달했을 때 헤지(hedge) 요청
  • 이 설정은 각 Retry 시도에서 타임아웃(perTryTimeout)이 발생하면 다른 엔드포인트로 요청을 보내도록 합니다.
Request Hedging은 타임아웃 시 다른 엔드포인트로 요청을 보내 경쟁시키는 전략으로,
EnvoyFilter를 통해 설정할 수 있습니다.

📌핵심 요약

  • Istio의 기본 Retry 정책은 Backoff 시간(25ms)과 Retriable Status Code(HTTP 503)를 고정적으로 사용하지만, EnvoyFilter를 통해 이를 확장할 수 있습니다.
  • 실습에서는 EnvoyFilter로 HTTP 400과 408을 Retriable Status Code로 추가하고, Backoff 시간을 50ms로 설정했습니다.
  • VirtualService에서 retryOn: retriable-status-codes를 반영해 HTTP 408 에러를 성공적으로 처리했습니다.
  • 또한, Request Hedging은 타임아웃 시 다른 엔드포인트로 요청을 보내는 고급 전략으로, EnvoyFilter로 설정 가능합니다.
  • 하지만 EnvoyFilter는 Envoy API의 변경 가능성 때문에 프로덕션에서 주의가 필요합니다.
  • 이러한 고급 Retry 설정은 서비스 안정성을 높이는 데 강력하지만, Timeout과 Retry의 상호작용, 시스템 부하를 신중히 고려해야 합니다.