共享的控制平面(多网络)

遵循本指南配置一个多集群网格,使用共享的 控制平面,并通过网关连通彼此网络隔离的集群。 Istio 位置感知的服务路由特性,可以根据请求源所在的位置将请求路由至不同的 endpoints。

遵循本指南中的说明,将安装一个两集群网格,如下图所示:

Shared Istio control plane topology spanning multiple Kubernetes clusters using gateways
Shared Istio control plane topology spanning multiple Kubernetes clusters using gateways

主集群 cluster1 运行全部的 Istio 控制平面组件集,而 cluster2 只运行 Istio Citadel、Sidecar 注入器以及 Ingress 网关。 不同集群的工作负载之间既不要求 VPN 连接也不要求直接网络访问。

前提条件

  • 两个或多个 Kubernetes 集群,版本为:1.17, 1.18, 1.19。

  • 有权限部署 Istio 控制平面

  • 两个 Kubernetes 集群(称为 cluster1cluster2)。

  • 你可以使用 kubectl 命令带上 --context 参数去访问集群 cluster1cluster2, 例如 kubectl get pods --context cluster1。 使用如下命令列出你的上下文:

    $ kubectl config get-contexts
    CURRENT   NAME       CLUSTER    AUTHINFO       NAMESPACE
    *         cluster1   cluster1   user@foo.com   default
              cluster2   cluster2   user@foo.com   default
    
  • 保存集群的上下文到环境变量:

    $ export CTX_CLUSTER1=$(kubectl config view -o jsonpath='{.contexts[0].name}')
    $ export CTX_CLUSTER2=$(kubectl config view -o jsonpath='{.contexts[1].name}')
    $ echo CTX_CLUSTER1 = ${CTX_CLUSTER1}, CTX_CLUSTER2 = ${CTX_CLUSTER2}
    CTX_CLUSTER1 = cluster1, CTX_CLUSTER2 = cluster2
    

安装多集群网格

在本配置中,安装 Istio 时同时开启控制平面和应用 pods 的双向 TLS。 对于共享的根 CA,使用 Istio 示例目录下相同的 Istio 证书,在 cluster1cluster2 中都创建相同的 cacerts secret。

下文命令安装 cluster2 时,创建一个无 selector 的服务,并为 istio-pilot.istio-system 创建一个 endpoint,其地址为 cluster1 的 Istio ingress gateway。 它们用于通过 ingress gateway 安全地访问 cluster1 中的 pilot,无需双向 TLS 终端。

