IstioをHelmでインストールしてRoutingとTelemetryを行いJaeger/Kialiで確認する

gcpistiokubernetes

IstioはEnvoyというProxyをSidecarとしてPodに入れてトラフィックを通すことでマイクロサービスのRoutingやTelemetryをサービスのコードに手を入れずに行うことができるサービスメッシュ。 もともとEnvoy自体は単体で、コネクションを張りっぱなしのgRPC(HTTP/2)をK8sのServiceのL4ロードバランサーでは分散できない問題の解決方法の一つとして 各PodのIPの一覧を返すHeadless Serviceと使われていたが、各Manifestに入れたりConfigMapを編集したりする必要があり少し面倒だった。 Istioにするとそれらが省けて、さらに賢いRoutingやモニタリングの仕組みまで付いてくるのでとても便利だ。

インストール

IstioをダウンロードしてきてHelmでインストールする。Istioには様々なコンポーネントが含まれているが、パラメータでインストールするものを選択することができる。

KubernetesのパッケージマネージャーHelmを使う - sambaiz-net

今回はデフォルトではfalseになっているGrafana/Jaeger/Kialiをtrueにしてほぼ全て入るようにしている。

RBACが有効な場合はServiceAccountを作ってcluster-adminあるいは必要なRoleをBindしておく。

RBACが有効なGKEでHelmを使う - sambaiz-net

$ curl -L https://git.io/getLatestIstio | sh -
$ cd istio-1.0.1/
# helm init --service-account tiller
$ helm install install/kubernetes/helm/istio --name istio --namespace istio-system --set grafana.enabled=true --set grafana.persist=true --set grafana.storageClassName=standard --set tracing.enabled=true --set kiali.enabled=true
$ kubectl get pod -n istio-system
NAME                                        READY     STATUS    RESTARTS   AGE
grafana-598678cbb-bglbq                     1/1       Running   0          3m
istio-citadel-6f9887d776-tvdg8              1/1       Running   0          3m
istio-egressgateway-84d78d84bf-zpxrq        1/1       Running   0          3m
istio-galley-556f5558f5-hk2r8               1/1       Running   0          3m
istio-ingressgateway-78cccbddbb-gh2xl       1/1       Running   0          3m
istio-pilot-799845f56d-l777d                2/2       Running   0          3m
istio-policy-7666fcd574-nbx8s               2/2       Running   0          3m
istio-sidecar-injector-7b6589c9-m7x77       1/1       Running   0          3m
istio-statsd-prom-bridge-55965ff9c8-s6dmj   1/1       Running   0          3m
istio-telemetry-844c8d6bff-9trcf            2/2       Running   0          3m
istio-tracing-77f9f94b98-g7v6f              1/1       Running   0          3m
kiali-bdf7fdc78-9lpd4                       1/1       Running   0          3m
prometheus-7456f56c96-drhlq                 1/1       Running   0          3m

default namespaceにラベルを貼って自動でEnvoyが各PodにInjectionされるようにする。

$ kubectl get namespace -L istio-injection
NAME           STATUS    AGE       ISTIO-INJECTION
default        Active    27m       
istio-system   Active    16m       
kube-public    Active    27m       
kube-system    Active    27m 

$ kubectl label namespace default istio-injection=enabled

併せてInit ContainersとしてInjectionされるistio-initのiptablesでEnvoy以外のトラフィックがEnvoyのポートに向くのでサービスのコードでは向き先を変える必要がない。

Routing

Pilot

全Envoyを管理するPilotにEnvoy間のトラフィックのルーティングや、リトライやサーキットブレーカーのルールを設定できる。 EnvoyはPilotからサービスディスカバリして他Envoyについて知り、定期的にロードバランシングプールに含まれるインスタンスのヘルスチェックをして失敗数がしきい値を超えたら成功数がしきい値を超えるまでプールから追い出し、ルールに従いトラフィックを制御する。

リソース

  • Gateway: HTTP/TCPロードバランサー。

