Istio 소개

마이크로 서비스 아키텍쳐로 어플리케이션을 개발하게되면 수많은 어플리케이션과 서비스가 나온다.

외부에서 한개의 api 호출을 하였음에도 내부적으론 수많은 어플리케이션을 지나 결과가 나오는데 만약에 문제가 생긴다면 어떠한 서비스에 문제가 있는지 알기 어렵다.

기존 도커 명령어를 통해 컨테이너를 관리하던것에서 쿠버네티스를 통해 Pod, Replicaset, ReplicationController, Deployment 등으로 추상화 시켜 관리 하듯이 네트워크 측면에서 추상화하여 관리할 수 있게 해주는 도구이며,

보안, 로드밸런싱, 모니터링, HealthCheck, 써킷 브레이커(circuit breaker), 트래픽 처리 로직, 등 서비스간의 통신을 인프라 내부에서 추상화를 통해 이러한 기능을 제공하는 것을 서비스 메쉬라 한다.

Istio는 서비스 메쉬의 구현체로써 서비스메쉬는 소프트웨어와 클러스터(쿠버네티스 등)사이의 추가적인 레이어를 통해 네트워크 통신을 추상화한다.

Sidecar(사이드카)

사이트카 패턴은 기존 어플리케이션에는 변경 없이 해당 어플리케이션에 추가적인 기능을 제공하는 컨테이를 붙이는 것을 의미한다. 대표적인 예시로는 로깅, 프록시, 설정 등이있다.

로깅을 예시로 든 문서를 읽어보면 이해가 빠를 것이다.

istio에서는 envoy를 sidecar-proxy(이하 envoy)로 사용하여 네트워크적인 조작을 한다. 즉 기존에는 pod과 서비스의 연결로 통신을 하였다면 istio에서는 envoy가 pod에 대한 네트워크 통신을 담당하게 된다.

그래서 envoy ↔ service 의 구조가 된다.

Sidecar Injection

istio는 envoy를 사용하여 네트워크 흐름을 제어한다. 그 뜻은 envoy가 없으면 istio는 아무 역할도 할 수 없다는 것이다. Pod은 1개이상의 컨테이너를 가질 수 있다.

그렇다면 사용자가 직접 추가해줘야 하는 걸까?

아니다 Istio SidecarInjector는 새로운 팟이 생성될때 Sidecar 컨테이너를 삽입 시켜준다.

모든 팟에 대해 작동하는 것이 아닌 레이블을 기반으로 istio-injection=enabled가 있는 네임스페이나 팟에 동작한다.

1
2
3
$ kgns --show-labels default # kubectl get ns --show-labels default 
NAME      STATUS   AGE   LABELS
default   Active   19d   istio-injection=enabled

nginx 이미지를 가진 pod을 sidecar-test로 명명하며 생성시켰다.

1
2
$ k run --image=nginx sidecar-test
pod/sidecar-test created

생성후 조회를 하였더니 Init Containers에 Istio-init과 Containers에 istio-proxy라는 사이드카가 자동적으로 추가되었다.

1
$ kdp sidecar-test # kubectl describe pod sidecar-test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
Name:         sidecar-test
Namespace:    default     
Priority:     0
Node:         docker-desktop/192.168.65.3
Start Time:   Sun, 20 Dec 2020 21:56:33 +0900
Labels:       run=sidecar-test
              security.istio.io/tlsMode=istio
              service.istio.io/canonical-name=sidecar-test
              service.istio.io/canonical-revision=latest
