多集群部署模型

根据官网的 部署模型说明,istio 部署时,在下面四个纬度都可以进行自由选择:

  • 单一或多个集群
  • 单一或多个网络
  • 单一或多控制平面
  • 单一或多个网格

因此多集群情况下,后面三个都可以任意组合,总共有 8 种可能。

多网络部署

多集群可以处在不同的网络中,不同网络中的工作负载只能通过一个或多个 istio 网关相互访问。 multi-net

控制平面纬度

多集群部署可以共享一个控制平面实例。在这种情况下,控制平面实例可以驻留在一个或多个集群中: shared-control

多集群部署也可以使用不同的控制平面,下面是在东西两个地域,分别部署了一个控制平面: multi-control

多网格部署

通过网格联邦可以实现多网格部署,多网格主要优点是:

  • 组织边界:业务范围
  • 服务名称或命名空间复用:比如 default 的使用
  • 加强隔离:将测试工作负载与生产工作负载隔离

部署架构如下: multi-mesh

多集群部署实践

实践准备

创建两个 k8s 集群,这里用两台云主机,分别安装 k3s 集群,参考 安装文档,因为 k3s 的安装的 kubectl 命令默认用的 kubernetes 配置路径为/etc/rancher/k3s/k3s.yaml,这里先设置环境 KUBECONFIG="/root/.kube/config",保证 kubectl 和 istioctl 配置一致。

1
2
3
4
vim ~/.bashrc # 文件中添加 export KUBECONFIG="/root/.kube/config"
source ~/.bashrc
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -
cp /etc/rancher/k3s/k3s.yaml ~/.kube/config

查看两个集群节点上的 kubeconfig 配置,除了认证外其他配置基本一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxx # 省略
    server: https://127.0.0.1:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
  user:
    client-certificate-data: xxx # 省略
    client-key-data: xxx # 省略

需要在两个集群节点上做如下设置 kubernetes 的 context,这里两台云主机的内网地址分别为:192.168.60.113、192.168.60.9。这里在 192.168.60.113 主机上执行以下步骤:

  • 进入 kubeconfig 目录:cd /root/.kube
  • 拷贝 config 为 config1:cp config config1
  • 拷贝另一个集群的 config 为 config2:scp root@192.168.60.9:/root/.kube/config config2
  • 修改 config1 和 config2,将集群名称、用户名称、context 名称改为不一样的,比如 cluster1、cluster2,并将 127.0.0.1 替换为内网地址
  • 将两个 config 合并为一个 config,KUBECONFIG=config1:config2 kubectl config view --flatten > config

此时查看/root/.kube/config 配置为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
clusters:
- cluster:
    server: https://192.168.60.113:6443
    certificate-authority-data: xxx # 省略
  name: cluster1
- cluster:
    server: https://192.168.60.9:6443
    certificate-authority-data: xxx # 省略
  name: cluster2
contexts:
- context:
    cluster: cluster1
    user: user-cluster1
  name: context-cluster1
- context:
    cluster: cluster2
    user: user-cluster2
  name: context-cluster2
current-context: context-cluster1
kind: Config
preferences: {}
users:
- name: user-cluster1
  user:
    client-certificate-data: xxx # 省略
    client-key-data: xxx # 省略
- name: user-cluster2
  user:
    client-certificate-data: xxx # 省略
    client-key-data: xxx # 省略

并将上面的 kubeconfig 配置复制为 cluster2 集群节点的~/.kube/config。这时使用 kubectl config 就可以在两个环境切换了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@cluster1:~/.kube# kubectl config use-context context-cluster1
Switched to context "context-cluster1".
root@cluster1:~/.kube# kubectl get nodes
NAME       STATUS   ROLES                  AGE     VERSION
cluster1   Ready    control-plane,master   6h20m   v1.25.4+k3s1
root@cluster1:~/.kube# kubectl config use-context context-cluster2
Switched to context "context-cluster2".
root@cluster1:~/.kube# kubectl get nodes
NAME      STATUS   ROLES                  AGE   VERSION
cluster2  Ready    control-plane,master   50d   v1.25.4+k3s1

设置 context 的环境变量:

1
2
export CTX_CLUSTER1=context-cluster1
export CTX_CLUSTER2=context-cluster2

