Ir al contenido

Blog


Mantenerse en la zona: cómo DoorDash utilizó una malla de servicios para gestionar la transferencia de datos, reduciendo los saltos y el gasto en la nube.

16 de enero de 2024

|
Hochuen Wong

Hochuen Wong

Levon Stepanian

Levon Stepanian

La evolución de DoorDash de una arquitectura de aplicación monolítica a otra basada en células y microservicios ha aportado muchas ventajas. La nueva arquitectura ha reducido el tiempo necesario para el desarrollo, las pruebas y el despliegue y, al mismo tiempo, ha mejorado la escalabilidad y la resistencia para los usuarios finales, incluidos comerciantes, Dashers y consumidores. Sin embargo, a medida que ha crecido el número de microservicios y back-ends, DoorDash ha observado un aumento de los costes de transferencia de datos entre zonas de disponibilidad (AZ). Estos costes de transferencia de datos -en los que se incurre tanto en el envío como en la recepción- permiten a DoorDash proporcionar a sus usuarios finales un servicio de alta disponibilidad que puede soportar degradaciones de una o más AZ.

El aumento de los costes llevó a nuestro equipo de ingeniería a investigar formas alternativas de proporcionar el mismo nivel de servicio de forma más eficiente. En esta entrada de blog, describimos el viaje que DoorDash emprendió utilizando una malla de servicios para ahorrar costes de transferencia de datos sin sacrificar la calidad del servicio.

Arquitectura de tráfico de DoorDash

Antes de sumergirnos en nuestra solución, repasemos la infraestructura de tráfico de DoorDash.

Arquitectura basada en células: Como se muestra en la Figura 1, nuestra arquitectura de tráfico sigue un diseño basado en células. Todos los pods de microservicios se despliegan en varias celdas aisladas. Cada servicio tiene un despliegue de Kubernetes por célula. Para garantizar el aislamiento entre células, no se permite el tráfico intercelular. Este enfoque nos permite reducir el radio de explosión de un fallo de una sola célula. Para los servicios singleton o aquellos que no han migrado a la arquitectura celular, el despliegue se produce en una única célula global. Los routers Envoy internos permiten la comunicación entre la célula global y las células replicadas.

Despliegue de clústeres Kubernetes: Cada célula consta de varios clústeres Kubernetes; cada microservicio se despliega exclusivamente en un clúster dentro de una célula determinada. Este diseño garantiza la escalabilidad y la fiabilidad a la vez que se alinea con nuestra arquitectura basada en células.

Figura 1: Despliegues multiclúster basados en células

Alta disponibilidad: Para mejorar la disponibilidad y la tolerancia a fallos, cada clúster de Kubernetes se despliega en varias AZ. Esta práctica minimiza las interrupciones causadas por la interrupción de una o más AZ.

Comunicación directa en una red plana: Aprovechando AWS-CNI, los pods de microservicios en clústeres distintos dentro de una célula pueden comunicarse directamente entre sí. Esta arquitectura de red plana agiliza las vías de comunicación, lo que facilita las interacciones eficaces entre microservicios.

Descubrimiento de servicios multiclúster personalizado: Nuestra solución personalizada de descubrimiento de servicios, DoorDash data center Service Discovery, o DDSD, proporciona un dominio DNS personalizado para soportar la comunicación multiclúster. Los clientes aprovechan los nombres DNS para descubrir dinámicamente todas las direcciones IP de pod de los servicios deseados. La funcionalidad de DDSD es similar a la de los servicios headless de Kubernetes, pero también funciona para la comunicación entre clústeres. Por ejemplo, un cliente en un clúster diferente puede utilizar payment-service.service.prod.ddsd para recuperar todas las direcciones IP de pod asociadas con el servicio de pago.

Equilibrio de carga del lado del cliente: La malla de servicios se encarga de equilibrar la carga en el lado del cliente. Sin embargo, para los servicios no integrados en la malla de servicios, el equilibrio de carga se produce en el lado de la aplicación cliente.

La figura 2 muestra las cuatro características descritas anteriormente:

Figura 2: Patrones de comunicación entre células y entre Zonas Aeroespaciales

Arquitectura de malla de servicios: La malla de servicios de DoorDash, como se indica en la figura 3, que se despliega en cada célula, adopta un patrón de diseño de contenedor sidecar, aprovechando el proxy Envoy como plano de datos. Hemos construido nuestro propio plano de control basado en xDS para gestionar las configuraciones de Envoy. El contenedor sidecar funciona como una solución plug-and-play, interceptando, controlando y transformando a la perfección todo el tráfico HTTP1/HTTP2/gRPC que entra y sale de los microservicios de DoorDash, todo ello sin necesidad de modificar el código de la aplicación.

Figura 3: Arquitectura de alto nivel de la malla de servicios

