部署多集群网关以实现加权流量拆分

本文档将指导您在两个 GKE 集群中对示例 store 应用进行蓝绿部署。蓝绿部署是一种有效的策略,可帮助您以最低风险将应用迁移到新的 GKE 集群。通过将流量从当前集群(蓝)逐步转移到新集群(绿),您可以在生产环境中验证新环境,然后再完全割接。

多集群网关提供了一种强大的方式来管理部署在多个 GKE 集群中的服务的流量。借助 Google 的全球负载均衡基础架构,您可以为应用创建一个统一的入口点,从而简化管理并提高可靠性。

在本教程中,您将使用示例 store 应用来模拟以下现实场景:在线购物服务由单独的团队拥有和运营,并且跨共享 GKE 集群舰队进行部署。

准备工作

多集群 Gateway 需要一些环境准备才能部署。在继续操作之前,请按照为多集群 Gateway 准备环境 中的步骤操作:

  1. 部署 GKE 集群。

  2. 向舰队注册集群(如果尚未注册)。

  3. 启用多集群 Service 和多集群 Gateway 控制器。

最后,请先查看 GKE Gateway Controller 的限制和已知问题,然后才可在环境中使用该控制器。

使用 Gateway 进行蓝绿多集群路由

gke-l7-global-external-managed-*gke-l7-regional-external-managed-*gke-l7-rilb-* GatewayClass 具有许多高级流量路由功能,包括流量分配、标头匹配、标头操纵、流量镜像等。在此示例中,您将演示如何使用基于权重的流量拆分来明确控制两个 GKE 集群中的流量比例。

此示例介绍了服务所有者在将应用迁移到或扩展到新 GKE 集群时将采取的一些实际步骤。蓝绿部署的目标是通过多个确认新集群正常运行的验证步骤来降低风险。本示例介绍了四个部署阶段:

  1. 100% - 基于标头的 Canary 版使用 HTTP 标头路由仅发送测试或合成流量新集群。
  2. 100% - 镜像流量将用户流量镜像到 Canary 版集群。这会通过将所有用户流量复制到此集群来测试 Canary 集群的容量。
  3. 90% - 10%对 10% 的流量进行 Canary 拆分,以缓慢地将新集群公开给实时流量。
  4. 0%-100%完全切换到新集群;如果发现任何错误,可切换回来。

两个 GKE 集群之间的蓝绿流量分配

此示例与前一个示例类似,只是改为部署内部多集群 Gateway。这将部署内部应用负载均衡器,该负载均衡器只能从 VPC 内部以私密方式访问。您将使用在先前步骤中部署的集群和同一应用,但需要通过其他 Gateway 部署它们。

前提条件

以下示例基于部署外部多集群 Gateway 中的一些步骤。在继续此示例之前,请确保您已完成以下步骤:

  1. 为多集群 Gateway 准备环境

  2. 部署演示应用

    此示例使用您设置的 gke-west-1gke-west-2 集群。这些集群位于同一区域,因为 gke-l7-rilb-mc GatewayClass 是区域级的,仅支持同一区域内的集群后端。

  3. 部署每个集群所需的 Service 和 ServiceExport。如果您在上一个示例中部署了 Service 和 ServiceExport,则其中一些已经部署。

    kubectl apply --context gke-west-1 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/multi-cluster-gateway/store-west-1-service.yaml
    kubectl apply --context gke-west-2 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/multi-cluster-gateway/store-west-2-service.yaml
    

    它会为每个集群部署一组类似的资源:

    service/store created
    serviceexport.net.gke.io/store created
    service/store-west-2 created
    serviceexport.net.gke.io/store-west-2 created
    

配置代理专用子网

如果尚未为要部署内部 Gateway 的每个区域配置代理专用子网,请执行此操作。此子网用于向负载均衡器代理提供内部 IP 地址,并且必须采用以下配置:将 --purpose 设置为仅限 REGIONAL_MANAGED_PROXY

您必须先创建代理专用子网,然后才能创建用于管理内部应用负载均衡器的 Gateway。在使用内部应用负载均衡器的虚拟私有云 (VPC) 网络中,每个区域都必须具有一个代理专用子网。

gcloud compute networks subnets create 命令会创建代理专用子网。

gcloud compute networks subnets create SUBNET_NAME \
    --purpose=REGIONAL_MANAGED_PROXY \
    --role=ACTIVE \
    --region=REGION \
    --network=VPC_NETWORK_NAME \
    --range=CIDR_RANGE

替换以下内容:

  • SUBNET_NAME:代理专用子网的名称。
  • REGION:代理专用子网的区域。
  • VPC_NETWORK_NAME:包含子网的 VPC 网络的名称。
  • CIDR_RANGE:子网的主要 IP 地址范围。使用的子网掩码长度不得超过 /26,以确保至少有 64 个 IP 地址可用于该区域中的代理。建议的子网掩码为 /23

部署网关