安装集群 1(主集群)

  1. cluster1 中部署 Istio:

    $ kubectl create --context=$CTX_CLUSTER1 ns istio-system
    $ kubectl create --context=$CTX_CLUSTER1 secret generic cacerts -n istio-system --from-file=samples/certs/ca-cert.pem --from-file=samples/certs/ca-key.pem --from-file=samples/certs/root-cert.pem --from-file=samples/certs/cert-chain.pem
    $ istioctl manifest apply --context=$CTX_CLUSTER1 \
      -f install/kubernetes/operator/examples/multicluster/values-istio-multicluster-primary.yaml
    

    等待 cluster1 中的 Istio pods 就绪:

    $ kubectl get pods --context=$CTX_CLUSTER1 -n istio-system
    NAME                                      READY   STATUS    RESTARTS   AGE
    istio-citadel-55d8b59798-6hnx4            1/1     Running   0          83s
    istio-galley-c74b77787-lrtr5              2/2     Running   0          82s
    istio-ingressgateway-684f5df677-shzhm     1/1     Running   0          83s
    istio-pilot-5495bc8885-2rgmf              2/2     Running   0          82s
    istio-policy-69cdf5db4c-x4sct             2/2     Running   2          83s
    istio-sidecar-injector-5749cf7cfc-pgd95   1/1     Running   0          82s
    istio-telemetry-646db5ddbd-gvp6l          2/2     Running   1          83s
    prometheus-685585888b-4tvf7               1/1     Running   0          83s
    
  2. 创建一个 ingress 网关访问 cluster2 中的服务:

    $ kubectl apply --context=$CTX_CLUSTER1 -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: cluster-aware-gateway
      namespace: istio-system
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 443
          name: tls
          protocol: TLS
        tls:
          mode: AUTO_PASSTHROUGH
        hosts:
        - "*.local"
    EOF
    

    本例 Gateway 配置 443 端口来将流经的入口流量导向请求 SNI 头中指明的目标服务,其中 SNI 的顶级域名为 _local_(譬如:Kubernetes DNS 域名)。 从源至目标 sidecar,始终使用双向 TLS 连接。

    尽管应用于 cluster1,该网关实例也会影响 cluster2,因为两个集群通过同一个 Pilot 通信。

  3. 确定 cluster1 的 ingress IP 和端口。

    1. 设置 kubectl 的当前上下文为 CTX_CLUSTER1

      $ export ORIGINAL_CONTEXT=$(kubectl config current-context)
      $ kubectl config use-context $CTX_CLUSTER1
      
    2. 按照确定 ingress IP 和端口中的说明,设置环境变量 INGRESS_HOSTSECURE_INGRESS_PORT

    3. 恢复之前的 kubectl 上下文:

      $ kubectl config use-context $ORIGINAL_CONTEXT
      $ unset ORIGINAL_CONTEXT
      
    4. 打印 INGRESS_HOSTSECURE_INGRESS_PORT

      $ echo The ingress gateway of cluster1: address=$INGRESS_HOST, port=$SECURE_INGRESS_PORT
      
  4. 更新网格网络配置中的网关地址。编辑 istio ConfigMap

    $ kubectl edit cm -n istio-system --context=$CTX_CLUSTER1 istio
    

    将网关地址和 network1 的端口分别更新为 cluster1 的 ingress 主机和端口,然后保存并退出。注意该地址在配置文件中出现两次,第二次位于 values.yaml: 下方。

    一旦保存,Pilot 将自动读取更新后的网络配置。