继续配置证书和密钥,使用 这里的配置方式,即需要先手动生成一个根证书,然后由根证书生成多个集群的中间证书。 ca-hierarchy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建根证书
cd ~/zt/istio/istio-1.16.1
mkdir -p certs && cd certs
make -f ../tools/certs/Makefile.selfsigned.mk root-ca
# 创建 cluster1 和 cluster2 的中间证书
make -f ../tools/certs/Makefile.selfsigned.mk cluster1-cacerts
make -f ../tools/certs/Makefile.selfsigned.mk cluster2-cacerts

# 在安装 istio 之前,配置两个 istio 集群的证书
kubectl create --context="${CTX_CLUSTER1}" namespace istio-system
kubectl create --context="${CTX_CLUSTER2}" namespace istio-system
kubectl create secret --context="${CTX_CLUSTER1}" generic cacerts -n istio-system \
      --from-file=cluster1/ca-cert.pem \
      --from-file=cluster1/ca-key.pem \
      --from-file=cluster1/root-cert.pem \
      --from-file=cluster1/cert-chain.pem
kubectl create secret --context="${CTX_CLUSTER2}" generic cacerts -n istio-system \
      --from-file=cluster2/ca-cert.pem \
      --from-file=cluster2/ca-key.pem \
      --from-file=cluster2/root-cert.pem \
      --from-file=cluster2/cert-chain.pem

创建的根证书和密钥时生成以下四个文件:

  • root-cert.pem:根证书
  • root-key.pem: 根密钥
  • root-ca.conf: 用来生成根证书的openssl配置
  • root-cert.csr:为根证书生成的证书签名请求文件

为clsuter1和cluster2生成的文件夹中包含以下文件:

  • ca-cert.pem:中间证书
  • ca-key.pem:中间密钥
  • cert-chain.pem: 生成的给istiod用的证书链
  • root-cert.pem:根证书

多网络多控制面板

多网络多控制面板部署方式,即在两个集群 cluster1 和 cluster2 中都部署 istio 控制面板,两个集群都成为主集群,cluster1 在 network1 中,cluster2 在 network2 中,两个集群必须通过东西向的 gateway 来通信,如下图所示: multi_net_multi_pri

分别在 cluster1 和 cluster2 安装 istio 并配置 gateway:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# cluster1 安装 istio
kubectl --context="${CTX_CLUSTER1}" get namespace istio-system && \
  kubectl --context="${CTX_CLUSTER1}" label namespace istio-system topology.istio.io/network=network1
cat <<EOF > cluster1.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  # 增加 envoy 响应日志
  meshConfig:
    accessLogFile: /dev/stdout
    accessLogEncoding: "JSON"
  values:
    global:
      meshID: mesh1
      multiCluster:
        clusterName: cluster1
      network: network1
EOF
istioctl install --context="${CTX_CLUSTER1}" -f cluster1.yaml
# cluster1 部署 gateway
samples/multicluster/gen-eastwest-gateway.sh \
    --mesh mesh1 --cluster cluster1 --network network1 | \
    istioctl --context="${CTX_CLUSTER1}" install -y -f -
# 查看 external ip,这个 ip 是这台 cluster1 云主机的内网 ip
kubectl --context="${CTX_CLUSTER1}" get svc istio-eastwestgateway -n istio-system
NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)
istio-eastwestgateway   LoadBalancer   10.45.180.156   192.168.60.113   15021:31814/TCP...
# cluster1 对外暴露 gateway 服务
kubectl --context="${CTX_CLUSTER1}" apply -n istio-system -f \
    samples/multicluster/expose-services.yaml

# cluster2 安装 istio
kubectl --context="${CTX_CLUSTER2}" get namespace istio-system && \
  kubectl --context="${CTX_CLUSTER2}" label namespace istio-system topology.istio.io/network=network2
cat <<EOF > cluster2.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  # 增加 envoy 响应日志
  meshConfig:
    accessLogFile: /dev/stdout
    accessLogEncoding: "JSON"
  values:
    global:
      meshID: mesh1
      multiCluster:
        clusterName: cluster2
      network: network2
