간단한 예제로 보는 Istio VirtualService와 Destination 을 활용한 Istio Traffic Routing의 이해

*kubernetes (minikube) 1.14.0, Istio 1.2.2

시작하기

Traffic Management 는 소스코드의 변경없이 트래픽의 경로를 경로를 변경 관리할 수 있는 기능으로 수많은 마이크로서비스과 그들간의 트래픽이 오고가는 서비스메시에서 기본이 되는 주요한 기능이라 할 수 있을 것입니다. 이 포스트는 이렇게 Istio 에서도 주요 feature 중 하나인 Traffic Management 의 핵심인 VirtualService 와 DestinationRule 동작 원리를 간단한 예제를 활용해서 룰셋을 적용해보고 Kubernetes의 service 와 비교를 통해 Istio 라우팅의 기본 개념을 이해하는데 목적이 있습니다.

예제 실행


준비작업

테스트 용 hello-server-v1, hello-server-v2 pod 2개, pod httpbin, app=hello service 준비

  • 클러스터에 Istio가 설치되어 있어야 합니다.
  • hello-server-v1 는 Hello server - v1 문자열을 리턴합니다.
  • hello-server-v2 는 Hello server - v2 문자열을 리턴합니다.
  • /error 라는 URI 로 요청하면 503 에러를 강제발생합니다.
  • httpbin 는 curl 을 사용하기 위한 유틸 pod 입니다. 트래픽 발생하는 클라이언트입니다.
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: hello-server-v1
  labels:
    app: hello
    version: v1
spec:
  containers:
  - image: docker.io/honester/hello-server:v1
    imagePullPolicy: IfNotPresent
    name: hello-server-v1
---
apiVersion: v1
kind: Pod
metadata:
  name: hello-server-v2
  labels:
    app: hello
    version: v2
spec:
  containers:
  - image: docker.io/honester/hello-server:v2
    imagePullPolicy: IfNotPresent
    name: hello-server-v2
---
apiVersion: v1
kind: Pod
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  containers:
  - image: docker.io/honester/httpbin:latest
    imagePullPolicy: IfNotPresent
    name: httpbin
EOF
  • 생성 확인
$ kubectl get all -l app=hello

NAME                  READY   STATUS    RESTARTS   AGE
pod/hello-server-v1   2/2     Running   0          20m
pod/hello-server-v2   2/2     Running   0          20m

Cases

다음은 개념 파악을 위한 5개 샘플 Case 입니다. Case 1,2 는 Kubernetes 의 service 를 통해 workload 를 서비스하는 경우이며 Case3,4는 VirtualService 를 통해 라우팅 룰셋 적용 case 입니다.

  • Case 1 : Kuberntes service 가 바라보는 endpoints 가 다수인 경우 트래픽은 endpoints 들로 자동 round robin 됩니다.
  • Case 2 : Kuberntes servicespec.selector 로 라벨을 지정하여 라우팅 룰을 수동으로 지정할 수 있습니다.
  • Case 3 : Istio VirtualService 를 활용하여 HTTP Request URI에 따라 각 pod 들로 라우팅 되도록 정의할 수 있습니다.
  • Case 4 : Istio VirtualService 를 활용하여 각 pod 들로 라우팅 되는 비율을 정의할 수 있습니다.
  • Case 5 : Istio DestinationRulesubset 을 구성하여 요청에 대한 destionation 을 정의합니다. Case 4와 동일한 결과를 리턴 받습니다.

Case 1

2개의 샘플 pod - hello-server-v1, hello-server-v2 - 가 서로 같은 App. 이라 정의하고 (실제로는 다르지만) svc-hello 로 트래픽을 발생시키면 해당 트래픽은 endpoints 로 round robin 되는것을 확인합니다.

routeapi-usecase-1

  • svc-hello service 생성
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: svc-hello
  labels:
    app: hello
spec:
  selector:
    app: hello
  ports:
  - name: http
    protocol: TCP
    port: 8080
EOF
  • endpoint 확인
    • svc-hello 추가 생성
$ kubectl get endpoints -l app=hello

NAME           ENDPOINTS                         AGE
svc-hello      172.17.0.5:8080,172.17.0.6:8080   92m
  • svc-hello 에 트래픽을 전달하여 결과 확인해 봅니다.
    • hello-server-v1 , hello-server-v2 각각으로 Round Robin