安装集群 2

  1. 输出 cluster1 的网关地址:

    $ export LOCAL_GW_ADDR=$(kubectl get --context=$CTX_CLUSTER1 svc --selector=app=istio-ingressgateway \
        -n istio-system -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}') && echo ${LOCAL_GW_ADDR}
    

    该命令将网关地址设置为网关的公共 IP 并显示。

  2. cluster2 中部署 Istio:

    $ kubectl create --context=$CTX_CLUSTER2 ns istio-system
    $ kubectl create --context=$CTX_CLUSTER2 secret generic cacerts -n istio-system --from-file=samples/certs/ca-cert.pem --from-file=samples/certs/ca-key.pem --from-file=samples/certs/root-cert.pem --from-file=samples/certs/cert-chain.pem
    $ CLUSTER_NAME=$(kubectl --context=$CTX_CLUSTER2 config view --minify=true -o jsonpath='{.clusters[].name}')
    $ istioctl manifest apply --context=$CTX_CLUSTER2 \
      --set profile=remote \
      --set values.global.mtls.enabled=true \
      --set values.gateways.enabled=true \
      --set values.security.selfSigned=false \
      --set values.global.createRemoteSvcEndpoints=true \
      --set values.global.remotePilotCreateSvcEndpoint=true \
      --set values.global.remotePilotAddress=${LOCAL_GW_ADDR} \
      --set values.global.remotePolicyAddress=${LOCAL_GW_ADDR} \
      --set values.global.remoteTelemetryAddress=${LOCAL_GW_ADDR} \
      --set values.gateways.istio-ingressgateway.env.ISTIO_META_NETWORK="network2" \
      --set values.global.network="network2" \
      --set values.global.multiCluster.clusterName=${CLUSTER_NAME} \
      --set autoInjection.enabled=true
    

    等待 cluster2 中的 Istio pods 就绪,istio-ingressgateway 除外。

    $ kubectl get pods --context=$CTX_CLUSTER2 -n istio-system -l istio!=ingressgateway
    NAME                                      READY   STATUS    RESTARTS   AGE
    istio-citadel-55d8b59798-nlk2z            1/1     Running   0          26s
    istio-sidecar-injector-5749cf7cfc-s6r7p   1/1     Running   0          25s
    
  3. 确定 cluster2 的 ingress IP 和口。

    1. 设置 kubectl 的当前上下文为 CTX_CLUSTER2

      $ export ORIGINAL_CONTEXT=$(kubectl config current-context)
      $ kubectl config use-context $CTX_CLUSTER2
      
    2. 按照确定 ingress IP 和端口中的说明,设置环境变量 INGRESS_HOSTSECURE_INGRESS_PORT

    3. 恢复之前的 kubectl 上下文:

      $ kubectl config use-context $ORIGINAL_CONTEXT
      $ unset ORIGINAL_CONTEXT
      
    4. 打印 INGRESS_HOSTSECURE_INGRESS_PORT

      $ echo The ingress gateway of cluster2: address=$INGRESS_HOST, port=$SECURE_INGRESS_PORT
      
  4. 更新网格网络配置中的网关地址。编辑 istio ConfigMap

    $ kubectl edit cm -n istio-system --context=$CTX_CLUSTER1 istio
    

    network2 的网关地址和端口分别更新为 cluster2 的 ingress 主机和端口,然后保存并退出。注意该地址在配置文件中出现两次,第二次位于 values.yaml: 下方。

    一旦保存,Pilot 将自动读取更新后的网络配置。

  5. 准备环境变量,构建服务账户 istio-reader-service-account 的配置文件 n2-k8s-config

    $ CLUSTER_NAME=$(kubectl --context=$CTX_CLUSTER2 config view --minify=true -o jsonpath='{.clusters[].name}')
    $ SERVER=$(kubectl --context=$CTX_CLUSTER2 config view --minify=true -o jsonpath='{.clusters[].cluster.server}')
    $ SECRET_NAME=$(kubectl --context=$CTX_CLUSTER2 get sa istio-reader-service-account -n istio-system -o jsonpath='{.secrets[].name}')
    $ CA_DATA=$(kubectl get --context=$CTX_CLUSTER2 secret ${SECRET_NAME} -n istio-system -o jsonpath="{.data['ca\.crt']}")
    $ TOKEN=$(kubectl get --context=$CTX_CLUSTER2 secret ${SECRET_NAME} -n istio-system -o jsonpath="{.data['token']}" | base64 --decode)
    
  6. 在工作目录中创建文件 n2-k8s-config

    $ cat <<EOF > n2-k8s-config
    apiVersion: v1
    kind: Config
    clusters:
      - cluster:
          certificate-authority-data: ${CA_DATA}
          server: ${SERVER}
        name: ${CLUSTER_NAME}
    contexts:
      - context:
          cluster: ${CLUSTER_NAME}
          user: ${CLUSTER_NAME}
        name: ${CLUSTER_NAME}
    current-context: ${CLUSTER_NAME}
    users:
      - name: ${CLUSTER_NAME}
        user:
          token: ${TOKEN}
    EOF
    

启动 watching 集群 2{start-watching-cluster-2}

  1. 执行下面命令,添加并标记 Kubernetes cluster2 的 secret。 执行完这些命令,cluster1 中的 Istio Pilot 将开始 watching cluster2 的服务和实例,如同对待 cluster1 一样。

    $ kubectl create --context=$CTX_CLUSTER1 secret generic n2-k8s-secret --from-file n2-k8s-config -n istio-system
    $ kubectl label --context=$CTX_CLUSTER1 secret n2-k8s-secret istio/multiCluster=true -n istio-system
    
  2. 等待 istio-ingressgateway 就绪:

    $ kubectl get pods --context=$CTX_CLUSTER2 -n istio-system -l istio=ingressgateway
    NAME                                    READY     STATUS    RESTARTS   AGE
    istio-ingressgateway-5c667f4f84-bscff   1/1       Running   0          16m
    

现在,cluster1cluster2 均已安装完成,可以部署一个案例服务。

部署案例服务

如上图所示,部署两个 helloworld 服务,一个运行在 cluster1 中,另一个运行在 cluster2 中。 二者的区别是 helloworld 镜像的版本不同。

