No se puede recibir el error de gRPC durante una llamada de transmisión de respuesta usando HAProxy en modo HTTP

No se puede recibir el error de gRPC durante una llamada de transmisión de respuesta usando HAProxy en modo HTTP

Estoy trabajando en una aplicación gRPC que se servirá detrás de HAProxy usando el modo http. Si la aplicación del servidor inmediatamente (es decir, antes de enviar cualquier respuesta) cancela la llamada de transmisión de respuesta con un error específico, entonces la aplicación del cliente recibirá CANCELLEDel error en lugar del que se envió. Los detalles del error seránSe recibió RST_STREAM con el código de error 8. Estoy trabajando con HAProxy 2.3.2 y grpc 1.34.0.

Para cada una de estas solicitudes, la entrada de registro de HAProxy tiene SD-- indicadores configurados en elestado de sesión en el momento de la desconexióncampo, por ej.

<134>Jan  9 18:09:39 1a8328663d74 haproxy[8]: 172.28.0.4:41698 [09/Jan/2021:18:09:39.346] grpc-server-fe grpc-server-fe/grpc-server-be 0/0/0/-1/+0 -1 +115 - - SD-- 1/1/0/0/0 0/0 "POST http://proxy:6000/service.Service/StreamStream HTTP/2.0"

En la documentación de HAProxy, esos indicadores se definen de la siguiente manera:

  • S: el servidor canceló inesperadamente la sesión TCP o el servidor la rechazó explícitamente.
  • D: la sesión estaba en la fase de DATOS.

Además:

Dakota del SurLa conexión con el servidor falló debido a un error durante la transferencia de datos. Esto generalmente significa que haproxy recibió un RST del servidor o un mensaje ICMP de un equipo intermedio mientras intercambiaba datos con el servidor. Esto puede deberse a una falla del servidor o a un problema de red en un equipo intermedio.

Sabiendo eso, y teniendo en cuenta que la conexión HTTP se abrió con la intención de transmitir datos, como solución alternativa, intenté enviar un mensaje gRPC vacío antes de generar el error. La solución ha ayudado parcialmente: el cliente podría haber recibido los códigos de error para la mayoría de las solicitudes, pero el problema persistía de vez en cuando.

Como siguiente paso, inspeccioné el tráfico de la red utilizando Wirehark. A continuación se muestra un rastro de la respuesta HTTP proporcionada por el servidor gRPC en un evento de interrupción inmediata de la llamada:

HyperText Transfer Protocol 2
    Stream: SETTINGS, Stream ID: 0, Length 0
        Length: 0
        Type: SETTINGS (4)
        Flags: 0x01
        0... .... .... .... .... .... .... .... = Reserved: 0x0
        .000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
    Stream: HEADERS, Stream ID: 1, Length 88, 200 OK
        Length: 88
        Type: HEADERS (1)
        Flags: 0x05
            .... ...1 = End Stream: True
            .... .1.. = End Headers: True
            .... 0... = Padded: False
            ..0. .... = Priority: False
            00.0 ..0. = Unused: 0x00
        0... .... .... .... .... .... .... .... = Reserved: 0x0
        .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
        [Pad Length: 0]
        Header Block Fragment: 88400c636f6e74656e742d74797065106170706c69636174...
        [Header Length: 120]
        [Header Count: 4]
        Header: :status: 200 OK
        Header: content-type: application/grpc
        Header: grpc-status: 7
        Header: grpc-message: Details sent by the server
    Stream: RST_STREAM, Stream ID: 1, Length 4
        Length: 4
        Type: RST_STREAM (3)
        Flags: 0x00
        0... .... .... .... .... .... .... .... = Reserved: 0x0
        .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
        Error: NO_ERROR (0)

Entonces, el servidor envía encabezados de respuesta con detalles sobre el error End Streamy End Headerslas banderas configuradas. Y luego cierra la transmisión con NO_ERRORcódigo. Según la respuesta dada enhttps://stackoverflow.com/questions/55511528/should-grpc-server-side-half-closing-implicitly-terminate-the-client/55522312todo está bien en esta etapa. También he repasado brevemente elRFC 7540y no pude encontrar nada que esté fuera de lugar en los términos del protocolo HTTP/2.

La respuesta HTTP del servidor gRPC citada va seguida del TCP de origen de HAProxy ACKy, a continuación, HAProxy envía su respuesta al cliente.

HyperText Transfer Protocol 2
    Stream: HEADERS, Stream ID: 1, Length 75, 200 OK
        Length: 75
        Type: HEADERS (1)
        Flags: 0x05
            .... ...1 = End Stream: True
            .... .1.. = End Headers: True
            .... 0... = Padded: False
            ..0. .... = Priority: False
            00.0 ..0. = Unused: 0x00
        0... .... .... .... .... .... .... .... = Reserved: 0x0
        .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
        [Pad Length: 0]
        Header Block Fragment: 885f106170706c69636174696f6e2f67727063000b677270...
        [Header Length: 120]
        [Header Count: 4]
        Header: :status: 200 OK
        Header: content-type: application/grpc
        Header: grpc-status: 7
        Header: grpc-message: Details sent by the server
    Stream: RST_STREAM, Stream ID: 1, Length 4
        Length: 4
        Type: RST_STREAM (3)
        Flags: 0x00
            0000 0000 = Unused: 0x00
        0... .... .... .... .... .... .... .... = Reserved: 0x0
        .000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
        Error: CANCEL (8)

Se puede ver que las banderas y todo el contenido del HEADERSmarco están en su lugar, por lo que los detalles del error se pasan al cliente, pero el código de RST_STREAMha cambiado a CANCEL. De hecho, el cliente recibe todos los datos esperados, pero luego recibe datos inesperados RST_STREAM(CANCEL)que se están asignando en un CANCELLEDerror de gRPC, como se especifica en eldocumentación gRPC.

En el curso de una investigación adicional, me he referido al código fuente de HAProxy. He descubierto que el código está configurado en elh2_do_shutrfuncion demux_h2.c(Los experimentos con compilaciones personalizadas de HAProxy han demostrado que efectivamente es este lugar). La rama de código involucrada tiene el siguiente comentario:

Ya se proporcionó una respuesta final, ya no queremos esta transmisión. Esto puede suceder cuando el servidor responde antes de que finalice una carga y se cierra rápidamente (redireccionar, denegar,...)

Estos son los detalles que he logrado reunir sobre el tema. No estoy del todo seguro de si el problema radica en el núcleo de gRPC (que es demasiado complicado en términos de manejo de transmisiones HTTP2) o en HAProxy (que es demasiado descuidado al reescribir RST_STREAMcódigos). La última pregunta es, ¿cómo puedo ajustar la configuración del servidor central HAProxy y gRPC para que funcione correctamente en un evento de interrupción inmediata de la llamada? La configuración mínima de HAProxy que reproduce el problema es la siguiente:

global
    log stdout local0

listen grpc-server-fe
    bind *:6000 proto h2

    mode http
    log global
    option logasap
    option httplog

    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

    server grpc-server-be server:6000 proto h2

he preparado unrepositorio con ejemplo mínimoque contiene un cliente y servidor Python simple. También contiene docker-composeun entorno de red que incluye HAProxy configurado.

información relacionada