EOF
istioctl install --context="${CTX_CLUSTER2}" -f cluster2.yaml
# cluster2 部署 gateway
samples/multicluster/gen-eastwest-gateway.sh \
    --mesh mesh1 --cluster cluster2 --network network2 | \
    istioctl --context="${CTX_CLUSTER2}" install -y -f -
# 查看 external ip,这个 ip 是这台 cluster2 云主机的内网 ip
kubectl --context="${CTX_CLUSTER2}" get svc istio-eastwestgateway -n istio-system
# cluster2 对外暴露 gateway 服务
kubectl --context="${CTX_CLUSTER2}" apply -n istio-system -f \
    samples/multicluster/expose-services.yaml

在两个集群中分别设置另一个集群 api-server 的访问证书的 secret:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 设置 cluster2 集群访问 cluster1 的访问证书
istioctl x create-remote-secret \
    --context="${CTX_CLUSTER1}" \
    --name=cluster1 | \
    kubectl apply -f - --context="${CTX_CLUSTER2}"
# 设置 cluster1 集群访问 cluster2 的访问证书
istioctl x create-remote-secret \
    --context="${CTX_CLUSTER2}" \
    --name=cluster2 | \
    kubectl apply -f - --context="${CTX_CLUSTER1}"

进行这些步骤后,多网络多控制面板部署方式就完成了,可以参考 验证部署 进行验证。

单网络多控制面板

注意:要使得安装的两个集群 pods、service 之间能互相通信,还需要在安装集群过程进行设置,比如使用 submariner,以下步骤不包含此设置。

这种部署模式,是在两个集群 cluster1 和 cluster2 都部署 istio 控制面板,两个集群都成为主集群,两个集群在同一个网络中。如下图所示,这种配置方式每个控制面板都会观测两个集群的服务变化,并且两个集群的 pods 负载之间可以直接通信。

envoy_arch

在 cluster1 创建 istio 配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cat <<EOF > cluster1.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  # 增加 envoy 响应日志
  meshConfig:
    accessLogFile: /dev/stdout
    accessLogEncoding: "JSON"
  values:
    global:
      meshID: mesh1
      multiCluster:
        clusterName: cluster1
      network: network1
EOF

执行安装 istio,执行结果如下:

1
2
3
4
5
6
7
8
9
root@cluster1:~/zt/istio/istio-1.16.1/multicluster# istioctl install --context="${CTX_CLUSTER1}" -f cluster1.yaml
This will install the Istio 1.16.1 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete
Making this installation the default for injection and validation.

Thank you for installing Istio 1.16.  Please take a few minutes to tell us about your install/upgrade experience!  https://forms.gle/99uiMML96AmsXY5d6

同样为 cluster2 集群创建配置并安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cat <<EOF > cluster2.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    global:
      meshID: mesh1
      multiCluster:
        clusterName: cluster2
      network: network1
EOF

istioctl install --context="${CTX_CLUSTER2}" -f cluster2.yaml

在两个集群中分别设置另一个集群 api-server 的访问证书的 secret:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 设置 cluster2 集群访问 cluster1 的访问证书
istioctl x create-remote-secret \
    --context="${CTX_CLUSTER1}" \
    --name=cluster1 | \
    kubectl apply -f - --context="${CTX_CLUSTER2}"
# 设置 cluster1 集群访问 cluster2 的访问证书
istioctl x create-remote-secret \
    --context="${CTX_CLUSTER2}" \
    --name=cluster2 | \
    kubectl apply -f - --context="${CTX_CLUSTER1}"

进行这些步骤后,多网络多控制面板部署方式就完成了,可以参考 验证部署 进行验证。

验证部署

通过部署一个 HelloWorld 应用的 v1 版本在 cluster1,v2 版本在 cluster2,当收到请求,将会根据请求的版本来进行响应。同时在两个集群都会部署一个 Sleep 容器,用这些 pods 作为请求源模拟网格内的流量。

在 cluster1 的 istio/istio-1.16.1 目录下执行以下命令来安装,在执行之前,可以在另一个窗口打印 istiod 的日志,查看执行输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 分别创建命名空间
kubectl create --context="${CTX_CLUSTER1}" namespace sample
kubectl create --context="${CTX_CLUSTER2}" namespace sample
# 分别开启 istio 注入
kubectl label --context="${CTX_CLUSTER1}" namespace sample istio-injection=enabled
kubectl label --context="${CTX_CLUSTER2}" namespace sample istio-injection=enabled
# 分别创建 service
kubectl apply --context="${CTX_CLUSTER1}" \
    -f samples/helloworld/helloworld.yaml \
    -l service=helloworld -n sample
