트래픽 관리 문제

트래픽 관리 문제 (Traffic Management Problems)

Istio 메시에서 자주 겪는 트래픽 관리·네트워킹 문제를 해결하는 방법을 배워요. Envoy가 거부하는 요청, 라우트 규칙이 동작하지 않는 경우, destination rule 후 503 오류, 헤드리스 서비스 접근 문제, TLS 구성 실수, EnvoyFilter 갑작스러운 동작 중단 등을 다뤄요.

출처: Istio 문서

본문

Envoy가 요청을 거부하는 경우

요청은 다양한 이유로 거부될 수 있어요. 요청이 거부되는 이유를 이해하는 가장 좋은 방법은 Envoy의 접근 로그(access log)를 확인하는 것이에요. 기본적으로 접근 로그는 컨테이너의 표준 출력으로 출력돼요. 다음 명령으로 로그를 확인하세요:

$ kubectl logs PODNAME -c istio-proxy -n NAMESPACE

기본 접근 로그 형식에서는 Envoy 응답 플래그가 응답 코드 뒤에 위치해요. 커스텀 로그 형식을 사용한다면 %RESPONSE_FLAGS%를 포함했는지 확인하세요.

응답 플래그에 대한 자세한 내용은 Envoy 응답 플래그를 참조하세요.

일반적인 응답 플래그는 다음과 같아요:

  • NR: 구성된 라우트 없음. DestinationRule 또는 VirtualService를 확인하세요.
  • UO: 서킷 브레이킹으로 인한 업스트림 오버플로우. DestinationRule의 서킷 브레이커 구성을 확인하세요.
  • UF: 업스트림에 연결 실패. Istio 인증을 사용한다면 mutual TLS 구성 충돌을 확인하세요.

라우트 규칙이 트래픽 흐름에 영향을 주지 않는 것처럼 보이는 경우

현재 Envoy 사이드카 구현에서는 가중치 기반 버전 분산이 관찰되기까지 최대 100개의 요청이 필요할 수 있어요.

라우트 규칙이 Bookinfo 샘플에서 완벽하게 동작하는데, 비슷한 버전 라우팅 규칙이 자신의 애플리케이션에는 전혀 효과가 없다면, Kubernetes 서비스를 조금 변경해야 할 수 있어요. Kubernetes 서비스는 Istio의 L7 라우팅 기능을 활용하려면 특정 제약을 지켜야 해요. 자세한 내용은 Pods and Services 요구 사항을 참조하세요.

또 다른 잠재적 문제는 라우트 규칙의 적용이 단순히 느릴 수 있다는 것이에요. Kubernetes의 Istio 구현은 모든 라우트 규칙을 포함한 올바른 구성을 모든 Envoy 사이드카가 가지도록 보장하는 최종 일관성(eventually consistent) 알고리즘을 활용해요. 구성 변경이 모든 사이드카에 전파되는 데는 시간이 걸려요. 대규모 배포에서는 전파가 더 오래 걸리고 수 초 단위의 지연이 있을 수 있어요.

destination rule 설정 후 503 오류

DestinationRule을 적용한 직후 서비스에 대한 요청이 HTTP 503 오류를 즉시 생성하기 시작하고, DestinationRule을 제거하거나 되돌릴 때까지 오류가 계속된다면, 그 DestinationRule이 서비스에 TLS 충돌을 일으키는 것일 수 있어요.

예를 들어, 클러스터에서 상호 TLS를 전역적으로 구성했다면 DestinationRule은 다음 trafficPolicy를 포함해야 해요:

trafficPolicy:
  tls:
    mode: ISTIO_MUTUAL

그렇지 않으면 mode가 DISABLE로 기본값이 설정되어 클라이언트 프록시 사이드카가 TLS 암호화 요청 대신 평문 HTTP 요청을 하게 돼요. 따라서 서버 프록시가 암호화된 요청을 기대하기 때문에 요청이 서버 프록시와 충돌해요.

DestinationRule을 적용할 때마다 trafficPolicy TLS mode가 전역 서버 구성과 일치하는지 확인하세요.

라우트 규칙이 인그레스 게이트웨이 요청에 영향을 주지 않는 경우

