本文档将指导您在两个 GKE 集群中对示例 store 应用进行蓝绿部署。蓝绿部署是一种有效的策略,可帮助您以最低风险将应用迁移到新的 GKE 集群。通过将流量从当前集群(蓝)逐步转移到新集群(绿),您可以在生产环境中验证新环境,然后再完全割接。
在本教程中,您将使用示例 store 应用来模拟以下现实场景:在线购物服务由单独的团队拥有和运营,并且跨共享 GKE 集群舰队进行部署。
准备工作
多集群 Gateway 需要一些环境准备才能部署。在继续操作之前,请按照为多集群 Gateway 准备环境 中的步骤操作:
最后,请先查看 GKE Gateway Controller 的限制和已知问题,然后才可在环境中使用该控制器。
使用 Gateway 进行蓝绿多集群路由
gke-l7-global-external-managed-*、gke-l7-regional-external-managed-* 和 gke-l7-rilb-* GatewayClass 具有许多高级流量路由功能,包括流量分配、标头匹配、标头操纵、流量镜像等。在此示例中,您将演示如何使用基于权重的流量拆分来明确控制两个 GKE 集群中的流量比例。
此示例介绍了服务所有者在将应用迁移到或扩展到新 GKE 集群时将采取的一些实际步骤。蓝绿部署的目标是通过多个确认新集群正常运行的验证步骤来降低风险。本示例介绍了四个部署阶段:
- 100% - 基于标头的 Canary 版:使用 HTTP 标头路由仅发送测试或合成流量新集群。
- 100% - 镜像流量:将用户流量镜像到 Canary 版集群。这会通过将所有用户流量复制到此集群来测试 Canary 集群的容量。
- 90% - 10%:对 10% 的流量进行 Canary 拆分,以缓慢地将新集群公开给实时流量。
- 0%-100%:完全切换到新集群;如果发现任何错误,可切换回来。
此示例与前一个示例类似,只是改为部署内部多集群 Gateway。这将部署内部应用负载均衡器,该负载均衡器只能从 VPC 内部以私密方式访问。您将使用在先前步骤中部署的集群和同一应用,但需要通过其他 Gateway 部署它们。
前提条件
以下示例基于部署外部多集群 Gateway 中的一些步骤。在继续此示例之前,请确保您已完成以下步骤:
-
此示例使用您设置的
gke-west-1和gke-west-2集群。这些集群位于同一区域,因为gke-l7-rilb-mcGatewayClass 是区域级的,仅支持同一区域内的集群后端。 部署每个集群所需的 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 集群。
将以下
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验证该 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 测试,服务所有者可以匹配来自真实用户的合成测试流量。通过这种方法可以轻松地验证应用的基本网络是否正常运行,而无需向用户公开。
将以下
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: canaryHTTP 标头)被路由到gke-west-1集群上的storePod - 对
store.example.internal的内部请求(使用env: canaryHTTP 标头)被路由到gke-west-2集群上的storePod
通过将流量发送到 Gateway IP 地址来验证 HTTPRoute 是否正常运行。
- 对
从
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 地址作为输出。
使用
env: canaryHTTP 标头向 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 集群。
使用镜像有助于确定流量负载如何影响应用性能,且不会以任何方式影响对客户端的响应。这可能是所有类型发布都需要的,但在发布可能会影响性能或负载的重大更改时非常有用。
将以下
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使用您的专用客户端向
internal-httpGateway 发送请求。使用/mirror路径,以便您可以在后续步骤中在应用日志中唯一标识此请求。curl -H "host: store.example.internal" http://VIP/mirror输出会确认客户端收到来自
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" }这可确认主集群是否正在响应流量。您仍然需要确认要迁移到的集群正在接收镜像流量。
检查
gke-west-2集群上的storepod 的应用日志。日志应确认 Pod 已收到来自负载均衡器的镜像流量。kubectl logs deployment/store --context gke-west-2 -n store | grep /mirror此输出确认
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 后端的流量的明确百分比,该百分比通常只占总流量的很小一部分,以便可以在对真实用户请求具有可接受的风险量的情况下确定推出是否成功。
通过拆分少数流量,服务所有者可以检查应用的运行状况和响应。如果所有信号看起来都运行状况良好,则可以继续进行完整割接。
将以下
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使用您的专用客户端向
internal- httpGateway 发送连续 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-1 和 gke-west-2 集群的 Pod。这样,负载均衡器就可以根据距离、运行状况和容量来决定“活跃-活跃”应用的流量去向。
将以下
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使用您的专用客户端向
internal- httpGateway 发送连续 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 集群的完全蓝绿应用迁移。
清理
完成本文档中的练习后,请按照以下步骤移除资源,防止您的账号产生不必要的费用:
删除集群。
如果不需要为其他目的注册集群,请从舰队中取消注册集群。
停用
multiclusterservicediscovery功能:gcloud container fleet multi-cluster-services disable停用 Multi Cluster Ingress:
gcloud container fleet ingress disable停用 API:
gcloud services disable \ multiclusterservicediscovery.googleapis.com \ multiclusteringress.googleapis.com \ trafficdirector.googleapis.com \ --project=PROJECT_ID
问题排查
没有健康的上行
具体情况:
创建网关但无法访问后端服务(503 响应代码)时,可能会出现以下问题:
no healthy upstream
原因:
此错误消息表示健康检查探测器找不到健康状况良好的后端服务。您的后端服务可能处于健康状况良好状态,但您可能需要自定义健康检查。
临时解决方法:
如需解决此问题,请使用 HealthCheckPolicy 根据应用的要求自定义监控状况检查(例如 /health)。
后续步骤
- 详细了解 Gateway Controller。