多集群 Istio 服务网格的跨集群无缝访问指南

探索如何在 Istio 多集群网格中利用 SPIRE 联邦、DNS 代理和东西向网关技术,有效实现服务的跨集群无缝访问。本指南将提供详细的配置示例和步骤,帮助你克服部署中的挑战,确保服务间的高效、安全通信。

版权声明
本文为 Jimmy Song 原创。转载请注明来源: https://jimmysong.io/blog/seamless-cross-cluster-access-istio/
查看本文大纲

前言

随着企业信息系统越来越多地采用微服务架构,如何在多集群环境中实现服务的高效、安全地跨集群访问成为了一个重要的挑战。Istio 作为一种流行的服务网格解决方案,提供了丰富的功能来支持跨集群服务的无缝连接。

在部署和使用多集群服务网格时有以下难点:

  • 跨集群的服务注册发现与路由
  • 集群间服务的身份识别与认证

本文将深入探讨如何在多集群多网格的 Istio 部署中,通过实施 SPIRE 联邦和东西向网关暴露服务的方式,实现跨集群的无缝访问。通过一系列配置和部署示例,本文旨在为读者提供一个清晰的指南,帮助理解和解决多集群服务网格部署中遇到的常见问题和挑战。

Istio 的部署模型

Istio 文档中根据集群、网络、控制平面、网格、信任域及租户等维度划分了多种部署模型,我将其总结并附上适用场景说明如下表所示。

维度 单一配置 多元配置 适用场景说明
集群 一个集群托管所有服务与控制平面。 跨多个集群分布服务,可以共享或分离控制平面。 单集群适用于资源需求较小、管理相对简单的环境;多集群适合于需要高可用性、地理冗余或遵守数据驻留政策的大型组织。
网络 所有服务在单一网络内通信,无需跨网络通信。 服务跨越多个网络,需通过 Istio 网关进行通信。 单网络适用于网络简单、无复杂跨网络通信需求的场景;多网络适合在多云、混合云环境中部署,或需要跨行政边界部署的场景。
控制平面 一个控制平面管理所有服务。 每个控制平面管理一个或多个集群,增强隔离与可用性。 单控制平面适用于小型至中型部署,易于管理;多控制平面适用于大规模部署,需要高度的容错能力和安全隔离。
网格 所有服务在一个连续的服务网格中。 服务网格之间通过联盟进行通信,适用于不同组织或区域。 单网格适用于组织内部密切协作的服务;多网格适合于需要隔离不同业务线或合作伙伴间的服务,或实施强隔离的大型组织。
信任域 所有服务使用同一套密钥和证书体系。 不同信任域使用不同的密钥和证书,需进行信任链交换。 单信任域适用于信任级别统一的环境;多信任域适用于需要严格隔离、满足不同安全级别需求的复杂组织或多方合作场景。
租户 整个网格为单一租户或用户服务。 通过命名空间隔离,支持多个租户在同一网格中运行服务。 单租户适用于所有资源和服务由单一组织管理的场景;多租户适用于云服务提供商或需要在同一物理基础设施上运营多个客户的场景。
Istio 的多维度部署模型及适用场景

选择合适的部署模型需要考虑到实际的业务需求、安全要求、管理复杂度以及成本等因素。在生产环境中,往往是对多种部署模型的组合使用。

下表展示了在实际应用中如何结合不同的部署模型来满足更复杂的业务和技术需求:

