基于istio实现多集群流量治理

本文分享自华为云社区《基于istio实现多集群流量治理》,作者: 可以交个朋友。

一 背景

对多云、混合云等异构基础设施的服务治理是Istio重点支持的场景之一。为了提高服务的可用性,避免厂商锁定,企业通常会选择将应用部署在多个地域的多个集群,甚至多云、混合云等多种云环境下,多集群的方案逐步成为企业应用部署的最佳选择。因此越来越多的用户对跨集群的服务治理有着强烈的需求,在此背景下Istio作为ServiceMesh领域的事实标准,推出了多种多集群管理方案。

二 简介

目前Istio支持4种多集群模型。

  1. 扁平网络单控制面模型
  2. 扁平网络多控制面模型
  3. 非扁平网络单控制面模型
  4. 非扁平网络多控制面模型

多集群的单控制面模型是指多个集群共用同一套Istio控制面,多集群的多控制面模型指每个集群都要独立使用一套Istio控制面,无论是单控制面还是多控制面模型,每套Istio控制面(istiod)都要连接所有集群的Kube-apiserver,并且List-Watch获取所有集群的Service、Endpoint、Pod 、Node ,并控制面集群内或集群间的服务访问,但是只监听主集群的VirtualService、DestinationRule、Gateway等Istio API对象。

根据集群间网络是否扁平,Istio又对两种控制面模型进行了细分:

  • 扁平网络:多集群容器网络通过VPN等技术打通,Pod跨集群访问直通。
  • 非扁平网络:每个集群的容器网络都相互隔离,跨集群的访问不能直通,必须通过东西向网关

生产环境上在选择 Istio 多集群模型时,当然需要结合自己的实际场景来决定。如果集群之间的网络是扁平的,那么可以选择扁平网络模型,如果集群之间的网络是隔离的,那么可以选择非扁平网络模型。如果集群规模较小,那么可以选择单控制面模型,如果集群规模较大,那么可以选择多控制面模型。

本文档选择非扁平网络多控制面模型来进行安装说明:安装模型如下所示

非扁平网络多控制面模型有如下特点。

  1. 不同的集群不需要在一张大网下,即容器网络不需要三层打通,跨集群的服务访问通过Istio East-West Gateway转发。
  2. 每个kubernetes集群的Pod地址范围与服务地址范围没有限制,可以与其他集群重叠,不同集群之间互不干扰
  3. 每个Kubernetes集群的Sidecar仅连接到本集群的Istio控制面,通信效率更高。
  4. Istiod只监听主集群的Istio配置,因此 VirtualService、DestinationRule、Gateway 等资源存在冗余复制问题
  5. 同一集群内部服务访问: Pod之间直接连接;跨集群的服务访问:依赖DNS代理解析其他集群的服务域名,由于集群之间的网络相互隔离,所以依赖Remote集群的 East-west Gateway中转流量。

三 ClusterMesh 环境搭建

搭建 cluster1 和 cluster2 两个集群,然后每个集群上安装 Istio 控制平面, 且将两者均设置为主集群(primary cluster)。 集群 cluster1 在 network1 网络上,而集群 cluster2 在 network2 网络上。

3.1 前提条件

本次搭建环境信息如下: 使用Kind搭建Kubernetes集群,Kind版本为v0.19.0。 Kubernetes 版本为1.27.3 ; Istio 版本为 1.20.1。


在搭建k8s 集群之前确保Linux节点已安装docker kubectl 和 kind。

下载istioctl二进制
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.20.1 TARGET_ARCH=x86_64 sh -
将 istioctl 客户端添加到路径

3.2 Kubernetes集群安装

cluster1和cluster2集群安装脚本如下

复制代码
# create-cluster.sh
# This script handles the creation of multiple clusters using kind and the
# ability to create and configure an insecure container registry.

set -o xtrace
set -o errexit
set -o nounset
set -o pipefail

