多集群服务网格中的分版本路由

如果花一点时间对 Istio 进行了解,你可能会注意到,大量的功能都可以在单一的 Kubernetes 集群中,用简单的任务示例所表达的方式来运行。但是真实世界中的云计算和基于微服务的应用往往不是这么简单的,会需要在不止一个地点分布运行,用户难免会产生怀疑,生产环境中是否还能这样运行?

幸运的是,Istio 提供了多种服务网格的配置方式,应用能够用近乎透明的方式加入一个跨越多个集群运行的服务网格之中,也就是多集群服务网格。最简单的设置多集群网格的方式,就是使用多控制平面拓扑,这种方式不需要特别的网络依赖。在这种条件下,每个 Kubernetes 集群都有自己的控制平面,但是每个控制平面都是同步的,并接受统一的管理。

本文中,我们会在多控制平面拓扑形式的多集群网格中尝试一下 Istio 的流量管理功能。我们会展示如何配置 Istio 路由规则,在多集群服务网格中部署 Bookinfo 示例reviews 服务的 v1 版本运行在一个集群上,而 v2v3 运行在另一个集群上,并完成远程服务调用。

集群部署

首先需要部署两个 Kubernetes 集群,并各自运行一个做了轻度定制的 Istio。

  • 依照使用 Gateway 连接多个集群中提到的步骤设置一个多集群环境。

  • kubectl 命令可以使用 --context 参数访问两个集群。 使用下面的命令列出所有 context

    $ kubectl config get-contexts
    CURRENT   NAME       CLUSTER    AUTHINFO       NAMESPACE
    *         cluster1   cluster1   user@foo.com   default
              cluster2   cluster2   user@foo.com   default
    
  • 将配置文件中的 context 名称赋值给两个环境变量:

    $ export CTX_CLUSTER1=<cluster1 context name>
    $ export CTX_CLUSTER2=<cluster2 context name>
    

cluster1 中部署 bookinfov1 版本

cluster1 中运行 productpagedetails 服务,以及 reviews 服务的 v1 版本。

$ kubectl label --context=$CTX_CLUSTER1 namespace default istio-injection=enabled
$ kubectl apply --context=$CTX_CLUSTER1 -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: productpage-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: productpage
        version: v1
    spec:
      containers:
      - name: productpage
        image: istio/examples-bookinfo-productpage-v1:1.10.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
---
apiVersion: v1
kind: Service
metadata:
  name: details
  labels:
    app: details
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: details
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: details-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: details
        version: v1
    spec:
      containers:
      - name: details
        image: istio/examples-bookinfo-details-v1:1.10.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
---
apiVersion: v1
kind: Service
metadata:
  name: reviews
  labels:
    app: reviews
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: reviews
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: reviews-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: reviews
        version: v1
    spec:
      containers:
      - name: reviews
        image: istio/examples-bookinfo-reviews-v1:1.10.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
EOF

cluster2 中部署 bookinfov2v3

cluster2 中运行 ratings 服务以及 reviews 服务的 v2v3 版本:

$ kubectl label --context=$CTX_CLUSTER2 namespace default istio-injection=enabled
$ kubectl apply --context=$CTX_CLUSTER2 -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: ratings
  labels:
    app: ratings
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: ratings
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ratings-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: ratings
        version: v1
    spec:
      containers:
      - name: ratings
        image: istio/examples-bookinfo-ratings-v1:1.10.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
---
apiVersion: v1
kind: Service
metadata:
  name: reviews
  labels:
    app: reviews
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: reviews
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: reviews-v2
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: reviews
        version: v2
    spec:
      containers:
      - name: reviews
        image: istio/examples-bookinfo-reviews-v2:1.10.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: reviews-v3
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: reviews
        version: v3
    spec:
      containers:
      - name: reviews
        image: istio/examples-bookinfo-reviews-v3:1.10.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
EOF

访问 bookinfo 应用

和平常一样,我们需要使用一个 Istio gateway 来访问 bookinfo 应用。

  • cluster1 中创建 bookinfo 的网关:

    Zip
    $ kubectl apply --context=$CTX_CLUSTER1 -f @samples/bookinfo/networking/bookinfo-gateway.yaml@
    
  • 遵循 Bookinfo 示例应用中的步骤,确定 Ingress 的 IP 和端口,用浏览器打开 http://$GATEWAY_URL/productpage