kubectl apply --context="${CTX_CLUSTER2}" \
    -f samples/helloworld/helloworld.yaml \
    -l service=helloworld -n sample
# 分别创建 deployment
kubectl apply --context="${CTX_CLUSTER1}" \
    -f samples/helloworld/helloworld.yaml \
    -l version=v1 -n sample
kubectl apply --context="${CTX_CLUSTER2}" \
    -f samples/helloworld/helloworld.yaml \
    -l version=v2 -n sample

这时在 cluster2 的 istiod 对应的日志为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 创建 cluster1 的 service helloworld 产生的日志
2023-01-16T09:13:51.374303Z  info  ads  Incremental push, service helloworld.sample.svc.cluster.local at shard Kubernetes/cluster1 has no endpoints
2023-01-16T09:13:51.474543Z  info  ads  Push debounce stable[19] 2 for config ServiceEntry/sample/helloworld.sample.svc.cluster.local: 100.177423ms since last change, 106.769424ms since last push, full=true
2023-01-16T09:13:51.475013Z  info  ads  XDS: Pushing:2023-01-16T09:13:51Z/12 Services:7 ConnectedEndpoints:1 Version:2023-01-16T09:13:51Z/12
2023-01-16T09:13:51.475432Z  info  ads  CDS: PUSH for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:15 size:14.5kB cached:13/14
2023-01-16T09:13:51.475475Z  info  ads  LDS: PUSH for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:0 size:0B
2023-01-16T09:13:51.479356Z  info  ads  EDS: PUSH request for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:1 size:52B empty:1 cached:0/1 filtered:13
# 创建 cluster2 的 service helloworld 产生的日志
2023-01-16T09:15:26.712662Z  info  ads  Incremental push, service helloworld.sample.svc.cluster.local at shard Kubernetes/cluster2 has no endpoints
2023-01-16T09:15:26.813742Z  info  ads  Push debounce stable[20] 2 for config ServiceEntry/sample/helloworld.sample.svc.cluster.local: 101.02765ms since last change, 104.914465ms since last push, full=true
2023-01-16T09:15:26.814195Z  info  ads  XDS: Pushing:2023-01-16T09:15:26Z/13 Services:7 ConnectedEndpoints:1 Version:2023-01-16T09:15:26Z/13
2023-01-16T09:15:26.814514Z  info  ads  CDS: PUSH for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:15 size:14.5kB cached:13/14
2023-01-16T09:15:26.814568Z  info  ads  EDS: PUSH INC for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:1 size:52B empty:1 cached:0/1
2023-01-16T09:15:26.814602Z  info  ads  LDS: PUSH for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:0 size:0B
# 创建 cluster1 的 deployment helloworld 产生的日志
2023-01-16T09:16:04.805493Z  info  ads  Incremental push, service helloworld.sample.svc.cluster.local at shard Kubernetes/cluster1 has no endpoints
2023-01-16T09:16:04.905918Z  info  ads  Push debounce stable[21] 1 for config ServiceEntry/sample/helloworld.sample.svc.cluster.local: 100.365169ms since last change, 100.364925ms since last push, full=false
2023-01-16T09:16:04.906018Z  info  ads  XDS: Incremental Pushing:2023-01-16T09:15:26Z/13 ConnectedEndpoints:1 Version:2023-01-16T09:15:26Z/13
2023-01-16T09:16:06.813715Z  info  ads  Full push, new service sample/helloworld.sample.svc.cluster.local
2023-01-16T09:16:06.914790Z  info  ads  Push debounce stable[22] 1 for config ServiceEntry/sample/helloworld.sample.svc.cluster.local: 100.821371ms since last change, 100.821083ms since last push, full=true
2023-01-16T09:16:06.915430Z  info  ads  XDS: Pushing:2023-01-16T09:16:06Z/14 Services:7 ConnectedEndpoints:1 Version:2023-01-16T09:16:06Z/14
2023-01-16T09:16:06.915867Z  info  ads  CDS: PUSH for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:15 size:14.5kB cached:13/14
2023-01-16T09:16:06.916113Z  info  ads  EDS: PUSH INC for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:1 size:217B empty:0 cached:0/1
2023-01-16T09:16:06.916175Z  info  ads  LDS: PUSH for node:istio-ingressgateway-5bc5cb6888-dqqkq.istio-system resources:0 size:0B
# 创建 cluster2 的 deployment helloworld 产生的日志
2023-01-16T09:17:51.310335Z  info  Sidecar injection request for sample/helloworld-v2-54dddc5567-***** (actual name not yet known)
2023-01-16T09:17:52.458115Z  info  ads  Incremental push, service helloworld.sample.svc.cluster.local at shard Kubernetes/cluster2 has no endpoints
2023-01-16T09:17:52.558430Z  info  ads  Push debounce stable[23] 1 for config ServiceEntry/sample/helloworld.sample.svc.cluster.local: 100.238395ms since last change, 100.238086ms since last push, full=false
2023-01-16T09:17:52.558500Z  info  ads  XDS: Incremental Pushing:2023-01-16T09:16:06Z/14 ConnectedEndpoints:1 Version:2023-01-16T09:16:06Z/14
2023-01-16T09:17:52.963838Z  info  ads  ADS: new connection for node:helloworld-v2-54dddc5567-99j9l.sample-4
2023-01-16T09:17:52.964083Z  info  ads  CDS: PUSH request for node:helloworld-v2-54dddc5567-99j9l.sample resources:18 size:15.9kB cached:13/14
2023-01-16T09:17:52.978204Z  info  ads  EDS: PUSH request for node:helloworld-v2-54dddc5567-99j9l.sample resources:14 size:2.7kB empty:0 cached:14/14
2023-01-16T09:17:52.995232Z  info  ads  LDS: PUSH request for node:helloworld-v2-54dddc5567-99j9l.sample resources:16 size:46.5kB
2023-01-16T09:17:53.033523Z  info  ads  RDS: PUSH request for node:helloworld-v2-54dddc5567-99j9l.sample resources:8 size:4.8kB cached:7/8
2023-01-16T09:17:53.471174Z  info  ads  CDS: PUSH for node:helloworld-v2-54dddc5567-99j9l.sample resources:18 size:15.9kB cached:13/14
2023-01-16T09:17:53.471290Z  info  ads  EDS: PUSH for node:helloworld-v2-54dddc5567-99j9l.sample resources:14 size:2.7kB empty:0 cached:14/14
2023-01-16T09:17:53.472629Z  info  ads  LDS: PUSH for node:helloworld-v2-54dddc5567-99j9l.sample resources:16 size:46.5kB
2023-01-16T09:17:53.472977Z  info  ads  RDS: PUSH for node:helloworld-v2-54dddc5567-99j9l.sample resources:8 size:4.8kB cached:7/8
2023-01-16T09:17:53.582899Z  info  ads  Push debounce stable[24] 1 for config ServiceEntry/sample/helloworld.sample.svc.cluster.local: 100.230617ms since last change, 100.230465ms since last push, full=false
2023-01-16T09:17:53.582963Z  info  ads  XDS: Incremental Pushing:2023-01-16T09:16:06Z/14 ConnectedEndpoints:2 Version:2023-01-16T09:16:06Z/14

