istio 庖丁解牛(五) 多集群网格实现分析

Posted on July 29, 2019

1.多集群网格背景介绍

Istio 并不是单一领域的技术, 它综合了诸多服务治理领域的解决方案和最佳实践。在模型上, istio 提供了多个层次的抽象, 以适配不同的平台场景; 在实际应用上, istio 提供了若干可选的开源系统和技术, 并合理的将这些系统组合在一起, 以实现服务网格中的「连接」、「安全」、「控制」和「可观测性」。

image-20190729153848023

在 istio 的应用场景中, 异地多集群网格是其中最复杂的场景之一。istio 在 1.1 后提供了三种多集群的连通拓扑:

  1. 多控制面
  2. 单网络单控制面
  3. 多网络单控制面

第一种「多控制面连通拓扑」, 严格的讲每个 kubernetes 集群仍然是独立的服务网格, 各网格之间服务实例无法共享, 互相访问不透明. 应用场景有限, 实现简单。

第二种「单网络单控制面拓扑」, 实现了多 kubernetes 集群融合为一个服务网格, 但是该种拓扑对网络有严格的要求: 需要所有集群处于同一个扁平网络, pod ip 互通且不重叠, 使用 VPN 连通多集群网络是常见的一个选项。 不过这些网络需求在实际环境可能难以满足, 也限制了该拓扑的应用场景。

第三种「多网络单控制面」, 同样实现了多 kubernetes 集群融合为一个服务网格, 且在网络上没有上述限制, 每个多 kubernetes 集群是一个独立的网络, 甚至可以分布于不同地域。 但其实现也最复杂, 该拓扑模型可以实现「地域感知路由」、「异地容灾」等高级应用场景。

本文将对第三种「多网络单控制面」的搭建和连通过程进行分析:

Shared Istio control plane topology spanning multiple Kubernetes clusters using gateways

本文约定术语:

  • 集群: 指单个 kubernetes 集群。

  • 主集群: 特指在「多网络单控制面」拓扑中, 包含控制面的 kubernetes 集群。

  • 子集群: 特指在「多网络单控制面」拓扑中, 不包含控制面的 kubernetes 集群。

  • 网格: 特指在「多网络单控制面」拓扑中, 所有 kubernetes 集群组成的唯一服务网格。

  • 网络: 在「多网络单控制面」拓扑中, 每个 kubernetes 集群都有独立的网络。

在该拓扑中, 只有主集群中存在一个控制面, 所有集群都可以包含数据面, 所有数据面的服务实例共享且可以透明互通。我们可以简单描述该拓扑的连通需求:

  • 主集群控制面需要获得所有集群的服务发现数据, 也就是控制面需要能访问所有集群的 kube api。
  • 子集群的数据面需要能主动联通主集群控制面, 因此主集群需要通过 ingress gateway, 将控制面组件暴露给子集群使用。
  • 所有包含数据面的集群, 需要将内部业务服务, 通过 ingress gateway 暴露出去, 用以实现整个网格的服务互通。
  • 主集群控制面需要知悉整个网格的网络拓扑, 每个服务实例也需要标记自身所处网络。 这是因为每个具体服务实例收到的 xDS 数据, 都是和自身所处的网络强相关的。

本文将尝试在广州和新加坡地域各自创建一个 kubernetes 集群, 在广州集群创建 istio 控制面, 然后将 2 个集群进行连通, 实现「多网络单控制面」的多集群服务网格。

相关代码汇总于: https://github.com/zhongfox/multicluster-demo


2. 主集群配置

首先分别在广州和新加坡地域创建好 kubernetes 集群, 并获得 kube api 访问凭证存于本地, kube config context 分别命名为 guangzhou 和 singapore.

在广州集群上, 安装好单集群 istio 组件:

image-20190729231121442

2.1 主集群访问子集群 kube api

istio 控制面只存在于广州主集群中, 控制面需要能获取到所有集群的服务发现数据, 因此主集群中需要配置子集群的访问凭证:

使用 guangzhou kube context:

% kubectl config use-context guangzhou

将 singapore 集群访问凭证存到 guangzhou 集群的 secret 中:

% kubectl --context=guangzhou -n istio-system create secret \
  generic singapore-secret --from-file singapore

同时需要给该 secret 设置 label istio/multiCluster=true:

kubectl label --context=guangzhou secret singapore-secret istio/multiCluster=true -n istio-system

在 Pilot 源码中, 会创建 SecretController 来 list/watch lable istio/multiCluster=true 的 secret, 并实例化 remoteKubeController, 作为网格服务发现数据的来源之一。