# shellcheck source=util.sh
NUM_CLUSTERS="${NUM_CLUSTERS:-2}"
KIND_IMAGE="${KIND_IMAGE:-}"
KIND_TAG="${KIND_TAG:-v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72}"
OS="$(uname)"
function create-clusters() {
  local num_clusters=${1}

  local image_arg=""
  if [[ "${KIND_IMAGE}" ]]; then
    image_arg="--image=${KIND_IMAGE}"
  elif [[ "${KIND_TAG}" ]]; then
    image_arg="--image=kindest/node:${KIND_TAG}"
  fi
  for i in $(seq "${num_clusters}"); do
    kind create cluster --name "cluster${i}" "${image_arg}"
    fixup-cluster "${i}"
    echo

  done
}

function fixup-cluster() {
  local i=${1} # cluster num

  if [ "$OS" != "Darwin" ];then
    # Set container IP address as kube API endpoint in order for clusters to reach kube API servers in other clusters.
    local docker_ip
    docker_ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "cluster${i}-control-plane")
    kubectl config set-cluster "kind-cluster${i}" --server="https://${docker_ip}:6443"
  fi

  # Simplify context name
  kubectl config rename-context "kind-cluster${i}" "cluster${i}"
}
echo "Creating ${NUM_CLUSTERS} clusters"
create-clusters "${NUM_CLUSTERS}"
kubectl config use-context cluster1

echo "Kind CIDR is $(docker network inspect -f '{{$map := index .IPAM.Config 0}}{{index $map "Subnet"}}' kind)"

echo "Complete"

以上集群安装的过程中,为了istiod能够访问对方集群的apiserver地址,集群kube-apiserver的地址设置为master节点的地址。因为是kind部署的集群,两个集群的master节点本质上都是同个宿主机上的docker运行的容器。

确认cluster1和cluster2 是否就绪

3.3 使用MetalLB为网关分配ExternalIP

由于使用的是kind部署多集群,istio南北向网关和东西向网关创建需要创建LoadBalencer service,均需要使用到ExternalIP。这里借助metalLB 实现LB ip地址的分发和宣告。

查看kind搭建集群使用节点子网网段: 172.18.0.0/16

采用metalLB L2模式进行部署。

cluster1中的metalLB配置清单: metallb-config-1.yaml

复制代码
### for cluster1
##配置IPAddressPool,用于lbip地址的分配。L2模式下,ippool地址和worker节点处于同一子网即可
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
    - 172.18.1.230-172.18.1.240
---
##配置L2Advertisement,用于地址宣告
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: first-adv
  namespace: metallb-system
spec:
  ipAddressPools: 
    - first-pool

cluster2集群中的metalLB配置清单:metallb-config-2.yaml

复制代码
### for cluster2
##配置IPAddressPool,用于lbip地址的分配。L2模式下,ippool地址和worker节点处于同一子网即可
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: second-pool
  namespace: metallb-system
spec:
  addresses:
    - 172.18.1.241-172.18.1.252
---
##配置L2Advertisement,用于地址宣告
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: second-adv
  namespace: metallb-system
spec:
  ipAddressPools: 
    - second-pool

使用脚本进行安装

复制代码
#!/usr/bin/env bash

set -o xtrace
set -o errexit
set -o nounset
set -o pipefail

NUM_CLUSTERS="${NUM_CLUSTERS:-2}"
for i in $(seq "${NUM_CLUSTERS}"); do
  echo "Starting metallb deployment in cluster${i}"
  kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml --context "cluster${i}"
  kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)" --context "cluster${i}"
  ## 增加等待时间,如果metallb负载没部署起来,创建IPAddressPool L2Advertisement 会报错
  sleep 10
  kubectl apply -f ./metallb-config-${i}.yaml --context "cluster${i}"
  echo "----"
done

确认metalLB部署情况


确认IPAddressPool信息:

3.4 集群共享根CA 配置信任关系

为了支持安全的跨集群mTLS通信,多控制面模型要求每个集群的控制面Istiod都使用相同的CA机构颁发的中间CA证书,供Citatel签发证书使用,以支持跨集群的TLS双向认证。

Istio东西向网关(跨集群访问)工作时使用基于SNI的路由,它根据TLS请求的SNI,自动将其路由到SNI对应的Cluster,因此非扁平网络的跨网络访问要求所有流量都必须经过TLS加密。