分别查看创建的 pods:

1
2
3
4
5
6
root@cluster1:~/zt/istio/istio-1.16.1# kubectl get pods --context="${CTX_CLUSTER1}" -nsample
NAME                             READY   STATUS    RESTARTS   AGE
helloworld-v1-78b9f5c87f-pkxxj   2/2     Running   0          5m40s
root@cluster1:~/zt/istio/istio-1.16.1# kubectl get pods --context="${CTX_CLUSTER2}" -nsample
NAME                             READY   STATUS    RESTARTS   AGE
helloworld-v2-54dddc5567-99j9l   2/2     Running   0          3m57s

分别部署 Sleep 应用:

1
2
3
4
kubectl apply --context="${CTX_CLUSTER1}" \
    -f samples/sleep/sleep.yaml -n sample
kubectl apply --context="${CTX_CLUSTER2}" \
    -f samples/sleep/sleep.yaml -n sample

为了验证多集群的负载均衡能按预期执行,可以在 Sleep pod 多次调用 HelloWorld 服务。在 cluster1 发送一次请求:

1
2
3
4
5
6
7
8
kubectl exec --context="${CTX_CLUSTER1}" -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

# 多执行几次,会从 v1 pods 和 v2 pods 返回结果
Hello version: v1, instance: helloworld-v1-78b9f5c87f-pkxxj
Hello version: v2, instance: helloworld-v2-54dddc5567-99j9l

