Implementa una puerta de enlace de varios clústeres para la división del tráfico ponderada

En este documento, se te guía a través de una implementación azul-verde de una aplicación de store de muestra en dos clústeres de GKE. Las implementaciones azul-verde son una estrategia eficaz para migrar tus aplicaciones a clústeres nuevos de GKE con un riesgo mínimo. Si cambias el tráfico de forma gradual del clúster actual (azul) al clúster nuevo (verde), puedes validar el entorno nuevo en producción antes de confirmar un cambio completo.

Las Gateways de varios clústeres proporcionan una forma eficaz de administrar el tráfico de los servicios implementados en varios clústeres de GKE. Si usas la infraestructura global de balanceo de cargas de Google, puedes crear un solo punto de entrada para tus aplicaciones, lo que simplifica la administración y mejora la confiabilidad.

En este instructivo, usarás una aplicación de ejemplo de store para simular una situación real en la que un servicio de compras en línea es propiedad de equipos independientes y está operado por ellos, y se implementa en una flota de clústeres de GKE compartidos.

Antes de comenzar

Las puertas de enlace de varios clústeres requieren cierta preparación del entorno antes de que puedan implementarse. Antes de continuar, sigue los pasos que se indican en Prepara tu entorno para las puertas de enlace de varios clústeres:

  1. Implemente clústeres de GKE

  2. Registra tus clústeres en una flota (si aún no lo hiciste).

  3. Habilita el Service de varios clústeres y los controladores de puertas de enlace de varios clústeres.

Por último, revisa las limitaciones y los problemas conocidos del controlador de puerta de enlace de GKE antes de usarlo en tu entorno.

Enrutamiento azul-verde y de varios clústeres con puerta de enlace

Las GatewayClasses gke-l7-global-external-managed-*, gke-l7-regional-external-managed-* y gke-l7-rilb-* tienen muchas capacidades avanzadas de enrutamiento de tráfico, que incluyen la división del tráfico, la coincidencia del encabezado, la manipulación del encabezado, la duplicación de tráfico y mucho más. En este ejemplo, demostrarás cómo usar la división de tráfico basada en la ponderación para controlar de forma explícita la proporción de tráfico en dos clústeres de GKE.

En este ejemplo, se explican algunos pasos realistas que seguiría un propietario del servicio para trasladar o expandir su aplicación a un clúster de GKE nuevo. El objetivo de las implementaciones azul-verde es reducir el riesgo mediante varios pasos de validación que confirman que el clúster nuevo funciona de forma correcta. En este ejemplo, se explican cuatro etapas de implementación:

  1. 100%-versión canary basada en encabezados: Usa el enrutamiento de encabezado HTTP para enviar solo tráfico sintético o de prueba al clúster nuevo.
  2. 100%-duplicación del tráfico: Duplica el tráfico de usuario al clúster de versión canary. Esto prueba la capacidad del clúster de versión canary mediante la copia del 100% del tráfico de usuario en este clúster.
  3. 90%-10%: Versión canary de una división del tráfico del 10% para exponer lentamente el clúster nuevo al tráfico en vivo.
  4. 0%-100%: Adopta una migración de sistemas completa al clúster nuevo con la opción de volver a la anterior si se observan errores.

División del tráfico azul-verde en dos clústeres de GKE

Este ejemplo es similar al anterior, excepto que implementa una puerta de enlace de varios clústeres interna en su lugar. De esta manera, se implementa un balanceador de cargas de aplicaciones interno al que solo se puede acceder de forma privada desde la VPC. Usarás los clústeres y la misma aplicación que implementaste en los pasos anteriores, con la excepción de que los implementarás a través de una puerta de enlace diferente.

Requisitos previos

El siguiente ejemplo se basa en algunos de los pasos de Implementa una puerta de enlace de varios clústeres externa. Antes de continuar con este ejemplo, asegúrate de haber realizado los siguientes pasos:

  1. Prepara tu entorno para las puertas de enlace de varios clústeres

  2. Implementa una aplicación de demostración

    En este ejemplo, se usan los clústeres gke-west-1 y gke-west-2 que ya configuraste. Estos clústeres están en la misma región porque GatewayClass gke-l7-rilb-mc es regional y solo admite backends de clúster en la misma región.

  3. Implementa el Service y las ServiceExports necesarios en cada clúster. Si implementaste Services y ServiceExports del ejemplo anterior, ya implementaste algunos de estos.

    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
    

    Implementa un conjunto similar de recursos en cada clúster:

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