Ingressとは異なりL4-L6のポートやTLSの設定は持つがL7の設定は持たず、VirtualServiceに持つ。 インフラ管理者とサービス開発者が触るであろうリソースが分離されている。

GKEでのService(ClusterIP/NodePort/LoadBalancer)とIngress - sambaiz-net

サンプルアプリケーションBookInfoを動かす。

$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
$ kubectl get pod
NAME                              READY     STATUS    RESTARTS   AGE
details-v1-7bcdcc4fd6-x92f5       2/2       Running   0          1m
productpage-v1-8584c875d8-jxl68   2/2       Running   0          1m
ratings-v1-54cf9dc8f8-wv9xz       2/2       Running   0          1m
reviews-v1-59cbdd7959-vxf2f       2/2       Running   0          1m
reviews-v2-dccb4cfc9-mc22f        2/2       Running   0          1m
reviews-v3-5465dc97bc-pgvbl       2/2       Running   0          1m

GatewayとVirtualServiceを作成して外に開く。

$ cat samples/bookinfo/networking/bookinfo-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
  - "*"
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        host: productpage
        port:
          number: 9080

$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
$ export GATEWAY_URL=http://$INGRESS_HOST:$INGRESS_PORT
$ open $GATEWAY_URL/productpage

BookInfo

DestinationRuleを作成する。この時点では各バージョンがラウンドロビンする。

$ cat samples/bookinfo/networking/destination-rule-all.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: ratings
spec:
  host: ratings
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v2-mysql
    labels:
      version: v2-mysql
  - name: v2-mysql-vm
    labels:
      version: v2-mysql-vm
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: details
spec:
  host: details
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---

$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

VirtualServiceのdestinationでsubsetを指定するとそのバージョンのみに飛ぶようになる。

$ cat samples/bookinfo/networking/virtual-service-all-v1.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: productpage
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: productpage
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: details
spec:
  hosts:
  - details
  http:
  - route:
    - destination:
        host: details
        subset: v1
---

$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml

VirtualServiceはheaderの中身をみてルーティングさせることもできる。

$ cat samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v2
  - route:
    - destination:
        host: reviews
        subset: v1

$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml

Fault Injectionして特定条件や割合で意図的に遅延を生じさせたり、エラーコードを返らせたりできる。バグの発見や調査、Chaos Engineeringもできそうだ。

$ cat samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    fault:
      delay:
        percent: 100
        fixedDelay: 7s
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1

$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml

routeには複数のdestinationを指定できてweightによって飛ばす割合を変えられる。 カナリアリリースやA/Bテストが簡単にできる。

$ cat samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 50
    - destination:
        host: reviews
        subset: v3
      weight: 50

$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml

Telemetry

Mixer

MixerはポリシーコントロールとTelemetryを行うコンポーネント。 AdapterによってStackdriverやCloudWatchなど様々なバックエンドに対応している。

Envoyは前提条件を確認するためリクエストの前と、Telemetryするためリクエストの後にMixerを呼び出す。ただしキャッシュとバッファを持っていて毎回呼び出しはしない。また、Mixerもキャッシュやバッファを持ちバックエンドの呼び出し回数を減らしている。 MixerがSPOFとなり可用性が下がるのではと思われるが、バックエンドの障害をある程度許容できるのに加えて Mixer自身はステートレスで他のバックエンドよりも可用性が高く設計されているためむしろSLOが向上するそうだ。

リソース

  • Handler(Adapter): Mixerからバックエンドへ送る。
  • Instance: EnvoyからMixerに送られてきたAttributeをAdapterの入力へマッピングする。
  • Rule: Handlerが特定のInstancesで呼び出されるルール。

リクエストの2倍の数を値とするmetric instance。configuration expression language(CEXL)が使われている。

$ cat new_telemetry.yaml 
# Configuration for metric instances
apiVersion: "config.istio.io/v1alpha2"
kind: metric
metadata:
  name: doublerequestcount
  namespace: istio-system