混合部署模型 描述 适用场景
多集群 + 多网格 + 多控制平面 不同的集群可以配置成不同的网格,每个网格都有自己的控制平面。通过网格联邦共享服务和策略。 适合大型组织,其中不同的业务单位需要独立运行并管理自己的服务,同时需要一定级别的服务共享和协作。
多信任域联邦 + 命名空间隔离的多租户 不同的网格可以拥有不同的信任域,通过信任域联邦共享密钥和证书。同时在一个网格内部通过命名空间实现租户隔离。 适用于需要强隔离但又要求跨组织或跨业务线协作的环境,如跨国公司或合作伙伴网络。
多集群 + 单网格 + 多控制平面 多个集群共享一个服务网格,但每个集群拥有自己的控制平面来管理本地服务的配置。 适用于需要高可用性和灾难恢复能力的应用,各地区的集群可以独立运行,减少单点故障风险。
多集群 + 多网格 + 单控制平面 多个集群分布在不同的网格中,但所有网格共享一个中心控制平面。 适用于中心化管理的大规模部署,可以减少管理的复杂性,但对控制平面的可用性要求极高。
多信任域 + 多网格 + 命名空间隔离的多租户 各网格拥有独立的信任域,增强安全性和隔离性。在单个网格内使用命名空间来隔离不同的租户。 适用于提供云服务的组织,需要隔离不同客户的数据和服务,同时在不同的法律和合规环境下操作。
Istio 的部署模型组合

这些混合模型提供了高度的灵活性和可扩展性,能够满足各种复杂的部署要求。在选择混合模型时,组织需要考虑到管理复杂性、成本、安全要求以及业务需求,以确定最合适的部署策略。通过适当的规划和设计,Istio 的灵活部署模型可以帮助组织构建出既安全又高效的服务网格架构。在大多数场景下,单信任域的多集群 + 单网格 + 多控制平面已足够满足需要。

本文将聚焦多集群 + 多网格 + 多控制平面 + 多信任域的混合部署模型,这是一种相当复杂的场景,如果你可以完成这种场景的部署,那么其他场景也就不在话下了。

多集群 Istio 服务网格中的 FQDN

网格间的服务要想互相访问,必须了解各自的 FQDN。FQDN 通常由服务名、命名空间和顶级域(如 svc.cluster.local)组成。在 Istio 的多集群或多网格设置中,可以通过不同的机制(如ServiceEntryVirtualServiceGateway 配置)来控制和管理服务的路由和访问,而不是通过修改 FQDN 来实现。

多集群服务网格中的 FQDN 与单集群并没有什么不同,通常遵循以下格式:

<service-name>.<namespace>.svc.cluster.local

也许你会想到通过 meshID 来区分网格?meshID 主要用于区分和管理在同一环境中或跨环境的多个 Istio 网格,meshID 并不用于直接构造服务的 FQDN。

meshID 的主要作用
  • 网格级的遥测数据聚合:区分不同网格的数据,以便在统一平台上进行监控和分析。
  • 网格联邦:在网格之间建立联邦关系,允许网格间共享一些配置和服务。
  • 跨网格的策略实施:识别和应用特定于网格的策略,如安全策略和访问控制。

跨集群的服务注册发现与路由

在 Istio 多网格环境中,东西向网关(East-West Gateway)起着关键作用,它不仅处理网格间的入口和出口流量,还支持服务的发现和连接。当一个集群需要访问另一个集群中的服务时,它通过这个网关路由到目标服务。

下图展示了跨集群的服务注册发现与路由的过程。

在跨集群的 Istio 网格配置中,服务注册、发现和路由的流程是至关重要的,它们确保了不同集群中的服务可以相互发现并通信。以下是跨集群 Istio 网格中服务注册、发现与路由的基本流程:

1. 服务注册

在每个 Kubernetes 集群中,当一个服务被部署时,它的信息会被注册到 Kubernetes 的 API Server。这包括服务的名称、标签、选择器、端口等信息。

2. 同步到 Istiod

Istiod,作为控制平面,负责监控 Kubernetes API Server 的状态变化。每当有新的服务被注册或现有服务被更新时,Istiod 会自动检测到这些变化。Istiod 接着提取必要的服务信息并构建内部的服务和端点的配置。

3. 跨集群服务发现

