Service Mesh

Istio - (4) Traffic Management

Operation CWAL 2021. 4. 5. 23:02

Traffic Management

Service Mesh 안에 생성된 Pod의 모든 트래픽은 Sidecar Proxy에 의해 Intercept 되는 것은 이미 배웠다.  이를 통해 Kiali Dashboard에서 트래픽이 어디에서 와서 어디로 가는지에 대한 Telemetry를 시각적으로 확인할 수도 있었다.

이번 시간엔 VirtualService와 DestinationRule을 통해 트래픽을 조건에 맞게 제어하고 관리함으로써 Kubernetes 경험을 확장할 수 있는 Traffic Management 기능에 대해 알아보자.

 

Weighted Routing

Canary Release는 새로운 버전을 배포하기 전, 일부 사용자에게만 적은 확률로 서비스를 제공하는 방식으로 리스크를 줄이는 기법 중 하나이다.

 

출처 - martinfowler.com

K8s에서도 기존/신규 버전 Pod 비율을 조정함으로써 어느 정도 Canary Release를 흉내낼 수 있지만, 정확한 퍼센티지를 맞출 수 없고 매뉴얼 방식으로 수행해야 하는 단점이 있다.

Istio는 버전이 같은 Pod을 subset으로 묶고, VirtualService에서 subset으로 보내는 트래픽의 가중치를 달리함으로써 Canary Release 기능을 완벽하게 지원한다. 사실 이렇게 글로 설명하는 것보다, 다음과 같이 실제 적용 예시를 보는게 이해하기 편할 것이다.

 

1. Pod에 버전 정보 Label 추가

현재 bookinfo App에는 3개 버전(v1, v2, v3)의 reviews Deplyment가 존재한다. 그리고 각 Deployment를 확인하면 다음과 같이 version에 대한 Label이 정의된 것을 확인할 수 있다.

 

 

하지만 reviews Service는 'app: reviews'에 대한 match 조건만 존재하므로, version에 따른 구분은 하지 않는 상황이다.

 

2. DestinationRule 생성

Istio는 Service를 대신해서 DestinationRule을 정의하여 label에 따른 subset으로 Pod을 구분할 수 있으며, 여기서 정의한 subset은 VirtualService의 host 추가시 참조할 수 있다. DestinationRule의 기본 Format은 다음과 같다.

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews # interpreted as reviews.svc.cluster.local
  subsets: # One or more named sets that represent individual versions of a service. Traffic policies can be overridden at subset level.
  - name: v1
    labels:
      version: v1 # label attached to Pod definition
  - name: v2
    labels:
      version: v2 # label attached to Pod definition
  - name: v3
    labels:
      version: v3 # label attached to Pod definition
  • host
    • Service Registry에 위치한 Service 이름 명시
    • in-cluster service: K8s Service
    • external service: ServiceEntries에 선언한 클러스터 외부 host
  • subset
    • name: subset 이름
    • label: Destination으로 지정할 Pod의 Label

위 Manifest를 'destination-rule-versioning.yaml'이라는 이름으로 저장 후 K8s에 적용해보자.

kubectl apply -f destination-rule-versioning.yaml

Kiali Dashboard의 좌측 Sidebar에서 'Istio Config'를 선택하면 'reviews' 라는 이름의 DestinationRule이 추가된 것을 볼 수 있다.

해당 아이템을 선택하면 Overview 메뉴로 이동하며, 해당 DestinationRule의 subset 목록과 YAML 파일 내용을 확인할 수 있다. YAML 파일은 해당 메뉴에서도 수정 가능하다.

 

3. VirtualService에 subset 및 weight 추가

아래 내용으로 VirtualService를 정의하고, 'virtualservice-canary.yaml'이라는 이름으로 Manifest 파일을 저장한다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts: # destinations that these routing rules apply to. VirtualService must be bound to the gateway and must have one or more hosts that match the hosts specified in a server
  - reviews
  http: # L7 load balancing by http path and host, just like K8s ingress resource
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 10 # <--- canary release. % of traffic to subset v1
    - destination:
        host: reviews
        subset: v2
      weight: 10
    - destination:
        host: reviews
        subset: v3
      weight: 80

크게 어려운 내용은 없으며, destination의 subset과 weight가 추가된 것을 알 수 있다. subset은 DestinationRule에 선언한 subset의 이름을 지정하고, weight는 몇 퍼센트의 트래픽을 해당 destination에 할당할지 결정한다. weight의 총합은 100이 되어야 한다.

이제 위 Manifest를 K8s에 추가해보자.

kubectl apply -f virtualservice-canary.yaml

 

Kiali에서 다음과 같은 reviews VirtualService가 생성된 것을 확인할 수 있다.

 

4. 테스트

저번 시간엔 브라우저에서 계속 새로고침 해가며 트래픽을 발생시켰지만 이번엔 다음 명령어를 사용하여 자동화된 테스트를 진행해보자.