인그레스 Gateway와 대응하는 VirtualService를 사용해서 내부 서비스에 접근한다고 가정해 봐요. 예를 들어, VirtualService가 다음과 같은 형태일 수 있어요:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - "myapp.com" # or maybe "*" if you are testing without DNS using the ingress-gateway IP (e.g., http://1.2.3.4/hello)
  gateways:
  - myapp-gateway
  http:
  - match:
    - uri:
        prefix: /hello
    route:
    - destination:
        host: helloworld.default.svc.cluster.local
  - match:
    ...

또한 helloworld 서비스의 트래픽을 특정 subset으로 라우팅하는 VirtualService도 있다고 가정해 봐요:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
  - helloworld.default.svc.cluster.local
  http:
  - route:
    - destination:
        host: helloworld.default.svc.cluster.local
        subset: v1

이 상황에서 인그레스 게이트웨이를 통한 helloworld 서비스로의 요청이 subset v1로 향하지 않고 기본 라운드로빈 라우팅을 계속 사용한다는 것을 알게 될 거예요.

인그레스 요청은 게이트웨이 호스트(예: myapp.com)를 사용하며, 이것이 helloworld 서비스의 임의 엔드포인트로 라우팅하는 myapp VirtualService의 규칙을 활성화해요. 호스트가 helloworld.default.svc.cluster.local인 내부 요청만 트래픽을 subset v1으로만 보내는 helloworld VirtualService를 사용해요.

게이트웨이의 트래픽을 제어하려면 myapp VirtualService에도 subset 규칙을 포함해야 해요:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - "myapp.com" # or maybe "*" if you are testing without DNS using the ingress-gateway IP (e.g., http://1.2.3.4/hello)
  gateways:
  - myapp-gateway
  http:
  - match:
    - uri:
        prefix: /hello
    route:
    - destination:
        host: helloworld.default.svc.cluster.local
        subset: v1
  - match:
    ...

또는 가능하다면 두 VirtualService를 하나로 결합할 수도 있어요:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - myapp.com # cannot use "*" here since this is being combined with the mesh services
  - helloworld.default.svc.cluster.local
  gateways:
  - mesh # applies internally as well as externally
  - myapp-gateway
  http:
  - match:
    - uri:
        prefix: /hello
      gateways:
      - myapp-gateway #restricts this rule to apply only to ingress gateway
    route:
    - destination:
        host: helloworld.default.svc.cluster.local
        subset: v1
  - match:
    - gateways:
      - mesh # applies to all services inside the mesh
    route:
    - destination:
        host: helloworld.default.svc.cluster.local
        subset: v1

부하 상태에서 Envoy가 크래시하는 경우

ulimit -a를 확인하세요. 많은 시스템에는 기본적으로 1024개의 열린 파일 디스크립터 제한이 있으며, 이로 인해 Envoy가 assert하고 다음과 같이 크래시해요:

[2017-05-17 03:00:52.735][14236][critical][assert] assert failure: fd_ != -1: external/envoy/source/common/network/connection_impl.cc:58

ulimit을 올리는 것을 잊지 마세요. 예: ulimit -n 16384

Envoy가 HTTP/1.0 서비스에 연결하지 못하는 경우

Envoy는 업스트림 서비스에 대해 HTTP/1.1 또는 HTTP/2 트래픽을 요구해요. 예를 들어, Envoy 뒤에서 NGINX로 트래픽을 서빙할 때는 NGINX 기본값이 1.0이기 때문에 NGINX 구성에서 proxy_http_version 지시어를 "1.1"로 설정해야 해요.

구성 예시:

upstream http_backend {
    server 127.0.0.1:8080;

    keepalive 16;
}

server {
    ...

    location /http/ {
        proxy_pass http://http_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        ...
    }
}

헤드리스 서비스에 접근할 때 503 오류

Istio가 다음 구성으로 설치되었다고 가정해 봐요:

  • 메시 내에서 mTLS mode가 STRICT로 설정됨
  • meshConfig.outboundTrafficPolicy.mode가 ALLOW_ANY로 설정됨

nginx가 default 네임스페이스에 StatefulSet으로 배포되고, 아래와 같이 대응하는 Headless Service가 정의되어 있다고 생각해 봐요:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: http-web  # Explicitly defining an http port
  clusterIP: None   # Creates a Headless Service
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web