为了使一个集群中的服务能够发现并通信到另一个集群的服务,Istiod 需要将服务端点信息同步到所有相关集群。这通常通过以下两种方式之一实现:

  • DNS 解析:Istio 可配置为利用 CoreDNS 或类似服务,在 DNS 查询中返回跨集群的服务端点。当一个服务尝试解析另一个集群中的服务时,DNS 查询会返回可以访问的远程服务的 IP 地址。在本文中我们将启用 Istio 的 DNS 代理实现跨集群的服务发现。在一个服务同时存在于本地和远程集群中时,在本地执行 DNS 查询只会返回本地服务的 ClusterIP,若该服务只存在于远程集群中时,DNS 查询将返回远程服务所在集群的东西向网关的负载均衡器地址,该特性也可以用于跨集群的故障恢复。
  • 服务入口同步:通过设置特定的 ServiceEntry 配置,使得一个集群的 Envoy 代理知道如何通过东西向网关找到并路由到另一个集群的服务。

4. 路由和负载均衡

当服务 A 需要与服务 B 通信时,它的 Envoy 代理首先解析服务 B 的名称获取 IP 地址,即服务 B 所在集群的东西向网关的负载均衡器地址。接着该东西向网关将请求路由到目标服务。Envoy 代理可以根据配置的负载均衡策略(如轮询、最少连接数等),选择最佳的服务实例来发送请求。

5. 流量管理

Istio 提供了丰富的流量管理功能,例如请求路由、故障注入、流量复制等。这些规则在 Istio 的控制平面中定义,并推送到各个 Envoy 代理执行。这样可以在跨集群环境中灵活地控制和优化服务间的通信。

集群间服务的身份识别与认证

当不同集群中运行的服务需要相互通信时,正确的身份认证和授权是确保服务安全的关键。使用 SPIFFE 可以帮助标识和验证服务的身份,但在多集群环境中需要确保这些身份是唯一且可验证的。

为此,我们将设置 SPIRE 联邦来为多集群的服务分配身份并实现跨集群的身份认证:

  • 使用 SPIFFE 来标识服务身份:在 SPIFFE 框架下,每个服务都会被分配一个格式为spiffe://<trust-domain>/<namespace>/<service>的唯一标识符。在多集群环境中,可以通过包括集群名称在内的“trust domain”来确保身份的唯一性。例如,可以设置spiffe://foo.com/ns/default/svc/service1spiffe://bar.com/ns/default/svc/service1,以区分不同集群中相同名称的服务。
  • 使用 SPIRE 联邦来管理集群间的证书:它可以增强多集群服务网格中的安全性。SPIRE(SPIFFE Runtime Environment)提供了一个高度可配置的平台,用于服务身份验证和证书颁发。当使用 SPIRE 联邦时,可以实现跨集群的服务认证,通过为每个 SPIRE 集群创建 Trust Bundle 实现跨集群的身份认证。

以下是实现 SPIRE 联邦的步骤说明。

1. 配置 Trust Domain

每个集群都配置为一个单独的 trust domain。这样,每个集群内的服务都将具有基于其所在 trust domain 的唯一 SPIFFE ID。例如,集群 1 的服务可能拥有 ID spiffe://cluster1/ns/default/svc/service1,而集群 2 的相同服务则为 spiffe://cluster2/ns/default/svc/service1

2. 建立 Trust Bundle

在 SPIRE 中配置 trust relationships 以允许不同 trust domain 的节点和工作负载相互验证。这涉及到 trust domain 之间交换和接受彼此的 CA 证书或 JWT keys,确保跨集群通信的安全性。

3. 配置 SPIRE Server 和 Agent

在每个集群中部署 SPIRE Server 和 SPIRE Agent。SPIRE Server 负责管理证书颁发和续签,而 SPIRE Agent 负责将证书和密钥安全地分发给集群内的服务。

Istio 使用 SPIRE 联邦时的工作负载注册兼容性问题
在本文中,我们将在 SPRIE Server 中使用传统的 Kubernetes Workload Registrar 来负责集群中的工作负载注册。从 SPIRE v1.5.4 起弃用了 Kubernetes Workload Registrar,转而是使用 SPIRE Controller Manager 代替,经我测试并不能与 Istio 很好的运行。

