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

遵循本指南配置一个多集群网格,使用共享的 控制平面,并通过网关连通彼此网络隔离的集群。 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.16, 1.17, 1.18, 1.19。

  • 有权限部署 Istio 控制平面

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

    为了运行本配置,cluster1 必须能够访问 cluster2 的 Kubernetes API server。

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

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

    1. $ export CTX_CLUSTER1=$(kubectl config view -o jsonpath='{.contexts[0].name}')
    2. $ export CTX_CLUSTER2=$(kubectl config view -o jsonpath='{.contexts[1].name}')
    3. $ echo CTX_CLUSTER1 = ${CTX_CLUSTER1}, CTX_CLUSTER2 = ${CTX_CLUSTER2}
    4. 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:

    当启用多集群所需的附加组件时,Istio 控制平面的资源占用量可能会增长,甚至超过 Kubernetes 集群安装平台安装步骤中的默认容量。 如果因 CPU 或内存资源不足导致 Istio 服务无法调度,可以考虑在集群中添加更多节点,或按需升级为更大内存容量的实例。

    1. $ kubectl create --context=$CTX_CLUSTER1 ns istio-system
    2. $ 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
    3. $ istioctl manifest apply --context=$CTX_CLUSTER1 \
    4. -f install/kubernetes/operator/examples/multicluster/values-istio-multicluster-primary.yaml

    注意网关地址设置为 0.0.0.0。这些是临时的占位值,在下文章节集群部署后,将被更新为 cluster1cluster2 的网关公网 IP。

    等待 cluster1 中的 Istio pods 就绪:

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

    1. $ kubectl apply --context=$CTX_CLUSTER1 -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: Gateway
    4. metadata:
    5. name: cluster-aware-gateway
    6. namespace: istio-system
    7. spec:
    8. selector:
    9. istio: ingressgateway
    10. servers:
    11. - port:
    12. number: 443
    13. name: tls
    14. protocol: TLS
    15. tls:
    16. mode: AUTO_PASSTHROUGH
    17. hosts:
    18. - "*.local"
    19. EOF

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

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

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

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

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

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

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

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

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

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

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

安装集群 2

  1. 输出 cluster1 的网关地址:

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

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

    若负载均衡配置没有设置 IP 地址,命令将执行失败。DNS 域名支持尚未实现,亟待解决。

  2. cluster2 中部署 Istio:

    1. $ kubectl create --context=$CTX_CLUSTER2 ns istio-system
    2. $ 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
    3. $ CLUSTER_NAME=$(kubectl --context=$CTX_CLUSTER2 config view --minify=true -o jsonpath='{.clusters[].name}')
    4. $ istioctl manifest apply --context=$CTX_CLUSTER2 \
    5. --set profile=remote \
    6. --set values.global.mtls.enabled=true \
    7. --set values.gateways.enabled=true \
    8. --set values.security.selfSigned=false \
    9. --set values.global.createRemoteSvcEndpoints=true \
    10. --set values.global.remotePilotCreateSvcEndpoint=true \
    11. --set values.global.remotePilotAddress=${LOCAL_GW_ADDR} \
    12. --set values.global.remotePolicyAddress=${LOCAL_GW_ADDR} \
    13. --set values.global.remoteTelemetryAddress=${LOCAL_GW_ADDR} \
    14. --set values.gateways.istio-ingressgateway.env.ISTIO_META_NETWORK="network2" \
    15. --set values.global.network="network2" \
    16. --set values.global.multiCluster.clusterName=${CLUSTER_NAME} \
    17. --set autoInjection.enabled=true

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

    1. $ kubectl get pods --context=$CTX_CLUSTER2 -n istio-system -l istio!=ingressgateway
    2. NAME READY STATUS RESTARTS AGE
    3. istio-citadel-55d8b59798-nlk2z 1/1 Running 0 26s
    4. istio-sidecar-injector-5749cf7cfc-s6r7p 1/1 Running 0 25s

    istio-ingressgateway 无法就绪,直到在 cluster1 的 Istio 控制面板中配置好 watch cluster2。下一节执行该操作。

  3. 确定 cluster2 的 ingress IP 和口。

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

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

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

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

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

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

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

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

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

    1. $ CLUSTER_NAME=$(kubectl --context=$CTX_CLUSTER2 config view --minify=true -o jsonpath='{.clusters[].name}')
    2. $ SERVER=$(kubectl --context=$CTX_CLUSTER2 config view --minify=true -o jsonpath='{.clusters[].cluster.server}')
    3. $ SECRET_NAME=$(kubectl --context=$CTX_CLUSTER2 get sa istio-reader-service-account -n istio-system -o jsonpath='{.secrets[].name}')
    4. $ CA_DATA=$(kubectl get --context=$CTX_CLUSTER2 secret ${SECRET_NAME} -n istio-system -o jsonpath="{.data['ca\.crt']}")
    5. $ TOKEN=$(kubectl get --context=$CTX_CLUSTER2 secret ${SECRET_NAME} -n istio-system -o jsonpath="{.data['token']}" | base64 --decode)

    在许多系统中,base64 --decode 可以替换为 openssl enc -d -base64 -A

  6. 在工作目录中创建文件 n2-k8s-config

    1. $ cat <<EOF > n2-k8s-config
    2. apiVersion: v1
    3. kind: Config
    4. clusters:
    5. - cluster:
    6. certificate-authority-data: ${CA_DATA}
    7. server: ${SERVER}
    8. name: ${CLUSTER_NAME}
    9. contexts:
    10. - context:
    11. cluster: ${CLUSTER_NAME}
    12. user: ${CLUSTER_NAME}
    13. name: ${CLUSTER_NAME}
    14. current-context: ${CLUSTER_NAME}
    15. users:
    16. - name: ${CLUSTER_NAME}
    17. user:
    18. token: ${TOKEN}
    19. EOF

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

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

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

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

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