Aunque la arquitectura de tráfico de DoorDash tiene componentes únicos, creemos que los retos y lecciones que hemos encontrado en nuestro viaje hacia la eficiencia relacionada con el tráfico de red también pueden aplicarse ampliamente a otras arquitecturas.

Patrones comunes de transferencia de datos de DoorDash

Para el tráfico entre zonas de Arizona, clasificamos nuestros patrones de tráfico del siguiente modo:

Tráfico HTTP1/HTTP2/gRPC: Comunicación directa de pod a pod entre microservicios dentro de la misma célula; el tráfico entre microservicios de la célula global y los de las células que implican un salto adicional en la ruta de llamada -como los routers internos- aumenta la probabilidad de tráfico entre zonas.

Tráfico de almacenamiento: Incluye el tráfico de microservicios a sistemas con estado como Aurora PostgreSQL, CockroachDB, Redis y Kafka.

Tráfico interno de la infraestructura: Tráfico interno de Kubernetes, como el tráfico de coredns o la comunicación entre los componentes del plano de control de Kubernetes. Este tipo de tráfico suele utilizar el DNS interno de Kubernetes en lugar del DDSD.

Primeras hipótesis

Creíamos que el tráfico HTTP1/HTTP2/gRPC dentro de la misma célula era la mayor fuente de costes de transferencia de datos entre zonas debido a nuestra arquitectura de microservicios. También determinamos que la malla de servicios podría permitir el enrutamiento por zonas para todos los microservicios que utilizan esa función en Envoy. Entendiendo ambas cosas, priorizamos la investigación y optimización de los patrones de tráfico HTTP1/HTTP2/gRPC para mejorar la eficiencia sin degradar la calidad del servicio.

Costes del tráfico HTTP1/HTTP2/gRPC

Originalmente, el tráfico entre los servicios se distribuía uniformemente entre diferentes AZ, como se muestra en la figura 4. Con la función de enrutamiento por zonas de Envoy, ahora los servicios que llaman prefieren dirigir el tráfico a los servicios que reciben la llamada en la misma AZ, como se muestra en la Figura 5, reduciendo así los costes de transferencia de datos entre AZ. 

Figura 4: Equilibrio de carga round-robin simple entre pods 
Figura 5: Enrutamiento por zonas

Para habilitar la función de enrutamiento por zonas de Envoy, hemos realizado cambios en nuestro plano de control para la malla de servicios, cambiando el tipo de detección de servicios de STRICT_DNS a servicio de detección de extremos(EDS). Como se muestra en la Figura 6 a continuación, para los dominios DDSD, el plano de control ahora envía dinámicamente recursos EDS desde cada clúster Envoy de vuelta a los contenedores sidecar Envoy. El recurso EDS incluye direcciones IP de pods e información sobre su AZ.

resources:
 - "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
   cluster_name: payment-service.service.prod.ddsd
   endpoints:
     - locality:
         zone: us-west-2a
       lb_endpoints:
         - endpoint:
             address:
               socket_address:
                 address: 1.1.1.1
                 port_value: 80
     - locality:
         zone: us-west-2b
       lb_endpoints:
         - endpoint:
             address:
               socket_address:
                 address: 2.2.2.2
                 port_value: 80
     - locality:
         zone: us-west-2c
       lb_endpoints:
         - endpoint:
             address:
               socket_address:
                 address: 3.3.3.3
                 port_value: 80

Figura 6: Ejemplo de una respuesta EDS

Con los datos proporcionados en las respuestas EDS, Envoy recupera la distribución AZ tanto del servicio que llama como del servicio que recibe la llamada. Esta información permite a Envoy calcular ponderaciones entre pods. Aunque se sigue dando prioridad al envío de tráfico a través de las AZ locales, es posible que parte del tráfico se dirija a través de las AZ para garantizar una distribución equilibrada y evitar la sobrecarga de un único pod, tal y como se muestra en la Figura 7.

Figura 7: Preferir la AZ local garantizando un tráfico equilibrado 

Esta solución de enrutamiento ofrece varias ventajas, entre ellas:

  • Mantenimiento del equilibrio del tráfico, incluso en situaciones en las que los servicios se distribuyen de forma desigual entre las ZA.
  • Posibilidad de establecer dinámicamente pesos de tráfico entre pods, eliminando la operación manual.
  • Reducción del radio de explosión de una interrupción de una o varias Zonas Aeroespaciales
  • Reducción de las latencias de tráfico: los servicios de llamadas se conectan a las calles más próximas.

Nuestra solución resulta aún más eficaz cuando los pods de servicio se despliegan uniformemente por las AZ. Para lograrlo, aprovechamos topologySpreadConstraints y establecimos maxSkew en 1 con whenUnsatisfiable: ScheduleAnyway como se muestra en la figura 8. Esto significa que el programador de Kubernetes programará los pods incluso si no se cumple la condición, dando prioridad a los nodos que minimizan el sesgo.