Annotations:  sidecar.istio.io/status:
                {"version":"1cdb312e0b39910b7401fa37c42113f6436e281598036cb126f9692adebf1545","initContainers":["istio-init"],"containers":["istio-proxy"]...
Status:       Running
IP:           10.1.1.84
IPs:
  IP:  10.1.1.84
Init Containers:
  istio-init:
    Container ID:  docker://e2c52570c7a68c4801296c5b4e4bb2de3463e99bf4e3dc529bcba384a0af91e6
    Image:         docker.io/istio/proxyv2:1.5.0
    Image ID:      docker-pullable://istio/proxyv2@sha256:89b5fe2df96920189a193dd5f7dbd776e00024e4c1fd1b59bb53867278e9645a
    Port:          <none>
    Host Port:     <none>
    Command:
      istio-iptables
      -p
      15001
      -z
      15006
      -u
      1337
      -m
      REDIRECT
      -i
      *
      -x

      -b
      *
      -d
      15090,15020
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Mon, 21 Dec 2020 17:25:37 +0900
      Finished:     Mon, 21 Dec 2020 17:25:38 +0900
    Ready:          True
 # 일부생략
Containers:
  sidecar-test:
    Container ID:   docker://7e25370ae3485196ef709595943bfc6748fd6182f8d0c205d2a15f2e172b6fc5
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Mon, 21 Dec 2020 17:25:46 +0900
    Last State:     Terminated
      Reason:       Error
      Exit Code:    255
      Started:      Sun, 20 Dec 2020 21:56:37 +0900
      Finished:     Mon, 21 Dec 2020 17:25:19 +0900
    Ready:          True
    Restart Count:  1
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-pdn4z (ro)
  istio-proxy:
    Container ID:  docker://f1d3bb26ee8b03854613bd599bb4ddbc37254114c40db02bfc067aed84adfa85
    Image:         docker.io/istio/proxyv2:1.5.0
    Image ID:      docker-pullable://istio/proxyv2@sha256:89b5fe2df96920189a193dd5f7dbd776e00024e4c1fd1b59bb53867278e9645a
    Port:          15090/TCP
    Host Port:     0/TCP
    Args:
      proxy
      sidecar
      --domain
      $(POD_NAMESPACE).svc.cluster.local
      --configPath
      /etc/istio/proxy
      --binaryPath
      /usr/local/bin/envoy
      --serviceCluster
      sidecar-test.default
      --drainDuration
      45s
      --parentShutdownDuration
      1m0s
      --discoveryAddress
      istiod.istio-system.svc:15012
      --zipkinAddress
      zipkin.istio-system:9411
      --proxyLogLevel=warning
      --proxyComponentLogLevel=misc:error
      --connectTimeout
      10s
      --proxyAdminPort
      15000
      --concurrency
      2
      --controlPlaneAuthPolicy
      NONE
      --dnsRefreshRate
      300s
      --statusPort
      15020
      --trust-domain=cluster.local
      --controlPlaneBootstrap=false
    State:          Running
      Started:      Mon, 21 Dec 2020 17:25:47 +0900
    #일부 생략

Istio 구성

istio-architecture

Istio는 위와 같은 기능을 제공하기 위해 지정된 pod에 Sidecar Proxy를 사용하여 서비스메쉬를 구현한다.

ControllPlane 컴포넌트들 은 아래와같다

  • istiod : istio daemon
    • pilot : CRD(CustumResourceDefinition)을 SidecarProxy가 이해할 수 있는 형식으로 변환 및 전파
    • galley : kubernetes yaml 파일을 istio가 이해할 수 있는 형식으로 변환
    • citadel : 인증 관리(TLS certification 등)

DataPlane 컴포넌트는 팟들에 Sidecar로 있는 Proxy 이다.

  • envoy
    • Dynamic service discovery : 서비스의 ip나 정보등의 변경이 있어도 자동으로 관리됨
    • Load balancing
    • TLS termination
    • HTTP/2 and gRPC proxies
    • Circuit breakers : 연속적인 api-call에서 도중에 한개라도 문제가 생기면 응답을 기다리게된다. 아래의 경우 F서비스에 문제가 생길경우 A서비스에도 장애가 전파된다. 이러할때 fallback method를 사용하여 장애를 깨는 것이 서킷브레이커이다.
      • A서비스에서 B, C, D 호출
      • B서비스에서 E 호출
      • E서비스에서 F호출
    • Health checks
    • Staged rollouts with %-based traffic split
    • Fault injection
    • Rich metrics

그외의 컴포넌트는 아래와 같다.

  • grafana : 시계열 데이터를 시각화 해주는 모니터링 툴
  • prometheus : 데이터를 주기마다 수집하여 시계열로 저장
  • istio-engress-gateway : 아웃바운드 트래픽 처리
  • istio-ingress-gateway : 인바운드 트래픽 처리
  • istio-tracing : 개별 트래픽을 추적해줌(jaeger 활용)
  • kiali : 트래픽 시각화 대시보드

istio 설치

istio 설치에는 여러가지 방법이 있겠지만 istio opertor를 통해 설치하는 방법을 알아보자

오퍼레이터(operator) 패턴이란 쿠버네티스를 사용하여 워크로드(여기선 istio)를 배포 및 실행을 자동화 할 수있다.

operator는 쿠버네티스 컨트롤러처럼 오브젝트 상태를 명시하면 해당 상태에 맞게 생성, 변경을 해준다.

istio operator설치

istio operator 설치를 위해서 istioctl이 필요하다.

1
2
$ curl -sL https://istio.io/downloadIstioctl | sh -
$ export PATH=$PATH:$HOME/.istioctl/bin

위 명령를 사용하여 istioctl을 설치 할 수있다.

1
2
#istio operator 1.7.5 버전 설치
$ istioctl operator init --tag 1.7.5

위 명령어 실행시 istio-opertor가 설치된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#istio-system namespace 생성
$ kubectl create ns istio-system
#컴포넌트 설정가능 
$ kubectl apply -f - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
  name: istiocontrolplane
spec:
  addonComponents: #add on component
    grafana: # 시계열 데이터 시각화
      enabled: true
    kiali: # 트래픽 시각화
      enabled: true
    prometheus: # 정보 수집
      enabled: true
    tracing: # 트래픽 흐름 추적가능
      enabled: true
  components:
    ingressGateways: 
    - name: istio-ingressgateway #기본게이트웨이
      enabled: true
  profile: default
  values:
    kiali:
      dashboard:
        auth:
          strategy: anonymous # kiali 로그인없이 접속가능
EOF

위 명령어를 입력하게되면 IstioOperator라는 오브젝트가 생기게된다.

해당 오브젝트가 자동으로 istio-system에 명시된대로 istio를 설치해 준다.

검증

검증을 위해 아래 명령어를 입력해보자. 이스티오 컴포넌트들이 설치된 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
$ kubectl get pod -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
grafana-6cf6cfdf8f-2dbcz                1/1     Running   0          88s
istio-tracing-75cffbb44d-wjczg          1/1     Running   0          89s
istio-ingressgateway-7b475f6b96-j2t6h   1/1     Running   0          90s
istiod-6ffcc65b96-hwwnw                 1/1     Running   0          90s
kiali-569c9f8b6c-6fpfj                  1/1     Running   0          90s
prometheus-7bbdd494fb-265tr             1/1     Running   0          90s

istio의 변경또한 operator의 spec에 명시하게되면 자동으로 동기화 된다. ingress-gateway 이름 변경과 포트 고정을 위해 spec을 변경시켜보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#컴포넌트 설정가능 
kubectl apply -f - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
  name: istiocontrolplane
spec:
  addonComponents: #add on component
    grafana: # 시계열 데이터 시각화
      enabled: true
    kiali: # 트래픽 시각화
      enabled: true
    prometheus: # 정보 수집
      enabled: true
    tracing: # 트래픽 흐름 추적가능
      enabled: true
  components:
    ingressGateways: 
    - name: istio-ingressgateway #기본게이트웨이라 자동생성
      enabled: false
    - name: ingressgateway
      enabled: true
      k8s: #포트고정을 위해 k8s spec overriding
        service:
          ports:
          - name: http2
            nodePort: 32502
            port: 80
            targetPort: 8080
          - name: https
            nodePort: 30832
            port: 443
            targetPort: 8443
          - name: status-port
            nodePort: 31507
            port: 15021
            targetPort: 15021
          - name: tls
            nodePort: 30086
            port: 15443
            targetPort: 15443
          type: NodePort
  profile: default
  values:
    kiali:
      dashboard:
        auth:
          strategy: anonymous # kiali 로그인없이 접속가능
EOF

아래 명령어를 통해 확인할 수 있다. 이름을 변경시킨 ingressgateway가 새로 생겨났으며 service도 지정된 NodePort에 할당 된것을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ kubectl get pod,svc -n istio-system
NAME                                 READY   STATUS    RESTARTS   AGE
pod/ingressgateway-776f79bb4-fq4wc   1/1     Running   0          3m38s
pod/grafana-6cf6cfdf8f-2dbcz         1/1     Running   0          163m
pod/istio-tracing-75cffbb44d-wjczg   1/1     Running   0          163m
pod/istiod-6ffcc65b96-hwwnw          1/1     Running   0          164m
pod/kiali-569c9f8b6c-6fpfj           1/1     Running   0          163m
pod/prometheus-7bbdd494fb-265tr      1/1     Running   0          163m

NAME                                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                      AGE
service/grafana                     ClusterIP   10.96.15.29      <none>        3000/TCP                                                     7m39s
service/ingressgateway              NodePort    10.105.82.182    <none>        80:32502/TCP,443:30832/TCP,15021:31507/TCP,15443:30086/TCP   3m37s
service/istiod                      ClusterIP   10.105.11.60     <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP,853/TCP                164m
service/jaeger-agent                ClusterIP   None             <none>        5775/UDP,6831/UDP,6832/UDP                                   163m
service/jaeger-collector            ClusterIP   10.102.57.152    <none>        14267/TCP,14268/TCP,14250/TCP                                163m
service/jaeger-collector-headless   ClusterIP   None             <none>        14250/TCP                                                    163m
service/jaeger-query                ClusterIP   10.107.18.99     <none>        16686/TCP                                                    163m
service/kiali                       ClusterIP   10.97.17.250     <none>        20001/TCP                                                    163m
service/prometheus                  ClusterIP   10.107.98.61     <none>        9090/TCP                                                     163m
service/tracing                     ClusterIP   10.101.165.213   <none>        80/TCP                                                       163m
service/zipkin                      ClusterIP   10.104.211.16    <none>        9411/TCP                                                     163m

오류시

kubectl 명령어를 통해 로그를 볼 수 있다.

yaml파일을 잘못 작성했을 가능성이 높다.

1
2
3
4
5
#pod-name 확인
$ kubectl get pod -n istio-operator
istio-operator-748456f49-hvrs9   1/1     Running   0          177m
#뒤에 붙는 문자는 각자 다르기떄문에 pod-name으로 표시하였음
$ kubectl -n istio-operator logs -f istio-operator-{pod-name}

Traffic management

비유 접기/펼치기

트래픽이 차를 타고 목적지까지 가야하는 사람이라고 생각해보자.

여기 서울에서 부산으로 식사하러 가는 사람이 있다. 서울에서 부산을 가는데 경부고속도로를 탈것이며 고속도로 톨게이트에서 부산을 갈 수 있는지 물어볼수도 있다.

서해안고속도로 톨게이트에서 부산을 갈수 없으니 해당 차량은 들어가지 않을 것이다. 즉 톨게이트는 Gateway이다.

그렇다고 경부고속도로의 모든 사람들이 부산을 가진 않는다. 수원, 안산 등을 각자 다양한 목적지를 가지고 고속도로를 탔을 것이며 각자의 목적지에서 내릴것이다. 각자의 목적지는 VirtualSerivce라고 볼 수 있다.

여기서 어떤사람은 부산의 아무식당이나 가도 괜찮은 사람도 있고 맛집이라는 레이블이 붙은 집을 가야하는 사람도 있다. 전자는 Destination Rule이 필요하지않으며 후자는 맛집 레이블이 있는 식당만 갈것이다.

Gateway

Gateway는 서비스메쉬의 가장 앞단에서 어떠한 인바운드, 아웃바운드 트래픽을 받을지 결정한다.

포트, 프로토콜, host, SNI등을 지정하여 트래픽을 받아드리며 tls설정(http to https redirection, 인증서 설정) 등을 할 수있다. Gateway만으론 서비스로 트래픽을 보내지 못한며 말 그대로 관문역할만 수행하며 뒤에나오는 VirtualService를 통해 라우팅이 가능하다.

host와 프로토콜 포트로 지정하는 점에서 httpd의 vhost와 유사하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: book-gateway
  namespace: book
spec:
  selector:
    app: istio-ingress-gateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - bookinfo.com
    tls:
      httpsRedirect: true # sends 301 redirect for http requests
  - port:
      number: 443
      name: https-443
      protocol: HTTPS
    hosts:
    - bookinfo.com
    tls:
      mode: SIMPLE # enables HTTPS on this port
      serverCertificate: /etc/certs/servercert.pem
      privateKey: /etc/certs/privatekey.pem

위는 Gateway의 예시이다.

selector는 istio-ingress-gateway라는 라벨을 가진 ingres-gateway에 트래픽을 처리하겠다는 의미이다.

servers는 selector의 ingres-gateway에서 들어온 트래픽중 명시한 조건에 맞는 트래픽만 처리하겠다라는 의미이다. 여기에서 servers는 배열로써 2개의항목을 가지고 있다.

  1. HTTP 프로토콜, 80포트, host가 bookinfo.com인 트래픽을 https로 리다이렉션한다.
  2. HTTPS 프로토콜, 443포트, host가 bookinfo.com인 트래픽을 Gateway가 처리하겠다. 또한 tls설정은 인증서 정보를 담고 있다.

VirtualService

VirtualService는 인바운드 트래픽을 어디로 전송하지 결정한다.(트래픽 라우팅)

프로토콜 http, tls, tcp에 따른 조건과 동작을 지정할 수 있다. http에서는 리다이렉션, 미러링, 헤더등 다양한 조작을 할 수있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: book-route
  namespace: book
spec:
  gateways:
  - mesh#모든 사이드카 의미
  - book-gateway
  hosts:
  - bookinfo.com
  http:
  - name: "reviews-v1-v2-canary-routes"
    match:
    - uri:
        prefix: "/wpcatalog"
    - uri:
        prefix: "/consumercatalog"
    rewrite:
      uri: "/newcatalog"
    route:
    - destination:
        host: reviews.book.svc.cluster.local 
        #host: reviews #위와같은의미
        subset: v2
      weight: 50
    - destination:
        host: reviews.book.svc.cluster.local
        subset: v1
      weight: 50
  - name: "reviews-v1-route"
    route:
    - destination:
        host: core.book.svc.cluster.local

위는 VirtualService의 예시이다.

gateways는 gateway의 selector와 비슷한 개념이다. 어떠한 경로로 들어온 트래픽에 대해 적용시킬건지 명시한다. 여기서는 mesh(모든 사이트카), book-gateway(위에서 생성한 Gateway)로 지정하였다.

http에서 세부적으로 라우팅 규칙 세트를 지정한다. 라우팅 규칙 세트는 배열이기때문에 여러개의 조건과 동작을 명시할 수 있다.

라우팅 규칙 세트는 순차적으로 평가되며 먼저일치하는 한개의 규칙만 적용된다.

  • reviews-v1-v2-canary-routes
    • match : match 항목은 여러개를 지정할 수 있으며 여러개일 경우 OR연산이다. 여기서는 url prefix 즉 bookinfo.com/wpcatalog/* OR bookinfo.com/consumercatalog/* 를 조건으로 지정했으며 조건에 맞는다면 아래의 rewrite와 route가 실행된다.
    • rewrite : /newcatalog로 rewrite
    • route : route는 목적지(destination)로 트래픽을 전송한다. 목적지는 배열로 여러 항목을 가질 수 있으며 현재 파일에서는 weight를 통해 1:1비율로 분배된다. 목적지의 host는 같으나 subset이 다르며 해당 subset을 통해 카나리배포가 가능하다. subset은 DestinationRule에서 사용된다.
  • reviews-v1-route
    • match 항목이 없기때문에 모든 항목에대해 동작한다.
    • route : reviews.book.svc.cluster.local로 트래픽을 전송한다.

DestinationRule

VirtualSerivce는 트래픽을 어디로 보낼지 였다면 DestinationRule은 어떻게 처리할지를 정의한다.

쿠버네티스에서는 서비스를 사용하여 고유한 어플리케이션을 노출시킨다. DestinationRule은 이러한 서비스에서 어떻게 pod에게 트래픽을 분배할 것인지 지정한다. 로드밸런싱과 subset을 이용한 특정 팟에 요청이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews-destination-rule
  namesapce: book
spec:
  host: reviews
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
    trafficPolicy:
      loadBalancer:
        simple: ROUND_ROBIN

위는 DestinationRule의 예시이다.

host는 어떤 서비스에 적용시킬지 선택하는 선택자이다.

trafficPolicy는 기본 로드밸런싱 규칙을 지정한다. subset에서 오버라이드하지않는다면 기본 규칙을 사용한다.

subset은 VirtualService에서 전달해준 값을 기반으로 팟을 선택하는 규칙을 정할 수 있다.

v1이름을 가진 subset은 version: v1 레이블을 가진 pod을 선택할 것이다. 이때 로드밸런싱 규칙을 정하지않았기 때문에 위에서 설정한 RANDOM을 사용한다.

v2이름을 가진 subset은 version: v2 레이블을 가진 pod을 선택할 것이다. 로드밸런싱 규칙을 ROUND_ROBIN으로 지정하였다.


참고 URL

Istio Docs의 예시를 조금 수정하였습니다.