以下 Gateway 是通过 gke-l7-rilb-mc GatewayClass 创建的,这是一个区域级内部 Gateway,只能定位同一区域中的目标 GKE 集群。

  1. 将以下 Gateway 清单应用于配置集群(在此示例中为 gke-west-1):

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: Gateway
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-http
      namespace: store
    spec:
      gatewayClassName: gke-l7-rilb-mc
      listeners:
      - name: http
        protocol: HTTP
        port: 80
        allowedRoutes:
          kinds:
          - kind: HTTPRoute
    EOF
    
  2. 验证该 Gateway 是否已启动。您可以使用以下命令仅过滤来自该 Gateway 的事件:

    kubectl get events --field-selector involvedObject.kind=Gateway,involvedObject.name=internal-http --context=gke-west-1 --namespace store
    

    如果输出类似于以下内容,则表示 Gateway 部署成功:

    LAST SEEN   TYPE     REASON   OBJECT                  MESSAGE
    5m18s       Normal   ADD      gateway/internal-http   store/internal-http
    3m44s       Normal   UPDATE   gateway/internal-http   store/internal-http
    3m9s        Normal   SYNC     gateway/internal-http   SYNC on store/internal-http was a success
    

基于标头的 Canary

借助基于标头的 Canary 测试,服务所有者可以匹配来自真实用户的合成测试流量。通过这种方法可以轻松地验证应用的基本网络是否正常运行,而无需向用户公开。

  1. 将以下 HTTPRoute 清单应用于配置集群(在此示例中为 gke-west-1):

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
      # Matches for env=canary and sends it to store-west-2 ServiceImport
      - matches:
        - headers:
          - name: env
            value: canary
        backendRefs:
          - group: net.gke.io
            kind: ServiceImport
            name: store-west-2
            port: 8080
      # All other traffic goes to store-west-1 ServiceImport
      - backendRefs:
        - group: net.gke.io
          kind: ServiceImport
          name: store-west-1
          port: 8080
    EOF
    

    部署后,此 HTTPRoute 将配置以下路由行为:

    • store.example.internal 的内部请求(不使用 env: canary HTTP 标头)被路由到 gke-west-1 集群上的 store Pod
    • store.example.internal 的内部请求(使用 env: canary HTTP 标头)被路由到 gke-west-2 集群上的 store Pod

    HTTPRoute 支持根据 HTTP 标头路由到不同的集群

    通过将流量发送到 Gateway IP 地址来验证 HTTPRoute 是否正常运行。

  2. internal-http 检索内部 IP 地址。

    kubectl get gateways.gateway.networking.k8s.io internal-http -o=jsonpath="{.status.addresses[0].value}" --context gke-west-1 --namespace store
    

    将以下步骤中的 VIP 替换为您收到的 IP 地址作为输出。

  3. 使用 env: canary HTTP 标头向 Gateway 发送请求。这将确认流量正路由到 gke-west-2。使用 GKE 集群所在 VPC 中的专用客户端,以确认请求是否正确路由。以下命令必须在对 Gateway IP 地址具有专用访问权限的机器上运行,否则将不起作用。

    curl -H "host: store.example.internal" -H "env: canary" http://VIP
    

    输出会确认请求由 gke-west-2 集群中的 Pod 处理:

    {
        "cluster_name": "gke-west-2", 
        "host_header": "store.example.internal",
        "node_name": "gke-gke-west-2-default-pool-4cde1f72-m82p.c.agmsb-k8s.internal",
        "pod_name": "store-5f5b954888-9kdb5",
        "pod_name_emoji": "😂",
        "project_id": "agmsb-k8s",
        "timestamp": "2021-05-31T01:21:55",
        "zone": "us-west1-a"
    }
    

流量镜像

此阶段会将流量发送到预期集群,但还会将流量镜像到 Canary 集群。