while true; \
do curl $(echo $(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')/productpage); \
sleep 0.5; \
done

0.5초마다 productpage에 request를 보내도록 설정하였으며, 사용자가 강제로 프로세스를 종료하기 전까지 무한히 반복한다.

 

다음은 Kiali Dashboard의 Graph 메뉴를 확인할 차례다. 우선 Display 항목 중 'Request Distribution', 'Traffic Animation'을 선택한다.

 

1분 경과

reviews 서비스를 확인하면 현재 트래픽이 어떤 비율로 분산되는지 확인할 수 있다. 1분 정도 수행했을 때 얻은 결과이며 완벽하게 1:1:8로 트래픽이 나뉘어지진 않지만 어느 정도는 비슷한 수치가 나오고 있다. 테스트를 시작한지 5분이 지났을 땐 VirtualService에 설정한 weight와 거의 일치하는 비율을 얻을 수 있었다.

5분 경과

VirtualService의 Weighted Routing을 통해 Canary Release를 쉽게 구현할 수 있음을 알 수 있다.

 

Header 기반 Routing

HTTP 헤더의 특정 key-value 페어를 확인하여 조건에 맞게 routing할 수도 있다. 특히 bookinfo App의 경우, 'log-as-this-user'라는 이름으로 로그인(비밀번호 X)할 경우 'end-user: log-in-as-this-user'라는 값이 HTTP 헤더에 추가되며 이를 VirtualService의 Routing Rule에서 참조할 수 있다.

 

 

아래는 해당 사용자로 로그인 시, 무조건 v1으로 routing하는 VirtualService 구성 예시이다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts: # destinations that these routing rules apply to. VirtualService must be bound to the gateway and must have one or more hosts that match the hosts specified in a server
  - reviews
  gateways: # names of gateways and sidecars that should apply these routes
  - bookinfo-gateway # Don't ONLY USE this gateway as "reviews" k8s service is used internally by productpage service, so this VS rule should be applied to Envoy sidecar proxy inside reviews pod, not edge proxy in gateway pod. 
  - mesh # applies to all the sidecars in the mesh. The reserved word mesh is used to imply all the sidecars in the mesh. When gateway field is omitted, the default gateway (mesh) will be used, which would apply the rule to all sidecars in the mesh. If a list of gateway names is provided, the rules will apply only to the gateways. To apply the rules to both gateways and sidecars, specify mesh as one of the gateway names. Ref: https://istio.io/latest/docs/reference/config/networking/virtual-service/#VirtualService
  http: # L7 load balancing by http path and host, just like K8s ingress resource
  - match: # <---- this routing to apply to all requests from the specifieduser
    - headers: # Note: The keys uri, scheme, method, and authority will be ignored.
        end-user: # WARNING: merely passing this key-value pair in curl won't work as productpage service won't propagate custom header attributes to review service! You need to login as a user Ref: https://stackoverflow.com/a/50878208/1528958
          exact: log-in-as-this-user
    route:
    - destination:
        host: reviews
        subset: v1 # then route traffic destined to reviews (defined in hosts above) to backend reviews service of version 1
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 10 # <--- canary release. % of traffic to subset v1
    - destination:
        host: reviews
        subset: v2
      weight: 10
    - destination:
        host: reviews
        subset: v3
      weight: 80

 

위 VirtualService 적용 후 테스트시, 몇번을 새로고침 하여도 별점은 나오지 않으며, 모든 Traffic이 reviews v1으로 향하는 것을 확인할 수 있다.

 

다만 실제 서비스 구현시, 위 기능을 사용하기 위해 반드시 Header Propagation이 코드 레벨에서 정의되어야 한다. MSA 구조상 현재 처리 중인 Request와 다른 서비스로 보낼 Request는 별도의 개체이며, 기본적으로 아무 연관이 없는 상태이기 때문이다. 따라서 기존 Header 값을 계속해서 다음 Request에 복사하여 전달해야 한다.

 

Query 기반 Routing

VirtualService의 match 조건 중 Query Parameter를 사용하여 Routing하는 방식도 정의 가능하다. 예를 들어

'<base url>/productpage?foo=bar'라는 형식으로 Request 발생시, 아래와 같이 reviews 서비스의 'v2' subset으로 routing할 수 있다.

  http:
  - match:
    - queryParams:
        foo:
          exact: "bar"
    route:
    - destination:
        host: reviews
        subset: v2

다만 bookinfo App에서 이 기능을 테스트해볼 수 없기 때문에 실습은 생략한다.

 

URI Path 기반 Routing

Query 기반 Routing과 비슷하게, Path를 사용하여 subset으로 routing하는 기능도 지원한다. 다음은 productpage/v2로 접근시 reviews v2로 routing하는 예시이다.

  http:
  - match:
    - uri:
        regex: '^/productpage/v2'
      ignoreUriCase: true
    route:
    - destination:
        host: reviews
        subset: v2

 

 

Fault Injection

Istio는 특정 서비스에 과부하나 예상치못한 에러가 발생했을 경우, Application이 어떻게 대응하는지 테스트할 수 있는 Fault Injection 기능을 지원한다. 별도의 테스트 코드를 작성하는 대신, VirtualService에 Fault Injection을 정의하면 되므로 손쉽게 Resilience Test를 수행할 수 있다.

 

Fixed Delay

다음은 tester로 로그인했을 때, ratings 서비스에 100% 확률로 2초 간의 고정 딜레이를 발생하도록 설정한 VirtualService다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - match:
    - headers:
        end-user:
          exact: tester
    fault: 
      delay: 
        percentage:
          value: 100.0
        fixedDelay: 2.0s
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1

참고로 reviews 서비스는 이미 코드 레벨에서 3초 이상 딜레이 발생시 Timeout을 발생시키기 때문에 2초로 설정하였다.

위 VirtualService를 적용하고, tester로 로그인하면 페이지 새로고침 속도가 확연하게 느려진 것을 확인할 수 있다.

Kiali에서도 Response Time으로 Graph를 확인하면, 적용 전/후 응답시간에 큰 차이(8ms -> 2.42s)가 있음을 알 수 있다.

Fixed Delay 적용 전
Fixed Delay 적용 후

HTTP Error

딜레이 외에도 지정된 HTTP Status Code를 확률적으로 반환하는 동작을 정의할 수 있다. 다음은 tester로 로그인했을 때, ratings 서비스에서 50% 확률로 HTTP 400 코드를 발생시키는 VirtualService 예시이다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - match:
    - headers:
        end-user:
          exact: tester
    fault: 
      abort:
        percentage:
          value: 50.0
        httpStatus: 400
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1

적용 후 테스트 수행시, 다음과 같이 reviews와 ratings 서빗스 사이에 HTTP 400 에러가 발생하는 것을 확인할 수 있다.

 

 

Timeout

Envoy 프록시가 HTTP Response를 기다릴 수 있는 최대 시간인 timeout 설정 역시 가능하다. 이 기능을 통해 서비스가 무한정 대기하는 hang 상태를 방지할 수 있고, 예상 시간 안에 무조건 성공 또는 실패 결과를 얻을 수 있다. VirtualService에 timeout을 따로 명시하지 않는 경우, 기본값은 15초로 설정된다. Code를 수정하지 않고도 서비스 단위의 timeout 적용이 가능하다는 것이 가장 큰 장점이다.

다음은 reviews 서비스에 timeout을 적용한 VirtualService 예시이다. 1초 이내에 reviews 서비스가 응답하지 않는 경우, timeout이 발생한다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  gateways:
  - bookinfo-gateway
  - mesh
  http: 
  - timeout: 1s
    route:
    - destination:
        host: reviews
        subset: v1
      weight: 10
    - destination:
        host: reviews
        subset: v2
      weight: 10
    - destination:
        host: reviews
        subset: v3
      weight: 80

참고로 reviews를 호출하는 productpage 서비스는 코드 레벨에서 3초의 timeout이 적용된 상태이므로 VirtualService의 timeout을 이보다 크게 설정하여도, timeout은 3초를 넘길 수 없다.

Retry

지정된 에러 타입에 대해, 정해진 횟수와 시간 간격을 두고 서비스를 재호출하는 기능이다. 다음은 ratings 서비스에 retry 기능을 적용한 VirtualService 예시이다. ratings 서비스를 호출하는 다른 서비스에 해당 설정이 적용된다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts: 
  - ratings
  http:
  - retries:
      attempts: 3
      perTryTimeout: 3s
      retryOn: 5xx,gateway-error,reset,connect-failure,refused-stream,retriable-4xx
    match:
    - headers:
        end-user:
          exact: tester
    fault: 
      delay:
        percentage:
          value: 100.0
        fixedDelay: 15s
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1
  • attempts: 재시도 횟수, Default 1
  • perTryTimeout: 재시도시 시간 간격
  • retryOn: retry 기능을 적용할 에러 타입
    • 적용 가능한 에러에 대해선 링크 참고

 

Mirroring

Istio의 Traffic Mirroring은 현재 트래픽과 동일한 복사본을 지정된 subset으로 전송하는 기능으로, Debugging 및 Troubleshooting에 매우 유용하다. 참고로 복사된 Request의 Header 속성 중 Host/Authority는 뒤에 '-shadow'라는 suffix(ex: reviews-shadow)를 갖게된다.

다음은 reviews v3로 전송되는 트래픽을 reviews v1으로 mirroring 하는 VirtualService 예시이다. mirror_percent 값을 변경하여 mirroring할 트래픽의 비율을 조정할 수 있다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - mirror:
      host: reviews
      subset: v1
    mirror_percent: 100.0
    route:
    - destination:
        host: reviews
        subset: v3
      weight: 100

 

 

What comes next?

다음 시간은 Istio의 DestinationRule을 변경하여 Load Balancing, Sticky Session, Rate Limiting, Circuit Breaker 기능을 설정하는 방법에 대해 알아볼 예정이다.

'Service Mesh' 카테고리의 다른 글

Istio - (5) Destination Rule 응용  (0) 2021.04.06
Istio - (3) Gateway와 VirtualService  (1) 2021.03.27
Istio - (2) Istio 설치(EKS)  (0) 2021.03.27
Istio - (1) Introduction  (0) 2021.03.27