在集群 2 中部署 helloworld v2

  1. 创建一个 sample 命名空间,用 label 标识开启 sidecar 自动注入:

    $ kubectl create --context=$CTX_CLUSTER2 ns sample
    $ kubectl label --context=$CTX_CLUSTER2 namespace sample istio-injection=enabled
    
  2. 部署 helloworld v2

    ZipZip
    $ kubectl create --context=$CTX_CLUSTER2 -f @samples/helloworld/helloworld.yaml@ -l app=helloworld -n sample
    $ kubectl create --context=$CTX_CLUSTER2 -f @samples/helloworld/helloworld.yaml@ -l version=v2 -n sample
    
  3. 确认 helloworld v2 正在运行:

    $ kubectl get po --context=$CTX_CLUSTER2 -n sample
    NAME                             READY     STATUS    RESTARTS   AGE
    helloworld-v2-7dd57c44c4-f56gq   2/2       Running   0          35s
    

在集群 1 中部署 helloworld v1

  1. 创建一个 sample 命名空间,用 label 标识开启 sidecar 自动注入:

    $ kubectl create --context=$CTX_CLUSTER1 ns sample
    $ kubectl label --context=$CTX_CLUSTER1 namespace sample istio-injection=enabled
    
  2. 部署 helloworld v1

    ZipZip
    $ kubectl create --context=$CTX_CLUSTER1 -f @samples/helloworld/helloworld.yaml@ -l app=helloworld -n sample
    $ kubectl create --context=$CTX_CLUSTER1 -f @samples/helloworld/helloworld.yaml@ -l version=v1 -n sample
    
  3. 确认 helloworld v1 正在运行:

    $ kubectl get po --context=$CTX_CLUSTER1 -n sample
    NAME                            READY     STATUS    RESTARTS   AGE
    helloworld-v1-d4557d97b-pv2hr   2/2       Running   0          40s
    

跨集群路由实践

为了演示访问 helloworld 服务的流量如何跨两个集群进行分发,我们从网格内的另一个 sleep 服务请求 helloworld 服务。

  1. 在两个集群中均部署 sleep 服务:

    ZipZip
    $ kubectl apply --context=$CTX_CLUSTER1 -f @samples/sleep/sleep.yaml@ -n sample
    $ kubectl apply --context=$CTX_CLUSTER2 -f @samples/sleep/sleep.yaml@ -n sample
    
  2. 等待 sleep 服务启动:

    $ kubectl get po --context=$CTX_CLUSTER1 -n sample -l app=sleep
    sleep-754684654f-n6bzf           2/2     Running   0          5s
    
    $ kubectl get po --context=$CTX_CLUSTER2 -n sample -l app=sleep
    sleep-754684654f-dzl9j           2/2     Running   0          5s
    
  3. cluster1 请求 helloworld.sample 服务若干次:

    $ kubectl exec --context=$CTX_CLUSTER1 -it -n sample -c sleep $(kubectl get pod --context=$CTX_CLUSTER1 -n sample -l app=sleep -o jsonpath='{.items[0].metadata.name}') -- curl helloworld.sample:5000/hello
    
  4. cluster2 请求 helloworld.sample 服务若干次:

    $ kubectl exec --context=$CTX_CLUSTER2 -it -n sample -c sleep $(kubectl get pod --context=$CTX_CLUSTER2 -n sample -l app=sleep -o jsonpath='{.items[0].metadata.name}') -- curl helloworld.sample:5000/hello
    

如果设置正确,访问 helloworld.sample 的流量将在 cluster1cluster2 之间分发,返回的响应结果或者为 v1 或者为 v2

Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv

也可以通过打印 sleep 的 istio-proxy 容器日志,验证访问 endpoints 的 IP 地址。