4. 使用 Workload API

服务可以通过 SPIRE 的 Workload API 请求和更新其身份证书。这样,服务即使在不同集群中运行,也能持续验证其身份,并安全地与其他服务通信。我们将配置 Istio 网格中的代理共享 SPIRE Agent 中的 Unix Domain Socket,从而访问 Workload API 来管理证书。

5. 自动化证书轮换

我们将使用 cert-manager 作为 SPIRE 的 UpstreamAuthority,配置 SPIRE 自动轮换服务证书和密钥,增强系统的安全性。通过自动化轮换,即使证书被泄露,攻击者也只能在很短的时间内利用这些证书。

通过这些步骤,你可以建立一个跨集群的、安全的服务身份验证框架,使得各个集群的服务能够安全地识别和通信,从而有效地降低安全风险并简化证书管理。这样的配置不仅增强了安全性,还通过分散的信任域提高了系统的可扩展性和灵活性。

部署多集群

下图展示了 Istio 多集群及 SPIRE 联邦的部署模型。

image
多集群网格部署模型

下面我将演示如何在多集群 Istio 网格中实现无缝地跨集群无缝访问。

  1. 在 GKE 中创建两个 Kubernetes 集群,分别命名为 cluster-1cluster-2
  2. 分别在这两个集群中部署 SPIRE 并设置联邦
  3. 分别在两个集群中安装 Istio,注意配置信任域、东西向网关、入口网关、 sidecarInjectorWebhook 挂载 SPIFFE UDS 的 workload-socket,并启用 DNS 代理
  4. 部署测试应用并验证跨集群的无缝访问

我们部署的各组件版本如下:

  • Kubernetes: v1.29.4
  • Istio: v1.22.1
  • SPIRE: v1.5.1
  • cert-manager: v1.15.1

我将所有命令及步骤说明保存在 Github 上:rootsongjc/istio-multi-cluster,你可以按照该项目中的说明操作。下面是对各主要步骤的说明。

1. 准备 Kubernetes 集群

打开 Google Cloud Shell 或本地终端,并确保你已经安装了 gcloud CLI。使用以下命令创建两个集群:

gcloud container clusters create cluster-1 --zone us-central1-a --num-nodes 3
gcloud container clusters create cluster-2 --zone us-central1-b --num-nodes 3

2. 部署 cert-manager

使用 cert-manager 作为根 CA 为 istiod 和 SPIRE 颁发证书。

./cert-manager/install-cert-manager.sh

3. 部署 SPIRE 联邦

SPIRE 联邦的基本信息如下:

Cluster Alias Trust Domain
cluster-1 foo.com
cluster-2 bar.com

注意:信任域不需要与 DNS 名称一致,但需要与 Istio Operator 配置中的信任域相同。

执行下面的命令部署 SPIRE 联邦:

./spire/install-spire.sh

想了解 Istio 中使用 SPIRE 进行身份管理的详情,请参考使用 cert-manager 和 SPIRE 管理 Istio 中的证书

4. 安装 Istio

我们将使用 IstioOperator 来安装 Istio,其中为每个集群配置了:

  • 自动 Sidecar 注入
  • 入口网关
  • 东西向网关
  • DNS 代理
  • SPIRE 集成
  • 访问远程 Kubernetes 集群的 Secret

执行下面的命令安装 Istio:

istio/install-istio.sh

验证流量联邦

为了验证多集群安装的正确性,我们将在两个集群中分别部署不同版本的 helloworld 应用,然后在 cluster-1 中访问 helloworld 服务,以测试以下跨集群访问场景:

  1. 东西向流量联邦:跨集群的服务冗余
  2. 东西向流量联邦:处理非本地目标服务
  3. 南北向流量联邦:通过远程入口网关访问服务

执行下面的命令在两个集群中部署 helloworld 应用:

./example/deploy-helloword.sh

东西向流量联邦:跨集群的服务冗余

部署完成 helloworld 应用后,从 cluster-1sleep pod 访问 hellowrold 服务:

kubectl exec --context=cluster-1 -n sleep deployment/sleep -c sleep \
	-- sh -c "while :; do curl -sS helloworld.helloworld:5000/hello; sleep 1; done"

下图展示的是该场景下的部署架构及流量路由路径。

image
东西向流量联邦:跨集群的服务冗余

从请求结果既有 helloworld-v1 又有 helloworld-v2 的响应来看,说明跨集群的服务冗余生效了。

验证 DNS

此时,因为 helloworld 服务既存在于本地又在远程集群中,若你在 cluster-1 中查询 helloworld 服务的 DNS 名称:

kubectl exec -it deploy/sleep --context=cluster-1 -n sleep -- nslookup helloworld.helloworld.svc.cluster.local

你将得到 cluster-1 集群中的 helloworld 服务的 ClusterIP。

验证流量路由

接下来我们将通过查看 Envoy 代理配置来验证跨集群的流量路由路径。

cluster-1 中查看 helloworld 服务的端点:

istioctl proxy-config endpoints deployment/sleep.sleep --context=cluster-1 --cluster "outbound|5000||helloworld.helloworld.svc.cluster.local"

你将得到类似下面的输出:

ENDPOINT               STATUS      OUTLIER CHECK     CLUSTER
10.76.3.22:5000        HEALTHY     OK                outbound|5000||helloworld.helloworld.svc.cluster.local
34.136.67.85:15443     HEALTHY     OK                outbound|5000||helloworld.helloworld.svc.cluster.local

这两个端点,一个是 cluster-1 中的 helloworld 服务的端点,另一个是 cluster-2istio-eastwestgateway 服务的负载均衡器地址。Istio 将为跨集群的 TLS 连接设置 SNI,在 cluster-2 中将通过 SNI 区分目标服务。

执行下面的命令,在 cluster-2 中查询前面 SNI 的端点:

istioctl proxy-config endpoints deploy/istio-eastwestgateway.istio-system --context=cluster-2 --cluster "outbound_.5000_._.helloworld.helloworld.svc.cluster.local"

你将得到类似下面的结果:

ENDPOINT           STATUS      OUTLIER CHECK     CLUSTER
10.88.2.4:5000     HEALTHY     OK                outbound_.5000_._.helloworld.helloworld.svc.cluster.local

这个端点就是 helloworld 服务在 cluster-2 集群中的端点。

通过以上步骤,你应该了解了跨集群冗余服务的流量路径。接下来我们将删除 cluster-1 中的 helloworld 服务,不需要对 Istio 做任何配置,就可以自动实现故障转移。

东西向流量联邦:故障转移

执行下面的命令将 cluster-1 中的 helloworld 副本数量缩容为 0:

kubectl -n helloworld scale deploy helloworld-v1 --context=cluster-1 --replicas 0

再次从 cluster-1 中访问 helloworld 服务:

kubectl exec --context=cluster-1 -n sleep deployment/sleep -c sleep \
	-- sh -c "while :; do curl -sS helloworld.helloworld:5000/hello; sleep 1; done"

依然可以获得来自 helloworld-v2 的响应。

现在,直接删除 cluster-1 中的 helloworld 服务:

kubectl delete service helloworld -n helloworld --context=cluster-1

依然可以获得来自 helloworld-v2 的响应,这说明跨集群的故障转移生效了。

下图展示了该场景下的流量路径。

image
东西向流量联邦:故障转移

验证 DNS

此时,因为 helloworld 服务既存在于本地又在远程集群中,若你在 cluster-1 中查询 helloworld 服务的 DNS 名称:

kubectl exec -it deploy/sleep --context=cluster-1 -n sleep -- nslookup helloworld.helloworld.svc.cluster.local

你将得到 cluster-2 集群中东西向网关的地址和 15443 端口。

南北向流量联邦:通过远程入口网关访问服务

通过入口网关访问远程集群中的服务,是最传统的跨集群访问方式,下图展示了该场景下的流量路径。