部署案例服务

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

在集群 2 中部署 helloworld v2

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

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

    ZipZip

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

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

在集群 1 中部署 helloworld v1

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

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

    ZipZip

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

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

跨集群路由实践

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

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

    ZipZip

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

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

    1. $ 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 服务若干次:

    1. $ 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

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

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

  1. $ 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
  2. [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 -
  3. [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)被记录。

  1. $ 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
  2. [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 -
  3. [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

  1. $ istioctl manifest generate --context=$CTX_CLUSTER2 \
  2. --set profile=remote \
  3. --set values.global.mtls.enabled=true \
  4. --set values.gateways.enabled=true \
  5. --set values.security.selfSigned=false \
  6. --set values.global.createRemoteSvcEndpoints=true \
  7. --set values.global.remotePilotCreateSvcEndpoint=true \
  8. --set values.global.remotePilotAddress=${LOCAL_GW_ADDR} \
  9. --set values.global.remotePolicyAddress=${LOCAL_GW_ADDR} \
  10. --set values.global.remoteTelemetryAddress=${LOCAL_GW_ADDR} \
  11. --set values.gateways.istio-ingressgateway.env.ISTIO_META_NETWORK="network2" \
  12. --set values.global.network="network2" \
  13. --set autoInjection.enabled=true | kubectl --context=$CTX_CLUSTER2 delete -f -
  14. $ kubectl delete --context=$CTX_CLUSTER2 ns sample
  15. $ rm n2-k8s-config
  16. $ unset CTX_CLUSTER2 CLUSTER_NAME SERVER SECRET_NAME CA_DATA TOKEN INGRESS_HOST SECURE_INGRESS_PORT INGRESS_PORT LOCAL_GW_ADDR

清除集群 cluster1

  1. $ istioctl manifest generate --context=$CTX_CLUSTER1 \
  2. -f install/kubernetes/operator/examples/multicluster/values-istio-multicluster-primary.yaml | kubectl --context=$CTX_CLUSTER1 delete -f -
  3. $ kubectl delete --context=$CTX_CLUSTER1 ns sample
  4. $ unset CTX_CLUSTER1
  5. $ rm n2-k8s-config

相关内容

共享控制平面(单一网络)

安装一个跨多个 Kubernetes 集群的 Istio 网格,多集群共享控制平面,并且集群间通过 VPN 互连。

控制平面副本集

通过控制平面副本集实例,在多个 Kubernetes 集群上安装 Istio 网格。

简化地多集群安装[实验性]

配置一个跨多个 Kubernetes 集群的 Istio 网格。

使用 Admiral 管理 Istio 多集群的配置和服务发现

为 Istio deployment(cluster)提供自动化 Istio 配置,并让其像单个网格一样工作。

DNS 证书管理

在 Istio 中配置和管理 DNS 证书。

安全管理 Webhook

一种更安全管理 Istio webhook 的方法。