在集群中插入证书和密钥,脚本如下(需要将该脚本移动到istio的安装包目录下):

复制代码
#!/usr/bin/env bash

set -o xtrace
#set -o errexit
set -o nounset
set -o pipefail
NUM_CLUSTERS="${NUM_CLUSTERS:-2}"
##在istio安装包的顶层目录下 创建目录 用来存放证书和密钥
mkdir -p certs
pushd certs

##生成根证书和密钥
make -f ../tools/certs/Makefile.selfsigned.mk root-ca

for i in $(seq "${NUM_CLUSTERS}"); do
  ##对于每个集群,为 Istio CA 生成一个中间证书和密钥
  make -f ../tools/certs/Makefile.selfsigned.mk "cluster${i}-cacerts"
  ##对于每个集群,创建istio-system 命名空间
  kubectl create namespace istio-system --context "cluster${i}"
  ## 对于每个集群,通过给istio系统命名空间打上topology.istio.io/network 标签添加网络标识
  kubectl --context="cluster${i}" label namespace istio-system topology.istio.io/network="network${i}"
  ##对于每个集群,给工作节点node打上地域和可用区标签,便于istio实现地域故障转移、地域负载均衡
  kubectl --context="cluster${i}" label node "cluster${i}-control-plane" topology.kubernetes.io/region="region${i}"
  kubectl --context="cluster${i}" label node "cluster${i}-control-plane" topology.kubernetes.io/zone="zone${i}"
  #在每个集群中,创建一个私密 cacerts,使用所有输入文件 ca-cert.pem, ca-key.pem,root-cert.pem 和 cert-chain.pem。
  kubectl delete secret cacerts -n istio-system --context "cluster${i}"
  kubectl create secret generic cacerts -n istio-system --context "cluster${i}" \
      --from-file="cluster${i}/ca-cert.pem" \
      --from-file="cluster${i}/ca-key.pem" \
      --from-file="cluster${i}/root-cert.pem" \
      --from-file="cluster${i}/cert-chain.pem"
  echo "----"
done
 

执行脚本,将会生成根证书和中间证书等文件

3.5 Istio服务网格安装

为cluster1,和cluster2 集群安装多控制面istio网格。

将cluster1 设置为主集群,在istio的安装目录下执行如下命令

复制代码
cat <<EOF > cluster1.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    global:
      meshID: mesh1
      multiCluster:  ##开启多集群配置
        clusterName: cluster1 #指定k8s集群名称
      network: network1 #指定网络标识
      logging:
        level: debug
EOF

将cluster2 设置为主集群,在istio的安装目录下执行如下命令

复制代码
cat <<EOF > cluster2.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    global:
      meshID: mesh2
      multiCluster:  ##开启多集群配置
        clusterName: cluster2 #指定k8s集群名称
      network: network2 #指定网络标识
      logging:
        level: debug
EOF

编写自动化安装脚本

复制代码
#!/usr/bin/env bash

set -o xtrace
set -o errexit
set -o nounset
set -o pipefail

OS="$(uname)"
NUM_CLUSTERS="${NUM_CLUSTERS:-2}"

for i in $(seq "${NUM_CLUSTERS}"); do

echo "Starting istio deployment in cluster${i}"

istioctl install --force --context="cluster${i}" -f "cluster${i}.yaml"

echo "Generate eastwest gateway in cluster${i}"

## 在每个集群中安装东西向网关。
bash samples/multicluster/gen-eastwest-gateway.sh \
--mesh "mesh${i}" --cluster "cluster${i}" --network "network${i}" | \
istioctl --context="cluster${i}" install -y -f -

echo

done

执行脚本,进行istio的安装部署

稍等片刻后,等待安装完成


可以发现每个集群中的网关使用的ExternalIP信息为配置的metalLB设置的IPPool中的地址。

3.6 在东西向网关开放服务

因为集群位于不同的网络中,所以我们需要在两个集群东西向网关上开放所有服务(*.local)。 虽然此网关在互联网上是公开的,但它背后的服务只能被拥有可信 mTLS 证书的服务访问, 就像它们处于同一网络一样。执行下面的命令在两个集群中暴露服务:

复制代码
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: cross-network-gateway
spec:
  selector:
    istio: eastwestgateway # 专用于东西向流量的网关
  servers:
    - port:
        number: 15443 # 已经声明了
        name: tls
        protocol: TLS
      tls:
        mode: AUTO_PASSTHROUGH # 东西向网关工作模式是 TLS AUTO_PASSTHROUGH
      hosts:
        - "*.local" # 暴露所有的服务

分别在每个集群中应用上述Gateway配置:
kubectl -n istio-system --context=cluster${i} apply -f samples/multicluster/expose-services.yaml

3.7 配置secret以便istiod访问远程集群apiserver

每个k8s集群中的 istiod 需要 List-Watch 其他集群的 Kube-APIServer,使用 K8s 集群的凭据来创建 Secret 对象,以允许 Istio 访问远程 Kubernetes apiserver。

复制代码
#!/usr/bin/env bash

set -o xtrace
set -o errexit
set -o nounset
set -o pipefail
OS="$(uname)"
NUM_CLUSTERS="${NUM_CLUSTERS:-2}"

for i in $(seq "${NUM_CLUSTERS}"); do
  for j in $(seq "${NUM_CLUSTERS}"); do
    if [ "$i" -ne "$j" ]
    then
      echo "Enable Endpoint Discovery between cluster${i} and cluster${j}"

      if [ "$OS" == "Darwin" ]
      then
        # Set container IP address as kube API endpoint in order for clusters to reach kube API servers in other clusters.
        docker_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "cluster${i}-control-plane")
        istioctl create-remote-secret \
        --context="cluster${i}" \
        --server="https://${docker_ip}:6443" \
        --name="cluster${i}" | \
          kubectl apply --validate=false --context="cluster${j}" -f -
      else
        istioctl create-remote-secret \
          --context="cluster${i}" \
          --name="cluster${i}" | \
          kubectl apply --validate=false --context="cluster${j}" -f -
      fi
    fi
  done
done

执行以上脚本:remote secret创建完成。

查看istiod日志发现已经监听远程集群了

四 Istio多集群流量治理实践


每个集群创建sample 命名空间,并设置sidecar自动注入

复制代码
kubectl create --context=cluster1 namespace sample
kubectl create --context=cluster2 namespace sample

kubectl label --context=cluster1 namespace sample \
    istio-injection=enabled
kubectl label --context=cluster2 namespace sample \
    istio-injection=enabled

kubectl apply --context=cluster1 \
    -f samples/helloworld/helloworld.yaml \
    -l service=helloworld -n sample
kubectl apply --context=cluster2 \
    -f samples/helloworld/helloworld.yaml \
    -l service=helloworld -n sample

分别在不同集群部署不同版本的服务
把应用 helloworld-v1 部署到 cluster1:

复制代码
kubectl apply --context=cluster1 \
    -f samples/helloworld/helloworld.yaml \
    -l version=v1 -n sample

把应用 helloworld-v2 部署到 cluster2:

复制代码
kubectl apply --context=cluster2 \
-f samples/helloworld/helloworld.yaml \
-l version=v2 -n sample

部署测试客户端

复制代码
kubectl apply --context=cluster1 \
    -f samples/sleep/sleep.yaml -n sample
kubectl apply --context=cluster2 \
    -f samples/sleep/sleep.yaml -n sample

确认负载实例部署成功,并且sidecar已经注入

4.1 验证跨集群流量

用 Sleep pod 重复调用服务 HelloWorld。 为了确认负载均衡按预期工作,需要从所有集群调用服务 HelloWorld。

从 cluster1 中的 Sleep pod 发送请求给服务 HelloWorld

从 cluster2 中的 Sleep pod 发送请求给服务 HelloWorld

4.3 验证从网关访问

通过网关访问服务端Helloworld

创建virtualservice、gateway等istio资源,配置清单如下

复制代码
# helloworld-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: helloworld-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
    - "*"
  gateways:
    - helloworld-gateway
  http:
    - match:
        - uri:
            exact: /hello
      route:
        - destination:
            host: helloworld
            port:
              number: 5000