Service 정의의 포트 이름 http-web은 해당 포트의 http 프로토콜을 명시적으로 지정해요.

또한 default 네임스페이스에 curl 파드 Deployment가 있다고 가정해 봐요. nginx를 이 curl 파드에서 Pod IP로 접근할 때(이것은 헤드리스 서비스에 접근하는 일반적인 방법 중 하나), 요청은 PassthroughCluster를 통해 서버 쪽으로 가지만, 서버 쪽의 사이드카 프록시는 nginx에 대한 라우트 항목을 찾지 못해 HTTP 503 UC로 실패해요.

$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}')
$ kubectl exec -it $SOURCE_POD -c curl -- curl 10.1.1.171 -s -o /dev/null -w "%{http_code}"
  503

10.1.1.171은 nginx 레플리카 중 하나의 Pod IP이고, 서비스는 containerPort 80으로 접근돼요.

이 503 오류를 피하는 방법은 몇 가지가 있어요:

  1. 올바른 Host 헤더 지정하기: 위 curl 요청의 Host 헤더는 기본적으로 Pod IP가 돼요. 우리의 nginx 요청에서 Host 헤더를 nginx.default로 지정하면 성공적으로 HTTP 200 OK가 반환돼요.
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}')
$ kubectl exec -it $SOURCE_POD -c curl -- curl -H "Host: nginx.default" 10.1.1.171 -s -o /dev/null -w "%{http_code}"
  200
  1. 포트 이름을 tcp 또는 tcp-web 또는 tcp-<custom_name>으로 설정하기: 여기서는 프로토콜이 tcp로 명시적으로 지정돼요. 이 경우 클라이언트 쪽과 서버 쪽 모두에서 사이드카 프록시의 TCP Proxy 네트워크 필터만 사용돼요. HTTP Connection Manager는 전혀 사용되지 않으므로 요청에 어떤 종류의 헤더도 기대하지 않아요. Host 헤더를 명시적으로 설정하든 하지 않든 nginx로의 요청은 성공적으로 HTTP 200 OK를 반환해요. 클라이언트가 요청에 헤더 정보를 포함할 수 없는 특정 시나리오에서 유용해요.
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}')
$ kubectl exec -it $SOURCE_POD -c curl -- curl 10.1.1.171 -s -o /dev/null -w "%{http_code}"
  200
$ kubectl exec -it $SOURCE_POD -c curl -- curl -H "Host: nginx.default" 10.1.1.171 -s -o /dev/null -w "%{http_code}"
  200
  1. Pod IP 대신 도메인 이름 사용하기: 헤드리스 서비스의 특정 인스턴스는 도메인 이름만으로도 접근할 수 있어요.
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}')
$ kubectl exec -it $SOURCE_POD -c curl -- curl web-0.nginx.default -s -o /dev/null -w "%{http_code}"
  200

여기서 web-0은 nginx의 3개 레플리카 중 하나의 파드 이름이에요.

헤드리스 서비스와 다양한 프로토콜의 트래픽 라우팅 동작에 대한 추가 정보는 이 트래픽 라우팅 페이지를 참조하세요.

TLS 구성 실수

많은 트래픽 관리 문제는 부정확한 TLS 구성 때문에 발생해요. 다음 섹션들은 가장 흔한 잘못된 구성 몇 가지를 설명해요.

HTTP 포트에 HTTPS 전송하기

애플리케이션이 HTTP로 선언된 서비스에 HTTPS 요청을 보내면, Envoy 사이드카는 요청을 전달하면서 HTTP로 파싱하려 시도하는데, HTTP가 예기치 않게 암호화되어 있기 때문에 실패할 거예요.

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: httpbin
spec:
  hosts:
  - httpbin.org
  ports:
  - number: 443
    name: http
    protocol: HTTP
  resolution: DNS