Configura una subred de solo proxy

Si aún no lo has hecho, configura una subred de solo proxy para cada región en la que implementes puertas de enlace einternas. Esta subred se usa para proporcionar direcciones IP internas a los proxies de balanceador de cargas y debe configurarse solo con un --purpose configurado como REGIONAL_MANAGED_PROXY.

Debes crear una subred de solo proxy antes de crear puertas de enlace con las que se administren los balanceadores de cargas de aplicaciones internos. Cada región de una red de nube privada virtual (VPC) en la que uses balanceadores de cargas de aplicaciones internos debe tener una subred de solo proxy.

Con el comando gcloud compute networks subnets create, se crea una subred de solo proxy.

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

Reemplaza lo siguiente:

  • SUBNET_NAME: El nombre de la subred de solo proxy
  • REGION: La región de la subred de solo proxy
  • VPC_NETWORK_NAME: El nombre de la red de VPC que contiene la subred
  • CIDR_RANGE: El rango de direcciones IP principal de la subred Debes usar una máscara de subred de un tamaño máximo de /26 a fin de que al menos 64 direcciones IP estén disponibles para los proxies de la región. La máscara de subred recomendada es /23.

Implementa la Gateway

La siguiente puerta de enlace se crea a partir de la GatewayClass gke-l7-rilb-mc, que es una puerta de enlace interna regional que solo puede segmentar clústeres de GKE en la misma región.

  1. Aplica el siguiente manifiesto Gateway al clúster de configuración, gke-west-1 en este ejemplo:

    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. Verifica que la puerta de enlace haya aparecido correctamente. Puedes filtrar solo los eventos de esta puerta de enlace con el siguiente comando:

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

    La implementación de la puerta de enlace se realizó de forma correcta si el resultado es similar al siguiente:

    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
    

Versión canary basada en encabezados

La versión canary basada en encabezados permite que el propietario del servicio haga coincidir el tráfico de prueba sintético que no proviene de usuarios reales. Esta es una forma fácil de validar que la red básica de la aplicación está funcionando sin exponer a los usuarios directamente.

  1. Aplica el siguiente manifiesto HTTPRoute al clúster de configuración, gke-west-1 en este ejemplo:

    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
    

    Una vez implementado, este HTTPRoute configura el siguiente comportamiento de enrutamiento:

    • Las solicitudes internas a store.example.internal sin el encabezado HTTP env: canary se enrutan a los Pods store en el clúster gke-west-1.
    • Las solicitudes internas a store.example.internal con el encabezado HTTP env: canary se enrutan a los Pods store en el clúster gke-west-2.

    La HTTPRoute permite el enrutamiento a clústeres diferentes en función de los encabezados HTTP

    Envía tráfico a la dirección IP de la puerta de enlace para validar que la HTTPRoute funcione correctamente.

  2. Recupera la dirección IP interna de internal-http.

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

    Reemplaza VIP en los siguientes pasos por la dirección IP que recibas como resultado.

  3. Envía una solicitud a la puerta de enlace con el encabezado HTTP env: canary. Esto confirmará que el tráfico se enruta a gke-west-2. Usa un cliente privado en la misma VPC que los clústeres de GKE para confirmar que las solicitudes se enrutan de forma correcta. El siguiente comando debe ejecutarse en una máquina que tenga acceso privado a la dirección IP de la puerta de enlace o no funcionará.

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

    El resultado confirma que un Pod entregó la solicitud desde el clúster gke-west-2:

    {
        "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"
    }
    

Duplicación de tráfico

En esta etapa, se envía el tráfico al clúster deseado, pero también se duplica ese tráfico en el clúster de versión canary.