问题

不能返回多版本结果

刚开始进行验证测试单网络多控制面板时,在 cluster1 执行访问 HelloWorld 服务,只能返回 v1 版本,在 cluster2 执行访问 HelloWorld 服务,只能返回 v2 版本。通过参考 调试 Envoy 和 Istiod,执行了以下命令进行排查:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
istioctl proxy-config --context="${CTX_CLUSTER2}" cluster -n sample sleep-78ff5975c6-pdk2v --fqdn helloworld.sample.svc.cluster.local -o json
[
    {
        "name": "outbound|5000||helloworld.sample.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|5000||helloworld.sample.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "commonLbConfig": {
            "localityWeightedLbConfig": {}
        },
    }
]

# 查看 endpoints 发现 cluster2 的 sleep pods 中 envoy 配置两个集群的 helloworld 的 pod 地址
istioctl proxy-config --context="${CTX_CLUSTER2}" endpoints -nsample sleep-78ff5975c6-pdk2v --cluster "outbound|5000||helloworld.sample.svc.cluster.local"
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.42.0.22:5000     HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local
10.42.0.27:5000     HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local

发现 envoy 配置中 cluster 和 endpoints 配置是正常的,再排查是否是因为两个环境不能联通。在 cluster2 的 sleep pods 执行:

1
2
/ $ curl  10.42.0.27:5000/hello
upstream connect error or disconnect/reset before headers. reset reason: connection failure, transport failure reason: delayed connect error: 113

解决办法:需要在安装 kubernetes 集群进行配置,使得两个集群的 pods 能够互相访问,比如使用 submariner

多集群通信原理

多网络多控制面板

多网络多控制面板部署方式,两个集群通信的关键在于配置了 gateway,下面对其中涉及的 gateway 配置和 envoy 配置进行解析,来说明两个集群是如何通信的。首先是部署配置了 gateway 的控制面板,在前面是用以下命名部署的:

1
2
3
samples/multicluster/gen-eastwest-gateway.sh \
    --mesh mesh1 --cluster cluster1 --network network1 | \
    istioctl --context="${CTX_CLUSTER1}" install -y -f -

使用samples/multicluster/gen-eastwest-gateway.sh --mesh mesh1 --cluster cluster1 --network network1 查看生成的 yaml,主要是部署 gateway 的 deploy、service 等资源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: eastwest
spec:
  revision: ""
  profile: empty
  components:
    ingressGateways:
      - name: istio-eastwestgateway
        label:
          istio: eastwestgateway
          app: istio-eastwestgateway
          topology.istio.io/network: network1
        enabled: true
        k8s: # 设置 gateway 资源的环境变量和 service 端口
          env:
            # traffic through this gateway should be routed inside the network
            - name: ISTIO_META_REQUESTED_NETWORK_VIEW
              value: network1
          service:
            ports:
              - name: status-port
                port: 15021
                targetPort: 15021
              - name: tls
                port: 15443
                targetPort: 15443
              - name: tls-istiod
                port: 15012
                targetPort: 15012
              - name: tls-webhook
                port: 15017
                targetPort: 15017
  values:
    gateways:
      istio-ingressgateway:
        injectionTemplate: gateway
    global:
      network: network1