$ for i in {1..5}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080; sleep 0.5; done

Hello server - v2
Hello server - v1
Hello server - v2
Hello server - v1
Hello server - v1

Case 2

Istio 의 Route API를 사용하지 않고 service svc-hello 에 라벨링을 통해서 pod hello-server-v1, hello-server-v2 라우팅룰을 지정할 수도 있습니다.

routeapi-usecase-2

  • spec.selectorversion: v1 라벨 추가
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: svc-hello
  labels:
    app: hello
spec:
  selector:
    app: hello
    version: v1
  ports:
  - name: http
    protocol: TCP
    port: 8080
EOF
  • endpoint 확인
$ kubectl get endpoints -l app=hello

NAME        ENDPOINTS                         AGE
svc-hello      172.17.0.5:8080                   92m
  • svc-hello 에 트래픽을 전달하여 결과 확인하기
$ for i in {1..5}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080; sleep 0.5; done

Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
  • spec.selectorversion: v2 라벨 변경
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: svc-hello
  labels:
    app: hello
spec:
  selector:
    app: hello
    version: v2
  ports:
  - name: http
    protocol: TCP
    port: 8080
EOF
  • endpoint 확인
$ kubectl get endpoints -l app=hello

NAME        ENDPOINTS                         AGE
svc-hello      172.17.0.6:8080                   92m
  • svc-hello 에 트래픽을 전달하여 결과 확인하기
$ for i in {1..5}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080; sleep 0.5; done

Hello server - v2
Hello server - v2
Hello server - v2
Hello server - v2
Hello server - v2

Case 3

pod 를 바라보는 svc-hello-v1, svc-hello-v2 를 생성하고 svc-hello serviceVirtualService CRDs 를 사용하여 라우트 룰셋을 정의하여 줍니다. 룰셋은 기본적으로 svc-hello-v1 로 라우트 되지만 URI prefix가 \v2 이면 svc-hello-v2로 라우트 되도록합니다..

routeapi-usecase-3

  • VirtualService 생성
    • 기본적으로 svc-hello-v1 로 라우트되고 URI prefix가 \v2 이면 svc-hello-v2로 라우트되는 룰셋
    • spec.hosts 는 대상 service
    • spec.http.route.destination 는 기본 라우트 service
    • spec.http.match.* 에 라우트 조건 지정
    • spec.*.destination.host 는 destination service
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: svc-hello
  labels:
    app: hello
spec:
  selector:
    app: hello
  ports:
  - name: http
    protocol: TCP
    port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-hello-v1
  labels:
    app: hello
spec:
  selector:
    app: hello
    version: v1
  ports:
  - name: http
    protocol: TCP
    port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-hello-v2
  labels:
    app: hello
spec:
  selector:
    app: hello
    version: v2
  ports:
  - name: http
    protocol: TCP
    port: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs-hello
spec:
  hosts:
  - "svc-hello.default.svc.cluster.local"
  http:
  - match:
    - uri:
        prefix: /v2
    route:
    - destination:
        host: "svc-hello-v2.default.svc.cluster.local"
  - route:
    - destination:
        host: "svc-hello-v1.default.svc.cluster.local"
EOF
  • endpoint 확인
    • 변경사항은 없고 이전과 동일합니다.
$ kubectl get endpoints -l app=hello

NAME           ENDPOINTS                         AGE
svc-hello      172.17.0.5:8080,172.17.0.6:8080   101m
svc-hello-v1   172.17.0.5:8080                   11m
svc-hello-v2   172.17.0.6:8080                   9m13s
  • svc-hello 로 트래픽을 발생시키면 svc-hello-v1 으로 라우트됩니다.
$ for i in {1..5}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080; sleep 0.5; done

Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
  • prefix \v2 로 트래픽을 발생하면 svc-hello-v2 으로 라우트됩니다.
$ for i in {1..5}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080/v2; sleep 0.5; done

Hello server - v2 (uri=/v2)
Hello server - v2 (uri=/v2)
Hello server - v2 (uri=/v2)
Hello server - v2 (uri=/v2)
Hello server - v2 (uri=/v2)

Case 4