El uso de la duplicación es útil para determinar cómo la carga de tráfico afectará el rendimiento de la aplicación sin afectar de ninguna manera las respuestas a los clientes. Puede no ser necesario para todos los tipos de implementaciones, pero puede ser útil cuando se implementan cambios grandes que podrían afectar el rendimiento o la carga.

  1. Aplica el siguiente manifiesto HTTPRoute al clúster de configuración, gke-west-1 en este ejemplo:

    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. Con tu cliente privado, envía una solicitud a la puerta de enlace internal-http. Usa la ruta de acceso /mirror para poder identificar de forma única esta solicitud en los registros de la aplicación en un paso posterior.

    curl -H "host: store.example.internal" http://VIP/mirror
    
  3. El resultado confirma que el cliente recibió una respuesta de un Pod en el clúster gke-west-1:

    {
        "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"
    }
    

    Esto confirma que el clúster principal responde al tráfico. Aún debes confirmar que el clúster al que migras reciba tráfico duplicado.

  4. Verifica los registros de la aplicación de un Pod store en el clúster gke-west-2. Los registros deben confirmar que el Pod recibió tráfico duplicado del balanceador de cargas.

    kubectl logs deployment/store --context gke-west-2 -n store | grep /mirror
    
  5. Este resultado confirma que los Pods en el clúster gke-west-2 también reciben las mismas solicitudes. Sin embargo, sus respuestas a estas solicitudes no se envían al cliente. Las direcciones IP que se ven en los registros son las de las direcciones IP internas del balanceador de cargas que se comunican con los Pods.

    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 -
    

División del tráfico

La división del tráfico es uno de los métodos más comunes para implementar código nuevo o implementar en entornos nuevos de forma segura. El propietario del servicio establece un porcentaje explícito de tráfico que se envía a los backends de versión canary, que suele ser una cantidad muy pequeña del tráfico general para que el éxito de la implementación se pueda determinar con una cantidad aceptable de riesgos para las solicitudes de usuarios reales.

Si se hace una división del tráfico con una minoría del tráfico, el propietario del servicio puede inspeccionar el estado de la aplicación y las respuestas. Si todos los indicadores se ven en buen estado, puede continuar con la migración de sistemas completa.

  1. Aplica el siguiente manifiesto HTTPRoute al clúster de configuración, gke-west-1 en este ejemplo:

    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. Con tu cliente privado, envía una solicitud curl continua a la puerta de enlaceinternal- http.

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

    El resultado será similar a este, lo que indica que se está realizando una división del tráfico de 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",
    ...
    

Migración de tráfico

La última etapa de la migración azul-verde es migrar por completo al clúster nuevo y quitar el clúster anterior. Si el propietario del servicio estuviera integrando un segundo clúster a un clúster existente, este último paso sería diferente, ya que el último paso tendría tráfico hacia ambos clústeres. En ese caso, se recomienda una sola ServiceImport store que tenga Pods de los clústeres gke-west-1 y gke-west-2. Esto permite que con el balanceador de cargas se puede decidir a dónde debe dirigirse el tráfico para una aplicación activa-activa, según la proximidad, el estado y la capacidad.

  1. Aplica el siguiente manifiesto HTTPRoute al clúster de configuración, gke-west-1 en este ejemplo:

    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. Con tu cliente privado, envía una solicitud curl continua a la puerta de enlaceinternal- http.

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

    El resultado será similar a este, lo que indica que todo el tráfico se dirige ahora a gke-west-2.

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

En este último paso, se completa una migración de aplicación azul-verde de un clúster de GKE a otro.

Realiza una limpieza

Después de completar los ejercicios de este documento, sigue estos pasos para quitar los recursos a fin de prevenir cobros no deseados en tu cuenta:

  1. Borra los clústeres.

  2. Anula el registro de tus clústeres en la flota si no es necesario que estén registrados para otro propósito.

  3. Inhabilita la función multiclusterservicediscovery:

    gcloud container fleet multi-cluster-services disable
    
  4. Inhabilitar entrada de varios clústeres

    gcloud container fleet ingress disable
    
  5. Inhabilita las API:

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

Soluciona problemas

Sin upstream en buen estado

Síntoma:

El siguiente problema puede ocurrir cuando creas una puerta de enlace, pero no puedes acceder a los servicios de backend (código de respuesta 503):

no healthy upstream

Motivo:

Este mensaje de error indica que el sistema de sondeo de verificación de estado no puede encontrar servicios de backend en buen estado. Es posible que tus servicios de backend estén en buen estado, pero es posible que debas personalizar las verificaciones de estado.

Solución alternativa:

Para resolver este problema, personaliza la verificación de estado según los requisitos de tu aplicación (por ejemplo, /health) mediante un HealthCheckPolicy.

¿Qué sigue?