image-20190729172916130

2.2 将控制面组件暴露给子集群使用

在主集群中创建 ingress gateway meshexpansion-gateway 将 Pilot, Mixer 和 citadel 暴露给子集群使用:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: meshexpansion-gateway
  namespace: istio-system
  labels:
    app: gateways
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 15011
      protocol: TCP
      name: tcp-pilot
    hosts:
    - "*"
  - port:
      number: 8060
      protocol: TCP
      name: tcp-citadel
    hosts:
    - "*"
  - port:
      number: 15004
      name: tls-mixer
      protocol: TLS
    tls:
      mode: AUTO_PASSTHROUGH
    hosts:
    - "*"

该 gateway 要生效, 还需要配置相关的 VirtualService:

  1. 创建 VirtualService meshexpansion-vs-pilot, 将主集群 Pilot 通过 gateway 暴露给子集群使用.
  2. 创建 VirtualService meshexpansion-vs-citadel, 将主集群 Citadel 通过 gateway 暴露给子集群使用.

以上配置在install/primarycluster-meshexpansion-gateway.yaml中, 等同于执行:

% kubectl apply -f install/primarycluster-meshexpansion-gateway.yaml

以上步骤配置完毕后, 我们还需要将网格中对 Pilot, Mixer 的访问调整为以上端口, 包括:

1) 网格全局配置:

% kubectl -nistio-system edit cm istio

......
defaultConfig:
  discoveryAddress: istio-pilot.istio-system:15011
......
mixerCheckServer: istio-policy.istio-system.svc.cluster.local:15004
mixerReportServer: istio-telemetry.istio-system.svc.cluster.local:15004

2) 控制面中 istio-ingressgateway istio-egressgateway的启动参数, 修改对 pilot 的访问地址, 从 15010(HTTP 端口)改为 15011(mTLS 端口):

- --discoveryAddress
- istio-pilot:15011

image-20190729173048701

2.3 数据面创建 ingress gateway

每个包含数据面的集群, 都需要提供 ingress gateway, 使得其他集群数据面可以访问本集群数据面服务:

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"

以上配置在install/primarycluster-cluster-aware-gateway.yaml中, 等同于执行:

% kubectl apply -f install/primarycluster-cluster-aware-gateway.yaml

image-20190729173523592

关于 mTLS 和 AUTO_PASSTHROUGH

通常来说, istio Ingress Gateway 需要配套指定服务的 VirtualService, 用以指定 ingress 流量的后端服务. 但在此拓扑中, 该 ingress Gateway 需要作为本数据面所有服务的流量入口. 也就是所有服务共享单个 ingress gateway (单个 IP), 这里其实是利用了 TLS 中的 SNI(Server Name Indication)

SNI(Server Name Indication)指定了 TLS 握手时要连接的 主机名。 SNI 协议是为了支持同一个 IP(和端口)支持多个域名

传统的 Ingress Gateway 承载的是南北流量(server-client), 这里的 Ingress Gateway 属于网格内部流量, 承载的是东西流量(server-server).

设置AUTO_PASSTHROUGH, 可以允许服务无需配置 VirtualService, 而直接使用 TLS 中的 SNI 值来表示 upstream, 服务相关的 service/subset/port 都可以编码到 SNI 内容中.

我们看看以上的 Gateway cluster-aware-gateway 443 端口开启AUTO_PASSTHROUGH 后的 xDS 效果:

image-20190730103626893

其中 Listener Filter envoy.listener.tls_inspector 会检测传输是否是 TLS, 如果是的话, 会进一步提取 SNI (或者 ALPN), SNI 信息在后续 FilterChain 中可以用来路由。

Network Filter envoy.filters.network.sni_cluster 会利用 SNI 信息来判断 upstream cluster, 该 filter 不会影响非 TLS 的连接。

2.4 控制面 mTLS 认证

1) 控制面组件citadel负责管理网格内的证书, 我们需要给 citadel 提供 ca 证书、秘钥和根证书:

% kubectl -n istio-system create secret generic cacerts \
  --from-file=certs/ca-cert.pem --from-file=certs/ca-key.pem \
  --from-file=certs/root-cert.pem --from-file=certs/cert-chain.pem

2) 创建好 secret 后, 我们需要将其挂载到 citadel pod 中:

     volumeMounts:
     - name: cacerts
       mountPath: /etc/cacerts
       readOnly: true
 ......
 volumes:
 - name: cacerts
   secret:
    secretName: cacerts
    optional: true

