在 GKE 上设置多集群网格
本指南介绍如何使用 Mesh CA 或 Istio CA 并启用跨集群负载均衡您可以轻松扩展此流程,将任意数量的集群整合到网格中。
多集群 Cloud Service Mesh 配置可以满足一些重要企业场景,例如扩缩、位置和隔离。如需了解详情,请参阅多集群用例。
前提条件
本指南假定您有两个或多个满足以下要求的 Google Cloud GKE 集群:
使用
asmcli install
在集群上安装了 Cloud Service Mesh 1.11 或更高版本。您需要asmcli
、istioctl
工具以及asmcli
下载到您运行asmcli install
时在--output_dir
中指定的目录的示例。如果需要设置,请按照安装依赖工具并验证集群中的步骤完成以下任务:网格中的集群必须在所有 Pod 之间建立连接,然后才能 配置 Cloud Service Mesh此外,如果您要加入的集群不在同一个项目中,则它们必须向同一舰队宿主项目注册,并且这些集群必须位于同一网络的共享 VPC配置中。我们还建议您使用一个项目来托管共享 VPC,并使用两个服务项目来创建集群。如需了解详情,请参阅通过共享 VPC 设置集群。
若您使用 Istio CA,请对这两个集群使用相同的自定义根证书。
如果您的 Cloud Service Mesh 是基于专用集群构建的,我们建议您 创建单个子网 否则,您必须确保:
- 控制层面可以通过集群专用 IP 访问远程专用集群控制层面。
- 您可以将调用控制层面的 IP 范围添加到远程专用集群的已获授权的网络。如需了解详情,请参阅在专用集群之间配置端点发现。
该 API 服务器必须可供 Cloud Service Mesh 的其他实例访问 部署在多集群网格中的控制平面
设置项目和集群变量
为项目 ID、集群可用区或区域、集群名称和上下文创建以下环境变量。
export PROJECT_1=PROJECT_ID_1 export LOCATION_1=CLUSTER_LOCATION_1 export CLUSTER_1=CLUSTER_NAME_1 export CTX_1="gke_${PROJECT_1}_${LOCATION_1}_${CLUSTER_1}" export PROJECT_2=PROJECT_ID_2 export LOCATION_2=CLUSTER_LOCATION_2 export CLUSTER_2=CLUSTER_NAME_2 export CTX_2="gke_${PROJECT_2}_${LOCATION_2}_${CLUSTER_2}"
如果这些集群是新创建的集群,请确保使用以下
gcloud
命令为每个集群提取凭据,否则其关联的context
将在本指南的后续步骤中不可用。这些命令取决于您的集群类型(区域级或可用区级):
区域级
gcloud container clusters get-credentials ${CLUSTER_1} --region ${LOCATION_1} gcloud container clusters get-credentials ${CLUSTER_2} --region ${LOCATION_2}
可用区级
gcloud container clusters get-credentials ${CLUSTER_1} --zone ${LOCATION_1} gcloud container clusters get-credentials ${CLUSTER_2} --zone ${LOCATION_2}
创建防火墙规则
在某些情况下,您需要创建防火墙规则以允许跨集群流量。例如,在以下情况下,您需要创建防火墙规则:
- 网格中的集群使用不同的子网。
- pod 会打开除 443 和 15002 以外的端口。
GKE 会自动向每个节点添加防火墙规则,以允许同一子网内的流量。如果网格包含多个子网,您必须明确设置防火墙规则以允许跨子网流量。您必须为每个子网添加新的防火墙规则,以允许所有传入流量的来源 IP CIDR 地址块和目标端口。
以下说明允许项目中所有集群之间或仅 $CLUSTER_1
和 $CLUSTER_2
之间进行通信。
收集关于集群的网络的信息。
所有项目集群
如果集群位于同一项目中,则可以使用以下命令,允许在项目中的所有集群之间进行通信。如果项目中有您不想公开的集群,请使用特定集群标签页中的命令。
function join_by { local IFS="$1"; shift; echo "$*"; } ALL_CLUSTER_CIDRS=$(gcloud container clusters list --project $PROJECT_1 --format='value(clusterIpv4Cidr)' | sort | uniq) ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}")) ALL_CLUSTER_NETTAGS=$(gcloud compute instances list --project $PROJECT_1 --format='value(tags.items.[0])' | sort | uniq) ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
特定集群
以下命令允许
$CLUSTER_1
和$CLUSTER_2
之间进行通信,并且不会公开项目中的其他集群。function join_by { local IFS="$1"; shift; echo "$*"; } ALL_CLUSTER_CIDRS=$(for P in $PROJECT_1 $PROJECT_2; do gcloud --project $P container clusters list --filter="name:($CLUSTER_1,$CLUSTER_2)" --format='value(clusterIpv4Cidr)'; done | sort | uniq) ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}")) ALL_CLUSTER_NETTAGS=$(for P in $PROJECT_1 $PROJECT_2; do gcloud --project $P compute instances list --filter="name:($CLUSTER_1,$CLUSTER_2)" --format='value(tags.items.[0])' ; done | sort | uniq) ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
创建防火墙规则。
GKE
gcloud compute firewall-rules create istio-multicluster-pods \ --allow=tcp,udp,icmp,esp,ah,sctp \ --direction=INGRESS \ --priority=900 \ --source-ranges="${ALL_CLUSTER_CIDRS}" \ --target-tags="${ALL_CLUSTER_NETTAGS}" --quiet \ --network=YOUR_NETWORK
Autopilot
TAGS="" for CLUSTER in ${CLUSTER_1} ${CLUSTER_2} do TAGS+=$(gcloud compute firewall-rules list --filter="Name:$CLUSTER*" --format="value(targetTags)" | uniq) && TAGS+="," done TAGS=${TAGS::-1} echo "Network tags for pod ranges are $TAGS" gcloud compute firewall-rules create asm-multicluster-pods \ --allow=tcp,udp,icmp,esp,ah,sctp \ --network=gke-cluster-vpc \ --direction=INGRESS \ --priority=900 --network=VPC_NAME \ --source-ranges="${ALL_CLUSTER_CIDRS}" \ --target-tags=$TAGS
配置端点发现
配置端点发现所需的步骤取决于您是要在舰队中的各集群之间应用声明式 API来启用该功能,还是要针对公共集群或专用集群手动启用该功能。
使用声明式 API 启用集群之间的端点发现(预览版)
您可以通过在 asm-options
configmap 中应用配置 "multicluster_mode":"connected"
来在舰队中的各集群之间启用端点发现。在同一舰队中启用了此配置的集群相互之间会自动启用跨集群服务发现。
此方法可用于所有平台上的托管式 Cloud Service Mesh 安装 发布渠道。在继续操作之前,您必须先创建防火墙规则。
对于多个项目,您必须在修订版本的 meshConfig
中手动将 FLEET_PROJECT_ID.svc.id.goog
添加到 trustDomainAliases
(如果它尚不存在)。
启用
如果集群中已存在 asm-options
configmap,请为集群启用端点发现:
kubectl patch configmap/asm-options -n istio-system --type merge -p '{"data":{"multicluster_mode":"connected"}}'
如果集群中不存在 asm-options
configmap,请使用关联数据创建它,并为集群启用端点发现:
kubectl --context ${CTX_1} create configmap asm-options -n istio-system --from-file <(echo '{"data":{"multicluster_mode":"connected"}}')
停用
停用集群的端点发现:
kubectl patch configmap/asm-options -n istio-system --type merge -p '{"data":{"multicluster_mode":"manual"}}'
如果您从集群中取消注册集群,但不停用端点发现,则 Secret 可能会保留在集群中。您必须手动清理所有剩余的 Secret。
运行以下命令以查找需要清理的 Secret:
kubectl get secrets -n istio-system -l istio.io/owned-by=mesh.googleapis.com,istio/multiCluster=true
删除每个 Secret:
kubectl delete secret SECRET_NAME
对每个剩余的 Secret 重复此步骤。
在公共集群之间配置端点发现
如需在 GKE 集群之间配置端点发现,可以运行 asmcli create-mesh
。此命令会执行以下操作:
- 将所有集群注册到同一舰队。
- 将网格配置为信任舰队工作负载身份。
- 创建远程 Secret。
您可以指定每个集群的 URI 或 kubeconfig 文件的路径。
集群 URI
在以下命令中,将 FLEET_PROJECT_ID
替换为舰队宿主项目的项目 ID,将集群 URI 替换为每个集群的集群名称、可用区或区域以及项目 ID。此示例仅显示两个集群,但您可以运行命令以在其他集群上启用端点发现,具体取决于您可以添加到舰队的最大允许集群数。
./asmcli create-mesh \
FLEET_PROJECT_ID \
${PROJECT_1}/${LOCATION_1}/${CLUSTER_1} \
${PROJECT_2}/${LOCATION_2}/${CLUSTER_2}
kubeconfig 文件
在以下命令中,将 FLEET_PROJECT_ID
替换为舰队宿主项目的项目 ID,将 PATH_TO_KUBECONFIG
替换为每个 kubeconfig
文件的路径。此示例仅显示两个集群,但您可以运行命令以在其他集群上启用端点发现,具体取决于您可以添加到舰队的最大允许集群数。
./asmcli create-mesh \
FLEET_PROJECT_ID \
PATH_TO_KUBECONFIG_1 \
PATH_TO_KUBECONFIG_2
在专用集群之间配置端点发现
配置远程 Secret 以使有权访问一个集群的 API 服务器能够访问另一个集群的 Cloud Service Mesh 控制平面。这些命令取决于您的 Cloud Service Mesh 类型(集群内或代管式):
A. 对于集群内 Cloud Service Mesh,您必须改为配置专用 IP 因为公共 IP 无法访问:
PRIV_IP=`gcloud container clusters describe "${CLUSTER_1}" --project "${PROJECT_1}" \ --zone "${LOCATION_1}" --format "value(privateClusterConfig.privateEndpoint)"` ./istioctl x create-remote-secret --context=${CTX_1} --name=${CLUSTER_1} --server=https://${PRIV_IP} > ${CTX_1}.secret
PRIV_IP=`gcloud container clusters describe "${CLUSTER_2}" --project "${PROJECT_2}" \ --zone "${LOCATION_2}" --format "value(privateClusterConfig.privateEndpoint)"` ./istioctl x create-remote-secret --context=${CTX_2} --name=${CLUSTER_2} --server=https://${PRIV_IP} > ${CTX_2}.secret
B. 对于托管式 Cloud Service Mesh:
PUBLIC_IP=`gcloud container clusters describe "${CLUSTER_1}" --project "${PROJECT_1}" \ --zone "${LOCATION_1}" --format "value(privateClusterConfig.publicEndpoint)"` ./istioctl x create-remote-secret --context=${CTX_1} --name=${CLUSTER_1} --server=https://${PUBLIC_IP} > ${CTX_1}.secret
PUBLIC_IP=`gcloud container clusters describe "${CLUSTER_2}" --project "${PROJECT_2}" \ --zone "${LOCATION_2}" --format "value(privateClusterConfig.publicEndpoint)"` ./istioctl x create-remote-secret --context=${CTX_2} --name=${CLUSTER_2} --server=https://${PUBLIC_IP} > ${CTX_2}.secret
将新 Secret 应用到集群中:
kubectl apply -f ${CTX_1}.secret --context=${CTX_2}
kubectl apply -f ${CTX_2}.secret --context=${CTX_1}
为专用集群配置授权网络
仅当您的网格符合以下所有条件时,才应遵循此部分:
部署多个专用集群时,Cloud Service Mesh 控制平面 每个集群都需要调用 远程集群。要允许流量,您需要将调用集群中的 pod 地址范围添加到远程集群的授权网络。
获取每个集群的 pod IP CIDR 地址块:
POD_IP_CIDR_1=`gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \ --format "value(ipAllocationPolicy.clusterIpv4CidrBlock)"`
POD_IP_CIDR_2=`gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \ --format "value(ipAllocationPolicy.clusterIpv4CidrBlock)"`
将 Kubernetes 集群 pod IP CIDR 地址块添加到远程集群:
EXISTING_CIDR_1=`gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \ --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"` gcloud container clusters update ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \ --enable-master-authorized-networks \ --master-authorized-networks ${POD_IP_CIDR_2},${EXISTING_CIDR_1//;/,}
EXISTING_CIDR_2=`gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \ --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"` gcloud container clusters update ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \ --enable-master-authorized-networks \ --master-authorized-networks ${POD_IP_CIDR_1},${EXISTING_CIDR_2//;/,}
如需了解详情,请参阅创建具有授权网络的集群。
验证授权网络是否已更新:
gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \ --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"
gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \ --format "value(masterAuthorizedNetworksConfig.cidrBlocks.cidrBlock)"
启用控制平面全球访问权限
仅当您的网格符合以下所有条件时,才应遵循此部分:
- 您使用的是专用集群。
- 网格中的集群使用不同的区域。
您必须启用控制平面全球访问权限 以允许每个集群中的 Cloud Service Mesh 控制平面调用 远程集群的 GKE 控制平面。
启用控制平面全球访问权限:
gcloud container clusters update ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1} \ --enable-master-global-access
gcloud container clusters update ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2} \ --enable-master-global-access
验证控制平面是否已启用访问权限:
gcloud container clusters describe ${CLUSTER_1} --project ${PROJECT_1} --zone ${LOCATION_1}
gcloud container clusters describe ${CLUSTER_2} --project ${PROJECT_2} --zone ${LOCATION_2}
输出中的
privateClusterConfig
部分显示masterGlobalAccessConfig
的状态。
验证多集群连接
本部分介绍如何将示例 HelloWorld
和 Sleep
服务部署到多集群环境,以验证跨集群负载均衡可正常工作。
设置示例目录的变量
前往
asmcli
的下载位置,然后运行以下命令以设置ASM_VERSION
export ASM_VERSION="$(./asmcli --version)"
将工作文件夹设置为用于验证跨集群负载均衡是否正常工作的示例。这些示例位于您在
asmcli install
命令中指定的--output_dir
目录的子目录中。在以下命令中,将OUTPUT_DIR
更改为您在--output_dir
中指定的目录。export SAMPLES_DIR=OUTPUT_DIR/istio-${ASM_VERSION%+*}
启用边车代理注入
找到修订版本标签值,您将在后面的步骤中用到该值。步骤 取决于您的 Cloud Service Mesh 类型(代管式或集群内)。
代管式
使用以下命令找到修订版本标签,您将在后面的步骤中用到该标签。
kubectl get controlplanerevision -n istio-system
输出类似于以下内容:
NAME RECONCILED STALLED AGE asm-managed-rapid True False 89d
在输出中的
NAME
列下,记下修订版本标签的值。在此示例中,该值为asm-managed-rapid
。在下一部分的步骤中使用该修订版本值。集群内
使用以下命令找到修订版本标签,您将在后面的步骤中用到该标签。
kubectl -n istio-system get pods -l app=istiod --show-labels
输出类似于以下内容:
NAME READY STATUS RESTARTS AGE LABELS istiod-asm-173-3-5788d57586-bljj4 1/1 Running 0 23h app=istiod,istio.io/rev=asm-173-3,istio=istiod,pod-template-hash=5788d57586 istiod-asm-173-3-5788d57586-vsklm 1/1 Running 1 23h app=istiod,istio.io/rev=asm-173-3,istio=istiod,pod-template-hash=5788d57586
在输出中的
LABELS
列下,记下istiod
修订版本标签的值,该值位于前缀istio.io/rev=
之后。在此示例中,该值为asm-173-3
。在下一部分的步骤中使用该修订版本值。
安装 HelloWorld 服务
在每个集群中创建示例命名空间和服务定义。 在以下命令中,将 REVISION 替换为您在上一步中记下的
istiod
修订版本标签。for CTX in ${CTX_1} ${CTX_2} do kubectl create --context=${CTX} namespace sample kubectl label --context=${CTX} namespace sample \ istio-injection- istio.io/rev=REVISION --overwrite done
其中,REVISION 是您之前记下的
istiod
修订版本标签。输出如下:
label "istio-injection" not found. namespace/sample labeled
您可以安全地忽略
label "istio-injection" not found.
在这两个集群中创建 HelloWorld 服务:
kubectl create --context=${CTX_1} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l service=helloworld -n sample
kubectl create --context=${CTX_2} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l service=helloworld -n sample
将 HelloWorld v1 和 v2 部署到每个集群
将
HelloWorld v1
部署到CLUSTER_1
,并将v2
部署到CLUSTER_2
,这可帮助以后验证跨集群负载均衡:kubectl create --context=${CTX_1} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l version=v1 -n sample
kubectl create --context=${CTX_2} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l version=v2 -n sample
使用以下命令确认
HelloWorld v1
和v2
正在运行。验证输出是否如下所示:kubectl get pod --context=${CTX_1} -n sample
NAME READY STATUS RESTARTS AGE helloworld-v1-86f77cd7bd-cpxhv 2/2 Running 0 40s
kubectl get pod --context=${CTX_2} -n sample
NAME READY STATUS RESTARTS AGE helloworld-v2-758dd55874-6x4t8 2/2 Running 0 40s
部署 Sleep 服务
将
Sleep
服务部署到这两个集群。此 pod 将出于演示目的生成虚假网络流量:for CTX in ${CTX_1} ${CTX_2} do kubectl apply --context=${CTX} \ -f ${SAMPLES_DIR}/samples/sleep/sleep.yaml -n sample done
等待在每个集群中启动
Sleep
服务。验证输出是否如下所示:kubectl get pod --context=${CTX_1} -n sample -l app=sleep
NAME READY STATUS RESTARTS AGE sleep-754684654f-n6bzf 2/2 Running 0 5s
kubectl get pod --context=${CTX_2} -n sample -l app=sleep
NAME READY STATUS RESTARTS AGE sleep-754684654f-dzl9j 2/2 Running 0 5s
验证跨集群负载均衡
多次调用 HelloWorld
服务,并检查输出以验证来自 v1 和 v2 的交替回复:
调用
HelloWorld
服务:kubectl exec --context="${CTX_1}" -n sample -c sleep \ "$(kubectl get pod --context="${CTX_1}" -n sample -l \ app=sleep -o jsonpath='{.items[0].metadata.name}')" \ -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
输出类似于如下所示内容:
Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8 Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv ...
再次调用
HelloWorld
服务:kubectl exec --context="${CTX_2}" -n sample -c sleep \ "$(kubectl get pod --context="${CTX_2}" -n sample -l \ app=sleep -o jsonpath='{.items[0].metadata.name}')" \ -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
输出类似于如下所示内容:
Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8 Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv ...
恭喜,您已成功验证负载均衡的多集群 Cloud Service Mesh!
清理 HelloWorld 服务
完成负载均衡验证后,请从集群中移除 HelloWorld
和 Sleep
服务。
kubectl delete ns sample --context ${CTX_1} kubectl delete ns sample --context ${CTX_2}