Este enfoque garantiza que los pods sigan programados; de lo contrario, se reduciría la cantidad de bin-packing, lo que aumentaría la computación ociosa y, en última instancia, reduciría los costes relacionados con el tráfico por zonas. En nuestro sistema de producción, observamos que el 10% del tráfico se envía a través de AZ con esta política topologySpreadConstraints.

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway

Figura 8: Configuración para desplegar pods uniformemente en las AZ

A medida que íbamos introduciendo gradualmente los cambios mencionados, observamos mejoras en nuestras tarifas de transferencia entre AWS y Arizona. Aun así, esperábamos mayores reducciones. Así que nos pusimos a investigar por qué no estábamos ahorrando tanto como habíamos previsto.

Manténgase informado con las actualizaciones semanales

Suscríbase a nuestro blog de ingeniería para estar al día de los proyectos más interesantes en los que trabaja nuestro equipo.

Encontrar la aguja en el pajar: Mejor visibilidad del tráfico entre zonas de Arizona

Confirmamos que el enrutamiento por zonas funcionaba según lo esperado validando las métricas de Envoy entre las zonas de disponibilidad y los registros de las aplicaciones, pero necesitábamos más visibilidad del tráfico entre zonas de disponibilidad para determinar la causa raíz de la reducción de los costes de transferencia de datos, que era menor de lo esperado. Esto nos llevó a utilizar una serie de herramientas de observabilidad, incluidos los registros de flujo de VPC, las métricas del agente ebpf y las métricas de bytes de red de Envoy para rectificar la situación.

Mediante los registros de flujo de la VPC, asignamos las direcciones IP srcaddr y dstaddr a las subredes correspondientes que se implementaron en la estructura de nuestra cuenta de AWS. Esta asignación proporcionó una regla de despliegue que nos permitió comprender los patrones generales de tráfico de entrada/salida entre las diferentes subredes que alojaban varias partes de nuestra huella en la nube, incluidas las subredes para clústeres Kubernetes de producción, soluciones de almacenamiento, cachés y agentes de mensajes, como se muestra en la Tabla 1. Pudimos confirmar que nuestra mayor fuente de costes de transferencia entre zonas geográficas es el tráfico no relacionado con el almacenamiento dentro de cada célula. Alrededor del 20% de este coste fue causado por el tráfico a un equilibrador de carga elástico (ELB) utilizado para nuestro canal de ingestión de eventos(Iguazu).

Tabla 1: Atribución de tráfico entre zonas geográficas utilizando registros de flujo de VPC

Además, examinamos las métricas HTTP1/HTTP2/gRPC de la malla de servicios, concretamente los bytes de conexión para todas las solicitudes y respuestas tanto para el tráfico de entrada como de salida. Las métricas más importantes del proxy Envoy de la malla de servicios son cx_rx_bytes_total y cx_tx_bytes_total tanto de los oyentes Envoy como de los clústeres. Sin embargo, debido a que no todos los servicios estaban integrados en la malla de servicios en ese momento, nos basamos en las métricas de BPFAgent, que se implementa globalmente en cada nodo Kubernetes de producción, para aumentar la visibilidad de los bytes de red totales. Utilizando las métricas de estas dos fuentes, descubrimos que el 90% de todo el tráfico es tráfico HTTP1/HTTP2/gRPC; el 45% de ese tráfico ya se había incorporado a la malla de servicios y el 91% de esa parte del tráfico se envía a Iguazú.

Lúpulo tras lúpulo: El tráfico de Iguazú

Después de analizar el flujo de tráfico de Iguazú como se muestra en la Figura 9, nos dimos cuenta de que había varios saltos intermedios entre los servicios de llamada y de llamada, o pods. Cuando el tráfico se mueve desde los servicios que llaman a Iguazú, inicialmente pasa a través de ELBs, antes de aterrizar en uno de los nodos de trabajo en un clúster Kubernetes diferente en la célula global. Dado que externalTrafficPolicy está configurado como un clúster, iptables redirige el tráfico a otro nodo para garantizar el equilibrio de carga.

Figura 9: Flujo de tráfico original de Iguazú

Observamos el siguiente comportamiento en la transferencia de datos:

  • Los clientes que enviaban tráfico a los ELB seguían utilizando directamente los nombres DNS de los ELB. Dado que los clientes no utilizaban los dominios DDSD internos, el tipo de detección de servicios en el clúster Envoy seguía siendo STRICT_DNS en lugar de EDS, que es un requisito previo para habilitar el enrutamiento por zonas. Esto significaba que los contenedores laterales de Envoy utilizaban un enfoque simple de round-robin para distribuir el tráfico a los ELB.   
  • El equilibrio de carga Cross-AZ se deshabilitó en el caso del tráfico desde los ELB a los nodos de trabajador de Kubernetes en el clúster 2. 
  • Cuando el tráfico llega a un nodo trabajador de Kubernetes desde el ELB de Iguazú, es posteriormente redirigido por iptables a un nodo aleatorio, lo que también aumenta la probabilidad de tráfico cruzado entre Zonas. 

