Istio シリーズです。
今回は VirtualService です。これを利用することで、コネクションプーリングの設定をしたり、レートリミットを入れたり、振り分け方法を指定したり、同じホスト名でアクセスしても条件によって振り分けを行えたり、指定の HTTP レスポンス (400 とか 500 Internal Server Error とか) を返したり、delay を入れたりすることができるようになります。また、後でやる Ingress Gateway からアクセスできるようになったりします。
複数 Version の Deployment を用意する
Version 違いを出し分けたりするテストを行うため、一旦今の httpbin-deployment を削除します。
$ kubectl delete deployment httpbin-deployment
本当はレスポンスが異なる Pod を用意すればわかりやすいのですが、ログでアクセスを確認するってことで、v1 と v2 と version label だけが異なる Deployment を2つ deploy します。
apiVersion: apps/v1 kind: Deployment metadata: name: httpbin-deployment-v1 labels: app: httpbin version: v1 spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: labels: app: httpbin version: v1 spec: containers: - name: httpbin image: kennethreitz/httpbin:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 readinessProbe: httpGet: path: /status/200 port: 80 initialDelaySeconds: 10 periodSeconds: 5 livenessProbe: httpGet: path: /status/200 port: 80 initialDelaySeconds: 15 periodSeconds: 5 --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin-deployment-v2 labels: app: httpbin version: v2 spec: replicas: 1 selector: matchLabels: app: httpbin version: v2 template: metadata: labels: app: httpbin version: v2 spec: containers: - name: httpbin image: kennethreitz/httpbin:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 readinessProbe: httpGet: path: /status/200 port: 80 initialDelaySeconds: 10 periodSeconds: 5 livenessProbe: httpGet: path: /status/200 port: 80 initialDelaySeconds: 15 periodSeconds: 5
httpbin-service として作成ずみの Service は selector が app: httpbin
だけであるため、v1 も v2 も両方とも対象となり、この状態でも v1, v2 両方にリクエストが振り分けられる状況ですが、振り分け方法を細かく制限したり Fault injection を行えるようにするため VirtualService を定義します。
v1 も v2 も一つの Service に含まれている様子。Endpoints に両方入っています。
$ kubectl get pods -o wide -l app=httpbin NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES httpbin-deployment-v1-7d95bdc6f6-5f69g 2/2 Running 0 13m 172.17.0.7 m01 <none> <none> httpbin-deployment-v2-ccd49cc9c-lgvjs 2/2 Running 0 12m 172.17.0.8 m01 <none> <none> $ kubectl describe svc httpbin-service Name: httpbin-service Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"httpbin-service","namespace":"default"},"spec":{"ports":[{"name":... Selector: app=httpbin Type: ClusterIP IP: 10.109.118.31 Port: http 80/TCP TargetPort: 80/TCP Endpoints: 172.17.0.7:80,172.17.0.8:80 Session Affinity: None Events: <none>
DestinationRule の作成
v1, v2 それぞれにアクセスするための DestinationRule を作成します。
# # httpbin-service という Service 宛 traffic で # VirtualService により、subset: v1 と指定された場合は # version label が v1 の Endpoint へ転送し、 # subnet: v2 の場合は label が v2 の Endpoint へ転送する # apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: httpbin-destination-rule spec: host: httpbin-service subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2
VirtualService
上で作った DestinationRule を使って VirtualServive を設定します。
重みづけで振り分ける
一番単純な例
# # httpbin-service 宛ての http リクエストを httpbin-service の v1 か v2 に半々の割合で転送する # apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-virtual-service spec: hosts: - httpbin-service http: - route: - destination: host: httpbin-service subset: v1 weight: 50 - destination: host: httpbin-service subset: v2 weight: 50
wegith を 100:0 に変更して試すと全部片方にしかリクエストが送られないことが確認できる。
HTTP Header を使って振り分ける
HTTPMatchRequest を使うことで HTTP の Request の内容によって振り分けを行うことができる。
# # queryString に v=1 があれば v1 へ、v=2 があれば v2 へ転送する # apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-virtual-service spec: hosts: - httpbin-service http: - match: - name: v1 queryParams: v: exact: "1" route: - destination: host: httpbin-service subset: v1 - match: - name: v2 queryParams: v: exact: "2" route: - destination: host: httpbin-service subset: v2
この例の様に `match` に `name` を設定しておけば、送信側の istio-proxy のログの `route_name` に subset 名が入っている。
{ "authority": "httpbin-service", "bytes_received": "0", "bytes_sent": "521", "downstream_local_address": "10.109.118.31:80", "downstream_remote_address": "172.17.0.9:50168", "duration": "3", "istio_policy_status": "-", "method": "GET", "path": "/headers?v=1", "protocol": "HTTP/1.1", "request_id": "c463717a-a722-47c6-8ec1-ea8f90b9ea58", "requested_server_name": "-", "response_code": "200", "response_flags": "-", "route_name": ".v1", "start_time": "2020-03-07T16:06:58.480Z", "upstream_cluster": "outbound|80|v1|httpbin-service.default.svc.cluster.local", "upstream_host": "172.17.0.7:80", "upstream_local_address": "172.17.0.9:60574", "upstream_service_time": "2", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" } { "authority": "httpbin-service", "bytes_received": "0", "bytes_sent": "521", "downstream_local_address": "10.109.118.31:80", "downstream_remote_address": "172.17.0.9:50224", "duration": "3", "istio_policy_status": "-", "method": "GET", "path": "/headers?v=2", "protocol": "HTTP/1.1", "request_id": "b5091908-4806-465a-b755-59141e3acd25", "requested_server_name": "-", "response_code": "200", "response_flags": "-", "route_name": ".v2", "start_time": "2020-03-07T16:07:02.565Z", "upstream_cluster": "outbound|80|v2|httpbin-service.default.svc.cluster.local", "upstream_host": "172.17.0.8:80", "upstream_local_address": "172.17.0.9:54576", "upstream_service_time": "3", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" } { "authority": "httpbin-service", "bytes_received": "0", "bytes_sent": "0", "downstream_local_address": "10.109.118.31:80", "downstream_remote_address": "172.17.0.9:50604", "duration": "0", "istio_policy_status": "-", "method": "GET", "path": "/headers?v=3", "protocol": "HTTP/1.1", "request_id": "24312033-fbf6-48d9-936a-bb23e3381d7f", "requested_server_name": "-", "response_code": "404", "response_flags": "NR", "route_name": "-", "start_time": "2020-03-07T16:07:28.632Z", "upstream_cluster": "-", "upstream_host": "-", "upstream_local_address": "-", "upstream_service_time": "-", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" }
3つ目のログは v=3 で、その定義はしていなかったため 404 が返されている。次の様に `match` をつけないで `route` を最後に書いておけばマッチしなかったものが全てそこに送られる。
# # queryString に v=1 があれば v1 へ、そうでなければ v2 へ転送する # apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-virtual-service spec: hosts: - httpbin-service http: - match: - name: v1 queryParams: v: exact: "1" route: - destination: host: httpbin-service subset: v1 - route: - destination: host: httpbin-service subset: v2
注意点として、VirtualService の振り分けは最初にマッチしたところで宛先が決まってしまう点。条件の厳しいものから順に書いておく必要がある。
次回は Fault Injection にしよう。
Istio 導入への道シリーズ
- Istio 導入への道 (1) – インストール編
- Istio 導入への道 (2) – サービス間通信編
- Istio 導入への道 (3) – VirtualService 編
- Istio 導入への道 (4) – Fault Injection 編
- Istio 導入への道 (5) – OutlierDetection と Retry 編
- Istio 導入への道 (6) – Ingress Gatway 編
- Istio 導入への道 (7) – 外部へのアクセス / ServiceEntry 編
- Istio 導入への道 (8) – 外部へのアクセスでも Fault Injection 編
- Istio 導入への道 (9) – gRPC でも Fault Injection 編
- Istio 導入への道 (10) – 図解
- Istio 導入への道 (11) – Ingress Gateway で TLS Termination 編