使用镜像有助于确定流量负载如何影响应用性能,且不会以任何方式影响对客户端的响应。这可能是所有类型发布都需要的,但在发布可能会影响性能或负载的重大更改时非常有用。

  1. 将以下 HTTPRoute 清单应用于配置集群(在此示例中为 gke-west-1):

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
      # Sends all traffic to store-west-1 ServiceImport
      - backendRefs:
        - name: store-west-1
          group: net.gke.io
          kind: ServiceImport
          port: 8080
        # Also mirrors all traffic to store-west-2 ServiceImport
        filters:
        - type: RequestMirror
          requestMirror:
            backendRef:
              group: net.gke.io
              kind: ServiceImport
              name: store-west-2
              port: 8080
    EOF
    
  2. 使用您的专用客户端向 internal-http Gateway 发送请求。使用 /mirror 路径,以便您可以在后续步骤中在应用日志中唯一标识此请求。

    curl -H "host: store.example.internal" http://VIP/mirror
    
  3. 输出会确认客户端收到来自 gke-west-1 集群中的 Pod 的响应:

    {
        "cluster_name": "gke-west-1", 
        "host_header": "store.example.internal",
        "node_name": "gke-gke-west-1-default-pool-65059399-ssfq.c.agmsb-k8s.internal",
        "pod_name": "store-5f5b954888-brg5w",
        "pod_name_emoji": "🎖",
        "project_id": "agmsb-k8s",
        "timestamp": "2021-05-31T01:24:51",
        "zone": "us-west1-a"
    }
    

    这可确认主集群是否正在响应流量。您仍然需要确认要迁移到的集群正在接收镜像流量。

  4. 检查 gke-west-2 集群上的 store pod 的应用日志。日志应确认 Pod 已收到来自负载均衡器的镜像流量。

    kubectl logs deployment/store --context gke-west-2 -n store | grep /mirror
    
  5. 此输出确认 gke-west-2 集群上的 Pod 也收到了相同的请求,但它们对这些请求的响应不会发送回客户端。日志中显示的 IP 地址是与 Pod 通信的负载均衡器内部 IP 地址的 IP 地址。

    Found 2 pods, using pod/store-5c65bdf74f-vpqbs
    [2023-10-12 21:05:20,805] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:20] "GET /mirror HTTP/1.1" 200 -
    [2023-10-12 21:05:27,158] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:27] "GET /mirror HTTP/1.1" 200 -
    [2023-10-12 21:05:27,805] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:27] "GET /mirror HTTP/1.1" 200 -
    

流量分配

流量拆分是发布新代码或安全地部署到新环境的最常用方法之一。服务所有者设置了发送到 Canary 后端的流量的明确百分比,该百分比通常只占总流量的很小一部分,以便可以在对真实用户请求具有可接受的风险量的情况下确定推出是否成功。

通过拆分少数流量,服务所有者可以检查应用的运行状况和响应。如果所有信号看起来都运行状况良好,则可以继续进行完整割接。

  1. 将以下 HTTPRoute 清单应用于配置集群(在此示例中为 gke-west-1):

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
      - backendRefs:
        # 90% of traffic to store-west-1 ServiceImport
        - name: store-west-1
          group: net.gke.io
          kind: ServiceImport
          port: 8080
          weight: 90
        # 10% of traffic to store-west-2 ServiceImport
        - name: store-west-2
          group: net.gke.io
          kind: ServiceImport
          port: 8080
          weight: 10
    EOF
    
  2. 使用您的专用客户端向 internal- http Gateway 发送连续 curl 请求。

    while true; do curl -H "host: store.example.internal" -s VIP | grep "cluster_name"; sleep 1; done
    

    输出将类似于以下内容,表示发生了 90/10 流量拆分。

    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    ...
    

流量切换

蓝绿迁移的最后一个阶段是完全切换到新集群并移除旧集群。如果服务所有者实际上将第二个集群添加到现有集群,则最后一步是不同的,因为最后一步会产生流量进入这两个集群。在这种情况下,建议使用单个 store ServiceImport,其中包含来自 gke-west-1gke-west-2 集群的 Pod。这样,负载均衡器就可以根据距离、运行状况和容量来决定“活跃-活跃”应用的流量去向。

  1. 将以下 HTTPRoute 清单应用于配置集群(在此示例中为 gke-west-1):

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
        - backendRefs:
          # No traffic to the store-west-1 ServiceImport
          - name: store-west-1
            group: net.gke.io
            kind: ServiceImport
            port: 8080
            weight: 0
          # All traffic to the store-west-2 ServiceImport
          - name: store-west-2
            group: net.gke.io
            kind: ServiceImport
            port: 8080
            weight: 100
    EOF
    
  2. 使用您的专用客户端向 internal- http Gateway 发送连续 curl 请求。

    while true; do curl -H "host: store.example.internal" -s VIP | grep "cluster_name"; sleep 1; done
    

    输出将与此类似,表明所有流量现在都会转到 gke-west-2

    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-2",
    ...
    

最后一步完成从一个 GKE 集群到另一个 GKE 集群的完全蓝绿应用迁移。

清理

完成本文档中的练习后,请按照以下步骤移除资源,防止您的账号产生不必要的费用:

  1. 删除集群

  2. 如果不需要为其他目的注册集群,请从舰队中取消注册集群

  3. 停用 multiclusterservicediscovery 功能:

    gcloud container fleet multi-cluster-services disable
    
  4. 停用 Multi Cluster Ingress:

    gcloud container fleet ingress disable
    
  5. 停用 API:

    gcloud services disable \
        multiclusterservicediscovery.googleapis.com \
        multiclusteringress.googleapis.com \
        trafficdirector.googleapis.com \
        --project=PROJECT_ID
    

问题排查

没有健康的上行

具体情况:

创建网关但无法访问后端服务(503 响应代码)时,可能会出现以下问题:

no healthy upstream

原因:

此错误消息表示健康检查探测器找不到健康状况良好的后端服务。您的后端服务可能处于健康状况良好状态,但您可能需要自定义健康检查。

临时解决方法:

如需解决此问题,请使用 HealthCheckPolicy 根据应用的要求自定义监控状况检查(例如 /health)。

后续步骤