image
南北向流量联邦:通过远程入口网关访问服务

执行下面的命令在 cluster-2 中创建 Gateway 和 VirtualService:

kubectl apply --context=cluster-2 \
	-f ./examples/helloworld-gateway.yaml -n helloworld

获取 cluster-2 中的入口网关地址:

GATEWAY_URL=$(kubectl -n istio-ingress --context=cluster-2 get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

执行下面的验证可以通过远程入口网关访问服务:

kubectl exec --context="${CTX_CLUSTER1}" -n sleep deployment/sleep -c sleep \
	-- sh -c "while :; do curl -s http://$GATEWAY_URL/hello; sleep 1; done"

你将得到来自 helloworld-v2 的响应。

验证身份

执行下面的命令获取 cluster-1 集群中 sleep pod 中的证书:

istioctl proxy-config secret deployment/sleep -o json --context=cluster-1| jq -r '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' | base64 --decode > chain.pem
split -p "-----BEGIN CERTIFICATE-----" chain.pem cert-
openssl x509 -noout -text -in cert-ab
openssl x509 -noout -text -in cert-aa

如果在输出的消息中看到下面的字段,说明身份分配正确:

Subject: C=US, O=SPIFFE

URI:spiffe://foo.com/ns/sample/sa/sleep

查看 SPIRE 中的身份信息:

kubectl --context=cluster-1 exec -i -t -n spire spire-server-0 -c spire-server \
	-- ./bin/spire-server entry show -socketPath /run/spire/sockets/server.sock --spiffeID spiffe://foo.com/ns/sleep/sa/sleep

你将看到类似下面的输出:

Found 1 entry
Entry ID         : 9b09080d-3b67-44c2-a5b8-63c42ee03a3a
SPIFFE ID        : spiffe://foo.com/ns/sleep/sa/sleep
Parent ID        : spiffe://foo.com/k8s-workload-registrar/cluster-1/node/gke-cluster-1-default-pool-18d66649-z1lm
Revision         : 1
X509-SVID TTL    : default
JWT-SVID TTL     : default
Selector         : k8s:node-name:gke-cluster-1-default-pool-18d66649-z1lm
Selector         : k8s:ns:sleep
Selector         : k8s:pod-uid:6800aca8-7627-4a30-ba30-5f9bdb5acdb2
FederatesWith    : bar.com
DNS name         : sleep-86bfc4d596-rgdkf
DNS name         : sleep.sleep.svc

生产环境建议

对于生产环境,建议使用统一网关,通过 Tier-2 架构,在 Tier-1 边缘网关配置全局的流量路由,该边缘网关将把转写的 Istio 配置下发给 Tier-2 集群中的各个入口网关。

下图展示了使用 TSB 部署的 Tier2 架构的 Istio 服务网格,其中使用 SPIRE 联邦。

image
使用 SPIRE、Tier2 架构的 TSB 部署的多集群 Istio 服务网格架构图

我们将这四个 Kubernetes 集群分为 Tier1 集群(tier1)和 Tier2 集群(cp-cluster-1cp-cluster-2cp-cluster-3)。在 T1 中安装 Edge Gateway,而在 T2 中安装 bookinfo 和 httpbin 应用程序。每个集群将拥有独立的信任域,所有这些集群将构成 SPIRE 联邦。

下图展示了用户通过入口网关访问 bookinfo 和 httpbin 服务的流量路由。

image
统一网关架构图

你需要在 Istio 之上创建一个适用于多集群的逻辑抽象层,关于 TSB 中的统一网关的详细信息,请参考 TSB 文档.

总结

本文详细介绍了在 Istio 多集群网格环境中实现服务身份验证、DNS 解析和跨集群流量管理的关键技术和方法。通过精确配置 Istio 和 SPIRE 联邦,我们不仅增强了系统的安全性,还提高了服务间通信的效率和可靠性。遵循这些步骤,你将能够构建一个强大的、可扩展的多集群服务网格,满足现代应用的复杂需求。

参考

最后更新于 2024/09/16