Dada la complejidad de los múltiples saltos dentro del gráfico de llamadas de Iguazú, decidimos que lo mejor sería migrar el servicio de Iguazú a la misma célula en la que estaban desplegados sus clientes. 

También configuramos las reglas de enrutamiento de los sidecars Envoy de todos los clientes para enrutar el tráfico a los pods de Iguazú en el nuevo clúster en lugar de hablar con los ELB, sin necesidad de que nuestros ingenieros realizaran ningún cambio de código o configuración en sus servicios. Esto nos permitió habilitar la comunicación directa de pod a pod para el tráfico de Iguazu, habilitando el enrutamiento consciente de la zona y reduciendo simultáneamente el volumen de tráfico procesado por los ELB, como se muestra en la Figura 10.  

Figura 10: Nuevo flujo de tráfico en Iguazú

Estas acciones hicieron mella en los costes de transferencia de datos de DoorDash, así como en nuestros costes de ELB, hasta el punto de que nuestro proveedor de servicios en la nube se puso en contacto con nosotros para preguntarnos si estábamos experimentando un incidente relacionado con la producción.

Lecciones aprendidas

Algunos de los principales descubrimientos realizados durante nuestro viaje son:

  • La tarificación de la transferencia de datos de los proveedores de servicios en nube es más compleja de lo que parece en un principio. Merece la pena invertir tiempo en comprender los modelos de precios para encontrar la solución más eficiente.
  • Es difícil tener una visión global de todo el tráfico que atraviesa Arizona. No obstante, la combinación de métricas de bytes de red procedentes de distintas fuentes puede bastar para identificar puntos conflictivos que, cuando se abordan, pueden reducir significativamente el uso y los costes.
  • El enrutamiento por zonas de Envoy puede enviar tráfico a su zona de disponibilidad local al tiempo que garantiza automáticamente la resistencia mediante un tráfico equilibrado.
  • A medida que aumenta el número de saltos en los gráficos de llamadas de microservicios, crece la probabilidad de que los datos se transmitan a través de AZ, lo que aumenta la complejidad de garantizar que todos los saltos admitan el enrutamiento sensible a la zona.
  • Si está considerando una malla de servicios por sus funciones de gestión del tráfico, también puede aprovecharla para lograr una mayor eficiencia.
  • Para obtener más información sobre el viaje de malla de servicio de DoorDash, consulte la sesión de malla de servicio de Hochuen en KubeCon + CloudNativeCon North America 2023.

Trabajos futuros

Ya hemos recopilado una lista de mejoras de la solución que nos gustaría implantar, entre ellas: 

  • Racionalizar el proceso para recopilar métricas de distintas fuentes y ofrecer una visión unificada del coste y el uso.
  • Habilitar la función de enrutamiento por zonas para enrutadores Envoy internos
  • Hacer la solución más extensible para soportar tráfico HTTP1/HTTP2/gRPC utilizando otros dominios más allá de DDSD.
  • Habilite la función de enrutamiento consciente de la topología para el tráfico interno de la infraestructura mediante el proxy de red de Kubernetes kube-proxy. 
  • Explore el enrutamiento optimizado para grandes operaciones de transferencia de datos hacia o desde los sistemas con estado de DoorDash, por ejemplo PostgreSQL, CRDB, Redis y Kafka. Esto permitirá que el tráfico permanezca en carriles de natación AZ únicos siempre que tenga sentido, reduciendo aún más los costes.

Agradecimiento

Damos las gracias a Darshit Gavhane y Allison Cheng por migrar todos los servicios de Iguazú a la misma célula donde se ejecutan los clientes, así como a Steve Hoffman y Sebastian Yates por sus consejos y orientación a lo largo del viaje.

About the Authors

  • Hochuen Wong

    Hochuen Wong is a Software Engineer on the Core Infrastructure team at DoorDash

  • Levon Stepanian

    Levon Stepanian is a Cloud FinOps Lead on the Core Infrastructure team at DoorDash.

Trabajos relacionados

Ubicación
Oakland, CA; San Francisco, CA
Departamento
Ingeniería
Ubicación
Oakland, CA; San Francisco, CA
Departamento
Ingeniería
Job ID: 2980899
Ubicación
San Francisco, CA; Sunnyvale, CA
Departamento
Ingeniería
Ubicación
Pune, India
Departamento
Ingeniería
Ubicación
Pune, India
Departamento
Ingeniería