部署完成后,查看环境中的 IstioOperator 资源、deploy 资源、service 资源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
root@cluster1:~/zt/istio/istio-1.16.1# kubectl get IstioOperator -nistio-system
NAME                       REVISION   STATUS   AGE
installed-state                                3h51m # 第一次 istioctl install 生成的
installed-state-eastwest                       3h50m # 第二次增加 gateway 时 istioctl install 生成的
root@cluster1:~/zt/istio/istio-1.16.1# kubectl get deploy -nistio-system
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
istiod                  1/1     1            1           4h22m
istio-ingressgateway    1/1     1            1           4h22m
istio-eastwestgateway   1/1     1            1           4h21m
# 查看 cluster2 的 service,暴露的端口跟上面 IstioOperator 一致
root@cluster2:~/zt/istio# kubectl --context="${CTX_CLUSTER2}" get svc istio-eastwestgateway -n istio-system
NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)
istio-eastwestgateway   LoadBalancer   10.145.117.132   192.168.60.9   15021:32700/TCP,15443:30138/TCP,15012:31335/TCP,15017:30145/TCP

要了解跨网络情况下两个集群的 pod 是如何通信的,即要知道示例中 cluster1 集群的 sleep pod 请求 helloworld 服务时,请求具体是怎么到达 cluster1 的 helloworld 服务的。

通过查看 cluster1 集群的 sleep pod 中 envoy 的 cluster 配置(需要先获取 listeners 和 routes 来确定 cluster 名称),知道请求会被转发到 serviceName 对应的 endpoints:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
istioctl proxy-config --context="${CTX_CLUSTER2}" cluster -n sample sleep-78ff5975c6-pdk2v --fqdn helloworld.sample.svc.cluster.local -o json

[
    {
        "transportSocketMatches": [
            {
                "name": "tlsMode-istio",
                "match": {
                    "tlsMode": "istio"
                },
            },
        ],
        "name": "outbound|5000||helloworld.sample.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|5000||helloworld.sample.svc.cluster.local"
        },
        "connectTimeout": "10s",
    }
]

查看 cluster1 集群的 sleep pods 中 serviceName 对应的 endpoints 配置:

1
2
3
4
root@cluster1:~/zt/istio/istio-1.16.1# istioctl proxy-config --context="${CTX_CLUSTER1}" endpoints -nsample sleep-78ff5975c6-4xpv4 --cluster "outbound|5000||helloworld.sample.svc.cluster.local"
ENDPOINT               STATUS      OUTLIER CHECK     CLUSTER
10.44.0.13:5000        HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local
192.168.60.9:15443     HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local

可以看到其中一个 endpoint 指向的是 cluster2 的 istio-eastwestgateway 暴露的 15443 端口,即 istio 自动将 cluster2 集群的 helloworld-v2 pod 的地址和端口替换为了 cluster2 集群 istio-eastwestgateway 地址,达到了通过 gateway 来转发两个集群之间的请求。因为 gateway 和 sidecar 用的镜像相同,都是使用的 envoy,通过查看 cluster2 的 gateway pods 中对应的 endpoints 配置:

1
2
3
4
5
6
root@cluster1:~/zt/istio/istio-1.16.1# istioctl proxy-config --context="${CTX_CLUSTER2}" endpoints -nistio-system istio-eastwestgateway-74bcf5c77c-m5qbr --cluster "outbound|5000||helloworld.sample.svc.cluster.local"
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.144.0.15:5000    HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local
root@cluster1:~/zt/istio/istio-1.16.1# kubectl --context="${CTX_CLUSTER2}" get endpoints -nsample helloworld
NAME         ENDPOINTS         AGE
helloworld   10.144.0.15:5000   4h51m

因此 cluster2 的 gateway pods 会将请求转发到 helloworld 的 pods 去。

单网络多控制面板

在单网络多控制面板部署方式下,两个集群没有 gateway 配置。cluster2 的 sleep pods 中 envoy 配置两个集群的 helloworld 的 pod 地址:

1
2
3
4
istioctl proxy-config --context="${CTX_CLUSTER2}" endpoints -nsample sleep-78ff5975c6-pdk2v --cluster "outbound|5000||helloworld.sample.svc.cluster.local"
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.42.0.22:5000     HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local
10.42.0.27:5000     HEALTHY     OK                outbound|5000||helloworld.sample.svc.cluster.local

因此只要 cluster2 的 sleep pods 可以直接通过 pods ip 连接 cluster1 的 helloworld pods,就可以直接进行通信,因此问题关键在于需要打通两个环境的 pods ip 的直连,比如 submariner

参考