spec:
  value: "2" # count each request twice
  dimensions:
    reporter: conditional((context.reporter.kind | "inbound") == "outbound", "client", "server")
    source: source.workload.name | "unknown"
    destination: destination.workload.name | "unknown"
    message: '"twice the fun!"'
  monitored_resource_type: '"UNSPECIFIED"'
---

このmetricを受け取るprometheus handler。

# Configuration for a Prometheus handler
apiVersion: "config.istio.io/v1alpha2"
kind: prometheus
metadata:
  name: doublehandler
  namespace: istio-system
spec:
  metrics:
  - name: double_request_count # Prometheus metric name
    instance_name: doublerequestcount.metric.istio-system # Mixer instance name (fully-qualified)
    kind: COUNTER
    label_names:
    - reporter
    - source
    - destination
    - message
---

そしてこれらを紐づけるrule。

# Rule to send metric instances to a Prometheus handler
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: doubleprom
  namespace: istio-system
spec:
  actions:
  - handler: doublehandler.prometheus
    instances:
    - doublerequestcount.metric
---

logentryはログを表すinstance。 あとはそれを標準出力するhandlerとrule。

# Configuration for logentry instances
apiVersion: "config.istio.io/v1alpha2"
kind: logentry
metadata:
  name: newlog
  namespace: istio-system
spec:
  severity: '"warning"'
  timestamp: request.time
  variables:
    source: source.labels["app"] | source.workload.name | "unknown"
    user: source.user | "unknown"
    destination: destination.labels["app"] | destination.workload.name | "unknown"
    responseCode: response.code | 0
    responseSize: response.size | 0
    latency: response.duration | "0ms"
  monitored_resource_type: '"UNSPECIFIED"'
---
# Configuration for a stdio handler
apiVersion: "config.istio.io/v1alpha2"
kind: stdio
metadata:
  name: newhandler
  namespace: istio-system
spec:
 severity_levels:
   warning: 1 # Params.Level.WARNING
 outputAsJson: true
---
# Rule to send logentry instances to a stdio handler
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: newlogstdio
  namespace: istio-system
spec:
  match: "true" # match for all requests
  actions:
   - handler: newhandler.stdio
     instances:
     - newlog.logentry
---

$ kubectl apply -f new_telemetry.yaml 

Prometheus handlerによってdoublerequestcount metricが届いている。

$ kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=prometheus -o jsonpath='{.items[0].metadata.name}') 9090:9090
$ open http://localhost:9090/graph

Prometheus

stdio handlerによってnewlog logentryが出力されている。

$ kubectl -n istio-system logs -f $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') -c mixer | grep \"instance\":\"newlog.logentry.istio-system\" 
{"level":"warn","time":"2018-09-01T13:53:45.897141Z","instance":"l","destination":"telemetry","latency":"3.817501ms","responseCode":200,"responseSize":5,"source":"details","user":"unknown"}
{"level":"warn","time":"2018-09-01T13:53:45.899237Z","instance":"newlog.logentry.istio-system","destination":"telemetry","latency":"4.423947ms","responseCode":200,"responseSize":5,"source":"productpage","user":"unknown"}

Grafana

Grafanaを開くと最初からIstioのダッシュボードがいくつか作られている。 Prometheusに送ったmetricをGrafanaで可視化できる。

$ kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000:3000
$ open http://localhost:3000

Grafana

Jaeger

Jaegerは分散トレーシングツール。 パフォーマンスが問題になったときにどこのサービスがボトルネックになっているかが分かる。

$ kubectl port-forward -n istio-system $(kubectl get pod -n istio-system -l app=jaeger -o jsonpath='{.items[0].metadata.name}') 16686:16686
$ open http://localhost:16686

Jaeger

Kiali

Kialiはサービスメッシュを可視化するツール。v1とv3にリクエストが飛んでいてv2には飛んでいないことが分かる。

$ kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=kiali -o jsonpath='{.items[0].metadata.name}') 20001:20001
$ open http://localhost:20001

Kiali

参考

kubernetesでgRPCするときにenvoy挟んでみたよ

Why You Should Care About Istio Gateways - The New Stack