本文分享自华为云社区《基于istio实现多集群流量治理》,作者: 可以交个朋友。
一 背景
对多云、混合云等异构基础设施的服务治理是 Istio 重点支持的场景之一。为了提高服务的可用性,避免厂商锁定,企业通常会选择将应用部署在多个地域的多个集群,甚至多云、混合云等多种云环境下,多集群的方案逐步成为企业应用部署的最佳选择。因此越来越多的用户对跨集群的服务治理有着强烈的需求,在此背景下 Istio 作为 ServiceMesh 领域的事实标准,推出了多种多集群管理方案。
二 简介
目前 Istio 支持 4 种多集群模型。
扁平网络单控制面模型
扁平网络多控制面模型
非扁平网络单控制面模型
非扁平网络多控制面模型
多集群的单控制面模型是指多个集群共用同一套 Istio 控制面,多集群的多控制面模型指每个集群都要独立使用一套 Istio 控制面,无论是单控制面还是多控制面模型,每套 Istio 控制面(istiod)都要连接所有集群的 Kube-apiserver,并且 List-Watch 获取所有集群的Service、Endpoint、Pod 、Node
,并控制面集群内或集群间的服务访问,但是只监听主集群的VirtualService、DestinationRule、Gateway
等 Istio API 对象。
根据集群间网络是否扁平,Istio 又对两种控制面模型进行了细分:
生产环境上在选择 Istio 多集群模型时,当然需要结合自己的实际场景来决定。如果集群之间的网络是扁平的,那么可以选择扁平网络模型,如果集群之间的网络是隔离的,那么可以选择非扁平网络模型。如果集群规模较小,那么可以选择单控制面模型,如果集群规模较大,那么可以选择多控制面模型。
本文档选择非扁平网络多控制面模型来进行安装说明:安装模型如下所示
非扁平网络多控制面模型有如下特点。
不同的集群不需要在一张大网下,即容器网络不需要三层打通,跨集群的服务访问通过Istio East-West Gateway
转发。
每个 kubernetes 集群的 Pod 地址范围与服务地址范围没有限制,可以与其他集群重叠,不同集群之间互不干扰
每个 Kubernetes 集群的 Sidecar 仅连接到本集群的 Istio 控制面,通信效率更高。
Istiod 只监听主集群的 Istio 配置,因此 VirtualService、DestinationRule、Gateway
等资源存在冗余复制问题
同一集群内部服务访问: 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 -> zone1
和 region1 -> 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 的其他可用实例。
五 备注
参考文献如下:
istio 开源社区(跨网络多主架构的安装说明): https://istio.io/latest/zh/docs/setup/install/multicluster/multi-primary_multi-network/
kind 安装集群脚本参考: https://github.com/cnych/multi-cluster-istio-kind/tree/main/kind-create
多集群证书管理参考:https://istio.io/latest/zh/docs/tasks/security/cert-management/plugin-ca-cert/
点击关注,第一时间了解华为云新鲜技术~
评论