注意: 两个集群都需要应用该配置

访问效果如下:

4.3 验证地域负载均衡

对流量进行更精细的控制,将 region1 -> zone1region1 -> zone2 两个地区的权重分别为 80% 和 20%,使用 DestinationRule 来配置权重分布

复制代码
# locality-lb-weight.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: helloworld
  namespace: sample
spec:
  host: helloworld.sample.svc.cluster.local
  trafficPolicy:
    connectionPool:
      http:
        maxRequestsPerConnection: 1
    loadBalancer:
      simple: ROUND_ROBIN
      localityLbSetting:
        enabled: true
        distribute:
          - from: region1/*
            to:
              "region1/*": 80
              "region2/*": 20
          - from: region2/*
            to:
              "region2/*": 80
              "region1/*": 20
    outlierDetection:
      consecutive5xxErrors: 1
      interval: 1s
      baseEjectionTime: 1m

注意: 两个集群都需要应用该配置

从 cluster1 中通过网关发送请求给服务 HelloWorld


从 cluster2中通过网关发送请求给服务 HelloWorld

4.4 验证地域故障转移

当多个地区/区域部署多个服务实例时,如果某个地区/区域的服务实例不可用,可以将流量转移到其他地区/区域的服务实例上,实现地域故障转移,这样就可以保证服务的高可用性。

复制代码
# locality-lb-failover.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: helloworld
  namespace: sample
spec:
  host: helloworld.sample.svc.cluster.local
  trafficPolicy:
    connectionPool:
      http:
        maxRequestsPerConnection: 1 # 关闭 HTTP Keep-Alive,强制每个HTTP请求使用一个新连接的策略
    loadBalancer:
      simple: ROUND_ROBIN
      localityLbSetting:  # 地域负载均衡配置,开启异常点检测后,默认开启。
        enabled: true     
        failover:         # 地域故障转移策略
          - from: region1  
            to: region2
          - from: region2
            to: region1
    outlierDetection:
      consecutive5xxErrors: 1 # 连续 1 次 5xx 错误
      interval: 1s # 检测间隔 1s
      baseEjectionTime: 1m # 基础驱逐时间 1m

注意: 两个集群都需要应用该配置

从 cluster1 中通过网关发送请求给服务 HelloWorld

模拟故障,手动将cluster1集群中Helloworld V1版本设置故障

再次访问,故障检测生效,触发故障转移,并验证响应中的 version 始终为 v2,也就是说我们访问的是 region2 的 helloworld 服务,这样就实现了地域故障转移。

故障转移的前提是当前region内,所有实例都不可用时,才会转移到到目前region,否则流量还会发往当前region的其他可用实例。

五 备注

参考文献如下:

  1. istio开源社区(跨网络多主架构的安装说明): https://istio.io/latest/zh/docs/setup/install/multicluster/multi-primary_multi-network/

  2. kind安装集群脚本参考: https://github.com/cnych/multi-cluster-istio-kind/tree/main/kind-create

  3. 多集群证书管理参考:https://istio.io/latest/zh/docs/tasks/security/cert-management/plugin-ca-cert/

点击关注,第一时间了解华为云新鲜技术~

相关推荐
是垚不是土1 天前
Istio流量镜像测试
运维·kubernetes·云计算·istio
莱特昂2 天前
virtualBox部署minikube+istio
云原生·istio
牛角上的男孩2 天前
部署istio应用未能产生Envoy sidecar代理
云原生·istio
牛角上的男孩2 天前
部署Prometheus、Grafana、Zipkin、Kiali监控度量Istio
grafana·prometheus·istio
牛角上的男孩8 天前
Istio和Kubernetes版本对应关系
kubernetes·istio
Hello.Reader8 天前
Istio 服务网格深度解析
云原生·istio
风车带走过往11 天前
微服务网格Istio介绍
微服务·架构·istio
是垚不是土11 天前
云原生Istio基础
云原生·istio
Charles Yan14 天前
K8S如何基于Istio重新实现微服务
k8s·istio·1024程序员节
CSH05616 天前
envoyFilter导致的webSockets协议无法正常工作
istio