포트 443에 의도적으로 평문을 보낸다면(예: curl http://httpbin.org:443) 위 구성이 올바를 수 있지만, 일반적으로 포트 443은 HTTPS 트래픽 전용이에요.

기본적으로 포트 443을 사용하는 curl https://httpbin.org 같은 HTTPS 요청을 보내면 curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number 같은 오류가 발생해요. 접근 로그에도 400 DPE 같은 오류가 표시될 수 있어요.

이를 수정하려면 포트 프로토콜을 HTTPS로 변경해야 해요:

spec:
  ports:
  - number: 443
    name: https
    protocol: HTTPS

게이트웨이와 virtual service 간의 TLS 불일치

virtual service를 게이트웨이에 바인딩할 때 발생할 수 있는 두 가지 일반적인 TLS 불일치가 있어요.

  1. 게이트웨이가 TLS를 종료하는데 virtual service가 TLS 라우팅을 구성하는 경우
  2. 게이트웨이가 TLS 패스쓰루를 하는데 virtual service가 HTTP 라우팅을 구성하는 경우
TLS 종료가 있는 게이트웨이
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    hosts:
      - "*"
    tls:
      mode: SIMPLE
      credentialName: sds-credential
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "*.example.com"
  gateways:
  - istio-system/gateway
  tls:
  - match:
    - sniHosts:
      - "*.example.com"
    route:
    - destination:
        host: httpbin.org

이 예시에서 게이트웨이는 TLS를 종료하는 반면 (게이트웨이의 tls.mode 구성이 PASSTHROUGH가 아닌 SIMPLE), virtual service는 TLS 기반 라우팅을 사용해요. 라우팅 규칙의 평가는 게이트웨이가 TLS를 종료한 후에 발생하므로, 요청이 그 시점에는 HTTPS가 아닌 HTTP이기 때문에 TLS 규칙이 효과가 없어요.

이 잘못된 구성으로는 요청이 HTTP 라우팅으로 보내지지만 구성된 HTTP 라우트가 없기 때문에 404 응답을 받게 될 거예요. 이것은 istioctl proxy-config routes 명령으로 확인할 수 있어요.

이 문제를 해결하려면 virtual service가 tls 대신 http 라우팅을 지정하도록 전환해야 해요:

spec:
  ...
  http:
  - match:
    - headers:
        ":authority":
          regex: "*.example.com"
TLS 패스쓰루가 있는 게이트웨이
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - "*"
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: virtual-service
spec:
  gateways:
  - gateway
  hosts:
  - httpbin.example.com
  http:
  - route:
    - destination:
        host: httpbin.org

이 구성에서 virtual service는 게이트웨이를 통과하는 TLS 트래픽에 대해 HTTP 트래픽을 일치시키려 시도해요. 이로 인해 virtual service 구성이 효과가 없게 돼요. HTTP 라우트가 적용되지 않았다는 것을 istioctl proxy-config listener와 istioctl proxy-config route 명령으로 관찰할 수 있어요.

이를 수정하려면 virtual service가 tls 라우팅을 구성하도록 전환해야 해요:

spec:
  tls:
  - match:
    - sniHosts: ["httpbin.example.com"]
    route:
    - destination:
        host: httpbin.org

또는 패스쓰루 대신 TLS를 종료하도록 게이트웨이의 tls 구성을 전환할 수도 있어요:

spec:
  ...
    tls:
      credentialName: sds-credential
      mode: SIMPLE

이중 TLS (TLS 요청에 대한 TLS 오리지네이션)

Istio가 TLS 오리지네이션을 수행하도록 구성할 때는 애플리케이션이 사이드카에 평문 요청을 보내고, 사이드카가 TLS를 오리지네이트해야 함을 확인해야 해요.

다음 DestinationRule은 httpbin.org 서비스로의 요청에 대해 TLS를 오리지네이트하지만, 대응하는 ServiceEntry는 포트 443에서 프로토콜을 HTTPS로 정의해요.

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: httpbin
spec:
  hosts:
  - httpbin.org
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  resolution: DNS
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: originate-tls
spec:
  host: httpbin.org
  trafficPolicy:
    tls:
      mode: SIMPLE

이 구성으로 사이드카는 애플리케이션이 포트 443에서 TLS 트래픽(예: curl https://httpbin.org)을 보낼 것을 기대하지만, 요청을 전달하기 전에 TLS 오리지네이션도 수행해요. 이로 인해 요청이 이중으로 암호화될 거예요.

예를 들어 curl https://httpbin.org 같은 요청을 보내면 (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number라는 오류가 발생해요.

이 예시는 ServiceEntry의 포트 프로토콜을 HTTP로 변경해서 수정할 수 있어요:

spec:
  hosts:
  - httpbin.org
  ports:
  - number: 443
    name: http
    protocol: HTTP

이 구성으로는 TLS 오리지네이션이 포트를 변경하지 않기 때문에 애플리케이션이 curl http://httpbin.org:443처럼 포트 443으로 평문 요청을 보내야 한다는 점에 주의하세요. 그러나 Istio 1.8부터는 HTTP 포트 80을 애플리케이션에 노출하고(예: curl http://httpbin.org) 요청을 targetPort 443으로 리디렉션해서 TLS 오리지네이션할 수 있어요:

spec:
  hosts:
  - httpbin.org
  ports:
  - number: 80
    name: http
    protocol: HTTP
    targetPort: 443

동일한 TLS 인증서로 여러 게이트웨이를 구성하면 404 오류 발생

HTTP/2 연결 재사용을 활용하는 브라우저(즉, 대부분의 브라우저)에서 동일한 TLS 인증서를 사용하는 게이트웨이를 둘 이상 구성하면, 다른 호스트와 연결이 이미 수립된 후 두 번째 호스트에 접근할 때 404 오류가 발생해요.

예를 들어, 동일한 TLS 인증서를 공유하는 2개의 호스트가 있다고 가정해 봐요:

  • istio-ingressgateway에 설치된 와일드카드 인증서 *.test.com
  • 호스트 service1.test.com, selector istio: ingressgateway, 게이트웨이의 마운트된(와일드카드) 인증서를 사용하는 TLS를 가진 Gateway 구성 gw1
  • 호스트 service2.test.com, selector istio: ingressgateway, 게이트웨이의 마운트된(와일드카드) 인증서를 사용하는 TLS를 가진 Gateway 구성 gw2
  • 호스트 service1.test.com과 게이트웨이 gw1을 가진 VirtualService 구성 vs1
  • 호스트 service2.test.com과 게이트웨이 gw2를 가진 VirtualService 구성 vs2

두 게이트웨이가 같은 워크로드(즉, selector istio: ingressgateway)로 서빙되므로 두 서비스(service1.test.com과 service2.test.com)로의 요청은 같은 IP로 확인돼요. service1.test.com을 먼저 접근하면 service2.test.com으로의 연결이 같은 인증서를 사용할 수 있음을 나타내는 와일드카드 인증서(*.test.com)를 반환할 거예요. 따라서 Chrome과 Firefox 같은 브라우저는 service2.test.com으로의 요청에 대해 기존 연결을 재사용할 거예요. gw1 게이트웨이는 service2.test.com에 대한 라우트가 없기 때문에 404(Not Found) 응답을 반환해요.

이 문제를 피하려면 둘(gw1과 gw2) 대신 단일 와일드카드 Gateway를 구성하면 돼요. 그러면 두 VirtualService를 모두 여기에 바인딩하면 돼요:

  • 호스트 *.test.com, selector istio: ingressgateway, 게이트웨이의 마운트된(와일드카드) 인증서를 사용하는 TLS를 가진 Gateway 구성 gw
  • 호스트 service1.test.com과 게이트웨이 gw를 가진 VirtualService 구성 vs1
  • 호스트 service2.test.com과 게이트웨이 gw를 가진 VirtualService 구성 vs2

SNI를 보내지 않을 때 SNI 라우팅 구성하기

hosts 필드를 지정하는 HTTPS Gateway는 수신 요청에 대해 SNI 매치를 수행해요. 예를 들어, 다음 구성은 SNI에서 *.example.com과 일치하는 요청만 허용해요:

servers:
- port:
    number: 443
    name: https
    protocol: HTTPS
  hosts:
  - "*.example.com"

이것은 특정 요청이 실패하게 할 수 있어요.

예를 들어, DNS를 설정하지 않고 host 헤더를 직접 설정하는 경우(curl 1.2.3.4 -H "Host: app.example.com") SNI가 설정되지 않아 요청이 실패할 수 있어요. 대신 DNS를 설정하거나 curl의 --resolve 플래그를 사용할 수 있어요. 자세한 내용은 Secure Gateways 태스크를 참조하세요.

또 다른 흔한 문제는 Istio 앞의 로드 밸런서예요. 대부분의 클라우드 로드 밸런서는 SNI를 전달하지 않으므로, 클라우드 로드 밸런서에서 TLS를 종료한다면 다음 중 하나를 수행해야 할 수 있어요:

  • 클라우드 로드 밸런서가 대신 TLS 연결을 패스쓰루하도록 구성
  • hosts 필드를 *로 설정해서 Gateway의 SNI 매칭 비활성화

이것의 흔한 증상은 로드 밸런서 헬스 체크는 성공하지만 실제 트래픽은 실패하는 경우예요.

변경되지 않은 Envoy 필터 구성이 갑자기 동작을 멈추는 경우

다른 필터에 상대적인 삽입 위치를 지정하는 EnvoyFilter 구성은, 기본적으로 평가 순서가 필터의 생성 시간을 기준으로 하기 때문에 매우 취약할 수 있어요. 다음 사양을 가진 필터를 생각해 봐요:

spec:
  configPatches:
  - applyTo: NETWORK_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        portNumber: 443
        filterChain:
          filter:
            name: istio.stats
    patch:
      operation: INSERT_BEFORE
      value:
        ...

이 필터 구성이 제대로 동작하려면 istio.stats 필터가 자신보다 오래된 생성 시간을 가져야 해요. 그렇지 않으면 INSERT_BEFORE 작업은 조용히 무시될 거예요. 이 필터가 체인에 추가되지 않았다는 것을 나타내는 오류 로그는 없어요.

이것은 istio.stats처럼 버전별 필터(즉, 일치 기준에 proxyVersion 필드를 포함하는 필터)를 일치시킬 때 특히 문제가 돼요. 이러한 필터는 Istio를 업그레이드할 때 제거되거나 더 새로운 것으로 교체될 수 있어요. 결과적으로 위와 같은 EnvoyFilter는 처음에는 완벽하게 동작할 수 있지만 Istio를 최신 버전으로 업그레이드한 후에는 사이드카의 네트워크 필터 체인에 더 이상 포함되지 않을 거예요.

이 문제를 피하려면 다른 필터의 존재에 의존하지 않는 작업으로 변경하거나(예: INSERT_FIRST), EnvoyFilter에 명시적 우선 순위를 설정해 기본 생성 시간 기반 순서를 덮어쓸 수 있어요. 예를 들어 위 필터에 priority: 10을 추가하면 기본 우선 순위가 0인 istio.stats 필터 이후에 처리되도록 보장할 수 있어요.

Fault injection과 retry/timeout 정책을 가진 virtual service가 예상대로 동작하지 않는 경우

현재 Istio는 동일한 VirtualService에서 fault injection과 retry 또는 timeout 정책을 구성하는 것을 지원하지 않아요. 다음 구성을 생각해 봐요:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
    - "*"
  gateways:
  - helloworld-gateway
  http:
  - match:
    - uri:
        exact: /hello
    fault:
      abort:
        httpStatus: 500
        percentage:
          value: 50
    retries:
      attempts: 5
      retryOn: 5xx
    route:
    - destination:
        host: helloworld
        port:
          number: 5000

구성된 5회의 재시도 시도가 주어지면 helloworld 서비스를 호출할 때 사용자가 거의 오류를 보지 못할 것이라고 기대할 거예요. 그러나 fault와 retries가 동일한 VirtualService에 구성되어 있기 때문에 retry 구성은 효과가 없어서 50%의 실패율이 발생해요. 이 문제를 해결하려면 VirtualService에서 fault 구성을 제거하고 대신 EnvoyFilter를 사용해 업스트림 Envoy 프록시에 fault를 주입할 수 있어요:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: hello-world-filter
spec:
  workloadSelector:
    labels:
      app: helloworld
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND # will match outbound listeners in all sidecars
      listener:
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.fault
        typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault"
          abort:
            http_status: 500
            percentage:
              numerator: 50
              denominator: HUNDRED

이 방식이 동작하는 이유는 클라이언트 프록시에는 retry 정책이 구성되고 업스트림 프록시에는 fault injection이 구성되기 때문이에요.

더 알아보기 (Learn more)