$ kubectl logs --context=$CTX_CLUSTER1 -n sample $(kubectl get pod --context=$CTX_CLUSTER1 -n sample -l app=sleep -o jsonpath='{.items[0].metadata.name}') istio-proxy
[2018-11-25T12:37:52.077Z] "GET /hello HTTP/1.1" 200 - 0 60 190 189 "-" "curl/7.60.0" "6e096efe-f550-4dfa-8c8c-ba164baf4679" "helloworld.sample:5000" "192.23.120.32:15443" outbound|5000||helloworld.sample.svc.cluster.local - 10.20.194.146:5000 10.10.0.89:59496 -
[2018-11-25T12:38:06.745Z] "GET /hello HTTP/1.1" 200 - 0 60 171 170 "-" "curl/7.60.0" "6f93c9cc-d32a-4878-b56a-086a740045d2" "helloworld.sample:5000" "10.10.0.90:5000" outbound|5000||helloworld.sample.svc.cluster.local - 10.20.194.146:5000 10.10.0.89:59646 -

cluster1 中,当请求分发给 v2 时,cluster2 的网关 IP(192.23.120.32:15443)被记录,当请求分发给 v1 时,cluster1 的实例 IP(10.10.0.90:5000)被记录。

$ kubectl logs --context=$CTX_CLUSTER2 -n sample $(kubectl get pod --context=$CTX_CLUSTER2 -n sample -l app=sleep -o jsonpath='{.items[0].metadata.name}') istio-proxy
[2019-05-25T08:06:11.468Z] "GET /hello HTTP/1.1" 200 - "-" 0 60 177 176 "-" "curl/7.60.0" "58cfb92b-b217-4602-af67-7de8f63543d8" "helloworld.sample:5000" "192.168.1.246:15443" outbound|5000||helloworld.sample.svc.cluster.local - 10.107.117.235:5000 10.32.0.10:36840 -
[2019-05-25T08:06:12.834Z] "GET /hello HTTP/1.1" 200 - "-" 0 60 181 180 "-" "curl/7.60.0" "ce480b56-fafd-468b-9996-9fea5257cb1e" "helloworld.sample:5000" "10.32.0.9:5000" outbound|5000||helloworld.sample.svc.cluster.local - 10.107.117.235:5000 10.32.0.10:36886 -

cluster2 中,当请求分发给 v1 时,cluster1 的网关 IP (192.168.1.246:15443)被记录,当请求分发给 v2 时,cluster2 的网关 IP(10.32.0.9:5000)被记录。

清除

执行如下命令清除示例服务以及 Istio 组件。

清除集群 cluster2

$ istioctl manifest generate --context=$CTX_CLUSTER2 \
  --set profile=remote \
  --set values.global.mtls.enabled=true \
  --set values.gateways.enabled=true \
  --set values.security.selfSigned=false \
  --set values.global.createRemoteSvcEndpoints=true \
  --set values.global.remotePilotCreateSvcEndpoint=true \
  --set values.global.remotePilotAddress=${LOCAL_GW_ADDR} \
  --set values.global.remotePolicyAddress=${LOCAL_GW_ADDR} \
  --set values.global.remoteTelemetryAddress=${LOCAL_GW_ADDR} \
  --set values.gateways.istio-ingressgateway.env.ISTIO_META_NETWORK="network2" \
  --set values.global.network="network2" \
  --set autoInjection.enabled=true | kubectl --context=$CTX_CLUSTER2 delete -f -
$ kubectl delete --context=$CTX_CLUSTER2 ns sample
$ rm n2-k8s-config
$ unset CTX_CLUSTER2 CLUSTER_NAME SERVER SECRET_NAME CA_DATA TOKEN INGRESS_HOST SECURE_INGRESS_PORT INGRESS_PORT LOCAL_GW_ADDR

清除集群 cluster1

$ istioctl manifest generate --context=$CTX_CLUSTER1 \
  -f install/kubernetes/operator/examples/multicluster/values-istio-multicluster-primary.yaml | kubectl --context=$CTX_CLUSTER1 delete -f -
$ kubectl delete --context=$CTX_CLUSTER1 ns sample
$ unset CTX_CLUSTER1
$ rm n2-k8s-config
这些信息有用吗?
Do you have any suggestions for improvement?

Thanks for your feedback!