Istio 導入への道 - VirtualService 編

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 導入への道シリーズ

Built with Hugo
テーマ StackJimmy によって設計されています。