这里会看到 productpage,其中包含了 reviews 的内容,但是没有出现 ratings,这是因为只有 reviews 服务的 v1 版本运行在 cluster1 上,我们还没有配置到 cluster2 的访问。

cluster1 上为远端的 reviews 服务创建 ServiceEntry 以及 DestinationRule

根据配置指南中的介绍,远程服务可以用一个 .global 的 DNS 名称进行访问。在我们的案例中,就是 reviews.default.global,所以我们需要为这个主机创建 ServiceEntryDestinationRuleServiceEntry 会使用 cluster2 网关作为端点地址来访问服务。可以使用网关的 DNS 名称或者公共 IP:

$ export CLUSTER2_GW_ADDR=$(kubectl get --context=$CTX_CLUSTER2 svc --selector=app=istio-ingressgateway \
    -n istio-system -o jsonpath="{.items[0].status.loadBalancer.ingress[0].ip}")

用下面的命令来创建 ServiceEntryDestinationRule

$ kubectl apply --context=$CTX_CLUSTER1 -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: reviews-default
spec:
  hosts:
  - reviews.default.global
  location: MESH_INTERNAL
  ports:
  - name: http1
    number: 9080
    protocol: http
  resolution: DNS
  addresses:
  - 127.255.0.3
  endpoints:
  - address: ${CLUSTER2_GW_ADDR}
    labels:
      cluster: cluster2
    ports:
      http1: 15443 # 不要修改端口值
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews-global
spec:
  host: reviews.default.global
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
  subsets:
  - name: v2
    labels:
      cluster: cluster2
  - name: v3
    labels:
      cluster: cluster2
EOF

ServiceEntry 的地址 127.255.0.3 可以是任意的未分配 IP。在 127.0.0.0/8 的范围里面进行选择是个不错的主意。阅读通过网关进行连接的多集群一文,能够获得更多相关信息。

注意 DestinationRule 中的 subset 的标签,cluster: cluster2 对应的是 cluster2 网关。一旦流量到达目标集群,就会由本地目的 DestinationRule 来鉴别实际的 Pod 标签(version: v1 或者 version: v2

在所有集群上为本地 reviews 服务创建 DestinationRule

技术上来说,我们只需要为每个集群定义本地的 subset 即可(cluster1 中的 v1cluster2 中的 v2v3),但是定义一个用不到的并未部署的版本也没什么大碍,为了清晰一点,我们会在两个集群上都创建全部三个 subset

$ kubectl apply --context=$CTX_CLUSTER1 -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews.default.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
EOF
$ kubectl apply --context=$CTX_CLUSTER2 -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews.default.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
EOF

创建 VirtualService 来路由 reviews 服务的流量

目前所有调用 reviews 服务的流量都会进入本地的 reviews Pod,也就是 v1,如果查看一下远吗,会发现 productpage 的实现只是简单的对 http://reviews:9080 (也就是 reviews.default.svc.cluster.local)发起了请求,也就是本地版本。对应的远程服务名称为 reviews.default.global,所以需要用路由规则来把请求转发到远端集群。

创建下列的 VirtualService,把 jason 的流量转发给运行在 cluster2 上的 v2v3 版本的 reviews,两个版本各负责一半流量。其他用户的流量还是会发给 v1 版本的 reviews

$ kubectl apply --context=$CTX_CLUSTER1 -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews.default.svc.cluster.local
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews.default.global
        subset: v2
      weight: 50
    - destination:
        host: reviews.default.global
        subset: v3
      weight: 50
  - route:
    - destination:
        host: reviews.default.svc.cluster.local
        subset: v1
EOF

回到浏览器,用 jason 的身份登录。刷新页面几次,会看到星形图标在红黑两色之间切换(v2v3)。如果登出,就只会看到没有 ratingsreviews 服务了。

总结

本文中,我们看到在多控制平面拓扑的多集群网格中,如何使用 Istio 路由规则进行跨集群的流量分配。 这里我们手工配置了 .globalServiceEntry 以及 DestinationRule,用于进行对远端集群中 reviews 服务的访问。实际上如果我们想要的话,可以让任何服务都在远端或本地运行,当然需要为远端服务配置 .global 的相关资源。幸运的是,这个过程可以自动化,并且可能在 Istio 的未来版本中实现。