3) 并将其传入 citadel 启动参数:

- --self-signed-ca=false
- --signing-cert=/etc/cacerts/ca-cert.pem
- --signing-key=/etc/cacerts/ca-key.pem
- --root-cert=/etc/cacerts/root-cert.pem
- --cert-chain=/etc/cacerts/cert-chain.pem

4) 更新网格全局配置, 设置controlPlaneAuthPolicyMUTUAL_TLS

% kubectl -nistio-system edit cm istio
......
defaultConfig:
  controlPlaneAuthPolicy: MUTUAL_TLS

5) 控制面组件 istio-pilot、istio-telemetry、isito-policy 的 sidecar, 以及 istio-ingressgateway, istio-egressgateway 增加启动参数以支持控制面 mTLS 认证:

- --controlPlaneAuthPolicy
- MUTUAL_TLS
关于 ControlPlaneAuth

控制面组件开启MUTUAL_TLS, 最终会体现到控制面组件(telemetry/policy/pilot)的 envoy xDS 中:

tls_context:
  common_tls_context:
    alpn_protocols:
    - h2
    tls_certificates:
    - certificate_chain:
        filename: /etc/certs/cert-chain.pem
      private_key:
        filename: /etc/certs/key.pem
    validation_context:
      trusted_ca:
        filename: /etc/certs/root-cert.pem
  require_client_certificate: true

image-20190729173651673

2.4 开启服务间 mTLS 认证

使用 istio CRD MeshPolicy开启网格全局 mTLS:

apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
  name: "default"
  labels:
    app: security
    chart: security
    heritage: Tiller
    release: istio
spec:
  peers:
  - mtls: {}

以上配置会开启网格内流量的服务端和客户端 TLS 认证, 需要注意的是, 客户端 TLS 认证会被关联的 DestinationRule 里的tls属性覆盖, 所以如果使用了 DestinationRule, 需要在 DestinationRule 中显示指定开启 mTLS:

tls:
  mode: ISTIO_MUTUAL

另外在全局 mtls 认证会有些例外: 比如 k8s api server 没有 sidecar, 所以客户端访问 api server 需要禁用 mtls:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: "api-server"
  namespace: istio-system
  labels:
    app: security
spec:
  host: "kubernetes.default.svc.cluster.local"
  trafficPolicy:
    tls:
      mode: DISABLE

以上配置在install/primarycluster-services-mtls.yaml中, 等同于执行:

% kubectl apply -f install/primarycluster-services-mtls.yaml

image-20190729173735897


3. 子集群配置

3.1 部署子集群 istio 配置

获取主集群控制面的 gateway 地址, 作为子集群访问控制面的入口地址:

export CONTROL_PANEL_GW=$(kubectl --context guangzhou -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

参考https://istio.io/docs/setup/kubernetes/install/multicluster/shared-gateways/#setup-cluster-2, 安装子集群 istio 配置:

% helm template --name istio-remote --namespace=istio-system \
  --values install/kubernetes/helm/istio/values-istio-remote.yaml \
  --set global.mtls.enabled=true \
  --set gateways.enabled=true \
  --set security.selfSigned=false \
  --set global.controlPlaneSecurityEnabled=true \
  --set global.createRemoteSvcEndpoints=true \
  --set global.remotePilotCreateSvcEndpoint=true \
  --set global.remotePilotAddress=${CONTROL_PANEL_GW} \
  --set global.remotePolicyAddress=${CONTROL_PANEL_GW} \
  --set global.remoteTelemetryAddress=${CONTROL_PANEL_GW} \
  --set gateways.istio-ingressgateway.env.ISTIO_META_NETWORK="network2" \
  --set global.network="network2" \
  install/kubernetes/helm/istio > istio-remote.yaml

该文件内容较多, 输出内容可以参考https://github.com/zhongfox/multicluster-demo/blob/master/install/istio-remote-1.1.11.yaml

将输出内容部署到新加坡集群中:

% kubectl config use-context singapore
% kubectl create ns istio-system
% kubectl apply -f istio-remote.yaml

子集群同样需要创建 mTLS 认证所需的 secret:

% kubectl create secret generic cacerts -n istio-system --from-file=certs/ca-cert.pem --from-file=certs/ca-key.pem --from-file=certs/root-cert.pem --from-file=certs/cert-chain.pem

3.2 子集群配置解读

子集群中并没有安装 istio 控制面组件, 也不存在任何 istio CRD, 以上操作在子集群中创建了若干 kubernetes 原生资源, 用以实现子集群和主集群的连通, 我们看看其中的一些重要配置:

image-20190729161910641

在新加坡集群中, 并没有 pilot、telemetry、policy 等组件 Pod, 但是存在相关的 Headless Service (ClusterIP 为 None), 并添加它们的 endpoints 指向广州主集群的控制面 ingress Gateway IP, 以此实现单控制面共享。


4. 网络拓扑配置

控制面需要知悉整个网格的网络拓扑, 每个服务实例也需要标记自身所处网络. 这是因为每个具体服务实例收到的 xDS 数据, 都是和自身所处的网络强相关的。

1) 获取各集群的 ingress gateway ip:

% export GZ_INGRESS=$(kubectl -n istio-system --context guangzhou get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

% export SG_INGRESS=$(kubectl -n istio-system --context singapore get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

2) 将各集群 ip 配置到网格全局配置中(替换 IP):

% kubectl --context guangzhou -n istio-system edit cm istio

meshNetworks: |-
  networks:
    network1:
      endpoints:
      - fromRegistry: Kubernetes
      gateways:
      - address: ${GZ_INGRESS}
        port: 443
    network2:
      endpoints:
      - fromRegistry: singapore
      gateways:
      - address: ${SG_INGRESS}
        port: 443

3) 每个集群的网络标识需要独立设置, 该标识是注入到 sidecar 的环境变量中, 因此需要在各集群中修改 sidecar injector 配置:

广州主集群:

% kubectl --context guangzhou -nistio-system edit cm istio-sidecar-injector
......
env:
- name: ISTIO_META_NETWORK
  value: "network1"

新加坡子集群:

% kubectl --context singapore -nistio-system edit cm istio-sidecar-injector
......
env:
- name: ISTIO_META_NETWORK
  value: "network2"

4) 更新已有组件的网络标识: 控制面组件的 sidecar 并不是由istio-sidecar-injector注入的, 因此需要手动修改它们的网络标识, 包括istio-ingressgateway istio-egressgateway等:

kubectl --context=guangzhou -nistio-system edit deploy istio-ingressgateway
......
env:
- name: ISTIO_META_NETWORK
  value: "network1"

5. 多集群网格验证

至此, 由广州和新加坡 2 个异地集群组成的服务网格已经搭建完成, 我们来验证一下:

我们在 2 个集群中分别部署一套在线电子商城 demo( github.com/TencentCloudContainerTeam/tcm-demo), 其中的推荐系统(recommend), 广州集群部署 v1 版本, 新加坡集群部署 v2 版本, 其中 recommend v1 版本不包含 banner, recommend v2 版本会显示一个 banner:

% kubectl --context guangzhou apply -f install/primarycluster-apps.yaml
% kubectl --context singapore apply -f install/subcluster-apps.yaml

image-20190729175507376

创建 ingress gateway, 放通对 mall 服务的访问流量:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: mall-gateway
  namespace: base
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: mall
  namespace: base
spec:
  hosts:
  - "*"
  gateways:
  - mall-gateway
  http:
  - route:
    - destination:
        host: mall
        port:
          number: 7000
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: mall
  namespace: base
spec:
  host: mall
  subsets:
  - labels:
      app: mall
    name: v1
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

等价于执行:

% kubectl --context guangzhou apply -f install/primarycluster-apps-routing.yaml

注意虽然以上流控 CRD 是 apply 到主集群, 但是因为广州和新加坡共享一个控制面, 因此这些流控设置在 2 个集群都会生效:

image-20190729213030343

现在我们分别通过广州和新加坡地域的 ingress gateway IP 访问 mall 应用, 多访问几次, 可以发现, 无论从哪里地域进入, 随机的可以访问到 recommend v1 (无 banner)和 recommend v2 (有 banner), 证明 2 个地域的服务实例是透明共享的:

image-20190729145727003

还可以通过工具 istioctl 查看 xDS 数据, 比如我们查看广州集群 mall pod 获得的 xDS, 其中有 2 个 recommend 服务的 endpoints:

image-20190729150633160

172.25.0.26:7000是广州集群的 recommend v1 pod, 而119.28.109.157:443 (新加坡 ingress gateway)在这里对应的是新加坡集群 recommend v2 的服务实例. 这些服务实例对业务代码来说是透明的, 访问 recommend 服务可以随机路由到任一集群。


本文主要对 istio 多集群网格的实现进行分析, 关于多集群网格的应用场景, 且看下回分解。