VirtualService 는 Destination weight 스펙을 통해 라우트되는 비율을 정의할 수 있습니다.

routeapi-usecase-4

  • VirtualService 를 수정 적용
    • v1:v2 = 90:10 비율로 라우트 되도록 합니다.
    • spec..route..destination.weight 에 라우트 비율 정의 (%)
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs-hello
spec:
  hosts:
  - "svc-hello.default.svc.cluster.local"
  http:
  - route:
    - destination:
        host: "svc-hello-v1.default.svc.cluster.local"
      weight: 90
    - destination:
        host: "svc-hello-v2.default.svc.cluster.local"
      weight: 10
EOF
  • 트래픽을 전달하여 결과를 확인합니다.
$ for i in {1..20}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080; sleep 0.5; done

Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
...
Hello server - v2
....
Hello server - v1

Case 5

Case 4 와 같이 각 podservice 를 구성하는 대신 DestinationRule 을 이용하여 동일한 결과 구현합니다.

routeapi-usecase-5

  • DestinationRule 생성하여 subset 을 구성하고 VirtualService 에서 이를 지정합니다.
    • service 는 label app=hello 로 타켓 endpoints 정의
    • DestinationRule 에서 label version=v1, version=v2 으로 subset v1, v2 을 구성
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: dr-hello
spec:
  host: svc-hello.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs-hello
spec:
  hosts:
  - "svc-hello.default.svc.cluster.local"
  http:
  - route:
    - destination:
        host: "svc-hello.default.svc.cluster.local"
        subset: v1
      weight: 90
    - destination:
        host: "svc-hello.default.svc.cluster.local"
        subset: v2
      weight: 10
EOF
  • 트래픽을 전달하여 결과를 확인합니다.
$ for i in {1..10}; do kubectl exec -it httpbin -c httpbin -- curl http://svc-hello.default.svc.cluster.local:8080; sleep 0.5; done

Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
Hello server - v1
...
Hello server - v2
....
Hello server - v1

Clean-up

$ kubectl delete pod/httpbin pod/hello-server-v1 pod/hello-server-v2 service/svc-hello service/svc-hello-v1 service/svc-hello-v2 vs/vs-hello dr/dr-hello

VirtualService


프로토콜(http/tls/tcp)별로 트래픽 라우트 룰셋(destination)을 정의

주요 Spec

  • hosts

    • service 이름
    • service registry 또는 ServiceEntry(external Mesh) 에서 lookup
    • short 네임은 대신 full 네임 권장 (reviews interpreted reviews.default.svc.cluster.local)
    • only * 설정 불가하지만 *.default.svc.cluster.local, *.svc.cluster.local 가능
    • 라우팅할 destination 이 없으면 무시(drop)됨
  • spec.[http/tls/tcp].match

    • 라우팅 조건을 정의합니다.
    • exact, prefix, regex
    • 정의 가능 조건 : uri, method, headers, port, source Labels, gateways, queryParams
  • spec.[http/tls/tcp].route

    • default 트래픽 라우팅 룰셋을 정의
  • spec.[http/tls/tcp]…route.destination

    • (필수) 요청/연결을 포워딩해야 하는 service 인스턴스 고유한 식별자
  • spec.[http/tls/tcp].*.destination.host

    • 타켓 service 인스턴스
  • spec.[http/tls/tcp].*.destination.subset

    • DestinationRule의 subset
  • spec.gateways

    • 라우트 해야하는 gateway, sidecar 이름
  • spec.exportTo

    • 현재 virtual service 를 export 하는 네임스페이스를 정의

DestinationRule


라우팅 발생 이후 트래픽 관련된 정책을 정의하는 Custom Resource Definition

  • 트래픽 정책
    • Load-balancing
    • connection-pool
    • pool 에서 unhealthy 한 서비스 detect & 제거
  • subset
    • 버전 특정 정책들 정의하고 service 레벨의 특정 설정을 overriding 합니다.

Conclusion


Istio 의 VirtualService 는 Kubernetes service 를 세분화한 추상화된 Custom Resource Definition이며, 다양한 조건 정의를 통해 사용자에게 소스 또는 어플리케이션 설정정보 변경없이 선언적으로 트래픽이 라우팅 되도록합니다.

posted at 2019/07/24 10:01