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
- VirtualService: ルーティングのルール。
- DestinationRule: ロードバランシングのルール。
例
サンプルアプリケーション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
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
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
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
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